NSXv3: Add support for trunk service driver

This patch adds support for trunk extensions in the NSXv3
plugin.
Now you can create trunk and subports which map to CIF
on the backend.
i.e. trunk port <-> parent port and subport <-> child port
on the backend.
If backend fails to update subports, the status of trunk will be set
to ERROR.

Use OSC commands for trunk CRUD operations.
For e.g.
Create trunk with a subport:
openstack network trunk create --parent-port <parent-port>
    --subport port=child-port,segmentation-type=vlan,segmentation-id=200
    TRUNK_NAME

Delete trunk:
openstack network trunk delete TRUNK_NAME

Change-Id: Iedd47d868d803ca8c52856554885fd7d14668924
This commit is contained in:
Abhishek Raut 2016-08-18 22:04:15 -07:00
parent cdf7ab939c
commit ff5ebec12c
9 changed files with 397 additions and 0 deletions

View File

@ -0,0 +1,7 @@
---
prelude: >
Support VLAN-aware-VM feature in NSXv3 plugin.
features:
- Trunk driver for NSXv3 plugin which allows creation of trunk ports
and subports which subsequently create parent port and child ports
relationship in the backend.

View File

@ -59,3 +59,6 @@ BRIDGE_ENDPOINT = "BRIDGEENDPOINT"
# NSX service type
SERVICE_DHCP = "dhcp"
# NSXv3 CORE PLUGIN PATH
VMWARE_NSX_V3_PLUGIN_NAME = 'vmware_nsx.plugin.NsxV3Plugin'

View File

@ -94,6 +94,7 @@ from vmware_nsx.nsxlib.v3 import router
from vmware_nsx.nsxlib.v3 import security
from vmware_nsx.services.qos.common import utils as qos_com_utils
from vmware_nsx.services.qos.nsx_v3 import utils as qos_utils
from vmware_nsx.services.trunk.nsx_v3 import driver as trunk_driver
LOG = log.getLogger(__name__)
@ -215,6 +216,9 @@ class NsxV3Plugin(agentschedulers_db.AZDhcpAgentSchedulerDbMixin,
# translate configured transport zones/rotuers names to uuid
self._translate_configured_names_2_uuids()
# Register NSXv3 trunk driver to support trunk extensions
self.trunk_driver = trunk_driver.NsxV3TrunkDriver.create(self)
def _init_nsx_profiles(self):
LOG.debug("Initializing NSX v3 port spoofguard switching profile")
# XXX improve logic to avoid requiring setting this to none.

View File

@ -0,0 +1,12 @@
=========================================
Enabling NSX trunk driver using DevStack
=========================================
1. Download DevStack
2. Enable trunk service and configure following flags in ``local.conf``::
[[local]|[localrc]]
# Trunk plugin NSXv3 driver config
ENABLED_SERVICES+=,q-trunk
Q_SERVICE_PLUGIN_CLASSES=trunk

View File

View File

@ -0,0 +1,188 @@
# Copyright 2016 VMware, 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 oslo_config import cfg
from oslo_log import log as logging
from oslo_utils import excutils
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.extensions import portbindings
from neutron.services.trunk import constants as trunk_consts
from neutron.services.trunk.drivers import base
from vmware_nsx._i18n import _LE
from vmware_nsx.common import nsx_constants as nsx_consts
from vmware_nsx.common import utils as nsx_utils
from vmware_nsx.db import db as nsx_db
from vmware_nsx.nsxlib.v3 import exceptions as nsxlib_exc
from vmware_nsx.nsxlib.v3 import resources as nsx_resources
LOG = logging.getLogger(__name__)
SUPPORTED_INTERFACES = (
portbindings.VIF_TYPE_OVS,
)
SUPPORTED_SEGMENTATION_TYPES = (
trunk_consts.VLAN,
)
class NsxV3TrunkHandler(object):
"""Class to handle trunk events."""
def __init__(self, plugin_driver):
self.plugin_driver = plugin_driver
#TODO(abhiraut): Refactor nsxlib code and reuse here.
def _build_switching_profile_ids(self, profiles):
switch_profile_ids = []
for profile in profiles:
switch_profile = nsx_resources.SwitchingProfileTypeId(
profile_type=profile['key'],
profile_id=profile['value'])
switch_profile_ids.append(switch_profile)
return switch_profile_ids
def _update_port_at_backend(self, context, parent_port_id, subport):
# Retrieve the child port details
child_port = self.plugin_driver.get_port(context, subport.port_id)
# Retrieve the logical port ID based on the child port's neutron ID
nsx_child_port_id = nsx_db.get_nsx_switch_and_port_id(
session=context.session, neutron_id=subport.port_id)[1]
# Retrieve child logical port from the backend
try:
nsx_child_port = self.plugin_driver._port_client.get(
nsx_child_port_id)
except nsxlib_exc.ResourceNotFound:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Child port %s not found on the backend. "
"Setting trunk status to ERROR."),
nsx_child_port_id)
# Build address bindings and switch profiles otherwise backend will
# clear that information during port update
address_bindings = self.plugin_driver._build_address_bindings(
child_port)
switching_profile_ids = self._build_switching_profile_ids(
nsx_child_port.get('switching_profile_ids', []))
attachment_type = None
seg_id = None
if parent_port_id:
# Set properties for VLAN trunking
if subport.segmentation_type == nsx_utils.NsxV3NetworkTypes.VLAN:
attachment_type = nsx_consts.ATTACHMENT_CIF
seg_id = subport.segmentation_id
else:
# Unset the parent port properties from child port
attachment_type = nsx_consts.ATTACHMENT_VIF
seg_id = None
# Update logical port in the backend to set/unset parent port
try:
self.plugin_driver._port_client.update(
lport_id=nsx_child_port.get('id'),
vif_uuid=subport.port_id,
name=nsx_child_port.get('display_name'),
admin_state=nsx_child_port.get('admin_state'),
address_bindings=address_bindings,
switch_profile_ids=switching_profile_ids,
attachment_type=attachment_type,
parent_vif_id=parent_port_id,
parent_tag=seg_id)
except nsxlib_exc.ManagerError as e:
with excutils.save_and_reraise_exception():
LOG.error(_LE("Unable to update subport for attachment "
"type. Setting trunk status to ERROR. "
"Exception is %s"), e)
def _set_subports(self, context, parent_port_id, subports):
for subport in subports:
# Update port with parent port for backend.
self._update_port_at_backend(context, parent_port_id, subport)
def _unset_subports(self, context, subports):
for subport in subports:
# Update port and remove parent port attachment in the backend
self._update_port_at_backend(
context=context, parent_port_id=None, subport=subport)
def trunk_created(self, context, trunk):
try:
if trunk.sub_ports:
self._set_subports(context, trunk.port_id, trunk.sub_ports)
trunk.update(status=trunk_consts.ACTIVE_STATUS)
except (nsxlib_exc.ManagerError, nsxlib_exc.ResourceNotFound):
trunk.update(status=trunk_consts.ERROR_STATUS)
def trunk_deleted(self, context, trunk):
self._unset_subports(context, trunk.sub_ports)
def subports_added(self, context, trunk, subports):
try:
self._set_subports(context, trunk.port_id, subports)
trunk.update(status=trunk_consts.ACTIVE_STATUS)
except (nsxlib_exc.ManagerError, nsxlib_exc.ResourceNotFound):
trunk.update(status=trunk_consts.ERROR_STATUS)
def subports_deleted(self, context, trunk, subports):
try:
self._unset_subports(context, subports)
except (nsxlib_exc.ManagerError, nsxlib_exc.ResourceNotFound):
trunk.update(status=trunk_consts.ERROR_STATUS)
def trunk_event(self, resource, event, trunk_plugin, payload):
if event == events.AFTER_CREATE:
self.trunk_created(payload.context, payload.current_trunk)
elif event == events.AFTER_DELETE:
self.trunk_deleted(payload.context, payload.original_trunk)
def subport_event(self, resource, event, trunk_plugin, payload):
if event == events.AFTER_CREATE:
self.subports_added(
payload.context, payload.original_trunk, payload.subports)
elif event == events.AFTER_DELETE:
self.subports_deleted(
payload.context, payload.original_trunk, payload.subports)
class NsxV3TrunkDriver(base.DriverBase):
"""Driver to implement neutron's trunk extensions."""
@property
def is_loaded(self):
try:
return nsx_consts.VMWARE_NSX_V3_PLUGIN_NAME == cfg.CONF.core_plugin
except cfg.NoSuchOptError:
return False
@classmethod
def create(cls, plugin_driver):
cls.plugin_driver = plugin_driver
return cls(nsx_consts.VMWARE_NSX_V3_PLUGIN_NAME, SUPPORTED_INTERFACES,
SUPPORTED_SEGMENTATION_TYPES,
agent_type=None, can_trunk_bound_port=False)
def register(self, resource, event, trigger, **kwargs):
super(NsxV3TrunkDriver, self).register(
resource, event, trigger, **kwargs)
self._handler = NsxV3TrunkHandler(self.plugin_driver)
for event in (events.AFTER_CREATE, events.AFTER_DELETE):
registry.subscribe(self._handler.trunk_event,
trunk_consts.TRUNK,
event)
registry.subscribe(self._handler.subport_event,
trunk_consts.SUBPORTS,
event)
LOG.debug("VMware NSXv3 trunk driver initialized.")

View File

@ -0,0 +1,183 @@
# Copyright (c) 2016 VMware, Inc.
#
# 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 import context
from neutron.tests import base
from oslo_config import cfg
from oslo_utils import importutils
from vmware_nsx.common import nsx_constants
from vmware_nsx.services.trunk.nsx_v3 import driver as trunk_driver
from vmware_nsx.tests.unit.nsx_v3 import test_constants as test_consts
from vmware_nsx.tests.unit.nsx_v3 import test_plugin as test_nsx_v3_plugin
class TestNsxV3TrunkHandler(test_nsx_v3_plugin.NsxV3PluginTestCaseMixin,
base.BaseTestCase):
def setUp(self):
super(TestNsxV3TrunkHandler, self).setUp()
self.context = context.get_admin_context()
self.core_plugin = importutils.import_object(test_consts.PLUGIN_NAME)
self.handler = trunk_driver.NsxV3TrunkHandler(self.core_plugin)
self.handler._update_port_at_backend = mock.Mock()
self.trunk_1 = mock.Mock()
self.trunk_1.port_id = "parent_port_1"
self.trunk_2 = mock.Mock()
self.trunk_2.port_id = "parent_port_2"
self.sub_port_1 = mock.Mock()
self.sub_port_1.segmentation_id = 40
self.sub_port_1.trunk_id = "trunk-1"
self.sub_port_1.port_id = "sub_port_1"
self.sub_port_2 = mock.Mock()
self.sub_port_2.segmentation_id = 41
self.sub_port_2.trunk_id = "trunk-2"
self.sub_port_2.port_id = "sub_port_2"
self.sub_port_3 = mock.Mock()
self.sub_port_3.segmentation_id = 43
self.sub_port_3.trunk_id = "trunk-2"
self.sub_port_3.port_id = "sub_port_3"
def test_trunk_created(self):
# Create trunk with no subport
self.trunk_1.sub_ports = []
self.handler.trunk_created(self.context, self.trunk_1)
self.handler._update_port_at_backend.assert_not_called()
# Create trunk with 1 subport
self.trunk_1.sub_ports = [self.sub_port_1]
self.handler.trunk_created(self.context, self.trunk_1)
self.handler._update_port_at_backend.assert_called_with(
self.context,
self.trunk_1.port_id,
self.sub_port_1)
# Create trunk with multiple subports
self.trunk_2.sub_ports = [self.sub_port_2, self.sub_port_3]
self.handler.trunk_created(self.context, self.trunk_2)
calls = [mock.call._update_port_at_backend(
self.context,
self.trunk_2.port_id,
self.sub_port_2),
mock.call._update_port_at_backend(
self.context,
self.trunk_2.port_id,
self.sub_port_3)]
self.handler._update_port_at_backend.assert_has_calls(
calls, any_order=True)
def test_trunk_deleted(self):
# Delete trunk with no subport
self.trunk_1.sub_ports = []
self.handler.trunk_deleted(self.context, self.trunk_1)
self.handler._update_port_at_backend.assert_not_called()
# Delete trunk with 1 subport
self.trunk_1.sub_ports = [self.sub_port_1]
self.handler.trunk_deleted(self.context, self.trunk_1)
self.handler._update_port_at_backend.assert_called_with(
context=self.context,
parent_port_id=None,
subport=self.sub_port_1)
# Delete trunk with multiple subports
self.trunk_2.sub_ports = [self.sub_port_2, self.sub_port_3]
self.handler.trunk_deleted(self.context, self.trunk_2)
calls = [mock.call._update_port_at_backend(
context=self.context,
parent_port_id=None,
subport=self.sub_port_2),
mock.call._update_port_at_backend(
context=self.context,
parent_port_id=None,
subport=self.sub_port_3)]
self.handler._update_port_at_backend.assert_has_calls(
calls, any_order=True)
def test_subports_added(self):
# Update trunk with no subport
sub_ports = []
self.handler.subports_added(self.context, self.trunk_1, sub_ports)
self.handler._update_port_at_backend.assert_not_called()
# Update trunk with 1 subport
sub_ports = [self.sub_port_1]
self.handler.subports_added(self.context, self.trunk_1, sub_ports)
self.handler._update_port_at_backend.assert_called_with(
self.context,
self.trunk_1.port_id,
self.sub_port_1)
# Update trunk with multiple subports
sub_ports = [self.sub_port_2, self.sub_port_3]
self.handler.subports_added(self.context, self.trunk_2, sub_ports)
calls = [mock.call._update_port_at_backend(
self.context,
self.trunk_2.port_id,
self.sub_port_2),
mock.call._update_port_at_backend(
self.context,
self.trunk_2.port_id,
self.sub_port_3)]
self.handler._update_port_at_backend.assert_has_calls(
calls, any_order=True)
def test_subports_deleted(self):
# Update trunk to remove no subport
sub_ports = []
self.handler.subports_deleted(self.context, self.trunk_1, sub_ports)
self.handler._update_port_at_backend.assert_not_called()
# Update trunk to remove 1 subport
sub_ports = [self.sub_port_1]
self.handler.subports_deleted(self.context, self.trunk_1, sub_ports)
self.handler._update_port_at_backend.assert_called_with(
context=self.context,
parent_port_id=None,
subport=self.sub_port_1)
# Update trunk to remove multiple subports
sub_ports = [self.sub_port_2, self.sub_port_3]
self.handler.subports_deleted(self.context, self.trunk_2, sub_ports)
calls = [mock.call._update_port_at_backend(
context=self.context,
parent_port_id=None,
subport=self.sub_port_2),
mock.call._update_port_at_backend(
context=self.context,
parent_port_id=None,
subport=self.sub_port_3)]
self.handler._update_port_at_backend.assert_has_calls(
calls, any_order=True)
class TestNsxV3TrunkDriver(base.BaseTestCase):
def setUp(self):
super(TestNsxV3TrunkDriver, self).setUp()
def test_is_loaded(self):
driver = trunk_driver.NsxV3TrunkDriver.create(mock.Mock())
cfg.CONF.set_override('core_plugin',
nsx_constants.VMWARE_NSX_V3_PLUGIN_NAME)
self.assertTrue(driver.is_loaded)
cfg.CONF.set_override('core_plugin', 'not_vmware_nsx_plugin')
self.assertFalse(driver.is_loaded)