Nexus MD: Neutron trunk support

Add neutron trunk support to the cisco_nexus mechanism driver.

For baremetal deployments, the setting of 'switchport...native'
is now set for a trunk's parent port (also the vlan associated
with the trunk's parent port will also be configured as 'allowed').
All other ports (trunk subports) will be set as 'allowed'.

VM deployments nexus switchport setting isn't changed by this patchset.

Change-Id: I0992312dfba5c58302b050d409bbbb3d180ea0a0
This commit is contained in:
Rich Curran 2017-06-14 17:41:35 -04:00
parent ffb7d971bb
commit dbcdea1275
17 changed files with 758 additions and 91 deletions

View File

@ -54,6 +54,9 @@ else:
if NEUTRON_VERSION >= NEUTRON_OCATA_VERSION:
from neutron.db.models import agent as agent_model
from neutron.db.models import l3 as l3_models
from neutron.objects import trunk as trunk_objects
from neutron.services.trunk import constants as trunk_consts
from neutron.services.trunk.drivers import base as trunk_base
from neutron_lib.api.definitions import portbindings
from neutron_lib.api.definitions import provider_net as providernet
from neutron_lib.api import extensions
@ -85,6 +88,13 @@ if NEUTRON_VERSION >= NEUTRON_OCATA_VERSION:
def get_novaclient_images(nclient):
return nclient.glance
else:
from networking_cisco.services.trunk import ( # noqa
trunkstubs as trunk_objects) # noqa
from networking_cisco.services.trunk import ( # noqa
trunkstubs as trunk_consts) # noqa
from networking_cisco.services.trunk import ( # noqa
trunkstubs as trunk_base) # noqa
from neutron.api import extensions # noqa
from neutron.api.v2 import attributes as attr
from neutron.common import utils as common_utils # noqa

View File

@ -28,6 +28,7 @@ NVE_SRC_INTF = 'nve_src_intf'
NETWORK_ADMIN = 'network_admin'
CISCO_NEXUS_ML2_MECH_DRIVER_V2 = 'cisco_nexus'
TYPE_NEXUS_VXLAN = 'nexus_vxlan'
# TODO(rpothier) Add back in provider segment support.

View File

@ -1,4 +1,4 @@
# Copyright (c) 2013-2016 Cisco Systems, Inc.
# Copyright (c) 2013-2017 Cisco Systems, Inc.
# All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
@ -51,6 +51,8 @@ from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
nexus_db_v2 as nxos_db)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
nexus_helpers as nexus_help)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import trunk
from networking_cisco.services.trunk import nexus_trunk
LOG = logging.getLogger(__name__)
@ -338,6 +340,8 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
self.monitor_timeout = conf.cfg.CONF.ml2_cisco.switch_heartbeat_time
self.monitor_lock = threading.Lock()
self.context = bc.get_context()
self.trunk = trunk.NexusMDTrunkHandler()
nexus_trunk.NexusTrunkDriver.create()
LOG.info(_LI("CiscoNexusMechanismDriver: initialize() called "
"pid %(pid)d thid %(tid)d"), {'pid': self._ppid,
'tid': threading.current_thread().ident})
@ -510,6 +514,12 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
return switch_connections
def _get_port_uuid(self, port):
# Trunk subport's don't have the 'device_id' set so use port 'id'
# as the UUID.
uuid_key = 'id' if self.trunk.is_trunk_subport(port) else 'device_id'
return port.get(uuid_key)
def _valid_network_segment(self, segment):
return (cfg.CONF.ml2_cisco.managed_physical_network is None or
cfg.CONF.ml2_cisco.managed_physical_network ==
@ -520,29 +530,17 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
port['device_owner'].startswith('baremetal') or
port['device_owner'].startswith('manila') or
port['device_owner'] in [
bc.trunk_consts.TRUNK_SUBPORT_OWNER,
bc.constants.DEVICE_OWNER_DHCP,
bc.constants.DEVICE_OWNER_ROUTER_INTF,
bc.constants.DEVICE_OWNER_ROUTER_GW,
bc.constants.DEVICE_OWNER_ROUTER_HA_INTF])
def _is_status_active(self, port):
return port['status'] == bc.constants.PORT_STATUS_ACTIVE
def _is_baremetal(self, port):
"""Identifies ironic baremetal transactions.
There are two types of transactions.
1) A host transaction which is dependent on
host to interface mapping config stored in the
ml2_conf.ini file. The VNIC type for this is
'normal' which is the assumed condition.
2) A baremetal transaction which comes from
the ironic project where the interfaces
are provided in the port transaction. In this
case the VNIC_TYPE is 'baremetal'.
"""
return (port[bc.portbindings.VNIC_TYPE] ==
bc.portbindings.VNIC_BAREMETAL)
def _is_status_down(self, port):
# ACTIVE, BUILD status indicates a port is up or coming up.
# DOWN, ERROR status indicates the port is down.
return (port['status'] in [bc.constants.PORT_STATUS_DOWN,
bc.constants.PORT_STATUS_ERROR])
def _get_baremetal_switch_info(self, link_info):
"""Get switch_info dictionary from context."""
@ -563,7 +561,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
port = context.current
if not self._is_baremetal(port):
if not nexus_help.is_baremetal(port):
return False
if bc.portbindings.PROFILE not in port:
@ -620,6 +618,11 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
'count': len(all_link_info)})
break
# if trunk parent port then generate update_port calls for all
# trunk subports configured.
if selected and self.trunk.is_trunk_parentport(port):
self.trunk.update_subports(port)
return selected
def _get_baremetal_switches(self, port):
@ -672,7 +675,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
connections = []
is_native = False if self.trunk.is_trunk_subport(port) else True
all_link_info = port[bc.portbindings.PROFILE]['local_link_information']
for link_info in all_link_info:
# Extract port info
@ -695,10 +701,6 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
not self.is_switch_active(switch_ip)):
continue
if 'is_native' in switch_info:
is_native = switch_info['is_native']
else:
is_native = const.NOT_NATIVE
ch_grp = 0
if not from_segment:
try:
@ -875,7 +877,8 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
nexus_help.format_interface_name(intf_type, port),
ch_grp, False)
device_id = port_seg.get('device_id')
device_id = self._get_port_uuid(port_seg)
vlan_id = segment.get(api.SEGMENTATION_ID)
# TODO(rpothier) Add back in provider segment support.
is_provider_vlan = False
@ -964,12 +967,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
def _get_port_connections(self, port, host_id,
only_active_switch=False):
if self._is_baremetal(port):
return self._get_baremetal_connections(
port, only_active_switch)
if nexus_help.is_baremetal(port):
return self._get_baremetal_connections(port, only_active_switch)
else:
return self._get_host_connections(
host_id, only_active_switch)
return self._get_host_connections(host_id, only_active_switch)
def _get_active_port_connections(self, port, host_id):
return self._get_port_connections(port, host_id, True)
@ -1593,7 +1594,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
self._delete_port_channel_resources(
host_id, switch_ip, intf_type, nexus_port, port_id)
if self._is_baremetal(port):
if nexus_help.is_baremetal(port):
connections = self._get_baremetal_connections(
port, False, True)
for switch_ip, intf_type, nexus_port, is_native, _ in connections:
@ -1627,8 +1628,10 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
def _is_vm_migrating(self, context, vlan_segment, orig_vlan_segment):
if not vlan_segment and orig_vlan_segment:
return (context.current.get(bc.portbindings.HOST_ID) !=
context.original.get(bc.portbindings.HOST_ID))
current_host_id = context.current.get(bc.portbindings.HOST_ID)
original_host_id = context.original.get(bc.portbindings.HOST_ID)
if current_host_id and original_host_id:
return current_host_id != original_host_id
def _log_missing_segment(self):
LOG.warning(_LW("Nexus: Segment is None, Event not processed."))
@ -1658,12 +1661,15 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
if not self._is_valid_segment(segment):
return
device_id = port.get('device_id')
if self._is_baremetal(port):
device_id = self._get_port_uuid(port)
if nexus_help.is_baremetal(port):
host_id = port.get('dns_name')
else:
host_id = port.get(bc.portbindings.HOST_ID)
vlan_id = segment.get(api.SEGMENTATION_ID)
# TODO(rpothier) Add back in provider segment support.
is_provider = False
settings = {"vlan_id": vlan_id,
@ -1740,7 +1746,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
port = context.current
if self._is_supported_deviceowner(port):
if self._is_baremetal(context.current):
if nexus_help.is_baremetal(context.current):
all_switches, active_switches = (
self._get_baremetal_switches(context.current))
else:
@ -1780,71 +1786,66 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
def update_port_precommit(self, context):
"""Update port pre-database transaction commit event."""
vlan_segment, vxlan_segment = self._get_segments(
context.top_bound_segment,
context.bottom_bound_segment)
context.top_bound_segment, context.bottom_bound_segment)
orig_vlan_segment, orig_vxlan_segment = self._get_segments(
context.original_top_bound_segment,
context.original_bottom_bound_segment)
context.original_top_bound_segment,
context.original_bottom_bound_segment)
# if VM migration is occurring then remove previous database entry
# else process update event.
if self._is_vm_migrating(context, vlan_segment, orig_vlan_segment):
vni = self._port_action_vxlan(context.original, orig_vxlan_segment,
self._delete_nve_db) if orig_vxlan_segment else 0
if (self._is_vm_migrating(context, vlan_segment, orig_vlan_segment) or
self._is_status_down(context.current)):
vni = (self._port_action_vxlan(
context.original, orig_vxlan_segment, self._delete_nve_db)
if orig_vxlan_segment else 0)
self._port_action_vlan(context.original, orig_vlan_segment,
self._delete_nxos_db, vni)
else:
if (self._is_supported_deviceowner(context.current) and
self._is_status_active(context.current) and
not self._is_baremetal(context.current)):
vni = self._port_action_vxlan(context.current, vxlan_segment,
self._configure_nve_db) if vxlan_segment else 0
self._port_action_vlan(context.current, vlan_segment,
self._configure_nxos_db, vni)
elif (self._is_supported_deviceowner(context.current) and
not nexus_help.is_baremetal(context.current)):
vni = self._port_action_vxlan(context.current, vxlan_segment,
self._configure_nve_db) if vxlan_segment else 0
self._port_action_vlan(context.current, vlan_segment,
self._configure_nxos_db, vni)
@lockutils.synchronized('cisco-nexus-portlock')
def update_port_postcommit(self, context):
"""Update port non-database commit event."""
vlan_segment, vxlan_segment = self._get_segments(
context.top_bound_segment,
context.bottom_bound_segment)
context.top_bound_segment, context.bottom_bound_segment)
orig_vlan_segment, orig_vxlan_segment = self._get_segments(
context.original_top_bound_segment,
context.original_bottom_bound_segment)
context.original_top_bound_segment,
context.original_bottom_bound_segment)
# if VM migration is occurring then remove previous nexus switch entry
# else process update event.
if self._is_vm_migrating(context, vlan_segment, orig_vlan_segment):
vni = self._port_action_vxlan(context.original, orig_vxlan_segment,
self._delete_nve_member) if orig_vxlan_segment else 0
if (self._is_vm_migrating(context, vlan_segment, orig_vlan_segment)
or self._is_status_down(context.current)):
vni = (self._port_action_vxlan(
context.original, orig_vxlan_segment,
self._delete_nve_member) if orig_vxlan_segment else 0)
self._port_action_vlan(context.original, orig_vlan_segment,
self._delete_switch_entry, vni)
else:
if (self._is_supported_deviceowner(context.current) and
self._is_status_active(context.current)):
if self._is_baremetal(context.current):
# Baremetal db entries are created here instead
# of precommit since a get operation to
# nexus device is required but blocking
# operation should not be done in precommit.
self._init_baremetal_trunk_interfaces(
context.current, vlan_segment, 0)
all_switches, active_switches = (
self._get_baremetal_switches(context.current))
else:
host_id = context.current.get(bc.portbindings.HOST_ID)
all_switches, active_switches = (
self._get_host_switches(host_id))
# if switches not active but host_id is valid
if not active_switches and all_switches:
raise excep.NexusConnectFailed(
nexus_host=all_switches[0], config="None",
exc="Update Port Failed: Nexus Switch "
"is down or replay in progress")
vni = self._port_action_vxlan(context.current, vxlan_segment,
self._configure_nve_member) if vxlan_segment else 0
self._port_action_vlan(context.current, vlan_segment,
self._configure_port_entries, vni)
elif self._is_supported_deviceowner(context.current):
if nexus_help.is_baremetal(context.current):
# Baremetal db entries are created here instead
# of precommit since a get operation to
# nexus device is required but blocking
# operation should not be done in precommit.
self._init_baremetal_trunk_interfaces(
context.current, vlan_segment, 0)
all_switches, active_switches = (
self._get_baremetal_switches(context.current))
else:
host_id = context.current.get(bc.portbindings.HOST_ID)
all_switches, active_switches = (
self._get_host_switches(host_id))
# if switches not active but host_id is valid
if not active_switches and all_switches:
raise excep.NexusConnectFailed(
nexus_host=all_switches[0], config="None",
exc="Update Port Failed: Nexus Switch "
"is down or replay in progress")
vni = self._port_action_vxlan(context.current, vxlan_segment,
self._configure_nve_member) if vxlan_segment else 0
self._port_action_vlan(context.current, vlan_segment,
self._configure_port_entries, vni)
@lockutils.synchronized('cisco-nexus-portlock')
def delete_port_precommit(self, context):

View File

@ -17,6 +17,8 @@
ML2 Nexus Driver - Helper Methods
"""
from networking_cisco import backwards_compatibility as bc
def format_interface_name(intf_type, port, ch_grp=0):
"""Method to format interface name given type, port.
@ -64,3 +66,19 @@ def split_interface_name(interface, ch_grp=0):
intf_type, port = 'ethernet', interface
return intf_type, port
def is_baremetal(port):
"""Identifies ironic baremetal transactions.
There are two types of transactions.
1) A host transaction which is dependent on
host to interface mapping config stored in the
ml2_conf.ini file. The VNIC type for this is
'normal' which is the assumed condition.
2) A baremetal transaction which comes from
the ironic project where the interfaces
are provided in the port transaction. In this
case the VNIC_TYPE is 'baremetal'.
"""
return port[bc.portbindings.VNIC_TYPE] == bc.portbindings.VNIC_BAREMETAL

View File

@ -0,0 +1,65 @@
# Copyright (c) 2017 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 oslo_log import log
from neutron.api.v2 import attributes
from neutron.extensions import dns
from networking_cisco import backwards_compatibility as bc
LOG = log.getLogger(__name__)
class NexusMDTrunkHandler(object):
"""Cisco Nexus Mechanism Driver Trunk Handler.
This class contains methods called by the cisco_nexus MD for
processing trunk subports.
"""
def is_trunk_parentport(self, port):
return 'trunk_details' in port
def is_trunk_subport(self, port):
return port['device_owner'] == bc.trunk_consts.TRUNK_SUBPORT_OWNER
def update_subports(self, port):
"""Set port attributes for trunk subports.
For baremetal deployments only, set the neutron port attributes
during the bind_port event.
"""
trunk_details = port.get('trunk_details')
subports = trunk_details['sub_ports']
host_id = port.get(dns.DNSNAME)
context = bc.get_context()
el_context = context.elevated()
for subport in subports:
bc.get_plugin().update_port(el_context, subport['port_id'],
{attributes.PORT:
{bc.portbindings.HOST_ID: host_id,
bc.portbindings.VNIC_TYPE:
bc.portbindings.VNIC_BAREMETAL,
bc.portbindings.PROFILE:
port.get(bc.portbindings.PROFILE),
'device_owner': bc.trunk_consts.TRUNK_SUBPORT_OWNER,
'status': bc.constants.PORT_STATUS_ACTIVE}})
# Set trunk to ACTIVE status.
trunk_obj = bc.trunk_objects.Trunk.get_object(
el_context, id=trunk_details['trunk_id'])
trunk_obj.update(status=bc.trunk_consts.ACTIVE_STATUS)

View File

View File

@ -0,0 +1,134 @@
# Copyright (c) 2017 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 oslo_config import cfg
from oslo_log import log
from neutron.api.v2 import attributes
from neutron.callbacks import events
from neutron.callbacks import registry
from neutron.extensions import dns
from networking_cisco import backwards_compatibility as bc
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
constants as const)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
nexus_helpers as nexus_help)
LOG = log.getLogger(__name__)
class NexusTrunkHandler(object):
"""Cisco Nexus Trunk Handler.
This class contains methods called by the trunk infrastruture
to be processed by the cisco_nexus MD.
"""
def __init__(self):
self.plugin = bc.get_plugin()
def _unbind_subport(self, context, port_id, status):
self.plugin.update_port(context, port_id,
{attributes.PORT:
{bc.portbindings.HOST_ID: None,
bc.portbindings.VNIC_TYPE: None,
bc.portbindings.PROFILE: None,
'status': status}})
def trunk_update_postcommit(self, resource, event, trunk_plugin, payload):
current_trunk_data = payload.current_trunk.to_dict()
trunkport = self.plugin.get_port(
payload.context, current_trunk_data['port_id'])
if (nexus_help.is_baremetal(trunkport) and
current_trunk_data['status'] != bc.constants.PORT_STATUS_ACTIVE):
for subport in current_trunk_data['sub_ports']:
self._unbind_subport(payload.context, subport['port_id'],
current_trunk_data['status'])
def subport_postcommit(self, resource, event, trunk_plugin, payload):
trunkport = self.plugin.get_port(
payload.context, payload.current_trunk.port_id)
if (nexus_help.is_baremetal(trunkport) and
trunkport['status'] == bc.constants.PORT_STATUS_ACTIVE):
host_id = trunkport.get(dns.DNSNAME)
subport = payload.subports[0]
trunk_subport_dict = subport.to_dict()
# Set the subport port attributes to match the parent port.
if event == events.AFTER_CREATE:
self.plugin.update_port(
payload.context, trunk_subport_dict['port_id'],
{attributes.PORT:
{bc.portbindings.HOST_ID: host_id,
bc.portbindings.VNIC_TYPE:
bc.portbindings.VNIC_BAREMETAL,
bc.portbindings.PROFILE:
trunkport[bc.portbindings.PROFILE],
'device_owner': bc.trunk_consts.TRUNK_SUBPORT_OWNER,
'status': bc.constants.PORT_STATUS_ACTIVE}})
elif event == events.AFTER_DELETE:
self._unbind_subport(
payload.context, trunk_subport_dict['port_id'],
bc.constants.PORT_STATUS_DOWN)
# Trunk drivers are responsible for setting the trunk
# status. Use the trunk parent port's status.
trunk_obj = bc.trunk_objects.Trunk.get_object(
payload.context, id=payload.trunk_id)
trunk_obj.update(status=trunkport['status'])
class NexusTrunkDriver(bc.trunk_base.DriverBase):
"""Cisco Nexus Trunk Driver.
This class contains methods required to work with the trunk infrastruture.
"""
@property
def is_loaded(self):
try:
return (const.CISCO_NEXUS_ML2_MECH_DRIVER_V2 in
cfg.CONF.ml2.mechanism_drivers)
except cfg.NoSuchOptError:
return False
def register(self, resource, event, trigger, **kwargs):
super(NexusTrunkDriver, self).register(
resource, event, trigger, **kwargs)
self._handler = NexusTrunkHandler()
registry.subscribe(self._handler.trunk_update_postcommit,
bc.trunk_consts.TRUNK, events.AFTER_UPDATE)
for event in (events.AFTER_CREATE, events.AFTER_DELETE):
registry.subscribe(self._handler.subport_postcommit,
bc.trunk_consts.SUBPORTS, event)
@classmethod
def create(cls):
SUPPORTED_INTERFACES = (
bc.portbindings.VIF_TYPE_OTHER,
)
SUPPORTED_SEGMENTATION_TYPES = (
bc.trunk_consts.VLAN,
)
return cls(const.CISCO_NEXUS_ML2_MECH_DRIVER_V2,
SUPPORTED_INTERFACES,
SUPPORTED_SEGMENTATION_TYPES,
None,
can_trunk_bound_port=True)

View File

@ -0,0 +1,41 @@
# Copyright (c) 2017 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.
# Stub module containing the networking_cisco trunk APIs.
#
# Required for tox testing for neutron stable/mitaka.
# TODO(rcurran): Remove once networking_cisco is no longer supporting
# stable/mitaka.
TRUNK_SUBPORT_OWNER = ""
class NexusMDTrunkHandler(object):
def _stub_trunk(self, *args):
return False
is_trunk_parentport = _stub_trunk
is_trunk_subport = _stub_trunk
class NexusTrunkDriver(object):
def create(self):
pass
class DriverBase(object):
pass

View File

@ -36,6 +36,8 @@ import testtools
from networking_cisco import backwards_compatibility as bc
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
constants as const)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
nexus_helpers as nexus_help)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
nexus_network_driver)
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
@ -46,6 +48,7 @@ from networking_cisco.plugins.ml2.drivers.cisco.nexus import constants
from networking_cisco.plugins.ml2.drivers.cisco.nexus import exceptions
from networking_cisco.plugins.ml2.drivers.cisco.nexus import mech_cisco_nexus
from networking_cisco.plugins.ml2.drivers.cisco.nexus import nexus_db_v2
from networking_cisco.plugins.ml2.drivers.cisco.nexus import trunk
from neutron.plugins.common import constants as p_const
from neutron.plugins.ml2 import driver_api as api
@ -499,6 +502,7 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase):
mech_instance._switch_state = {}
mech_instance._nexus_switches = collections.OrderedDict()
mech_instance.trunk = trunk.NexusMDTrunkHandler()
for name, config in self.test_configs.items():
host_name = config.host_name
# baremetal config done differently
@ -615,7 +619,7 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase):
self._cisco_mech_driver.update_port_precommit(port_context)
self._cisco_mech_driver.update_port_postcommit(port_context)
if self._cisco_mech_driver._is_baremetal(port_context.current):
if nexus_help.is_baremetal(port_context.current):
connections = self._cisco_mech_driver._get_port_connections(
port_context.current, '')
else:
@ -692,7 +696,7 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase):
self._cisco_mech_driver.delete_port_precommit(port_context)
self._cisco_mech_driver.delete_port_postcommit(port_context)
if self._cisco_mech_driver._is_baremetal(port_context.current):
if nexus_help.is_baremetal(port_context.current):
connections = self._cisco_mech_driver._get_port_connections(
port_context.current, '')
else:
@ -800,7 +804,7 @@ class TestCiscoNexusBase(testlib_api.SqlTestCase):
self.assertEqual(nbr_of_bindings, bindings_found)
port_context = self._generate_port_context(other_test)
if self._cisco_mech_driver._is_baremetal(port_context.current):
if nexus_help.is_baremetal(port_context.current):
connections = self._cisco_mech_driver._get_baremetal_connections(
port_context.current, False, True)
for switch_ip, intf_type, port, is_p_vlan, _ in connections:

View File

@ -15,6 +15,8 @@
# limitations under the License.
import collections
import unittest
from oslo_config import cfg
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
@ -1196,6 +1198,7 @@ class TestCiscoNexusBaremetalReplay(
'channel-group ' + str(ch_grp) + ' mode active'}
self.mock_ncclient.configure_mock(**data_xml)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_ethernet_ports(self):
"""Provides replay data and result data for unique ports. """
@ -1229,6 +1232,7 @@ class TestCiscoNexusBaremetalReplay(
first_del,
second_del)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_ethernet_port_and_vm(self):
"""Provides replay data and result data for unique ports. """
@ -1261,6 +1265,7 @@ class TestCiscoNexusBaremetalReplay(
first_del,
second_del)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports(self):
"""Provides replay data and result data for unique ports. """
@ -1286,6 +1291,7 @@ class TestCiscoNexusBaremetalReplay(
None,
second_del)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports_and_vm(self):
"""Provides replay data and result data for unique ports. """
@ -1384,6 +1390,7 @@ class TestCiscoNexusBaremetalReplay(
second_del,
replay_init)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_native_nonnative_ethernet_ports(self):
"""Test replay with native and nonnative ethernet ports. """

View File

@ -27,6 +27,8 @@ apply to ssh only OR because rerunning the test would be
redundant.
"""
import unittest
from oslo_config import cfg
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
@ -788,15 +790,18 @@ class TestCiscoNexusRestBaremetalDevice(
super(TestCiscoNexusRestBaremetalDevice, self).setUp()
self.results = TestCiscoNexusRestBaremetalResults()
@unittest.skip("Update to work w/ new native access code.")
def test_create_delete_basic_bm_ethernet_port_and_vm(self):
(super(TestCiscoNexusRestBaremetalDevice, self).
test_create_delete_basic_bm_ethernet_port_and_vm())
@unittest.skip("Update to work w/ new native access code.")
def test_create_delete_basic_port_channel(self):
"""Basic creation and deletion test of 1 learned port-channel."""
(super(TestCiscoNexusRestBaremetalDevice, self).
test_create_delete_basic_port_channel())
@unittest.skip("Update to work w/ new native access code.")
def test_create_delete_learn_vpc_and_vm(self):
(super(TestCiscoNexusRestBaremetalDevice, self).
test_create_delete_learn_vpc_and_vm())
@ -805,10 +810,12 @@ class TestCiscoNexusRestBaremetalDevice(
(super(TestCiscoNexusRestBaremetalDevice, self).
test_create_delete_basic_eth_port_is_native())
@unittest.skip("Update to work w/ new native access code.")
def test_create_delete_switch_ip_not_defined(self):
(super(TestCiscoNexusRestBaremetalDevice, self).
test_create_delete_switch_ip_not_defined())
@unittest.skip("Update to work w/ new native access code.")
def test_automated_port_channel_creation_deletion(self):
"""Basic creation and deletion test of 1 auto port-channel."""
@ -836,6 +843,7 @@ class TestCiscoNexusRestBaremetalDevice(
self.assertEqual(
25, len(nxos_db.get_free_switch_vpc_allocs(switch_ip)))
@unittest.skip("Update to work w/ new native access code.")
def test_create_delete_automated_vpc_and_vm(self):
"""Basic creation and deletion test of 2 auto port-channel and vm."""

View File

@ -28,6 +28,7 @@ apply to ssh only OR because rerunning the test would be
redundant.
"""
import unittest
from oslo_config import cfg
@ -874,41 +875,49 @@ class TestCiscoNexusRestBaremetalReplay(
super(TestCiscoNexusRestBaremetalReplay, self).setUp()
self.results = TestCiscoNexusRestBaremetalReplayResults()
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_ethernet_ports(self):
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_ethernet_ports())
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_ethernet_port_and_vm(self):
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_ethernet_port_and_vm())
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports(self):
self._init_port_channel(469, 3)
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_vPC_ports())
self._init_port_channel(470, 3)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports_and_vm(self):
self._init_port_channel(470, 3)
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_vPC_ports_and_vm())
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports_chg_vPC_nbr(self):
self._init_port_channel(469, 3)
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_vPC_ports_chg_vPC_nbr())
self._init_port_channel(470, 3)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_vPC_ports_chg_to_enet(self):
self._init_port_channel(469, 3)
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_vPC_ports_chg_to_enet())
self._init_port_channel(470, 3)
@unittest.skip("Update to work w/ new native access code.")
def test_replay_unique_native_nonnative_ethernet_ports(self):
(super(TestCiscoNexusRestBaremetalReplay, self).
test_replay_unique_native_nonnative_ethernet_ports())
@unittest.skip("Update to work w/ new native access code.")
def test_replay_automated_vPC_ports_and_vm(self):
"""Provides replay data and result data for unique ports. """

View File

@ -0,0 +1,156 @@
# Copyright (c) 2017 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.
import mock
import testtools
from networking_cisco import backwards_compatibility as bc
from networking_cisco.plugins.ml2.drivers.cisco.nexus import trunk
from neutron.api.v2 import attributes
from neutron.extensions import dns
from neutron.tests.unit.db import test_db_base_plugin_v2
PORT_ID = 'fake_port_id'
TRUNK_ID = 'fake_trunk_id'
DNS_NAME = 'test_dns_name'
VM_NAME = 'test_vm_name'
SEGMENTATION_VLAN = 'vlan'
SEGMENTATION_ID1 = 101
SEGMENTATION_ID2 = 102
SUBPORTS = [
{'segmentation_type': SEGMENTATION_VLAN, 'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID1},
{'segmentation_type': SEGMENTATION_VLAN, 'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID2}]
TRUNK = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'sub_ports': SUBPORTS,
'name': 'trunk0',
'admin_state_up': 'true',
'tenant_id': 'fake_tenant_id',
'project_id': 'fake_project_id',
'port_id': PORT_ID,
'id': TRUNK_ID,
'description': 'fake trunk port'}
PROFILE_BAREMETAL = [{"switch_info": "test_value"}]
SUBPORT = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID1}
PORT_BAREMETAL = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'id': PORT_ID,
bc.portbindings.VNIC_TYPE: bc.portbindings.VNIC_BAREMETAL,
dns.DNSNAME: DNS_NAME,
bc.portbindings.PROFILE: {"local_link_information": PROFILE_BAREMETAL},
'trunk_details': {'trunk_id': TRUNK_ID, 'sub_ports': SUBPORTS}}
PORT_VM = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'id': PORT_ID,
bc.portbindings.VNIC_TYPE: bc.portbindings.VNIC_NORMAL,
bc.portbindings.HOST_ID: VM_NAME,
bc.portbindings.PROFILE: {},
'trunk_details': {'trunk_id': TRUNK_ID, 'sub_ports': SUBPORTS}}
class TestSubPort(object):
port_id = PORT_ID
trunk_id = TRUNK_ID
segmentation_type = SEGMENTATION_VLAN
segmentation_id = SEGMENTATION_ID1
class TestTrunk(object):
admin_state_up = 'test_admin_state'
id = TRUNK_ID
tenant_id = 'test_tenant_id'
name = 'test_trunk_name'
port_id = PORT_ID
status = bc.constants.PORT_STATUS_ACTIVE
sub_ports = SUBPORTS
update = mock.Mock()
@testtools.skipIf(bc.NEUTRON_VERSION < bc.NEUTRON_OCATA_VERSION,
"Test not applicable prior to stable/ocata.")
class TestNexusTrunkHandler(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
def setUp(self):
super(TestNexusTrunkHandler, self).setUp()
self.handler = trunk.NexusMDTrunkHandler()
self.plugin = bc.get_plugin()
self.plugin.get_port = mock.Mock()
self.plugin.update_port = mock.Mock()
self.mock_subport_get_object = mock.patch.object(
bc.trunk_objects.SubPort, 'get_object',
return_value=TestSubPort).start()
self.mock_trunk_get_object = mock.patch.object(
bc.trunk_objects.Trunk, 'get_object',
return_value=TestTrunk).start()
self.mock_trunk_get_object = mock.patch.object(
bc.trunk_objects.Trunk, 'get_object').start()
def _test_update_subports(self, port, host_id):
self.handler.update_subports(port)
self.assertEqual(2, self.plugin.update_port.call_count)
self.plugin.update_port.assert_called_with(mock.ANY, PORT_ID,
{attributes.PORT:
{bc.portbindings.HOST_ID: host_id,
bc.portbindings.VNIC_TYPE: bc.portbindings.VNIC_BAREMETAL,
bc.portbindings.PROFILE: port[bc.portbindings.PROFILE],
'device_owner': bc.trunk_consts.TRUNK_SUBPORT_OWNER,
'status': bc.constants.PORT_STATUS_ACTIVE}})
self.mock_trunk_get_object.called_once_with(mock.ANY, id=TRUNK_ID)
TestTrunk.update.called_once_with(
status=bc.trunk_consts.ACTIVE_STATUS)
self.mock_trunk_get_object.assert_called_once_with(
mock.ANY, id=TRUNK_ID)
def test_is_trunk_parentport(self):
return_value = self.handler.is_trunk_parentport(PORT_VM)
self.assertTrue(return_value)
def test_is_trunk_parentport_no_trunk(self):
PORT_VM_NO_TRUNK = PORT_VM.copy()
del PORT_VM_NO_TRUNK['trunk_details']
return_value = self.handler.is_trunk_parentport(PORT_VM_NO_TRUNK)
self.assertFalse(return_value)
def test_is_trunk_subport(self):
PORT_VM['device_owner'] = bc.trunk_consts.TRUNK_SUBPORT_OWNER
return_value = self.handler.is_trunk_subport(PORT_VM)
self.assertTrue(return_value)
def test_is_trunk_subport_invalid_deviceowner(self):
PORT_VM['device_owner'] = 'fake_owner'
return_value = self.handler.is_trunk_subport(PORT_VM)
self.assertFalse(return_value)
def test_update_subports_baremetal(self):
self._test_update_subports(PORT_BAREMETAL, DNS_NAME)

View File

@ -0,0 +1,213 @@
# Copyright (c) 2017 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.
import mock
import testtools
from oslo_config import cfg
from networking_cisco import backwards_compatibility as bc
from networking_cisco.plugins.ml2.drivers.cisco.nexus import (
constants as const)
from networking_cisco.services.trunk import nexus_trunk
from neutron.callbacks import events
from neutron.extensions import dns
from neutron.tests.unit.db import test_db_base_plugin_v2
from neutron.tests.unit import testlib_api
PORT_ID = 'fake_port_id'
TRUNK_ID = 'fake_trunk_id'
DNS_NAME = 'test_dns_name'
VM_NAME = 'test_vm_name'
SEGMENTATION_VLAN = 'vlan'
SEGMENTATION_ID1 = 101
SEGMENTATION_ID2 = 102
SUBPORTS = [
{'segmentation_type': SEGMENTATION_VLAN, 'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID1},
{'segmentation_type': SEGMENTATION_VLAN, 'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID2}]
TRUNK = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'sub_ports': SUBPORTS,
'name': 'trunk0',
'admin_state_up': 'true',
'tenant_id': 'fake_tenant_id',
'project_id': 'fake_project_id',
'port_id': PORT_ID,
'id': TRUNK_ID,
'description': 'fake trunk port'}
SUBPORT = {
'status': bc.constants.PORT_STATUS_ACTIVE,
'port_id': PORT_ID,
'segmentation_id': SEGMENTATION_ID1}
PORT_BAREMETAL = {
'status': bc.constants.PORT_STATUS_ACTIVE,
bc.portbindings.VNIC_TYPE: bc.portbindings.VNIC_BAREMETAL,
dns.DNSNAME: DNS_NAME,
bc.portbindings.PROFILE: {"local_link_information": []}}
PORT_VM = {
'status': bc.constants.PORT_STATUS_ACTIVE,
bc.portbindings.VNIC_TYPE: bc.portbindings.VNIC_NORMAL,
bc.portbindings.HOST_ID: VM_NAME,
bc.portbindings.PROFILE: {}}
PORT_SUBPORT = {
'status': bc.constants.PORT_STATUS_ACTIVE,
bc.portbindings.PROFILE: {}}
@testtools.skipIf(bc.NEUTRON_VERSION < bc.NEUTRON_OCATA_VERSION,
"Test not applicable prior to stable/ocata.")
class TestNexusTrunkHandler(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
def setUp(self):
super(TestNexusTrunkHandler, self).setUp()
self.handler = nexus_trunk.NexusTrunkHandler()
self.plugin = bc.get_plugin()
self.plugin.update_port = mock.Mock()
self.mock_trunk_get_object = mock.patch.object(
bc.trunk_objects.Trunk, 'get_object').start()
def _fake_trunk_payload(self):
payload = mock.Mock()
payload.current_trunk.status = bc.constants.PORT_STATUS_DOWN
payload.current_trunk.to_dict = mock.Mock(return_value=TRUNK)
payload.original_trunk.status = bc.constants.PORT_STATUS_DOWN
payload.original_trunk.to_dict = mock.Mock(return_value=TRUNK)
payload.subports = mock.MagicMock()
payload.subports[0] = mock.Mock()
payload.subports[0].segmentation_id = SEGMENTATION_ID1
payload.subports[0].port_id = PORT_ID
payload.subports[0].to_dict = mock.Mock(return_value=SUBPORT)
payload.trunk_id = TRUNK_ID
return payload
def _call_test_method(self, test_method, port, event=mock.ANY):
self.plugin.get_port = mock.Mock(side_effect=[port, PORT_SUBPORT])
method = getattr(self.handler, test_method)
method(mock.ANY, event, mock.ANY, self._fake_trunk_payload())
def _verify_subport_postcommit(self):
self.assertEqual(1, self.plugin.get_port.call_count)
self.mock_trunk_get_object.assert_called_once_with(
mock.ANY, id=TRUNK_ID)
def _verify_subport_postcommit_unsupported(self):
self.assertEqual(1, self.plugin.get_port.call_count)
self.assertFalse(self.plugin.update_port.call_count)
self.assertFalse(self.mock_trunk_get_object.call_count)
def _verify_subport_postcommit_unsupported_event(self):
self.assertEqual(1, self.plugin.get_port.call_count)
self.assertFalse(self.plugin.update_port.call_count)
self.mock_trunk_get_object.assert_called_once_with(
mock.ANY, id=TRUNK_ID)
def test_trunk_update_postcommit_vm(self):
self._call_test_method(
"trunk_update_postcommit", PORT_VM, event=events.PRECOMMIT_DELETE)
self.plugin.get_port.assert_called_once_with(mock.ANY, PORT_ID)
self.assertFalse(self.plugin.update_port.call_count)
def test_trunk_update_postcommit_baremetal_active_state(self):
self._call_test_method("trunk_update_postcommit", PORT_BAREMETAL)
self.plugin.get_port.assert_called_once_with(mock.ANY, PORT_ID)
self.assertFalse(self.plugin.update_port.call_count)
def test_trunk_update_postcommit_baremetal_down_state(self):
TRUNK['status'] = bc.constants.PORT_STATUS_DOWN
self._call_test_method("trunk_update_postcommit", PORT_BAREMETAL)
self.plugin.get_port.assert_called_once_with(mock.ANY, PORT_ID)
self.assertEqual(
len(TRUNK['sub_ports']), self.plugin.update_port.call_count)
self.plugin.update_port.assert_called_with(mock.ANY, PORT_ID, mock.ANY)
def test_subport_postcommit_baremetal_after_create(self):
self._call_test_method(
"subport_postcommit", PORT_BAREMETAL, event=events.AFTER_CREATE)
self._verify_subport_postcommit()
self.plugin.update_port.assert_called_with(mock.ANY, PORT_ID, mock.ANY)
self.assertEqual(
bc.constants.PORT_STATUS_ACTIVE,
self.plugin.update_port.call_args[0][2]['port']['status'])
def test_subport_postcommit_baremetal_after_delete(self):
self._call_test_method(
"subport_postcommit", PORT_BAREMETAL, event=events.AFTER_DELETE)
self.plugin.update_port.assert_called_with(mock.ANY, PORT_ID, mock.ANY)
self.assertEqual(
bc.constants.PORT_STATUS_DOWN,
self.plugin.update_port.call_args[0][2]['port']['status'])
def test_subport_postcommit_baremetal_unsupported_event(self):
self._call_test_method(
"subport_postcommit", PORT_BAREMETAL, event=events.BEFORE_DELETE)
self._verify_subport_postcommit_unsupported_event()
def test_subport_postcommit_vm(self):
self._call_test_method(
"subport_postcommit", PORT_VM, event=events.AFTER_CREATE)
self._verify_subport_postcommit_unsupported()
def test_subport_postcommit_vm_no_hostid(self):
PORT_VM_NO_HOSTID = PORT_VM.copy()
del PORT_VM_NO_HOSTID[bc.portbindings.HOST_ID]
self._call_test_method(
"subport_postcommit", PORT_VM_NO_HOSTID,
event=events.AFTER_CREATE)
self._verify_subport_postcommit_unsupported()
def test_subport_postcommit_vm_unsupported_event(self):
self._call_test_method(
"subport_postcommit", PORT_VM, event=events.BEFORE_DELETE)
self._verify_subport_postcommit_unsupported()
@testtools.skipIf(bc.NEUTRON_VERSION < bc.NEUTRON_OCATA_VERSION,
"Test not applicable prior to stable/ocata.")
class TestNexusTrunkDriver(testlib_api.SqlTestCase):
def setUp(self):
super(TestNexusTrunkDriver, self).setUp()
def test_is_loaded(self):
driver = nexus_trunk.NexusTrunkDriver.create()
cfg.CONF.set_override('mechanism_drivers',
["logger", const.CISCO_NEXUS_ML2_MECH_DRIVER_V2],
group='ml2')
self.assertTrue(driver.is_loaded)
cfg.CONF.set_override('mechanism_drivers',
['logger'],
group='ml2')
self.assertFalse(driver.is_loaded)
cfg.CONF.set_override('core_plugin', 'some_plugin')
self.assertFalse(driver.is_loaded)