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)
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,

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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')

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

@ -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}