diff --git a/melange/db/sqlalchemy/api.py b/melange/db/sqlalchemy/api.py index 3a7e371b..199204f9 100644 --- a/melange/db/sqlalchemy/api.py +++ b/melange/db/sqlalchemy/api.py @@ -218,7 +218,7 @@ def find_allowed_ips(ip_address_model, return query -def remove_allowd_ip(**conditions): +def remove_allowed_ip(**conditions): _query_by(mappers.AllowedIp).\ filter_by(**conditions).\ delete() diff --git a/melange/ipam/models.py b/melange/ipam/models.py index 63815241..c983255c 100644 --- a/melange/ipam/models.py +++ b/melange/ipam/models.py @@ -266,9 +266,9 @@ class IpBlock(ModelBase): 'netmask'] @classmethod - def find_allocated_ip(cls, ip_block_id, address, tenant_id): + def find_allocated_ip(cls, ip_block_id, tenant_id, **conditions): block = IpBlock.find_by(id=ip_block_id, tenant_id=tenant_id) - allocated_ip = block.find_ip(address=address) + allocated_ip = block.find_ip(**conditions) if allocated_ip.locked(): raise AddressLockedError() return allocated_ip @@ -406,8 +406,8 @@ class IpBlock(ModelBase): other_network = netaddr.IPNetwork(other_block.cidr) return network in other_network or other_network in network - def find_ip(self, address): - return IpAddress.find_by(ip_block_id=self.id, address=address) + def find_ip(self, **kwargs): + return IpAddress.find_by(ip_block_id=self.id, **kwargs) def deallocate_ip(self, address): ip_address = IpAddress.find_by(ip_block_id=self.id, address=address) @@ -822,7 +822,7 @@ class Interface(ModelBase): db_api.save_allowed_ip(self.id, ip.id) def disallow_ip(self, ip): - db_api.remove_allowd_ip(interface_id=self.id, ip_address_id=ip.id) + db_api.remove_allowed_ip(interface_id=self.id, ip_address_id=ip.id) def ips_allowed(self): explicitly_allowed = Query(IpAddress, @@ -972,6 +972,17 @@ class Network(ModelBase): for ip in ips: ip.deallocate() + def find_allocated_ip(self, **conditions): + for ip_block in self.ip_blocks: + try: + return IpAddress.find_by(ip_block_id=ip_block.id, **conditions) + except ModelNotFoundError: + pass + raise ModelNotFoundError(_("IpAddress with %(conditions)s for " + "network %(network)s not found") + % (dict(conditions=conditions, + network=self.id))) + def _block_partitions(self): return [[block for block in self.ip_blocks if not block.is_ipv6()], diff --git a/melange/ipam/service.py b/melange/ipam/service.py index 8346f068..c15ef97e 100644 --- a/melange/ipam/service.py +++ b/melange/ipam/service.py @@ -131,7 +131,7 @@ class IpAddressController(BaseController): def show(self, request, address, ip_block_id, tenant_id): ip_block = self._find_block(id=ip_block_id, tenant_id=tenant_id) - return dict(ip_address=ip_block.find_ip(address).data()) + return dict(ip_address=ip_block.find_ip(address=address).data()) def delete(self, request, address, ip_block_id, tenant_id): ip_block = self._find_block(id=ip_block_id, tenant_id=tenant_id) @@ -152,7 +152,7 @@ class IpAddressController(BaseController): def restore(self, request, ip_block_id, address, tenant_id, body=None): ip_block = self._find_block(id=ip_block_id, tenant_id=tenant_id) - ip_address = ip_block.find_ip(address) + ip_address = ip_block.find_ip(address=address) ip_address.restore() @@ -212,18 +212,18 @@ class InsideGlobalsController(BaseController): def create(self, request, ip_block_id, address, tenant_id, body=None): local_ip = models.IpBlock.find_allocated_ip(ip_block_id, - address, - tenant_id) + tenant_id, + address=address) addresses = body['ip_addresses'] global_ips = [models.IpBlock.find_allocated_ip(ip["ip_block_id"], - ip["ip_address"], - tenant_id) + tenant_id, + address=ip["ip_address"]) for ip in addresses] local_ip.add_inside_globals(global_ips) def index(self, request, ip_block_id, tenant_id, address): ip_block = models.IpBlock.find_by(id=ip_block_id, tenant_id=tenant_id) - ip = ip_block.find_ip(address) + ip = ip_block.find_ip(address=address) global_ips, marker = ip.inside_globals().paginated_collection( **self._extract_limits(request.params)) return dict(ip_addresses=[ip.data() for ip in global_ips]) @@ -231,7 +231,7 @@ class InsideGlobalsController(BaseController): def delete(self, request, ip_block_id, address, tenant_id, inside_globals_address=None): ip_block = models.IpBlock.find_by(id=ip_block_id, tenant_id=tenant_id) - local_ip = ip_block.find_ip(address) + local_ip = ip_block.find_ip(address=address) local_ip.remove_inside_globals(inside_globals_address) @@ -239,20 +239,22 @@ class InsideLocalsController(BaseController): def create(self, request, ip_block_id, address, tenant_id, body=None): global_ip = models.IpBlock.find_allocated_ip(ip_block_id, - address, - tenant_id) + tenant_id, + address=address, + ) addresses = body['ip_addresses'] local_ips = [models.IpBlock.find_allocated_ip(ip["ip_block_id"], - ip["ip_address"], - tenant_id) + tenant_id, + address=ip["ip_address"], + ) for ip in addresses] global_ip.add_inside_locals(local_ips) def index(self, request, ip_block_id, address, tenant_id): ip_block = models.IpBlock.find_by(id=ip_block_id, tenant_id=tenant_id) - ip = ip_block.find_ip(address) + ip = ip_block.find_ip(address=address) local_ips, marker = ip.inside_locals().paginated_collection( **self._extract_limits(request.params)) return dict(ip_addresses=[ip.data() for ip in local_ips]) @@ -260,7 +262,7 @@ class InsideLocalsController(BaseController): def delete(self, request, ip_block_id, address, tenant_id, inside_locals_address=None): ip_block = models.IpBlock.find_by(id=ip_block_id, tenant_id=tenant_id) - global_ip = ip_block.find_ip(address) + global_ip = ip_block.find_ip(address=address) global_ip.remove_inside_locals(inside_locals_address) @@ -428,6 +430,23 @@ class MacAddressRangesController(BaseController): return wsgi.Result(dict(mac_address_range=mac_range.data()), 201) +class InterfaceAllowedIpsController(BaseController): + + def index(self, request, interface_id, tenant_id): + interface = models.Interface.find_by(virtual_interface_id=interface_id, + tenant_id=tenant_id) + return dict(ip_addresses=[ip.data() for ip in interface.ips_allowed()]) + + def create(self, request, interface_id, tenant_id, body=None): + params = self._extract_required_params(body, 'allowed_ip') + interface = models.Interface.find_by( + virtual_interface_id=interface_id, tenant_id=tenant_id) + network = models.Network.find_by(id=params['network_id']) + ip = network.find_allocated_ip(address=params['ip_address']) + interface.allow_ip(ip) + return wsgi.Result(dict(ip_address=ip.data()), 201) + + class API(wsgi.Router): def __init__(self): @@ -469,12 +488,19 @@ class API(wsgi.Router): def _interface_mapper(self, mapper): interface_res = InterfacesController().create_resource() - path = ("/ipam/interfaces") - mapper.resource("ip_interfaces", path, controller=interface_res) - self._connect(mapper, "/ipam/tenants/{tenant_id}/interfaces/{id}", + interface_allowed_ips = InterfaceAllowedIpsController() + path = "/ipam/interfaces" + mapper.resource("interfaces", path, controller=interface_res) + self._connect(mapper, + "/ipam/tenants/{tenant_id}/interfaces/{id}", controller=interface_res, action="show", conditions=dict(method=['GET'])) + mapper.resource("allowed_ips", + "/allowed_ips", + controller=interface_allowed_ips.create_resource(), + path_prefix=("/ipam/tenants/{tenant_id}/" + "interfaces/{interface_id}")) def _mac_address_range_mapper(self, mapper): range_res = MacAddressRangesController().create_resource() @@ -530,8 +556,7 @@ class API(wsgi.Router): SubnetController().create_resource(), block_as_parent) - def _subnet_mapper(self, mapper, subnet_controller, - parent_resource): + def _subnet_mapper(self, mapper, subnet_controller, parent_resource): path_prefix = "%s/{%s_id}" % (parent_resource["collection_path"], parent_resource["member_name"]) with mapper.submapper(controller=subnet_controller, diff --git a/melange/tests/unit/test_ipam_models.py b/melange/tests/unit/test_ipam_models.py index d7254658..2d6d0ae6 100644 --- a/melange/tests/unit/test_ipam_models.py +++ b/melange/tests/unit/test_ipam_models.py @@ -469,7 +469,7 @@ class TestIpBlock(tests.BaseTest): def test_find_ip(self): block = factory_models.PrivateIpBlockFactory(cidr="10.0.0.1/8") ip = _allocate_ip(block) - self.assertEqual(block.find_ip(ip.address).id, ip.id) + self.assertEqual(block.find_ip(address=ip.address).id, ip.id) def test_find_ip_for_nonexistent_address(self): block = factory_models.PrivateIpBlockFactory(cidr="10.0.0.1/8") @@ -477,7 +477,7 @@ class TestIpBlock(tests.BaseTest): self.assertRaisesExcMessage(models.ModelNotFoundError, "IpAddress Not Found", block.find_ip, - "10.0.0.1") + address="10.0.0.1") def test_policy(self): policy = factory_models.PolicyFactory(name="Some Policy") @@ -763,9 +763,8 @@ class TestIpBlock(tests.BaseTest): block = factory_models.PrivateIpBlockFactory() actual_ip = _allocate_ip(block) - expected_ip = models.IpBlock.find_allocated_ip(block.id, - actual_ip.address, - block.tenant_id) + expected_ip = models.IpBlock.find_allocated_ip( + block.id, block.tenant_id, address=actual_ip.address) self.assertEqual(expected_ip, actual_ip) @@ -778,8 +777,8 @@ class TestIpBlock(tests.BaseTest): self.assertRaises(models.AddressLockedError, models.IpBlock.find_allocated_ip, block.id, - ip.address, - block.tenant_id) + block.tenant_id, + address=ip.address) def test_find_allocated_ip_when_ip_block_not_belongs_to_tenant(self): block = factory_models.PrivateIpBlockFactory(cidr="10.0.0.0/30") @@ -788,8 +787,8 @@ class TestIpBlock(tests.BaseTest): self.assertRaises(models.ModelNotFoundError, models.IpBlock.find_allocated_ip, block.id, - ip.address, - "wrong_tenant_id_for_block") + "wrong_tenant_id", + address=ip.address) def test_deallocate_ip(self): interface = factory_models.InterfaceFactory() @@ -801,8 +800,8 @@ class TestIpBlock(tests.BaseTest): self.assertRaises(models.AddressLockedError, models.IpBlock.find_allocated_ip, block.id, - ip.address, - block.tenant_id) + block.tenant_id, + address=ip.address) self.assertRaises(models.DuplicateAddressError, block.allocate_ip, @@ -1960,6 +1959,30 @@ class TestNetwork(tests.BaseTest): self.assertModelsEqual(allocated_ips, [ip1, ip2, ip4]) + def test_find_allocated_ip(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") + noise_block = factory_models.IpBlockFactory(network_id=1, + cidr="30.0.0.0/24") + ip1 = _allocate_ip(ip_block1) + ip2 = _allocate_ip(ip_block2) + network = models.Network.find_by(id=1) + + self.assertEqual(network.find_allocated_ip(address=ip1.address), ip1) + self.assertEqual(network.find_allocated_ip(address=ip2.address), ip2) + + def test_find_allocated_ip_raises_model_not_found_for_invalid_ip(self): + ip_block = factory_models.IpBlockFactory(network_id=1, + cidr="10.0.0.0/24") + network = models.Network.find_by(id=1) + self.assertRaisesExcMessage(models.ModelNotFoundError, + ("IpAddress with {'address': '10.1.1.1'} " + "for network %s not found" % network.id), + network.find_allocated_ip, + address="10.1.1.1") + class TestInterface(tests.BaseTest): diff --git a/melange/tests/unit/test_ipam_service.py b/melange/tests/unit/test_ipam_service.py index fae3673f..8cfe8485 100644 --- a/melange/tests/unit/test_ipam_service.py +++ b/melange/tests/unit/test_ipam_service.py @@ -2228,6 +2228,44 @@ class TestMacAddressRangesController(BaseTestController): _data(mac_range)) +class TestInterfaceAllowedIpsController(BaseTestController): + + def test_index(self): + interface = factory_models.InterfaceFactory( + tenant_id="tnt_id", virtual_interface_id="vif_id") + noise_interface = factory_models.InterfaceFactory() + ip1 = factory_models.IpAddressFactory() + ip2 = factory_models.IpAddressFactory() + ip3 = factory_models.IpAddressFactory() + ip4 = factory_models.IpAddressFactory() + interface.allow_ip(ip1) + interface.allow_ip(ip2) + interface.allow_ip(ip3) + noise_interface.allow_ip(ip3) + noise_interface.allow_ip(ip4) + + response = self.app.get( + "/ipam/tenants/tnt_id/interfaces/vif_id/allowed_ips") + + self.assertItemsEqual(response.json['ip_addresses'], + _data([ip1, ip2, ip3])) + + def test_create(self): + interface = factory_models.InterfaceFactory( + tenant_id="tnt_id", virtual_interface_id="vif_id") + block = factory_models.IpBlockFactory(network_id="net123") + ip = block.allocate_ip(factory_models.InterfaceFactory( + tenant_id="tnt_id")) + + response = self.app.post_json( + ("/ipam/tenants/tnt_id/interfaces/%s/allowed_ips" + % interface.virtual_interface_id), + {'allowed_ip': {'network_id': "net123", 'ip_address': ip.address}}) + + self.assertEqual(response.status_int, 201) + self.assertEqual(response.json['ip_address'], _data(ip)) + + def _allocate_ips(*args): interface = factory_models.InterfaceFactory() return [models.sort([_allocate_ip(ip_block, interface=interface) diff --git a/run_tests.sh b/run_tests.sh index 771f64d5..81711f88 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -86,7 +86,7 @@ function run_pep8 { GLOBIGNORE="$ignore_scripts:$ignore_files" srcfiles=`find bin -type f ! -name "melange.conf*"` srcfiles+=" `find tools/*`" - srcfiles+=" setup.py bin" + srcfiles+=" setup.py bin melange" # Just run PEP8 in current environment # # NOTE(sirp): W602 (deprecated 3-arg raise) is being ignored for the