Separate L3 APIC Driver

This separates the APIC-specific pieces into its own
driver.

Change-Id: Ifd566c621108a17a30117d946dfe1fa8f472c5b4
Closes-Bug: #1577446
(cherry picked from commit 915404c2fd)
This commit is contained in:
Thomas Bachman
2016-03-29 10:26:44 -04:00
committed by Thomas Bachman
parent d68ece2eb8
commit 15851fdc00
4 changed files with 366 additions and 109 deletions

View File

@@ -0,0 +1,144 @@
# Copyright (c) 2016 Cisco Systems Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from neutron.common import constants as q_const
from neutron import context as n_ctx
from neutron.extensions import l3
from neutron import manager
from apic_ml2.neutron.services.l3_router import apic_driver_api
class ApicGBPL3Driver(apic_driver_api.ApicL3DriverBase):
def __init__(self, plugin):
super(ApicGBPL3Driver, self).__init__()
self._plugin = plugin
self._apic_gbp = None
@property
def apic_gbp(self):
if not self._apic_gbp:
self._apic_gbp = manager.NeutronManager.get_service_plugins()[
'GROUP_POLICY'].policy_driver_manager.policy_drivers[
'apic'].obj
return self._apic_gbp
def _get_port_id_for_router_interface(self, context, router_id, subnet_id):
filters = {'device_id': [router_id],
'device_owner': [q_const.DEVICE_OWNER_ROUTER_INTF],
'fixed_ips': {'subnet_id': [subnet_id]}}
ports = self._plugin._core_plugin.get_ports(context.elevated(),
filters=filters)
return ports[0]['id']
def _update_router_gw_info(self, context, router_id, info, router=None):
super(ApicGBPL3Driver, self)._update_router_gw_info(
context, router_id, info, router)
if info and 'network_id' in info:
filters = {'device_id': [router_id],
'device_owner': [q_const.DEVICE_OWNER_ROUTER_GW],
'network_id': [info['network_id']]}
ports = self._plugin._core_plugin.get_ports(context.elevated(),
filters=filters)
self._plugin._core_plugin.update_port_status(
context, ports[0]['id'], q_const.PORT_STATUS_ACTIVE)
def add_router_interface_postcommit(self, context, router_id,
interface_info):
if 'subnet_id' in interface_info:
port_id = self._get_port_id_for_router_interface(
context, router_id, interface_info['subnet_id'])
else:
port_id = interface_info['port_id']
self._plugin._core_plugin.update_port_status(context,
port_id, q_const.PORT_STATUS_ACTIVE)
def remove_router_interface_precommit(self, context, router_id,
interface_info):
if 'subnet_id' in interface_info:
port_id = self._get_port_id_for_router_interface(
context, router_id, interface_info['subnet_id'])
else:
port_id = interface_info['port_id']
self._plugin._core_plugin.update_port_status(context,
port_id, q_const.PORT_STATUS_DOWN)
# Floating IP API
def create_floatingip_precommit(self, context, floatingip):
fip = floatingip['floatingip']
tenant_id = self._plugin._get_tenant_id_for_create(context, fip)
if self.apic_gbp:
context.nat_pool_list = []
for nat_pool in self.apic_gbp.nat_pool_iterator(context,
tenant_id, floatingip):
context.nat_pool_list.append(nat_pool)
def create_floatingip_postcommit(self, context, floatingip):
port_id = floatingip.get('floatingip', {}).get('port_id')
self._notify_port_update(port_id)
if getattr(context, 'result', None):
context.result['status'] = self._update_floatingip_status(
context, context.result['id'])
def update_floatingip_precommit(self, context, id, floatingip):
port_id = self._get_port_mapped_to_floatingip(context, id)
context.port_id_list = [port_id]
def update_floatingip_postcommit(self, context, id, floatingip):
port_id_list = getattr(context, 'port_id_list', [])
port_id_list.append(
floatingip.get('floatingip', {}).get('port_id'))
for p in port_id_list:
self._notify_port_update(p)
status = self._update_floatingip_status(context, id)
if getattr(context, 'result', None):
context.result['status'] = status
def delete_floatingip_precommit(self, context, id):
port_id_list = [self._get_port_mapped_to_floatingip(context, id)]
context.port_id_list = port_id_list
def delete_floatingip_postcommit(self, context, id):
self._notify_port_update(context.port_id_list[0])
def _notify_port_update(self, port_id):
context = n_ctx.get_admin_context()
if self.apic_gbp and port_id:
self.apic_gbp._notify_port_update(context, port_id)
ptg, _ = self.apic_gbp._port_id_to_ptg(context, port_id)
if ptg:
self.apic_gbp._notify_head_chain_ports(ptg['id'])
def _update_floatingip_status(self, context, fip_id):
status = q_const.FLOATINGIP_STATUS_DOWN
try:
fip = self._plugin.get_floatingip(context, fip_id)
if fip.get('port_id'):
status = q_const.FLOATINGIP_STATUS_ACTIVE
self._plugin.update_floatingip_status(context, fip_id, status)
except l3.FloatingIPNotFound:
pass
return status
def _get_port_mapped_to_floatingip(self, context, fip_id):
try:
fip = self._plugin.get_floatingip(context, fip_id)
return fip.get('port_id')
except l3.FloatingIPNotFound:
pass
return None

View File

@@ -16,17 +16,16 @@
from neutron._i18n import _LW
from neutron.common import constants as q_const
from neutron.common import exceptions as n_exc
from neutron import context as n_ctx
from neutron.db import common_db_mixin
from neutron.db import extraroute_db
from neutron.db import l3_gwmode_db
from neutron.extensions import l3
from neutron import manager
from neutron.plugins.common import constants
from oslo_log import log as logging
LOG = logging.getLogger(__name__)
from gbpservice.neutron.services.l3_router import apic_driver
class ApicGBPL3ServicePlugin(common_db_mixin.CommonDbMixin,
extraroute_db.ExtraRoute_db_mixin,
@@ -36,15 +35,7 @@ class ApicGBPL3ServicePlugin(common_db_mixin.CommonDbMixin,
def __init__(self):
super(ApicGBPL3ServicePlugin, self).__init__()
self._apic_gbp = None
def _get_port_id_for_router_interface(self, context, router_id, subnet_id):
filters = {'device_id': [router_id],
'device_owner': [q_const.DEVICE_OWNER_ROUTER_INTF],
'fixed_ips': {'subnet_id': [subnet_id]}}
ports = self._core_plugin.get_ports(context.elevated(),
filters=filters)
return ports[0]['id']
self._apic_driver = apic_driver.ApicGBPL3Driver(self)
def _update_router_gw_info(self, context, router_id, info, router=None):
super(ApicGBPL3ServicePlugin, self)._update_router_gw_info(
@@ -67,109 +58,57 @@ class ApicGBPL3ServicePlugin(common_db_mixin.CommonDbMixin,
"""Returns string description of the plugin."""
return _("L3 Router Service Plugin for basic L3 using the APIC")
@property
def apic_gbp(self):
if not self._apic_gbp:
self._apic_gbp = manager.NeutronManager.get_service_plugins()[
'GROUP_POLICY'].policy_driver_manager.policy_drivers[
'apic'].obj
return self._apic_gbp
def add_router_interface(self, context, router_id, interface_info):
port = super(ApicGBPL3ServicePlugin, self).add_router_interface(
context, router_id, interface_info)
if 'subnet_id' in interface_info:
port_id = self._get_port_id_for_router_interface(
context, router_id, interface_info['subnet_id'])
else:
port_id = interface_info['port_id']
self._core_plugin.update_port_status(context,
port_id, q_const.PORT_STATUS_ACTIVE)
self._apic_driver.add_router_interface_postcommit(
context, router_id, interface_info)
return port
def remove_router_interface(self, context, router_id, interface_info):
if 'subnet_id' in interface_info:
port_id = self._get_port_id_for_router_interface(
context, router_id, interface_info['subnet_id'])
else:
port_id = interface_info['port_id']
self._core_plugin.update_port_status(context,
port_id, q_const.PORT_STATUS_DOWN)
self._apic_driver.remove_router_interface_precommit(
context, router_id, interface_info)
super(ApicGBPL3ServicePlugin, self).remove_router_interface(
context, router_id, interface_info)
# Floating IP API
def create_floatingip(self, context, floatingip):
res = None
result = None
fip = floatingip['floatingip']
if self.apic_gbp and not fip.get('subnet_id'):
tenant_id = self._get_tenant_id_for_create(context, fip)
for nat_pool in self.apic_gbp.nat_pool_iterator(context,
tenant_id, floatingip):
if not fip.get('subnet_id'):
self._apic_driver.create_floatingip_precommit(context, floatingip)
nat_pool_list = getattr(context, 'nat_pool_list', [])
for nat_pool in nat_pool_list:
if not nat_pool:
continue
fip['subnet_id'] = nat_pool['subnet_id']
try:
res = super(ApicGBPL3ServicePlugin,
self).create_floatingip(context, floatingip)
result = super(ApicGBPL3ServicePlugin,
self).create_floatingip(context, floatingip)
except n_exc.IpAddressGenerationFailure as ex:
LOG.warning(_LW("Floating allocation failed: %s"),
ex.message)
if res:
if result:
break
if not res:
res = super(ApicGBPL3ServicePlugin, self).create_floatingip(
context, floatingip)
port_id = floatingip.get('floatingip', {}).get('port_id')
self._notify_port_update(port_id)
if res:
res['status'] = self._update_floatingip_status(context, res['id'])
return res
if not result:
result = super(ApicGBPL3ServicePlugin,
self).create_floatingip(context, floatingip)
context.result = result
self._apic_driver.create_floatingip_postcommit(context, floatingip)
return result
def update_floatingip(self, context, id, floatingip):
port_id = [self._get_port_mapped_to_floatingip(context, id)]
res = super(ApicGBPL3ServicePlugin, self).update_floatingip(
context, id, floatingip)
port_id.append(floatingip.get('floatingip', {}).get('port_id'))
for p in port_id:
self._notify_port_update(p)
status = self._update_floatingip_status(context, id)
if res:
res['status'] = status
return res
self._apic_driver.update_floatingip_precommit(context, id, floatingip)
result = super(ApicGBPL3ServicePlugin,
self).update_floatingip(context, id, floatingip)
context.result = result
self._apic_driver.update_floatingip_postcommit(context,
id, floatingip)
return result
def delete_floatingip(self, context, id):
port_id = self._get_port_mapped_to_floatingip(context, id)
res = super(ApicGBPL3ServicePlugin, self).delete_floatingip(
context, id)
self._notify_port_update(port_id)
return res
def _get_port_mapped_to_floatingip(self, context, fip_id):
try:
fip = self.get_floatingip(context, fip_id)
return fip.get('port_id')
except l3.FloatingIPNotFound:
pass
return None
def _notify_port_update(self, port_id):
context = n_ctx.get_admin_context()
if self.apic_gbp and port_id:
self.apic_gbp._notify_port_update(context, port_id)
ptg, _ = self.apic_gbp._port_id_to_ptg(context, port_id)
if ptg:
self.apic_gbp._notify_head_chain_ports(ptg['id'])
def _update_floatingip_status(self, context, fip_id):
status = q_const.FLOATINGIP_STATUS_DOWN
try:
fip = self.get_floatingip(context, fip_id)
if fip.get('port_id'):
status = q_const.FLOATINGIP_STATUS_ACTIVE
self.update_floatingip_status(context, fip_id, status)
except l3.FloatingIPNotFound:
pass
return status
self._apic_driver.delete_floatingip_precommit(context, id)
result = super(ApicGBPL3ServicePlugin,
self).delete_floatingip(context, id)
self._apic_driver.delete_floatingip_postcommit(context, id)
return result

View File

@@ -0,0 +1,171 @@
# Copyright (c) 2016 Cisco Systems
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import mock
from neutron.common import constants as q_const
from neutron import context
import apicapi.apic_mapper # noqa
from gbpservice.neutron.services.l3_router import l3_apic
from gbpservice.neutron.tests.unit.services.grouppolicy import (
test_apic_mapping)
TENANT = 'tenant1'
ROUTER = 'router1'
SUBNET = 'subnet1'
NETWORK = 'network1'
PORT = 'port1'
NETWORK_NAME = 'one_network'
FLOATINGIP = 'fip1'
# TODO(tbachman): create better test class hierarchy to inherit
class TestCiscoApicGBPL3Driver(test_apic_mapping.ApicMappingTestCase):
def setUp(self):
super(TestCiscoApicGBPL3Driver, self).setUp()
# Some actual dicts to return
self.subnet = {'network_id': NETWORK, 'tenant_id': TENANT}
self.port = {'tenant_id': TENANT,
'network_id': NETWORK,
'fixed_ips': [{'subnet_id': SUBNET}],
'id': PORT}
self.interface_info = {'subnet': {'subnet_id': SUBNET},
'port': {'port_id': self.port['id']}}
self.floatingip = {'id': FLOATINGIP,
'floating_network_id': NETWORK_NAME,
'port_id': PORT}
self.context = context.get_admin_context()
self.context.tenant_id = TENANT
# Create our plugin, but mock some superclass and
# core plugin methods
self.plugin = l3_apic.ApicGBPL3ServicePlugin()
self.plugin._apic_driver._notify_port_update = mock.Mock()
self.plugin._apic_driver._apic_gbp = mock.Mock()
self.plugin._core_plugin.get_ports = mock.Mock(
return_value=[self.port])
self.plugin._core_plugin.get_port = mock.Mock(return_value=self.port)
self.plugin._core_plugin.get_subnet = mock.Mock(
return_value = self.subnet)
self.plugin._core_plugin.update_port_status = mock.Mock()
# Floating IP updates to agents are mocked
self.plugin.update_floatingip_status = mock.Mock()
self.plugin.get_floatingip = mock.Mock(return_value=self.floatingip)
# This is so we can inherit from the ApicMappingTestCase
# TODO(tbachman): fix hack used because of class hierarchy
def test_reverse_on_delete(self):
pass
def _check_call_list(self, expected, observed):
for call in expected:
self.assertTrue(call in observed,
msg='Call not found, expected:\n%s\nobserved:'
'\n%s' % (str(call), str(observed)))
observed.remove(call)
self.assertFalse(
len(observed),
msg='There are more calls than expected: %s' % str(observed))
def _test_add_router_interface_postcommit(self, interface_info):
apic_driver = self.plugin._apic_driver
apic_driver.add_router_interface_postcommit(self.context,
ROUTER, interface_info)
test_assert = self.plugin._core_plugin.update_port_status
test_assert.assert_called_once_with(self.context,
self.port['id'], q_const.PORT_STATUS_ACTIVE)
def test_add_router_interface_postcommit_subnet(self):
self._test_add_router_interface_postcommit(
self.interface_info['subnet'])
def test_add_router_interface_postcommit_port(self):
self._test_add_router_interface_postcommit(self.interface_info['port'])
def _test_remove_router_interface_precommit(self, interface_info):
plugin = self.plugin._core_plugin
apic_driver = self.plugin._apic_driver
apic_driver.remove_router_interface_precommit(self.context, ROUTER,
interface_info)
plugin.update_port_status.assert_called_once_with(
self.context, mock.ANY, q_const.PORT_STATUS_DOWN)
def test_remove_router_interface_precommit_subnet(self):
self._test_remove_router_interface_precommit(
self.interface_info['subnet'])
def test_remove_router_interface_precommit_port(self):
self._test_remove_router_interface_precommit(
self.interface_info['port'])
def _dummy_generator(self, context, tenant_id, floatingip):
self._dummy_list = [0, 1, 2, 3]
for item in self._dummy_list:
yield item
def test_create_floatingip_precommit(self):
fip = {'floatingip': self.floatingip,
'id': FLOATINGIP, 'port_id': PORT}
apic_driver = self.plugin._apic_driver
apic_gbp = apic_driver.apic_gbp
apic_gbp.nat_pool_iterator = self._dummy_generator
apic_driver.create_floatingip_precommit(self.context, fip)
for nat_pool in self.context.nat_pool_list:
self.assertTrue(nat_pool in self._dummy_list)
def test_create_floatingip_postcommit(self):
fip = {'floatingip': self.floatingip,
'id': FLOATINGIP, 'port_id': PORT}
apic_driver = self.plugin._apic_driver
self.context.result = fip
apic_driver.create_floatingip_postcommit(self.context, fip)
apic_driver._notify_port_update.assert_called_once_with(PORT)
apic_driver._plugin.update_floatingip_status.assert_called_once_with(
mock.ANY, FLOATINGIP, q_const.FLOATINGIP_STATUS_ACTIVE)
self.assertEqual(q_const.FLOATINGIP_STATUS_ACTIVE, fip['status'])
def test_update_floatingip_precommit(self):
fip = {'floatingip': self.floatingip}
apic_driver = self.plugin._apic_driver
apic_driver.update_floatingip_precommit(self.context, FLOATINGIP, fip)
self.assertEqual(PORT, self.context.port_id_list[0])
def test_update_floatingip_postcommit(self):
fip = {'floatingip': self.floatingip,
'id': FLOATINGIP, 'port_id': PORT}
self.context.port_id_list = []
apic_driver = self.plugin._apic_driver
apic_driver.update_floatingip_postcommit(self.context, FLOATINGIP, fip)
self.assertEqual(self.port['id'], self.context.port_id_list[0])
apic_driver._notify_port_update.assert_called_once_with(PORT)
apic_driver._plugin.update_floatingip_status.assert_called_once_with(
mock.ANY, FLOATINGIP, q_const.FLOATINGIP_STATUS_ACTIVE)
def test_delete_floatingip_precommit(self):
apic_driver = self.plugin._apic_driver
apic_driver.delete_floatingip_precommit(self.context, FLOATINGIP)
self.assertEqual(PORT, self.context.port_id_list[0])
def test_delete_floatingip_postcommit(self):
self.context.port_id_list = [PORT]
apic_driver = self.plugin._apic_driver
apic_driver.delete_floatingip_postcommit(self.context, FLOATINGIP)
apic_driver._notify_port_update.assert_called_once_with(PORT)

View File

@@ -62,7 +62,7 @@ class TestCiscoApicL3Plugin(test_apic_mapping.ApicMappingTestCase):
# Create our plugin, but mock some superclass and
# core plugin methods
self.plugin = l3_apic.ApicGBPL3ServicePlugin()
self.plugin.apic_gbp._notify_port_update = mock.Mock()
self.plugin._apic_driver._notify_port_update = mock.Mock()
self.plugin._core_plugin.get_ports = mock.Mock(
return_value=[self.port])
@@ -139,10 +139,11 @@ class TestCiscoApicL3Plugin(test_apic_mapping.ApicMappingTestCase):
'create_floatingip',
new=mock.Mock(return_value=self.floatingip)):
# create floating-ip with mapped port
self.plugin.create_floatingip(self.context,
{'floatingip': self.floatingip})
self.plugin.apic_gbp._notify_port_update.assert_called_once_with(
mock.ANY, PORT)
plugin = self.plugin
plugin.create_floatingip(self.context,
{'floatingip': self.floatingip})
plugin._apic_driver._notify_port_update.assert_called_once_with(
PORT)
def test_floatingip_port_notify_on_reassociate(self):
with mock.patch('neutron.db.l3_db.L3_NAT_db_mixin.'
@@ -153,26 +154,28 @@ class TestCiscoApicL3Plugin(test_apic_mapping.ApicMappingTestCase):
self.plugin.update_floatingip(self.context, FLOATINGIP,
{'floatingip': new_fip})
self._check_call_list(
[mock.call(mock.ANY, PORT),
mock.call(mock.ANY, 'port-another')],
self.plugin.apic_gbp._notify_port_update.call_args_list)
[mock.call(PORT),
mock.call('port-another')],
self.plugin._apic_driver._notify_port_update.call_args_list)
def test_floatingip_port_notify_on_disassociate(self):
with mock.patch('neutron.db.l3_db.L3_NAT_db_mixin.'
'update_floatingip',
new=mock.Mock(return_value=self.floatingip)):
# dissociate mapped port
self.plugin.update_floatingip(self.context, FLOATINGIP,
{'floatingip': {}})
self.plugin.apic_gbp._notify_port_update.assert_called_once_with(
mock.ANY, PORT)
plugin = self.plugin
plugin.update_floatingip(self.context, FLOATINGIP,
{'floatingip': {}})
plugin._apic_driver._notify_port_update.assert_any_call(
PORT)
def test_floatingip_port_notify_on_delete(self):
with mock.patch('neutron.db.l3_db.L3_NAT_db_mixin.delete_floatingip'):
# delete
self.plugin.delete_floatingip(self.context, FLOATINGIP)
self.plugin.apic_gbp._notify_port_update.assert_called_once_with(
mock.ANY, PORT)
plugin = self.plugin
plugin.delete_floatingip(self.context, FLOATINGIP)
plugin._apic_driver._notify_port_update.assert_called_once_with(
PORT)
def test_floatingip_status(self):
with mock.patch('neutron.db.l3_db.L3_NAT_db_mixin.'