Add support for VLAN aware VMs in AIM

improve get_request_details to provide the trunk information if
needed.

Change-Id: I81b0cbb6b1aa12e5a10962005e00e17ea37c78eb
This commit is contained in:
Ivar Lazzaro 2017-11-03 18:08:22 -07:00
parent 904c5476b8
commit 5beb9c92f5
10 changed files with 189 additions and 13 deletions

View File

@ -270,6 +270,12 @@ class LocalAPI(object):
raise exc.GroupPolicyDeploymentError()
return servicechain_plugin
@property
def _trunk_plugin(self):
# REVISIT(rkukura): Need initialization method after all
# plugins are loaded to grab and store plugin.
return directory.get_plugin('trunk')
def _create_resource(self, plugin, context, resource, attrs,
do_notify=True):
# REVISIT(rkukura): Do create.start notification?

View File

@ -64,6 +64,7 @@ from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import config # noqa
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import db
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import exceptions
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import extension_db
from gbpservice.neutron.plugins.ml2plus.drivers.apic_aim import trunk_driver
LOG = log.getLogger(__name__)
DEVICE_OWNER_SNAT_PORT = 'apic:snat-pool'
@ -200,6 +201,7 @@ class ApicMechanismDriver(api_plus.MechanismDriver,
enable_iptables_firewall)
local_api.QUEUE_OUT_OF_PROCESS_NOTIFICATIONS = True
self._ensure_static_resources()
trunk_driver.register()
def _ensure_static_resources(self):
session = db_api.get_writer_session()

View File

@ -0,0 +1,59 @@
# 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 oslo_config import cfg
from oslo_log import log as logging
from neutron.services.trunk import constants as trunk_consts
from neutron.services.trunk.drivers import base
from neutron_lib.api.definitions import portbindings
from opflexagent import constants as ofcst
LOG = logging.getLogger(__name__)
NAME = 'apic_aim'
SUPPORTED_INTERFACES = (
portbindings.VIF_TYPE_OVS,
portbindings.VIF_TYPE_VHOST_USER,
)
SUPPORTED_SEGMENTATION_TYPES = (
trunk_consts.VLAN,
)
DRIVER = None
class OpflexDriver(base.DriverBase):
@property
def is_loaded(self):
try:
return NAME in cfg.CONF.ml2.mechanism_drivers
except cfg.NoSuchOptError:
return False
@classmethod
def create(cls):
return OpflexDriver(NAME,
SUPPORTED_INTERFACES,
SUPPORTED_SEGMENTATION_TYPES,
ofcst.AGENT_TYPE_OPFLEX_OVS)
def register():
"""Register the driver."""
global DRIVER
DRIVER = OpflexDriver.create()
LOG.debug('Opflex trunk driver registered')

View File

@ -13,6 +13,10 @@
from neutron.common import rpc as n_rpc
from neutron.common import topics
from neutron.db import api as db_api
from neutron.db import db_base_plugin_common
from neutron.objects import base as objects_base
from neutron.objects import trunk as trunk_objects
from neutron.plugins.ml2 import rpc as ml2_rpc
from opflexagent import rpc as o_rpc
from oslo_log import log
@ -96,7 +100,9 @@ class AIMMappingRPCMixin(ha_ip_db.HAIPOwnerDbMixin):
'gbp_details': self._get_gbp_details(context, request,
host),
'neutron_details': ml2_rpc.RpcCallbacks(
None, None).get_device_details(context, **request)}
None, None).get_device_details(context, **request),
'trunk_details': self._get_trunk_details(context,
request, host)}
return result
except Exception as e:
LOG.error("An exception has occurred while requesting device "
@ -114,6 +120,42 @@ class AIMMappingRPCMixin(ha_ip_db.HAIPOwnerDbMixin):
LOG.debug("APIC ownership update for port %s", p)
self._send_port_update_notification(context, p)
@db_api.retry_db_errors
def _get_trunk_details(self, context, request, host):
if self._trunk_plugin:
device = request.get('device')
port_id = self._core_plugin._device_to_port_id(context, device)
# Find Trunk associated to this port (if any)
trunks = self._trunk_plugin.get_trunks(
context, filters={'port_id': [port_id]})
subports = None
if not trunks:
subports = self.retrieve_subports(
context, filters={'port_id': [port_id]})
if subports:
trunks = self._trunk_plugin.get_trunks(
context, filters={'id': [subports[0].trunk_id]})
if trunks:
return {'trunk_id': trunks[0]['id'],
'master_port_id': trunks[0]['port_id'],
'subports': (
[s.to_dict() for s in subports] if subports else
self._trunk_plugin.get_subports(
context, trunks[0]['id'])['sub_ports'])}
# NOTE(ivar): for some reason, the Trunk plugin doesn't expose a way to
# retrieve a subport starting from the port ID.
@db_base_plugin_common.filter_fields
def retrieve_subports(self, context, filters=None, fields=None,
sorts=None, limit=None, marker=None,
page_reverse=False):
filters = filters or {}
pager = objects_base.Pager(sorts=sorts, limit=limit,
page_reverse=page_reverse,
marker=marker)
return trunk_objects.SubPort.get_objects(context, _pager=pager,
**filters)
# Things you need in order to run this Mixin:
# - self._core_plugin: attribute that points to the Neutron core plugin;
# - self._is_port_promiscuous(context, port): define whether or not

View File

@ -82,10 +82,12 @@ class ApiManagerMixin(object):
def _create_resource(self, type, expected_res_status=None,
is_admin_context=False, **kwargs):
plural = cm.get_resource_plural(type)
defaults = getattr(cm,
'get_create_%s_default_attrs' % type)()
defaults_func = getattr(cm, 'get_create_%s_default_attrs' % type,
None)
defaults = {}
if defaults_func:
defaults = defaults_func()
defaults.update(kwargs)
data = {type: {'tenant_id': self._tenant_id}}
data[type].update(defaults)

View File

@ -41,7 +41,8 @@ class GroupPolicyMappingDbTestCase(tgpdb.GroupPolicyDbTestCase,
test_l3.L3NatTestCaseMixin):
def setUp(self, core_plugin=None, l3_plugin=None, gp_plugin=None,
service_plugins=None, sc_plugin=None, qos_plugin=None):
service_plugins=None, sc_plugin=None, qos_plugin=None,
trunk_plugin=None):
if not gp_plugin:
gp_plugin = DB_GP_PLUGIN_KLASS
if not service_plugins:
@ -53,6 +54,8 @@ class GroupPolicyMappingDbTestCase(tgpdb.GroupPolicyDbTestCase,
service_plugins['l3_plugin_name'] = l3_plugin or "router"
if qos_plugin:
service_plugins['qos_plugin_name'] = qos_plugin
if trunk_plugin:
service_plugins['trunk_plugin_name'] = trunk_plugin
super(GroupPolicyMappingDbTestCase, self).setUp(
core_plugin=core_plugin, gp_plugin=gp_plugin,
service_plugins=service_plugins

View File

@ -108,7 +108,8 @@ class AIMBaseTestCase(test_nr_base.CommonNeutronBaseTestCase,
_extension_path = None
def setUp(self, policy_drivers=None, core_plugin=None, ml2_options=None,
l3_plugin=None, sc_plugin=None, qos_plugin=None, **kwargs):
l3_plugin=None, sc_plugin=None, trunk_plugin=None,
qos_plugin=None, **kwargs):
core_plugin = core_plugin or ML2PLUS_PLUGIN
if not l3_plugin:
l3_plugin = "apic_aim_l3"
@ -137,7 +138,8 @@ class AIMBaseTestCase(test_nr_base.CommonNeutronBaseTestCase,
super(AIMBaseTestCase, self).setUp(
policy_drivers=policy_drivers, core_plugin=core_plugin,
ml2_options=ml2_opts, l3_plugin=l3_plugin,
sc_plugin=sc_plugin, qos_plugin=qos_plugin)
sc_plugin=sc_plugin, qos_plugin=qos_plugin,
trunk_plugin=trunk_plugin)
self.db_session = db_api.get_writer_session()
self.initialize_db_config(self.db_session)
self.l3_plugin = directory.get_plugin(n_constants.L3)
@ -5215,3 +5217,59 @@ class TestPerL3PImplicitContractsConfig(TestL2PolicyWithAutoPTG):
def test_create_not_called(self):
self.mock_create.assert_not_called()
class TestVlanAwareVM(AIMBaseTestCase):
def setUp(self, *args, **kwargs):
super(TestVlanAwareVM, self).setUp(trunk_plugin='trunk', **kwargs)
def _do_test_gbp_details_no_pt(self):
network = self._make_network(self.fmt, 'net1', True)
with self.subnet(network=network, cidr='1.1.2.0/24') as subnet:
with self.port(subnet=subnet) as port:
with self.port(subnet=subnet) as subp:
port_id = port['port']['id']
subp_id = subp['port']['id']
trunk = self._create_resource('trunk', port_id=port_id)
self.driver._trunk_plugin.add_subports(
nctx.get_admin_context(), trunk['trunk']['id'],
{'sub_ports': [{'port_id': subp_id,
'segmentation_type': 'vlan',
'segmentation_id': 100}]})
self._bind_port_to_host(port_id, 'h1')
req_mapping = self.driver.request_endpoint_details(
nctx.get_admin_context(),
request={'device': 'tap%s' % port_id,
'timestamp': 0, 'request_id': 'request_id'},
host='h1')
self.assertEqual(req_mapping['trunk_details']['trunk_id'],
trunk['trunk']['id'])
self.assertEqual(
req_mapping['trunk_details']['master_port_id'],
trunk['trunk']['port_id'])
self.assertEqual(
req_mapping['trunk_details']['subports'],
[{'port_id': subp_id,
'segmentation_type': 'vlan',
'segmentation_id': 100}])
# Retrieve the subport
self._bind_port_to_host(subp_id, 'h1')
req_mapping = self.driver.request_endpoint_details(
nctx.get_admin_context(),
request={'device': 'tap%s' % subp_id,
'timestamp': 0, 'request_id': 'request_id'},
host='h1')
self.assertEqual(req_mapping['trunk_details']['trunk_id'],
trunk['trunk']['id'])
self.assertEqual(
req_mapping['trunk_details']['master_port_id'],
port_id)
self.assertEqual(
req_mapping['trunk_details']['subports'],
[{'port_id': subp_id,
'segmentation_type': 'vlan',
'segmentation_id': 100}])
def test_trunk_master_port(self):
self._do_test_gbp_details_no_pt()

View File

@ -28,7 +28,7 @@ class ExtensionDriverTestBase(test_plugin.GroupPolicyPluginTestCase):
def setUp(self, policy_drivers=None, core_plugin=None,
l3_plugin=None, ml2_options=None,
sc_plugin=None, qos_plugin=None):
sc_plugin=None, qos_plugin=None, trunk_plugin=None):
config.cfg.CONF.set_override('extension_drivers',
self._extension_drivers,
group='group_policy')
@ -38,7 +38,7 @@ class ExtensionDriverTestBase(test_plugin.GroupPolicyPluginTestCase):
super(ExtensionDriverTestBase, self).setUp(
core_plugin=core_plugin, l3_plugin=l3_plugin,
ml2_options=ml2_options, sc_plugin=sc_plugin,
qos_plugin=qos_plugin)
qos_plugin=qos_plugin, trunk_plugin=trunk_plugin)
class ExtensionDriverTestCase(ExtensionDriverTestBase):

View File

@ -63,7 +63,8 @@ def get_status_for_test(self, context):
class GroupPolicyPluginTestBase(tgpmdb.GroupPolicyMappingDbTestCase):
def setUp(self, core_plugin=None, l3_plugin=None, gp_plugin=None,
ml2_options=None, sc_plugin=None, qos_plugin=None):
ml2_options=None, sc_plugin=None, qos_plugin=None,
trunk_plugin=None):
if not gp_plugin:
gp_plugin = GP_PLUGIN_KLASS
ml2_opts = ml2_options or {'mechanism_drivers': ['openvswitch'],
@ -75,7 +76,8 @@ class GroupPolicyPluginTestBase(tgpmdb.GroupPolicyMappingDbTestCase):
l3_plugin=l3_plugin,
gp_plugin=gp_plugin,
sc_plugin=sc_plugin,
qos_plugin=qos_plugin)
qos_plugin=qos_plugin,
trunk_plugin=trunk_plugin)
def _create_l2_policy_on_shared(self, **kwargs):
l3p = self.create_l3_policy(shared=True)['l3_policy']

View File

@ -33,7 +33,8 @@ CORE_PLUGIN = ('gbpservice.neutron.tests.unit.services.grouppolicy.'
class CommonNeutronBaseTestCase(test_plugin.GroupPolicyPluginTestBase):
def setUp(self, policy_drivers=None, core_plugin=None, l3_plugin=None,
ml2_options=None, sc_plugin=None, qos_plugin=None):
ml2_options=None, sc_plugin=None, qos_plugin=None,
trunk_plugin=None):
core_plugin = core_plugin or ML2PLUS_PLUGIN
policy_drivers = policy_drivers or ['neutron_resources']
config.cfg.CONF.set_override('policy_drivers',
@ -49,7 +50,8 @@ class CommonNeutronBaseTestCase(test_plugin.GroupPolicyPluginTestBase):
l3_plugin=l3_plugin,
ml2_options=ml2_options,
sc_plugin=sc_plugin,
qos_plugin=qos_plugin)
qos_plugin=qos_plugin,
trunk_plugin=trunk_plugin)
res = mock.patch('neutron.db.l3_db.L3_NAT_dbonly_mixin.'
'_check_router_needs_rescheduling').start()
res.return_value = None