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
This commit is contained in:
Pavel Bondar 2015-02-04 16:13:28 +03:00
parent 1e3b4f119a
commit b522896c31
7 changed files with 49 additions and 37 deletions

View File

@ -228,7 +228,7 @@ class DbBasePluginCommon(common_db_mixin.CommonDbMixin):
return self._fields(res, fields) return self._fields(res, fields)
def _make_subnet_args(self, shared, detail, 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 gateway_ip = str(detail.gateway_ip) if detail.gateway_ip else None
args = {'tenant_id': detail.tenant_id, args = {'tenant_id': detail.tenant_id,
'id': detail.subnet_id, 'id': detail.subnet_id,

View File

@ -15,7 +15,6 @@
import netaddr import netaddr
from oslo_config import cfg from oslo_config import cfg
from oslo_db import api as oslo_db_api
from oslo_db import exception as db_exc from oslo_db import exception as db_exc
from oslo_log import log as logging from oslo_log import log as logging
from oslo_utils import excutils 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.common import ipv6_utils
from neutron import context as ctx from neutron import context as ctx
from neutron.db import api as db_api 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 ipam_non_pluggable_backend
from neutron.db import models_v2 from neutron.db import models_v2
from neutron.db import sqlalchemyutils 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) 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): neutron_plugin_base_v2.NeutronPluginBaseV2):
"""V2 Neutron plugin interface implementation using SQLAlchemy models. """V2 Neutron plugin interface implementation using SQLAlchemy models.
@ -84,6 +84,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend,
__native_sorting_support = True __native_sorting_support = True
def __init__(self): def __init__(self):
self.set_ipam_backend()
if cfg.CONF.notify_nova_on_port_status_changes: if cfg.CONF.notify_nova_on_port_status_changes:
from neutron.notifiers import nova from neutron.notifiers import nova
# NOTE(arosen) These event listeners are here to hook into when # 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', event.listen(models_v2.Port.status, 'set',
self.nova_notifier.record_port_status_changed) 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): def _validate_host_route(self, route, ip_version):
try: try:
netaddr.IPNetwork(route['destination']) netaddr.IPNetwork(route['destination'])
@ -439,18 +443,15 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend,
external_gateway_info}} external_gateway_info}}
l3plugin.update_router(context, id, 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): def _create_subnet(self, context, subnet, subnetpool_id):
s = subnet['subnet'] s = subnet['subnet']
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
network = self._get_network(context, s["network_id"]) network = self._get_network(context, s["network_id"])
subnet = self._allocate_subnet(context, subnet = self.ipam.allocate_subnet(context,
network, network,
s, s,
subnetpool_id) subnetpool_id)
if hasattr(network, 'external') and network.external: if hasattr(network, 'external') and network.external:
self._update_router_gw_ports(context, self._update_router_gw_ports(context,
network, network,
@ -458,7 +459,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend,
# If this subnet supports auto-addressing, then update any # If this subnet supports auto-addressing, then update any
# internal ports on the network with addresses for this subnet. # internal ports on the network with addresses for this subnet.
if ipv6_utils.is_auto_address_subnet(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) return self._make_subnet_dict(subnet)
def _get_subnetpool_id(self, 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) s['tenant_id'] = self._get_tenant_id_for_create(context, s)
subnetpool_id = self._get_subnetpool_id(s) subnetpool_id = self._get_subnetpool_id(s)
if subnetpool_id: if subnetpool_id:
self._validate_pools_with_subnetpool(s) self.ipam.validate_pools_with_subnetpool(s)
else: else:
if not has_cidr: if not has_cidr:
msg = _('A cidr must be specified in the absence of a ' 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'], allocation_pools = [{'start': p['first_ip'],
'end': p['last_ip']} 'end': p['last_ip']}
for p in db_subnet.allocation_pools] 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): 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) result = self._make_subnet_dict(subnet)
# Keep up with fields that changed # Keep up with fields that changed
result.update(changes) result.update(changes)
@ -832,7 +834,7 @@ class NeutronDbPluginV2(ipam_non_pluggable_backend.IpamNonPluggableBackend,
db_port = self._create_port_with_mac( db_port = self._create_port_with_mac(
context, network_id, port_data, p['mac_address']) 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) 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) port = self._get_port(context, id)
new_mac = new_port.get('mac_address') new_mac = new_port.get('mac_address')
self._validate_port_for_update(context, port, new_port, new_mac) self._validate_port_for_update(context, port, new_port, new_mac)
changes = self._update_port_with_ips(context, port, changes = self.ipam.update_port_with_ips(context, port,
new_port, new_mac) new_port, new_mac)
result = self._make_port_dict(port) result = self._make_port_dict(port)
# Keep up with fields that changed # Keep up with fields that changed
if changes.original or changes.add or changes.remove: 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): def delete_port(self, context, id):
with context.session.begin(subtransactions=True): 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): def delete_ports_by_device_id(self, context, device_id, network_id=None):
query = (context.session.query(models_v2.Port.id) query = (context.session.query(models_v2.Port.id)

View File

@ -52,7 +52,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
return str(netaddr.IPNetwork(cidr_net).network + 1) return str(netaddr.IPNetwork(cidr_net).network + 1)
return subnet.get('gateway_ip') 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 """Verifies that allocation pools are set correctly
Allocation pools can be set for specific subnet request only 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'] del s['allocation_pools']
return result_pools return result_pools
def _update_db_subnet(self, context, subnet_id, s): def update_db_subnet(self, context, subnet_id, s):
changes = {} changes = {}
if "dns_nameservers" in s: if "dns_nameservers" in s:
changes['dns_nameservers'] = ( changes['dns_nameservers'] = (
@ -298,12 +298,11 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
self._validate_allocation_pools(allocation_pools, cidr) self._validate_allocation_pools(allocation_pools, cidr)
if gateway_ip: if gateway_ip:
self._validate_gw_out_of_pools(gateway_ip, self.validate_gw_out_of_pools(gateway_ip, allocation_pools)
allocation_pools)
return [netaddr.IPRange(p['start'], p['end']) return [netaddr.IPRange(p['start'], p['end'])
for p in allocation_pools] 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: for allocation_pool in pools:
pool_range = netaddr.IPRange( pool_range = netaddr.IPRange(
allocation_pool['start'], allocation_pool['start'],
@ -369,7 +368,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
original=prev_ips, original=prev_ips,
remove=remove_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). query = (context.session.query(models_v2.Port).
enable_eagerloads(False).filter_by(id=port_id)) enable_eagerloads(False).filter_by(id=port_id))
if not context.is_admin: if not context.is_admin:
@ -403,7 +402,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
nexthop=rt['nexthop']) nexthop=rt['nexthop'])
context.session.add(route) context.session.add(route)
self._save_allocation_pools(context, subnet, self.save_allocation_pools(context, subnet,
subnet_request.allocation_pools) subnet_request.allocation_pools)
return subnet return subnet

View File

@ -186,7 +186,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
return True return True
return False 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: for pool in allocation_pools:
first_ip = str(netaddr.IPAddress(pool.first, pool.version)) first_ip = str(netaddr.IPAddress(pool.first, pool.version))
last_ip = str(netaddr.IPAddress(pool.last, 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) last_ip=last_ip)
context.session.add(ip_range) 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'] network_id = port['port']['network_id']
ips = self._allocate_ips_for_port(context, port) ips = self._allocate_ips_for_port(context, port)
if ips: if ips:
@ -210,7 +210,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
self._store_ip_allocation(context, ip_address, network_id, self._store_ip_allocation(context, ip_address, network_id,
subnet_id, port_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=[]) changes = self.Changes(add=[], original=[], remove=[])
# Check if the IPs need to be updated # Check if the IPs need to be updated
network_id = db_port['network_id'] network_id = db_port['network_id']
@ -431,7 +431,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
return ips 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.""" """For an auto-address subnet, add addrs for ports on the net."""
with context.session.begin(subtransactions=True): with context.session.begin(subtransactions=True):
network_id = subnet['network_id'] network_id = subnet['network_id']
@ -470,7 +470,7 @@ class IpamNonPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
ip_address=ip_address) ip_address=ip_address)
return 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 subnetpool = None
if subnetpool_id: if subnetpool_id:
subnetpool = self._get_subnetpool(context, subnetpool_id) subnetpool = self._get_subnetpool(context, subnetpool_id)

View File

@ -40,6 +40,7 @@ from neutron.common import ipv6_utils
from neutron.common import test_lib from neutron.common import test_lib
from neutron.common import utils from neutron.common import utils
from neutron import context from neutron import context
from neutron.db import db_base_plugin_common
from neutron.db import db_base_plugin_v2 from neutron.db import db_base_plugin_v2
from neutron.db import ipam_non_pluggable_backend as non_ipam from neutron.db import ipam_non_pluggable_backend as non_ipam
from neutron.db import models_v2 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, self.assertEqual(res.status_int,
webob.exc.HTTPClientError.code) webob.exc.HTTPClientError.code)
@mock.patch.object(db_base_plugin_v2.NeutronDbPluginV2, @mock.patch.object(non_ipam.IpamNonPluggableBackend,
'_allocate_specific_ip') '_allocate_specific_ip')
def test_requested_fixed_ip_address_v6_slaac_router_iface( def test_requested_fixed_ip_address_v6_slaac_router_iface(
self, alloc_specific_ip): self, alloc_specific_ip):
@ -3812,6 +3813,9 @@ class TestSubnetsV2(NeutronDbPluginV2TestCase):
'dummy_key', 'dummy_key_table') 'dummy_key', 'dummy_key_table')
mock.patch.object(orm.Session, 'add', mock.patch.object(orm.Session, 'add',
side_effect=db_ref_err_for_ipalloc).start() 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 # Add an IPv6 auto-address subnet to the network
v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1', v6_subnet = self._make_subnet(self.fmt, network, 'fe80::1',
'fe80::/64', ip_version=6, 'fe80::/64', ip_version=6,
@ -5374,7 +5378,7 @@ class TestNeutronDbPluginV2(base.BaseTestCase):
context.session.query.side_effect = return_queries_side_effect context.session.query.side_effect = return_queries_side_effect
subnets = [mock.MagicMock()] subnets = [mock.MagicMock()]
db_base_plugin_v2.NeutronDbPluginV2._rebuild_availability_ranges( non_ipam.IpamNonPluggableBackend._rebuild_availability_ranges(
context, subnets) context, subnets)
actual = [[args[0].allocation_pool_id, actual = [[args[0].allocation_pool_id,
@ -5437,15 +5441,18 @@ class TestNeutronDbPluginV2(base.BaseTestCase):
expected) expected)
def _test__allocate_ips_for_port(self, subnets, port, 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() 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: '_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: '_check_unique_ip') as check_unique:
context = mock.Mock() context = mock.Mock()
get_subnets.return_value = subnets get_subnets.return_value = subnets
check_unique.return_value = True 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) self.assertEqual(expected, actual)
def test__allocate_ips_for_port_2_slaac_subnets(self): def test__allocate_ips_for_port_2_slaac_subnets(self):
@ -5537,7 +5544,7 @@ class NeutronDbPluginV2AsMixinTestCase(NeutronDbPluginV2TestCase,
ip_version=4)] ip_version=4)]
new_subnetpool_id = None new_subnetpool_id = None
self.assertRaises(n_exc.NetworkSubnetPoolAffinityError, self.assertRaises(n_exc.NetworkSubnetPoolAffinityError,
self.plugin._validate_network_subnetpools, self.plugin.ipam._validate_network_subnetpools,
network, new_subnetpool_id, 4) network, new_subnetpool_id, 4)

View File

@ -1577,6 +1577,8 @@ class TestMl2PluginCreateUpdateDeletePort(base.BaseTestCase):
'get_service_plugins', 'get_service_plugins',
return_value={'L3_ROUTER_NAT': l3plugin}): return_value={'L3_ROUTER_NAT': l3plugin}):
plugin = self._create_plugin_for_create_update_port(mock.Mock()) 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 # deleting the port will call registry.notify, which will
# run the transaction balancing function defined in this test # run the transaction balancing function defined in this test
plugin.delete_port(self.context, 'fake_id') plugin.delete_port(self.context, 'fake_id')

2
neutron/tests/unit/test_ipam.py Executable file → Normal file
View File

@ -314,6 +314,8 @@ class TestSubnetRequestFactory(IpamSubnetRequestTestCase):
'prefixlen': prefixlen, 'prefixlen': prefixlen,
'ip_version': ip_version, 'ip_version': ip_version,
'tenant_id': self.tenant_id, 'tenant_id': self.tenant_id,
'gateway_ip': None,
'allocation_pools': None,
'id': id or self.subnet_id} 'id': id or self.subnet_id}
subnetpool = {'ip_version': ip_version, subnetpool = {'ip_version': ip_version,
'default_prefixlen': prefixlen} 'default_prefixlen': prefixlen}