diff --git a/melange/common/client.py b/melange/common/client.py index 3ffcb07b..a3fd211b 100644 --- a/melange/common/client.py +++ b/melange/common/client.py @@ -19,6 +19,8 @@ import httplib import socket import urllib +from melange.common import exception + class HTTPClient(object): @@ -28,19 +30,6 @@ class HTTPClient(object): self.use_ssl = use_ssl self.timeout = timeout - def get(self, path, params=None, headers=None): - params = params or {} - headers = headers or {} - return self.do_request("GET", path, params=params, headers=headers) - - def post(self, path, body=None, headers=None): - headers = headers or {} - return self.do_request("POST", path, body=body, headers=headers) - - def delete(self, path, headers=None): - headers = headers or {} - return self.do_request("DELETE", path, headers=headers) - def _get_connection(self): if self.use_ssl: return httplib.HTTPSConnection(self.host, self.port, @@ -59,7 +48,10 @@ class HTTPClient(object): connection = self._get_connection() connection.request(method, url, body, headers) response = connection.getresponse() + if response.status >= 400: + raise exception.MelangeServiceResponseError(response.read()) return response except (socket.error, IOError) as error: - raise Exception(_("Error while communicating with server. " - "Got error: %s") % error) + raise exception.ClientConnectionError( + _("Error while communicating with server. " + "Got error: %s") % error) diff --git a/melange/common/exception.py b/melange/common/exception.py index 362b826d..d7a8eb37 100644 --- a/melange/common/exception.py +++ b/melange/common/exception.py @@ -18,6 +18,7 @@ from openstack.common import exception as openstack_exception +ClientConnectionError = openstack_exception.ClientConnectionError ProcessExecutionError = openstack_exception.ProcessExecutionError DatabaseMigrationError = openstack_exception.DatabaseMigrationError @@ -29,3 +30,8 @@ class MelangeError(openstack_exception.OpenstackException): if message is not None: self.message = message super(MelangeError, self).__init__(**kwargs) + + +class MelangeServiceResponseError(MelangeError): + + message = "Error while responding to service call" diff --git a/melange/common/pagination.py b/melange/common/pagination.py index 768be914..281416a6 100644 --- a/melange/common/pagination.py +++ b/melange/common/pagination.py @@ -22,23 +22,14 @@ from xml.dom import minidom class AtomLink(object): - def __init__(self, rel, href, link_type=None, hreflang=None, title=None): + def __init__(self, rel, href): self.rel = rel self.href = href - self.link_type = link_type - self.hreflang = hreflang - self.title = title def to_xml(self): ATOM_NAMESPACE = "http://www.w3.org/2005/Atom" doc = minidom.Document() atom_elem = doc.createElementNS(ATOM_NAMESPACE, "link") - if self.link_type: - atom_elem.setAttribute("link_type", self.link_type) - if self.hreflang: - atom_elem.setAttribute("hreflang", self.hreflang) - if self.title: - atom_elem.setAttribute("title", self.title) atom_elem.setAttribute("rel", self.rel) atom_elem.setAttribute("href", self.href) return atom_elem diff --git a/melange/ipam/models.py b/melange/ipam/models.py index b12b7e94..52bf4ee5 100644 --- a/melange/ipam/models.py +++ b/melange/ipam/models.py @@ -187,10 +187,6 @@ class ModelBase(object): def __getitem__(self, key): return getattr(self, key) - def __iter__(self): - self._i = iter(db_api.columns_of(self)) - return self - def __eq__(self, other): if not hasattr(other, 'id'): return False @@ -200,23 +196,7 @@ class ModelBase(object): return not self == other def __hash__(self): - return id.__hash__() - - def next(self): - n = self._i.next().name - return n, getattr(self, n) - - def keys(self): - return self.__dict__.keys() - - def values(self): - return self.__dict__.values() - - def items(self): - return self.__dict__.items() - - def to_dict(self): - return self.__dict__() + return self.id.__hash__() def data(self, **options): data_fields = self._data_fields + self._auto_generated_attrs @@ -233,9 +213,6 @@ class ModelBase(object): self.errors[attribute_name] = self.errors.get(attribute_name, []) self.errors[attribute_name].append(error_message) - def _has_error_on(self, attribute): - return self.errors.get(attribute, None) is not None - def ipv6_address_generator_factory(cidr, **kwargs): default_generator = "melange.ipv6.tenant_based_generator."\ @@ -282,10 +259,6 @@ class IpBlock(ModelBase): return (allocated_ip or block.allocate_ip(address=address)) - @classmethod - def find_all_by_policy(cls, policy_id): - return cls.find_all(policy_id=policy_id) - @classmethod def allowed_by_policy(cls, ip_block, policy, address): return policy == None or policy.allows(ip_block.cidr, address) @@ -682,10 +655,6 @@ class IpOctet(ModelBase): _columns = {'octet': 'integer'} _data_fields = ['octet', 'policy_id'] - @classmethod - def find_all_by_policy(cls, policy_id): - return cls.find_all(policy_id=policy_id) - def applies_to(self, address): return self.octet == netaddr.IPAddress(address).words[-1] @@ -710,6 +679,12 @@ class Network(ModelBase): type="private") return cls(id=id, ip_blocks=[ip_block]) + def allocated_ips(self, interface_id): + ips_by_block = [IpAddress.find_all(interface_id=interface_id, + ip_block_id=ip_block.id).all() + for ip_block in self.ip_blocks] + return [ip for sublist in ips_by_block for ip in sublist] + def allocate_ips(self, addresses=None, **kwargs): if addresses: return filter(None, [self._allocate_specific_ip(address, **kwargs) diff --git a/melange/ipam/service.py b/melange/ipam/service.py index 1f653350..381813ba 100644 --- a/melange/ipam/service.py +++ b/melange/ipam/service.py @@ -319,14 +319,10 @@ class NetworksController(BaseController): network.deallocate_ips(interface_id) def get_allocated_ips(self, request, network_id, interface_id, tenant_id): - ip_blocks = models.IpBlock.find_all(network_id=network_id, - tenant_id=tenant_id) - addresses = [models.IpAddress.find_all(interface_id=interface_id, - ip_block_id=ip_block.id) - for ip_block in ip_blocks] - return dict(ip_addresses=[item.data(with_ip_block=True) - for sublist in addresses - for item in sublist]) + network = models.Network.find_by(id=network_id, tenant_id=tenant_id) + ips_on_interface = network.allocated_ips(interface_id=interface_id) + return dict(ip_addresses=[ip.data(with_ip_block=True) + for ip in ips_on_interface]) class API(wsgi.Router): diff --git a/melange/tests/functional/__init__.py b/melange/tests/functional/__init__.py index 76414ef0..95e982f4 100644 --- a/melange/tests/functional/__init__.py +++ b/melange/tests/functional/__init__.py @@ -33,10 +33,10 @@ def setup(): print "Restarting melange server..." shutil.copyfile(melange.melange_etc_path("melange.conf.sample"), os.path.expanduser("~/melange.conf")) - svr = server.Server("melange", + srv = server.Server("melange", melange.melange_bin_path('melange')) _db_sync() - svr.restart(port=setup_unused_port()) + srv.restart(port=setup_unused_port()) _configure_db() diff --git a/melange/tests/functional/test_service.py b/melange/tests/functional/test_service.py index 6e6732b9..1b92bde8 100644 --- a/melange/tests/functional/test_service.py +++ b/melange/tests/functional/test_service.py @@ -17,6 +17,7 @@ from melange import tests from melange.common import client +from melange.common import exception from melange.tests import functional @@ -26,22 +27,28 @@ class FunctionalTest(tests.BaseTest): super(FunctionalTest, self).setUp() self.client = client.HTTPClient(port=functional.get_api_port()) + def client_get(self, path, params=None, headers=None): + params = params or {} + headers = headers or {} + return self.client.do_request("GET", path, params=params, + headers=headers) + class TestServiceConf(FunctionalTest): def test_root_url_returns_versions(self): - response = self.client.get("/") + response = self.client_get("/") self.assertEqual(response.status, 200) self.assertTrue("versions" in response.read()) def test_extensions_are_loaded(self): - response = self.client.get("/v0.1/extensions") + response = self.client_get("/v0.1/extensions") self.assertEqual(response.status, 200) self.assertTrue("extensions" in response.read()) def test_ipam_service_can_be_accessed(self): - response = self.client.get("/v0.1/ipam/tenants/123/ip_blocks") + response = self.client_get("/v0.1/ipam/tenants/123/ip_blocks") self.assertEqual(response.status, 200) self.assertTrue("ip_blocks" in response.read()) @@ -55,7 +62,7 @@ class TestMimeTypeVersioning(FunctionalTest): "version=0.1", } - response = self.client.get("/ipam/tenants/123/ip_blocks", + response = self.client_get("/ipam/tenants/123/ip_blocks", headers=headers) self.assertEqual(response.status, 200) @@ -69,8 +76,8 @@ class TestMimeTypeVersioning(FunctionalTest): "version=99.1", } - response = self.client.get("/ipam/tenants/123/ip_blocks", - headers=headers) - - self.assertEqual(response.status, 406) - self.assertTrue("version not supported" in response.read()) + self.assertRaisesExcMessage(exception.MelangeServiceResponseError, + "version not supported", + self.client_get, + "/ipam/tenants/123/ip_blocks", + headers=headers) diff --git a/melange/tests/unit/test_auth.py b/melange/tests/unit/test_auth.py index 5f4beb3b..38b749c4 100644 --- a/melange/tests/unit/test_auth.py +++ b/melange/tests/unit/test_auth.py @@ -52,31 +52,45 @@ class TestAuthMiddleware(tests.BaseTest): self.request.headers = {'X_TENANT': "tenant_id", 'X_ROLE': "Member"} def test_forbids_based_on_auth_providers(self): - self.auth_provider1.authorize(self.request, "tenant_id", ['Member']).\ - AndReturn(True) + self.auth_provider1.authorize(self.request, + "tenant_id", + ['Member']).AndReturn(True) self.auth_provider2.authorize(self.request, "tenant_id", ['Member']).\ AndRaise(webob.exc.HTTPForbidden("Auth Failed")) self.mock.ReplayAll() - self.assertRaisesExcMessage(webob.exc.HTTPForbidden, "Auth Failed", - self.auth_middleware, self.request) + self.assertRaisesExcMessage(webob.exc.HTTPForbidden, + "Auth Failed", + self.auth_middleware, + self.request) def test_authorizes_based_on_auth_providers(self): - self.auth_provider1.authorize(self.request, "tenant_id", ['Member']).\ - AndReturn(True) - self.auth_provider2.authorize(self.request, "tenant_id", ['Member']).\ - AndReturn(True) + self.auth_provider1.authorize(self.request, + "tenant_id", + ['Member']).AndReturn(True) + self.auth_provider2.authorize(self.request, + "tenant_id", + ['Member']).AndReturn(True) self.mock.ReplayAll() response = self.auth_middleware(self.request) self.assertEqual(response.status_int, 200) + def test_factory_adds_tenant_based_auth_as_one_of_auth_providers(self): + factory = auth.AuthorizationMiddleware.factory({}) -class DecoratorTestApp(wsgi.Router): + self.mock.StubOutWithMock(auth, 'TenantBasedAuth') + tenant_auth = self.mock.CreateMockAnything() + tenant_auth.authorize(self.request, + "tenant_id", + ['Member']).AndReturn(True) - def __init__(self): - super(DecoratorTestApp, self).__init__(mapper()) + auth.TenantBasedAuth().AndReturn(tenant_auth) + self.mock.ReplayAll() + + auth_middleware = factory(self.dummy_app) + self.assertTrue(auth_middleware(self.request)) def mapper(): @@ -89,9 +103,6 @@ def mapper(): class StubController(wsgi.Controller): - def admin_action(self, request): - pass - def unrestricted(self, request): pass @@ -172,7 +183,8 @@ class TestKeyStoneClient(tests.BaseTest): response_body = json.dumps({'auth': {'token': {'id': "auth_token"}}}) res = httplib2.Response(dict(status='200')) - client.request(urlparse.urljoin(url, "/v2.0/tokens"), "POST", + client.request(urlparse.urljoin(url, "/v2.0/tokens"), + "POST", headers=IgnoreArg(), body=request_body).AndReturn((res, response_body)) @@ -185,12 +197,14 @@ class TestKeyStoneClient(tests.BaseTest): self.mock.StubOutWithMock(client, "request") res = httplib2.Response(dict(status='401')) response_body = "Failed to get token" - client.request(urlparse.urljoin(url, "/v2.0/tokens"), "POST", + client.request(urlparse.urljoin(url, "/v2.0/tokens"), + "POST", headers=IgnoreArg(), body=IgnoreArg()).AndReturn((res, response_body)) self.mock.ReplayAll() expected_error_msg = ("Error occured while retrieving token :" " Failed to get token") - self.assertRaisesExcMessage(Exception, expected_error_msg, + self.assertRaisesExcMessage(Exception, + expected_error_msg, client.get_token) diff --git a/melange/tests/unit/test_ipam_models.py b/melange/tests/unit/test_ipam_models.py index f80aa36a..ffc5d7ed 100644 --- a/melange/tests/unit/test_ipam_models.py +++ b/melange/tests/unit/test_ipam_models.py @@ -81,16 +81,22 @@ class TestModelBase(tests.BaseTest): self.assertEqual(model.foo, True) - def test_equals(self): + def test_equals_is_true_when_ids_and_class_are_equal(self): self.assertEqual(models.ModelBase(id=1), models.ModelBase(id=1)) self.assertEqual(models.ModelBase(id=1, name="foo"), models.ModelBase(id=1, name="bar")) - def test_not_equals(self): + def test_equals_is_false_when_id_or_class_differ(self): self.assertNotEqual(models.ModelBase(), models.ModelBase()) self.assertNotEqual(models.ModelBase(id=1), models.ModelBase(id=2)) self.assertNotEqual(models.IpBlock(id=1), models.IpAddress(id=1)) + def test_hash_is_correct(self): + a = models.ModelBase(id="123", name="foo") + b = models.ModelBase(id="123", name="bar") + + self.assertEqual(hash(a), hash(b)) + class TestQuery(tests.BaseTest): @@ -492,9 +498,10 @@ class TestIpBlock(tests.BaseTest): def test_find_allocated_ip_for_nonexistent_address(self): block = factory_models.PrivateIpBlockFactory(cidr="10.0.0.1/8") - self.assertRaises(models.ModelNotFoundError, - block.find_allocated_ip, - '10.0.0.1') + self.assertRaisesExcMessage(models.ModelNotFoundError, + "IpAddress Not Found", + block.find_allocated_ip, + "10.0.0.1") def test_policy(self): policy = factory_models.PolicyFactory(name="Some Policy") @@ -1388,19 +1395,6 @@ class TestIpOctet(tests.BaseTest): self.assertEqual(data['created_at'], ip_octet.created_at) self.assertEqual(data['updated_at'], ip_octet.updated_at) - def test_find_all_by_policy(self): - policy1 = factory_models.PolicyFactory(name='blah') - policy2 = factory_models.PolicyFactory(name='blah') - ip_octet1 = factory_models.IpOctetFactory(octet=123, - policy_id=policy1.id) - ip_octet2 = factory_models.IpOctetFactory(octet=123, - policy_id=policy1.id) - noise_ip_octet = factory_models.IpOctetFactory(octet=123, - policy_id=policy2.id) - - actual_octets = models.IpOctet.find_all_by_policy(policy1.id).all() - self.assertModelsEqual(actual_octets, [ip_octet1, ip_octet2]) - def test_applies_to_is_true_if_address_last_octet_matches(self): ip_octet = factory_models.IpOctetFactory(octet=123) self.assertTrue(ip_octet.applies_to("10.0.0.123")) @@ -1532,3 +1526,19 @@ class TestNetwork(tests.BaseTest): for ip in ips: ip_address = models.IpAddress.get(ip.id) self.assertTrue(ip_address.marked_for_deallocation) + + def test_retrives_allocated_ips(self): + ip_block1 = factory_models.IpBlockFactory(network_id=1, + cidr="10.0.0.0/24") + ip_block2 = factory_models.IpBlockFactory(network_id=1, + cidr="20.0.0.0/24") + + ip1 = ip_block1.allocate_ip(interface_id="123") + ip2 = ip_block1.allocate_ip(interface_id="123") + ip3 = ip_block2.allocate_ip(interface_id="321") + ip4 = ip_block2.allocate_ip(interface_id="123") + + network = models.Network.find_by(id=1) + + self.assertModelsEqual(network.allocated_ips(interface_id="123"), + [ip1, ip2, ip4]) diff --git a/melange/tests/unit/test_wsgi.py b/melange/tests/unit/test_wsgi.py index d7a76994..fc25c347 100644 --- a/melange/tests/unit/test_wsgi.py +++ b/melange/tests/unit/test_wsgi.py @@ -237,9 +237,6 @@ class StubController(wsgi.Controller): def index(self, request, format=None): return {'fort': 'knox'} - def show(self, request, id, format=None): - return {'fort': 'knox'} - class TestController(tests.BaseTest):