From: Pavel Bondar Date: Wed, 4 Feb 2015 13:13:28 +0000 (+0300) Subject: Allow IPAM backend switch X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=b522896c3132ebde5dfe77fd1352df91d39d90e9;p=openstack-build%2Fneutron-build.git Allow IPAM backend switch Changed inheritance chain for NeutronDbPluginV2 to allow switching from non-pluggable to pluggable IPAM implementation. IpamNonPluggableBackend methods are called on it's instance, instead of previous way where IpamNonPluggableBackend was parent for NeutronDbPluginV2. It allows switching IPAM implementation in set_ipam_backend (IpamNonPluggableBackend to IpamPluggableBackend). All methods that became public in IpamNonPluggableBackend were renamed. This is refactoring step before Pluggable IPAM can be applied. Partially-Implements: blueprint neutron-ipam Change-Id: I81806a43ecc6f0a7b293ce3e70d09d1e266b9f02 --- diff --git a/neutron/db/db_base_plugin_common.py b/neutron/db/db_base_plugin_common.py index 1b795c075..9cf1ba6bb 100644 --- a/neutron/db/db_base_plugin_common.py +++ b/neutron/db/db_base_plugin_common.py @@ -228,7 +228,7 @@ class DbBasePluginCommon(common_db_mixin.CommonDbMixin): return self._fields(res, fields) def _make_subnet_args(self, shared, detail, - subnet, subnetpool_id=None): + subnet, subnetpool_id): gateway_ip = str(detail.gateway_ip) if detail.gateway_ip else None args = {'tenant_id': detail.tenant_id, 'id': detail.subnet_id, diff --git a/neutron/db/db_base_plugin_v2.py b/neutron/db/db_base_plugin_v2.py index a30cb7fb6..d2b5f8997 100644 --- a/neutron/db/db_base_plugin_v2.py +++ b/neutron/db/db_base_plugin_v2.py @@ -15,7 +15,6 @@ import netaddr from oslo_config import cfg -from oslo_db import api as oslo_db_api from oslo_db import exception as db_exc from oslo_log import log as logging from oslo_utils import excutils @@ -33,6 +32,7 @@ from neutron.common import exceptions as n_exc from neutron.common import ipv6_utils from neutron import context as ctx from neutron.db import api as db_api +from neutron.db import db_base_plugin_common from neutron.db import ipam_non_pluggable_backend from neutron.db import models_v2 from neutron.db import sqlalchemyutils @@ -66,7 +66,7 @@ def _check_subnet_not_used(context, subnet_id): raise n_exc.SubnetInUse(subnet_id=subnet_id, reason=e) -class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, +class NeutronDbPluginV2(db_base_plugin_common.DbBasePluginCommon, neutron_plugin_base_v2.NeutronPluginBaseV2): """V2 Neutron plugin interface implementation using SQLAlchemy models. @@ -84,6 +84,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, __native_sorting_support = True def __init__(self): + self.set_ipam_backend() if cfg.CONF.notify_nova_on_port_status_changes: from neutron.notifiers import nova # NOTE(arosen) These event listeners are here to hook into when @@ -96,6 +97,9 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, event.listen(models_v2.Port.status, 'set', self.nova_notifier.record_port_status_changed) + def set_ipam_backend(self): + self.ipam = ipam_non_pluggable_backend.IpamNonPluggableBackend() + def _validate_host_route(self, route, ip_version): try: netaddr.IPNetwork(route['destination']) @@ -439,18 +443,15 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, external_gateway_info}} l3plugin.update_router(context, id, info) - @oslo_db_api.wrap_db_retry(max_retries=db_api.MAX_RETRIES, - retry_on_request=True, - retry_on_deadlock=True) def _create_subnet(self, context, subnet, subnetpool_id): s = subnet['subnet'] with context.session.begin(subtransactions=True): network = self._get_network(context, s["network_id"]) - subnet = self._allocate_subnet(context, - network, - s, - subnetpool_id) + subnet = self.ipam.allocate_subnet(context, + network, + s, + subnetpool_id) if hasattr(network, 'external') and network.external: self._update_router_gw_ports(context, network, @@ -458,7 +459,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, # If this subnet supports auto-addressing, then update any # internal ports on the network with addresses for this subnet. if ipv6_utils.is_auto_address_subnet(subnet): - self._add_auto_addrs_on_network_ports(context, subnet) + self.ipam.add_auto_addrs_on_network_ports(context, subnet) return self._make_subnet_dict(subnet) def _get_subnetpool_id(self, subnet): @@ -514,7 +515,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, s['tenant_id'] = self._get_tenant_id_for_create(context, s) subnetpool_id = self._get_subnetpool_id(s) if subnetpool_id: - self._validate_pools_with_subnetpool(s) + self.ipam.validate_pools_with_subnetpool(s) else: if not has_cidr: msg = _('A cidr must be specified in the absence of a ' @@ -548,10 +549,11 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, allocation_pools = [{'start': p['first_ip'], 'end': p['last_ip']} for p in db_subnet.allocation_pools] - self._validate_gw_out_of_pools(s["gateway_ip"], allocation_pools) + self.ipam.validate_gw_out_of_pools(s["gateway_ip"], + allocation_pools) with context.session.begin(subtransactions=True): - subnet, changes = self._update_db_subnet(context, id, s) + subnet, changes = self.ipam.update_db_subnet(context, id, s) result = self._make_subnet_dict(subnet) # Keep up with fields that changed result.update(changes) @@ -832,7 +834,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, db_port = self._create_port_with_mac( context, network_id, port_data, p['mac_address']) - self._allocate_ips_for_port_and_store(context, port, port_id) + self.ipam.allocate_ips_for_port_and_store(context, port, port_id) return self._make_port_dict(db_port, process_extensions=False) @@ -859,8 +861,8 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, port = self._get_port(context, id) new_mac = new_port.get('mac_address') self._validate_port_for_update(context, port, new_port, new_mac) - changes = self._update_port_with_ips(context, port, - new_port, new_mac) + changes = self.ipam.update_port_with_ips(context, port, + new_port, new_mac) result = self._make_port_dict(port) # Keep up with fields that changed if changes.original or changes.add or changes.remove: @@ -870,7 +872,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend, def delete_port(self, context, id): with context.session.begin(subtransactions=True): - self._delete_port(context, id) + self.ipam.delete_port(context, id) def delete_ports_by_device_id(self, context, device_id, network_id=None): query = (context.session.query(models_v2.Port.id) diff --git a/neutron/db/ipam_backend_mixin.py b/neutron/db/ipam_backend_mixin.py index f50d160a3..d4de20937 100644 --- a/neutron/db/ipam_backend_mixin.py +++ b/neutron/db/ipam_backend_mixin.py @@ -52,7 +52,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): return str(netaddr.IPNetwork(cidr_net).network + 1) return subnet.get('gateway_ip') - def _validate_pools_with_subnetpool(self, subnet): + def validate_pools_with_subnetpool(self, subnet): """Verifies that allocation pools are set correctly Allocation pools can be set for specific subnet request only @@ -155,7 +155,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): del s['allocation_pools'] return result_pools - def _update_db_subnet(self, context, subnet_id, s): + def update_db_subnet(self, context, subnet_id, s): changes = {} if "dns_nameservers" in s: changes['dns_nameservers'] = ( @@ -298,12 +298,11 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): self._validate_allocation_pools(allocation_pools, cidr) if gateway_ip: - self._validate_gw_out_of_pools(gateway_ip, - allocation_pools) + self.validate_gw_out_of_pools(gateway_ip, allocation_pools) return [netaddr.IPRange(p['start'], p['end']) for p in allocation_pools] - def _validate_gw_out_of_pools(self, gateway_ip, pools): + def validate_gw_out_of_pools(self, gateway_ip, pools): for allocation_pool in pools: pool_range = netaddr.IPRange( allocation_pool['start'], @@ -369,7 +368,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): original=prev_ips, remove=remove_ips) - def _delete_port(self, context, port_id): + def delete_port(self, context, port_id): query = (context.session.query(models_v2.Port). enable_eagerloads(False).filter_by(id=port_id)) if not context.is_admin: @@ -403,7 +402,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon): nexthop=rt['nexthop']) context.session.add(route) - self._save_allocation_pools(context, subnet, - subnet_request.allocation_pools) + self.save_allocation_pools(context, subnet, + subnet_request.allocation_pools) return subnet diff --git a/neutron/db/ipam_non_pluggable_backend.py b/neutron/db/ipam_non_pluggable_backend.py index 45ba102f6..9346bcb7b 100644 --- a/neutron/db/ipam_non_pluggable_backend.py +++ b/neutron/db/ipam_non_pluggable_backend.py @@ -186,7 +186,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin): return True return False - def _save_allocation_pools(self, context, subnet, allocation_pools): + def save_allocation_pools(self, context, subnet, allocation_pools): for pool in allocation_pools: first_ip = str(netaddr.IPAddress(pool.first, pool.version)) last_ip = str(netaddr.IPAddress(pool.last, pool.version)) @@ -200,7 +200,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin): last_ip=last_ip) context.session.add(ip_range) - def _allocate_ips_for_port_and_store(self, context, port, port_id): + def allocate_ips_for_port_and_store(self, context, port, port_id): network_id = port['port']['network_id'] ips = self._allocate_ips_for_port(context, port) if ips: @@ -210,7 +210,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin): self._store_ip_allocation(context, ip_address, network_id, subnet_id, port_id) - def _update_port_with_ips(self, context, db_port, new_port, new_mac): + def update_port_with_ips(self, context, db_port, new_port, new_mac): changes = self.Changes(add=[], original=[], remove=[]) # Check if the IPs need to be updated network_id = db_port['network_id'] @@ -431,7 +431,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin): return ips - def _add_auto_addrs_on_network_ports(self, context, subnet): + def add_auto_addrs_on_network_ports(self, context, subnet): """For an auto-address subnet, add addrs for ports on the net.""" with context.session.begin(subtransactions=True): network_id = subnet['network_id'] @@ -470,7 +470,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin): ip_address=ip_address) return ip_address - def _allocate_subnet(self, context, network, subnet, subnetpool_id): + def allocate_subnet(self, context, network, subnet, subnetpool_id): subnetpool = None if subnetpool_id: subnetpool = self._get_subnetpool(context, subnetpool_id) diff --git a/neutron/tests/unit/db/test_db_base_plugin_v2.py b/neutron/tests/unit/db/test_db_base_plugin_v2.py index c396213c9..faab63dcf 100644 --- a/neutron/tests/unit/db/test_db_base_plugin_v2.py +++ b/neutron/tests/unit/db/test_db_base_plugin_v2.py @@ -40,6 +40,7 @@ from neutron.common import ipv6_utils from neutron.common import test_lib from neutron.common import utils from neutron import context +from neutron.db import db_base_plugin_common from neutron.db import db_base_plugin_v2 from neutron.db import ipam_non_pluggable_backend as non_ipam from neutron.db import models_v2 @@ -1626,7 +1627,7 @@ fixed_ips=ip_address%%3D%s&fixed_ips=ip_address%%3D%s&fixed_ips=subnet_id%%3D%s self.assertEqual(res.status_int, webob.exc.HTTPClientError.code) - @mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, + @mock.patch.object(non_ipam.IpamNonPluggableBackend, '_allocate_specific_ip') def test_requested_fixed_ip_address_v6_slaac_router_iface( self, alloc_specific_ip): @@ -3812,6 +3813,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase): 'dummy_key', 'dummy_key_table') mock.patch.object(orm.Session, 'add', side_effect=db_ref_err_for_ipalloc).start() + mock.patch.object(non_ipam.IpamNonPluggableBackend, + '_get_subnet', + return_value=mock.Mock()).start() # Add an IPv6 auto-address subnet to the network v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1', 'fe80::/64', ip_version=6, @@ -5374,7 +5378,7 @@ class TestNeutronDbPluginV2(base.BaseTestCase): context.session.query.side_effect = return_queries_side_effect subnets = [mock.MagicMock()] - db_base_plugin_v2.NeutronDbPluginV2._rebuild_availability_ranges( + non_ipam.IpamNonPluggableBackend._rebuild_availability_ranges( context, subnets) actual = [[args[0].allocation_pool_id, @@ -5437,15 +5441,18 @@ class TestNeutronDbPluginV2(base.BaseTestCase): expected) def _test__allocate_ips_for_port(self, subnets, port, expected): + # this test is incompatible with pluggable ipam, because subnets + # were not actually created, so no ipam_subnet exists + cfg.CONF.set_override("ipam_driver", None) plugin = db_base_plugin_v2.NeutronDbPluginV2() - with mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, + with mock.patch.object(db_base_plugin_common.DbBasePluginCommon, '_get_subnets') as get_subnets: - with mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, + with mock.patch.object(non_ipam.IpamNonPluggableBackend, '_check_unique_ip') as check_unique: context = mock.Mock() get_subnets.return_value = subnets check_unique.return_value = True - actual = plugin._allocate_ips_for_port(context, port) + actual = plugin.ipam._allocate_ips_for_port(context, port) self.assertEqual(expected, actual) def test__allocate_ips_for_port_2_slaac_subnets(self): @@ -5537,7 +5544,7 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase, ip_version=4)] new_subnetpool_id = None self.assertRaises(n_exc.NetworkSubnetPoolAffinityError, - self.plugin._validate_network_subnetpools, + self.plugin.ipam._validate_network_subnetpools, network, new_subnetpool_id, 4) diff --git a/neutron/tests/unit/plugins/ml2/test_plugin.py b/neutron/tests/unit/plugins/ml2/test_plugin.py index dc4f9ea3f..7f8fe7dbf 100644 --- a/neutron/tests/unit/plugins/ml2/test_plugin.py +++ b/neutron/tests/unit/plugins/ml2/test_plugin.py @@ -1577,6 +1577,8 @@ class TestMl2PluginCreateUpdateDeletePort(base.BaseTestCase): 'get_service_plugins', return_value={'L3_ROUTER_NAT': l3plugin}): plugin = self._create_plugin_for_create_update_port(mock.Mock()) + # Set backend manually here since __init__ was mocked + plugin.set_ipam_backend() # deleting the port will call registry.notify, which will # run the transaction balancing function defined in this test plugin.delete_port(self.context, 'fake_id') diff --git a/neutron/tests/unit/test_ipam.py b/neutron/tests/unit/test_ipam.py old mode 100755 new mode 100644 index 932159432..0b5bbe280 --- a/neutron/tests/unit/test_ipam.py +++ b/neutron/tests/unit/test_ipam.py @@ -314,6 +314,8 @@ class TestSubnetRequestFactory(IpamSubnetRequestTestCase): 'prefixlen': prefixlen, 'ip_version': ip_version, 'tenant_id': self.tenant_id, + 'gateway_ip': None, + 'allocation_pools': None, 'id': id or self.subnet_id} subnetpool = {'ip_version': ip_version, 'default_prefixlen': prefixlen}