ML2 Cisco Nexus MD: Create pre/post DB event handlers
Split ML2 cisco nexus event handers for update and delete into precommit (called during DB transactions) and postcommit (called after DB transactions) methods. Also fixes some unit tests that were incorrectly accessing context managers without using the "with" statement. Closes-Bug: #1241098 Change-Id: I59b046342706230222c1be39d13a455ca5a884ea
This commit is contained in:
committed by
Gerrit Code Review
parent
260a9f5935
commit
8d0abf2db7
@@ -58,6 +58,12 @@ class NexusPortBindingNotFound(exceptions.NeutronException):
|
|||||||
super(NexusPortBindingNotFound, self).__init__(filters=filters)
|
super(NexusPortBindingNotFound, self).__init__(filters=filters)
|
||||||
|
|
||||||
|
|
||||||
|
class NexusMissingRequiredFields(exceptions.NeutronException):
|
||||||
|
"""Missing required fields to configure nexus switch."""
|
||||||
|
message = _("Missing required field(s) to configure nexus switch: "
|
||||||
|
"%(fields)s")
|
||||||
|
|
||||||
|
|
||||||
class NoNexusSviSwitch(exceptions.NeutronException):
|
class NoNexusSviSwitch(exceptions.NeutronException):
|
||||||
"""No usable nexus switch found."""
|
"""No usable nexus switch found."""
|
||||||
message = _("No usable Nexus switch found to create SVI interface.")
|
message = _("No usable Nexus switch found to create SVI interface.")
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ from oslo.config import cfg
|
|||||||
|
|
||||||
from neutron.common import constants as n_const
|
from neutron.common import constants as n_const
|
||||||
from neutron.extensions import portbindings
|
from neutron.extensions import portbindings
|
||||||
from neutron.openstack.common import excutils
|
|
||||||
from neutron.openstack.common import log as logging
|
from neutron.openstack.common import log as logging
|
||||||
from neutron.plugins.ml2 import driver_api as api
|
from neutron.plugins.ml2 import driver_api as api
|
||||||
from neutron.plugins.ml2.drivers.cisco import config as conf
|
from neutron.plugins.ml2.drivers.cisco import config as conf
|
||||||
@@ -48,7 +47,7 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
|
|||||||
self.credentials = {}
|
self.credentials = {}
|
||||||
self.driver = nexus_network_driver.CiscoNexusDriver()
|
self.driver = nexus_network_driver.CiscoNexusDriver()
|
||||||
|
|
||||||
# Initialize credential store after database initialization
|
# Initialize credential store after database initialization.
|
||||||
cred.Store.initialize()
|
cred.Store.initialize()
|
||||||
|
|
||||||
def _valid_network_segment(self, segment):
|
def _valid_network_segment(self, segment):
|
||||||
@@ -68,145 +67,130 @@ class CiscoNexusMechanismDriver(api.MechanismDriver):
|
|||||||
def _is_status_active(self, port):
|
def _is_status_active(self, port):
|
||||||
return port['status'] == n_const.PORT_STATUS_ACTIVE
|
return port['status'] == n_const.PORT_STATUS_ACTIVE
|
||||||
|
|
||||||
def _get_credential(self, nexus_ip):
|
def _get_switch_info(self, host_id):
|
||||||
"""Return credential information for a given Nexus IP address.
|
|
||||||
|
|
||||||
If credential doesn't exist then also add to local dictionary.
|
|
||||||
"""
|
|
||||||
if nexus_ip not in self.credentials:
|
|
||||||
_nexus_username = cred.Store.get_username(nexus_ip)
|
|
||||||
_nexus_password = cred.Store.get_password(nexus_ip)
|
|
||||||
self.credentials[nexus_ip] = {
|
|
||||||
'username': _nexus_username,
|
|
||||||
'password': _nexus_password
|
|
||||||
}
|
|
||||||
return self.credentials[nexus_ip]
|
|
||||||
|
|
||||||
def _manage_port(self, vlan_name, vlan_id, host, instance):
|
|
||||||
"""Called during create and update port events.
|
|
||||||
|
|
||||||
Create a VLAN in the appropriate switch/port and configure the
|
|
||||||
appropriate interfaces for this VLAN.
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Grab the switch IP and port for this host
|
|
||||||
for switch_ip, attr in self._nexus_switches:
|
for switch_ip, attr in self._nexus_switches:
|
||||||
if str(attr) == str(host):
|
if str(attr) == str(host_id):
|
||||||
port_id = self._nexus_switches[switch_ip, attr]
|
port_id = self._nexus_switches[switch_ip, attr]
|
||||||
break
|
return port_id, switch_ip
|
||||||
else:
|
else:
|
||||||
raise excep.NexusComputeHostNotConfigured(host=host)
|
raise excep.NexusComputeHostNotConfigured(host=host_id)
|
||||||
|
|
||||||
# Check if this network is already in the DB
|
def _configure_nxos_db(self, context, vlan_id, device_id, host_id):
|
||||||
vlan_created = False
|
"""Create the nexus database entry.
|
||||||
vlan_trunked = False
|
|
||||||
|
|
||||||
|
Called during update precommit port event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
port_id, switch_ip = self._get_switch_info(host_id)
|
||||||
|
nxos_db.add_nexusport_binding(port_id, str(vlan_id), switch_ip,
|
||||||
|
device_id)
|
||||||
|
|
||||||
|
def _configure_switch_entry(self, context, vlan_id, device_id, host_id):
|
||||||
|
"""Create a nexus switch entry.
|
||||||
|
|
||||||
|
if needed, create a VLAN in the appropriate switch/port and
|
||||||
|
configure the appropriate interfaces for this VLAN.
|
||||||
|
|
||||||
|
Called during update postcommit port event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
port_id, switch_ip = self._get_switch_info(host_id)
|
||||||
|
vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
|
||||||
|
|
||||||
|
# Check to see if this is the first binding to use this vlan on the
|
||||||
|
# switch/port. Configure switch accordingly.
|
||||||
|
bindings = nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
||||||
|
if len(bindings) == 1:
|
||||||
|
LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
|
||||||
|
self.driver.create_and_trunk_vlan(switch_ip, vlan_id, vlan_name,
|
||||||
|
port_id)
|
||||||
|
else:
|
||||||
|
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
|
||||||
|
self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id, port_id)
|
||||||
|
|
||||||
|
def _delete_nxos_db(self, context, vlan_id, device_id, host_id):
|
||||||
|
"""Delete the nexus database entry.
|
||||||
|
|
||||||
|
Called during delete precommit port event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
row = nxos_db.get_nexusvm_binding(vlan_id, device_id)
|
||||||
|
nxos_db.remove_nexusport_binding(row.port_id, row.vlan_id,
|
||||||
|
row.switch_ip, row.instance_id)
|
||||||
|
except excep.NexusPortBindingNotFound:
|
||||||
|
return
|
||||||
|
|
||||||
|
def _delete_switch_entry(self, context, vlan_id, device_id, host_id):
|
||||||
|
"""Delete the nexus switch entry.
|
||||||
|
|
||||||
|
By accessing the current db entries determine if switch
|
||||||
|
configuration can be removed.
|
||||||
|
|
||||||
|
Called during update postcommit port event.
|
||||||
|
|
||||||
|
"""
|
||||||
|
port_id, switch_ip = self._get_switch_info(host_id)
|
||||||
|
|
||||||
|
# if there are no remaining db entries using this vlan on this nexus
|
||||||
|
# switch port then remove vlan from the switchport trunk.
|
||||||
try:
|
try:
|
||||||
nxos_db.get_port_vlan_switch_binding(port_id, vlan_id, switch_ip)
|
nxos_db.get_port_vlan_switch_binding(port_id, vlan_id, switch_ip)
|
||||||
except excep.NexusPortBindingNotFound:
|
except excep.NexusPortBindingNotFound:
|
||||||
# Check for vlan/switch binding
|
self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id, port_id)
|
||||||
|
|
||||||
|
# if there are no remaining db entries using this vlan on this
|
||||||
|
# nexus switch then remove the vlan.
|
||||||
try:
|
try:
|
||||||
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
nxos_db.get_nexusvlan_binding(vlan_id, switch_ip)
|
||||||
except excep.NexusPortBindingNotFound:
|
except excep.NexusPortBindingNotFound:
|
||||||
# Create vlan and trunk vlan on the port
|
self.driver.delete_vlan(switch_ip, vlan_id)
|
||||||
LOG.debug(_("Nexus: create & trunk vlan %s"), vlan_name)
|
|
||||||
self.driver.create_and_trunk_vlan(switch_ip, vlan_id,
|
|
||||||
vlan_name, port_id)
|
|
||||||
vlan_created = True
|
|
||||||
vlan_trunked = True
|
|
||||||
else:
|
|
||||||
# Only trunk vlan on the port
|
|
||||||
LOG.debug(_("Nexus: trunk vlan %s"), vlan_name)
|
|
||||||
self.driver.enable_vlan_on_trunk_int(switch_ip, vlan_id,
|
|
||||||
port_id)
|
|
||||||
vlan_trunked = True
|
|
||||||
|
|
||||||
try:
|
def _port_action(self, context, func):
|
||||||
nxos_db.add_nexusport_binding(port_id, str(vlan_id),
|
"""Verify configuration and then process event."""
|
||||||
switch_ip, instance)
|
device_id = context.current.get('device_id')
|
||||||
except Exception:
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
# Add binding failed, roll back any vlan creation/enabling
|
|
||||||
if vlan_created and vlan_trunked:
|
|
||||||
LOG.debug(_("Nexus: delete & untrunk vlan %s"), vlan_name)
|
|
||||||
self.driver.delete_and_untrunk_vlan(switch_ip, vlan_id,
|
|
||||||
port_id)
|
|
||||||
elif vlan_created:
|
|
||||||
LOG.debug(_("Nexus: delete vlan %s"), vlan_name)
|
|
||||||
self.driver.delete_vlan(switch_ip, vlan_id)
|
|
||||||
elif vlan_trunked:
|
|
||||||
LOG.debug(_("Nexus: untrunk vlan %s"), vlan_name)
|
|
||||||
self.driver.disable_vlan_on_trunk_int(switch_ip, vlan_id,
|
|
||||||
port_id)
|
|
||||||
|
|
||||||
def _invoke_nexus_on_port_event(self, context):
|
|
||||||
vlan_id = self._get_vlanid(context)
|
|
||||||
host_id = context.current.get(portbindings.HOST_ID)
|
host_id = context.current.get(portbindings.HOST_ID)
|
||||||
|
|
||||||
if vlan_id and host_id:
|
# Workaround until vlan can be retrieved during delete_port_postcommit
|
||||||
vlan_name = cfg.CONF.ml2_cisco.vlan_name_prefix + str(vlan_id)
|
# (or prehaps unbind_port) event.
|
||||||
instance_id = context.current.get('device_id')
|
if func == self._delete_switch_entry:
|
||||||
self._manage_port(vlan_name, vlan_id, host_id, instance_id)
|
vlan_id = self._delete_port_postcommit_vlan
|
||||||
else:
|
else:
|
||||||
LOG.debug(_("Vlan ID %(vlan_id)s or Host ID %(host_id)s missing."),
|
vlan_id = self._get_vlanid(context)
|
||||||
{'vlan_id': vlan_id, 'host_id': host_id})
|
|
||||||
|
if vlan_id and device_id and host_id:
|
||||||
|
func(context, vlan_id, device_id, host_id)
|
||||||
|
else:
|
||||||
|
fields = "vlan_id " if not vlan_id else ""
|
||||||
|
fields += "device_id " if not device_id else ""
|
||||||
|
fields += "host_id" if not host_id else ""
|
||||||
|
raise excep.NexusMissingRequiredFields(fields=fields)
|
||||||
|
|
||||||
|
# Workaround until vlan can be retrieved during delete_port_postcommit
|
||||||
|
# (or prehaps unbind_port) event.
|
||||||
|
if func == self._delete_nxos_db:
|
||||||
|
self._delete_port_postcommit_vlan = vlan_id
|
||||||
|
else:
|
||||||
|
self._delete_port_postcommit_vlan = 0
|
||||||
|
|
||||||
|
def update_port_precommit(self, context):
|
||||||
|
"""Update port pre-database transaction commit event."""
|
||||||
|
port = context.current
|
||||||
|
if self._is_deviceowner_compute(port) and self._is_status_active(port):
|
||||||
|
self._port_action(context, self._configure_nxos_db)
|
||||||
|
|
||||||
def update_port_postcommit(self, context):
|
def update_port_postcommit(self, context):
|
||||||
"""Update port post-database commit event."""
|
"""Update port non-database commit event."""
|
||||||
port = context.current
|
port = context.current
|
||||||
|
|
||||||
if self._is_deviceowner_compute(port) and self._is_status_active(port):
|
if self._is_deviceowner_compute(port) and self._is_status_active(port):
|
||||||
self._invoke_nexus_on_port_event(context)
|
self._port_action(context, self._configure_switch_entry)
|
||||||
|
|
||||||
def delete_port_precommit(self, context):
|
def delete_port_precommit(self, context):
|
||||||
"""Delete port pre-database commit event.
|
"""Delete port pre-database commit event."""
|
||||||
|
if self._is_deviceowner_compute(context.current):
|
||||||
|
self._port_action(context, self._delete_nxos_db)
|
||||||
|
|
||||||
Delete port bindings from the database and scan whether the network
|
def delete_port_postcommit(self, context):
|
||||||
is still required on the interfaces trunked.
|
"""Delete port non-database commit event."""
|
||||||
"""
|
if self._is_deviceowner_compute(context.current):
|
||||||
|
self._port_action(context, self._delete_switch_entry)
|
||||||
if not self._is_deviceowner_compute(context.current):
|
|
||||||
return
|
|
||||||
|
|
||||||
port = context.current
|
|
||||||
device_id = port['device_id']
|
|
||||||
vlan_id = self._get_vlanid(context)
|
|
||||||
|
|
||||||
if not vlan_id or not device_id:
|
|
||||||
return
|
|
||||||
|
|
||||||
# Delete DB row for this port
|
|
||||||
try:
|
|
||||||
row = nxos_db.get_nexusvm_binding(vlan_id, device_id)
|
|
||||||
except excep.NexusPortBindingNotFound:
|
|
||||||
return
|
|
||||||
|
|
||||||
switch_ip = row.switch_ip
|
|
||||||
nexus_port = None
|
|
||||||
if row.port_id != 'router':
|
|
||||||
nexus_port = row.port_id
|
|
||||||
|
|
||||||
nxos_db.remove_nexusport_binding(row.port_id, row.vlan_id,
|
|
||||||
row.switch_ip, row.instance_id)
|
|
||||||
|
|
||||||
# Check for any other bindings with the same vlan_id and switch_ip
|
|
||||||
try:
|
|
||||||
nxos_db.get_nexusvlan_binding(row.vlan_id, row.switch_ip)
|
|
||||||
except excep.NexusPortBindingNotFound:
|
|
||||||
try:
|
|
||||||
# Delete this vlan from this switch
|
|
||||||
if nexus_port:
|
|
||||||
self.driver.disable_vlan_on_trunk_int(switch_ip,
|
|
||||||
row.vlan_id,
|
|
||||||
nexus_port)
|
|
||||||
self.driver.delete_vlan(switch_ip, row.vlan_id)
|
|
||||||
except Exception:
|
|
||||||
# The delete vlan operation on the Nexus failed,
|
|
||||||
# so this delete_port request has failed. For
|
|
||||||
# consistency, roll back the Nexus database to what
|
|
||||||
# it was before this request.
|
|
||||||
with excutils.save_and_reraise_exception():
|
|
||||||
nxos_db.add_nexusport_binding(row.port_id,
|
|
||||||
row.vlan_id,
|
|
||||||
row.switch_ip,
|
|
||||||
row.instance_id)
|
|
||||||
|
|||||||
@@ -170,12 +170,13 @@ class CiscoNexusDriver(object):
|
|||||||
|
|
||||||
def enable_vlan_on_trunk_int(self, nexus_host, vlanid, interface):
|
def enable_vlan_on_trunk_int(self, nexus_host, vlanid, interface):
|
||||||
"""Enable a VLAN on a trunk interface."""
|
"""Enable a VLAN on a trunk interface."""
|
||||||
# If one or more VLANs are already configured on this interface,
|
# If more than one VLAN is configured on this interface then
|
||||||
# include the 'add' keyword.
|
# include the 'add' keyword.
|
||||||
if nexus_db_v2.get_port_switch_bindings(interface, nexus_host):
|
if len(nexus_db_v2.get_port_switch_bindings(interface,
|
||||||
snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET
|
nexus_host)) == 1:
|
||||||
else:
|
|
||||||
snippet = snipp.CMD_INT_VLAN_SNIPPET
|
snippet = snipp.CMD_INT_VLAN_SNIPPET
|
||||||
|
else:
|
||||||
|
snippet = snipp.CMD_INT_VLAN_ADD_SNIPPET
|
||||||
confstr = snippet % (interface, vlanid)
|
confstr = snippet % (interface, vlanid)
|
||||||
confstr = self.create_xml_snippet(confstr)
|
confstr = self.create_xml_snippet(confstr)
|
||||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||||
|
|||||||
@@ -28,7 +28,6 @@ from neutron.plugins.ml2 import config as ml2_config
|
|||||||
from neutron.plugins.ml2.drivers.cisco import config as cisco_config
|
from neutron.plugins.ml2.drivers.cisco import config as cisco_config
|
||||||
from neutron.plugins.ml2.drivers.cisco import exceptions as c_exc
|
from neutron.plugins.ml2.drivers.cisco import exceptions as c_exc
|
||||||
from neutron.plugins.ml2.drivers.cisco import mech_cisco_nexus
|
from neutron.plugins.ml2.drivers.cisco import mech_cisco_nexus
|
||||||
from neutron.plugins.ml2.drivers.cisco import nexus_db_v2
|
|
||||||
from neutron.plugins.ml2.drivers.cisco import nexus_network_driver
|
from neutron.plugins.ml2.drivers.cisco import nexus_network_driver
|
||||||
from neutron.plugins.ml2.drivers import type_vlan as vlan_config
|
from neutron.plugins.ml2.drivers import type_vlan as vlan_config
|
||||||
from neutron.tests.unit import test_db_plugin
|
from neutron.tests.unit import test_db_plugin
|
||||||
@@ -37,13 +36,19 @@ LOG = logging.getLogger(__name__)
|
|||||||
ML2_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
ML2_PLUGIN = 'neutron.plugins.ml2.plugin.Ml2Plugin'
|
||||||
PHYS_NET = 'physnet1'
|
PHYS_NET = 'physnet1'
|
||||||
COMP_HOST_NAME = 'testhost'
|
COMP_HOST_NAME = 'testhost'
|
||||||
|
COMP_HOST_NAME_2 = 'testhost_2'
|
||||||
VLAN_START = 1000
|
VLAN_START = 1000
|
||||||
VLAN_END = 1100
|
VLAN_END = 1100
|
||||||
NEXUS_IP_ADDR = '1.1.1.1'
|
NEXUS_IP_ADDR = '1.1.1.1'
|
||||||
|
NETWORK_NAME = 'test_network'
|
||||||
|
NETWORK_NAME_2 = 'test_network_2'
|
||||||
|
NEXUS_INTERFACE = '1/1'
|
||||||
|
NEXUS_INTERFACE_2 = '1/2'
|
||||||
CIDR_1 = '10.0.0.0/24'
|
CIDR_1 = '10.0.0.0/24'
|
||||||
CIDR_2 = '10.0.1.0/24'
|
CIDR_2 = '10.0.1.0/24'
|
||||||
DEVICE_ID_1 = '11111111-1111-1111-1111-111111111111'
|
DEVICE_ID_1 = '11111111-1111-1111-1111-111111111111'
|
||||||
DEVICE_ID_2 = '22222222-2222-2222-2222-222222222222'
|
DEVICE_ID_2 = '22222222-2222-2222-2222-222222222222'
|
||||||
|
DEVICE_OWNER = 'compute:None'
|
||||||
|
|
||||||
|
|
||||||
class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
||||||
@@ -82,7 +87,8 @@ class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
|||||||
(NEXUS_IP_ADDR, 'username'): 'admin',
|
(NEXUS_IP_ADDR, 'username'): 'admin',
|
||||||
(NEXUS_IP_ADDR, 'password'): 'mySecretPassword',
|
(NEXUS_IP_ADDR, 'password'): 'mySecretPassword',
|
||||||
(NEXUS_IP_ADDR, 'ssh_port'): 22,
|
(NEXUS_IP_ADDR, 'ssh_port'): 22,
|
||||||
(NEXUS_IP_ADDR, COMP_HOST_NAME): '1/1'}
|
(NEXUS_IP_ADDR, COMP_HOST_NAME): NEXUS_INTERFACE,
|
||||||
|
(NEXUS_IP_ADDR, COMP_HOST_NAME_2): NEXUS_INTERFACE_2}
|
||||||
nexus_patch = mock.patch.dict(
|
nexus_patch = mock.patch.dict(
|
||||||
cisco_config.ML2MechCiscoConfig.nexus_dict,
|
cisco_config.ML2MechCiscoConfig.nexus_dict,
|
||||||
nexus_config)
|
nexus_config)
|
||||||
@@ -96,15 +102,15 @@ class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
|||||||
'_import_ncclient',
|
'_import_ncclient',
|
||||||
return_value=self.mock_ncclient).start()
|
return_value=self.mock_ncclient).start()
|
||||||
|
|
||||||
# Mock port values for 'status' and 'binding:segmenation_id'
|
# Mock port values for 'status' and 'binding:segmentation_id'
|
||||||
mock_status = mock.patch.object(
|
mock_status = mock.patch.object(
|
||||||
mech_cisco_nexus.CiscoNexusMechanismDriver,
|
mech_cisco_nexus.CiscoNexusMechanismDriver,
|
||||||
'_is_status_active').start()
|
'_is_status_active').start()
|
||||||
mock_status.return_value = n_const.PORT_STATUS_ACTIVE
|
mock_status.return_value = n_const.PORT_STATUS_ACTIVE
|
||||||
|
|
||||||
def _mock_get_vlanid(context):
|
def _mock_get_vlanid(context):
|
||||||
port = context.current
|
network = context.network.current
|
||||||
if port['device_id'] == DEVICE_ID_1:
|
if network['name'] == NETWORK_NAME:
|
||||||
return VLAN_START
|
return VLAN_START
|
||||||
else:
|
else:
|
||||||
return VLAN_START + 1
|
return VLAN_START + 1
|
||||||
@@ -138,12 +144,35 @@ class CiscoML2MechanismTestCase(test_db_plugin.NeutronDbPluginV2TestCase):
|
|||||||
config = {attr: None}
|
config = {attr: None}
|
||||||
self.mock_ncclient.configure_mock(**config)
|
self.mock_ncclient.configure_mock(**config)
|
||||||
|
|
||||||
|
def _is_in_nexus_cfg(self, words):
|
||||||
|
"""Check if any config sent to Nexus contains all words in a list."""
|
||||||
|
for call in (self.mock_ncclient.connect.return_value.
|
||||||
|
edit_config.mock_calls):
|
||||||
|
configlet = call[2]['config']
|
||||||
|
if all(word in configlet for word in words):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def _is_in_last_nexus_cfg(self, words):
|
def _is_in_last_nexus_cfg(self, words):
|
||||||
"""Confirm last config sent to Nexus contains specified keywords."""
|
"""Confirm last config sent to Nexus contains specified keywords."""
|
||||||
last_cfg = (self.mock_ncclient.connect.return_value.
|
last_cfg = (self.mock_ncclient.connect.return_value.
|
||||||
edit_config.mock_calls[-1][2]['config'])
|
edit_config.mock_calls[-1][2]['config'])
|
||||||
return all(word in last_cfg for word in words)
|
return all(word in last_cfg for word in words)
|
||||||
|
|
||||||
|
def _is_vlan_configured(self, vlan_creation_expected=True,
|
||||||
|
add_keyword_expected=False):
|
||||||
|
vlan_created = self._is_in_nexus_cfg(['vlan', 'vlan-name'])
|
||||||
|
add_appears = self._is_in_last_nexus_cfg(['add'])
|
||||||
|
return (self._is_in_last_nexus_cfg(['allowed', 'vlan']) and
|
||||||
|
vlan_created == vlan_creation_expected and
|
||||||
|
add_appears == add_keyword_expected)
|
||||||
|
|
||||||
|
def _is_vlan_unconfigured(self, vlan_deletion_expected=True):
|
||||||
|
vlan_deleted = self._is_in_last_nexus_cfg(
|
||||||
|
['no', 'vlan', 'vlan-id-create-delete'])
|
||||||
|
return (self._is_in_nexus_cfg(['allowed', 'vlan', 'remove']) and
|
||||||
|
vlan_deleted == vlan_deletion_expected)
|
||||||
|
|
||||||
|
|
||||||
class TestCiscoBasicGet(CiscoML2MechanismTestCase,
|
class TestCiscoBasicGet(CiscoML2MechanismTestCase,
|
||||||
test_db_plugin.TestBasicGet):
|
test_db_plugin.TestBasicGet):
|
||||||
@@ -161,62 +190,48 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
test_db_plugin.TestPortsV2):
|
test_db_plugin.TestPortsV2):
|
||||||
|
|
||||||
@contextlib.contextmanager
|
@contextlib.contextmanager
|
||||||
def _create_resources(self, name='myname', cidr=CIDR_1,
|
def _create_resources(self, name=NETWORK_NAME, cidr=CIDR_1,
|
||||||
device_id=DEVICE_ID_1,
|
device_id=DEVICE_ID_1,
|
||||||
host_id=COMP_HOST_NAME,
|
host_id=COMP_HOST_NAME):
|
||||||
expected_exception=None):
|
|
||||||
"""Create network, subnet, and port resources for test cases.
|
"""Create network, subnet, and port resources for test cases.
|
||||||
|
|
||||||
Create a network, subnet, port and then update port, yield the result,
|
Create a network, subnet, port and then update the port, yield the
|
||||||
then delete the port, subnet, and network.
|
result, then delete the port, subnet and network.
|
||||||
|
|
||||||
:param name: Name of network to be created.
|
:param name: Name of network to be created.
|
||||||
:param cidr: cidr address of subnetwork to be created.
|
:param cidr: cidr address of subnetwork to be created.
|
||||||
:param device_id: Device ID to use for port to be created/updated.
|
:param device_id: Device ID to use for port to be created/updated.
|
||||||
:param host_id: Host ID to use for port create/update.
|
:param host_id: Host ID to use for port create/update.
|
||||||
:param expected_exception: Expected HTTP code.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
ctx = context.get_admin_context()
|
|
||||||
with self.network(name=name) as network:
|
with self.network(name=name) as network:
|
||||||
with self.subnet(network=network, cidr=cidr) as subnet:
|
with self.subnet(network=network, cidr=cidr) as subnet:
|
||||||
net_id = subnet['subnet']['network_id']
|
with self.port(subnet=subnet, cidr=cidr) as port:
|
||||||
args = (portbindings.HOST_ID, 'device_id', 'device_owner',
|
data = {'port': {portbindings.HOST_ID: host_id,
|
||||||
'admin_state_up')
|
'device_id': device_id,
|
||||||
port_dict = {portbindings.HOST_ID: host_id,
|
'device_owner': 'compute:none',
|
||||||
'device_id': device_id,
|
'admin_state_up': True}}
|
||||||
'device_owner': 'compute:none',
|
req = self.new_update_request('ports', data,
|
||||||
'admin_state_up': True}
|
port['port']['id'])
|
||||||
|
res = req.get_response(self.api)
|
||||||
|
yield res.status_int
|
||||||
|
|
||||||
res = self._create_port(self.fmt, net_id, arg_list=args,
|
def _assertExpectedHTTP(self, status, exc):
|
||||||
context=ctx, **port_dict)
|
"""Confirm that an HTTP status corresponds to an expected exception.
|
||||||
port = self.deserialize(self.fmt, res)
|
|
||||||
|
|
||||||
expected_exception = self._expectedHTTP(expected_exception)
|
Confirm that an HTTP status which has been returned for an
|
||||||
data = {'port': port_dict}
|
neutron API request matches the HTTP status corresponding
|
||||||
self._update('ports', port['port']['id'], data,
|
to an expected exception.
|
||||||
expected_code=expected_exception,
|
|
||||||
neutron_context=ctx)
|
|
||||||
|
|
||||||
try:
|
:param status: HTTP status
|
||||||
yield port
|
:param exc: Expected exception
|
||||||
finally:
|
|
||||||
self._delete('ports', port['port']['id'])
|
|
||||||
|
|
||||||
def _expectedHTTP(self, exc):
|
|
||||||
"""Map a Cisco exception to the HTTP status equivalent.
|
|
||||||
|
|
||||||
:param exc: Expected Cisco exception
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
if exc == None:
|
if exc in base.FAULT_MAP:
|
||||||
expected_http = wexc.HTTPOk.code
|
|
||||||
elif exc in base.FAULT_MAP:
|
|
||||||
expected_http = base.FAULT_MAP[exc].code
|
expected_http = base.FAULT_MAP[exc].code
|
||||||
else:
|
else:
|
||||||
expected_http = wexc.HTTPInternalServerError.code
|
expected_http = wexc.HTTPInternalServerError.code
|
||||||
|
self.assertEqual(status, expected_http)
|
||||||
return expected_http
|
|
||||||
|
|
||||||
def test_create_ports_bulk_emulated_plugin_failure(self):
|
def test_create_ports_bulk_emulated_plugin_failure(self):
|
||||||
real_has_attr = hasattr
|
real_has_attr = hasattr
|
||||||
@@ -289,16 +304,23 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
keyword 'add'.
|
keyword 'add'.
|
||||||
|
|
||||||
Confirm that for the second VLAN configured on a Nexus interface,
|
Confirm that for the second VLAN configured on a Nexus interface,
|
||||||
the command staring sent to the switch contains the keyword 'add'.
|
the command string sent to the switch contains the keyword 'add'.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with self._create_resources(name='net1', cidr=CIDR_1):
|
# First vlan should be configured without 'add' keyword
|
||||||
self.assertTrue(self._is_in_last_nexus_cfg(['allowed', 'vlan']))
|
with self._create_resources():
|
||||||
self.assertFalse(self._is_in_last_nexus_cfg(['add']))
|
self.assertTrue(self._is_vlan_configured(
|
||||||
with self._create_resources(name='net2', device_id=DEVICE_ID_2,
|
vlan_creation_expected=True,
|
||||||
|
add_keyword_expected=False))
|
||||||
|
self.mock_ncclient.reset_mock()
|
||||||
|
|
||||||
|
# Second vlan should be configured with 'add' keyword
|
||||||
|
with self._create_resources(name=NETWORK_NAME_2,
|
||||||
|
device_id=DEVICE_ID_2,
|
||||||
cidr=CIDR_2):
|
cidr=CIDR_2):
|
||||||
self.assertTrue(
|
self.assertTrue(self._is_vlan_configured(
|
||||||
self._is_in_last_nexus_cfg(['allowed', 'vlan', 'add']))
|
vlan_creation_expected=True,
|
||||||
|
add_keyword_expected=True))
|
||||||
|
|
||||||
def test_nexus_connect_fail(self):
|
def test_nexus_connect_fail(self):
|
||||||
"""Test failure to connect to a Nexus switch.
|
"""Test failure to connect to a Nexus switch.
|
||||||
@@ -310,7 +332,54 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
"""
|
"""
|
||||||
with self._patch_ncclient('connect.side_effect',
|
with self._patch_ncclient('connect.side_effect',
|
||||||
AttributeError):
|
AttributeError):
|
||||||
self._create_resources(expected_exception=c_exc.NexusConnectFailed)
|
with self._create_resources() as result_status:
|
||||||
|
self._assertExpectedHTTP(result_status,
|
||||||
|
c_exc.NexusConnectFailed)
|
||||||
|
|
||||||
|
def test_nexus_vlan_config_two_hosts(self):
|
||||||
|
"""Verify config/unconfig of vlan on two compute hosts."""
|
||||||
|
|
||||||
|
@contextlib.contextmanager
|
||||||
|
def _create_port_check_vlan(comp_host_name, device_id,
|
||||||
|
vlan_creation_expected=True):
|
||||||
|
with self.port(subnet=subnet, fmt=self.fmt) as port:
|
||||||
|
data = {'port': {portbindings.HOST_ID: comp_host_name,
|
||||||
|
'device_id': device_id,
|
||||||
|
'device_owner': DEVICE_OWNER,
|
||||||
|
'admin_state_up': True}}
|
||||||
|
req = self.new_update_request('ports', data,
|
||||||
|
port['port']['id'])
|
||||||
|
req.get_response(self.api)
|
||||||
|
self.assertTrue(self._is_vlan_configured(
|
||||||
|
vlan_creation_expected=vlan_creation_expected,
|
||||||
|
add_keyword_expected=False))
|
||||||
|
self.mock_ncclient.reset_mock()
|
||||||
|
yield
|
||||||
|
|
||||||
|
# Create network and subnet
|
||||||
|
with self.network(name=NETWORK_NAME) as network:
|
||||||
|
with self.subnet(network=network, cidr=CIDR_1) as subnet:
|
||||||
|
|
||||||
|
# Create an instance on first compute host
|
||||||
|
with _create_port_check_vlan(COMP_HOST_NAME, DEVICE_ID_1,
|
||||||
|
vlan_creation_expected=True):
|
||||||
|
# Create an instance on second compute host
|
||||||
|
with _create_port_check_vlan(COMP_HOST_NAME_2, DEVICE_ID_2,
|
||||||
|
vlan_creation_expected=False):
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Instance on second host is now terminated.
|
||||||
|
# Vlan should be untrunked from port, but vlan should
|
||||||
|
# still exist on the switch.
|
||||||
|
self.assertTrue(self._is_vlan_unconfigured(
|
||||||
|
vlan_deletion_expected=False))
|
||||||
|
self.mock_ncclient.reset_mock()
|
||||||
|
|
||||||
|
# Instance on first host is now terminated.
|
||||||
|
# Vlan should be untrunked from port and vlan should have
|
||||||
|
# been deleted from the switch.
|
||||||
|
self.assertTrue(self._is_vlan_unconfigured(
|
||||||
|
vlan_deletion_expected=True))
|
||||||
|
|
||||||
def test_nexus_config_fail(self):
|
def test_nexus_config_fail(self):
|
||||||
"""Test a Nexus switch configuration failure.
|
"""Test a Nexus switch configuration failure.
|
||||||
@@ -323,7 +392,9 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
with self._patch_ncclient(
|
with self._patch_ncclient(
|
||||||
'connect.return_value.edit_config.side_effect',
|
'connect.return_value.edit_config.side_effect',
|
||||||
AttributeError):
|
AttributeError):
|
||||||
self._create_resources(expected_exception=c_exc.NexusConfigFailed)
|
with self._create_resources() as result_status:
|
||||||
|
self._assertExpectedHTTP(result_status,
|
||||||
|
c_exc.NexusConfigFailed)
|
||||||
|
|
||||||
def test_nexus_extended_vlan_range_failure(self):
|
def test_nexus_extended_vlan_range_failure(self):
|
||||||
"""Test that extended VLAN range config errors are ignored.
|
"""Test that extended VLAN range config errors are ignored.
|
||||||
@@ -341,7 +412,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
with self._patch_ncclient(
|
with self._patch_ncclient(
|
||||||
'connect.return_value.edit_config.side_effect',
|
'connect.return_value.edit_config.side_effect',
|
||||||
mock_edit_config_a):
|
mock_edit_config_a):
|
||||||
self._create_resources(name='myname')
|
with self._create_resources() as result_status:
|
||||||
|
self.assertEqual(result_status, wexc.HTTPOk.code)
|
||||||
|
|
||||||
def mock_edit_config_b(target, config):
|
def mock_edit_config_b(target, config):
|
||||||
if all(word in config for word in ['no', 'shutdown']):
|
if all(word in config for word in ['no', 'shutdown']):
|
||||||
@@ -350,7 +422,8 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
with self._patch_ncclient(
|
with self._patch_ncclient(
|
||||||
'connect.return_value.edit_config.side_effect',
|
'connect.return_value.edit_config.side_effect',
|
||||||
mock_edit_config_b):
|
mock_edit_config_b):
|
||||||
self._create_resources(name='myname')
|
with self._create_resources() as result_status:
|
||||||
|
self.assertEqual(result_status, wexc.HTTPOk.code)
|
||||||
|
|
||||||
def test_nexus_vlan_config_rollback(self):
|
def test_nexus_vlan_config_rollback(self):
|
||||||
"""Test rollback following Nexus VLAN state config failure.
|
"""Test rollback following Nexus VLAN state config failure.
|
||||||
@@ -367,12 +440,12 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
with self._patch_ncclient(
|
with self._patch_ncclient(
|
||||||
'connect.return_value.edit_config.side_effect',
|
'connect.return_value.edit_config.side_effect',
|
||||||
mock_edit_config):
|
mock_edit_config):
|
||||||
with self._create_resources(
|
with self._create_resources() as result_status:
|
||||||
name='myname',
|
|
||||||
expected_exception=c_exc.NexusConfigFailed):
|
|
||||||
# Confirm that the last configuration sent to the Nexus
|
# Confirm that the last configuration sent to the Nexus
|
||||||
# switch was deletion of the VLAN.
|
# switch was deletion of the VLAN.
|
||||||
self.assertTrue(self._is_in_last_nexus_cfg(['<no>', '<vlan>']))
|
self.assertTrue(self._is_in_last_nexus_cfg(['<no>', '<vlan>']))
|
||||||
|
self._assertExpectedHTTP(result_status,
|
||||||
|
c_exc.NexusConfigFailed)
|
||||||
|
|
||||||
def test_nexus_host_not_configured(self):
|
def test_nexus_host_not_configured(self):
|
||||||
"""Test handling of a NexusComputeHostNotConfigured exception.
|
"""Test handling of a NexusComputeHostNotConfigured exception.
|
||||||
@@ -381,55 +454,20 @@ class TestCiscoPortsV2(CiscoML2MechanismTestCase,
|
|||||||
a fictitious host name during port creation.
|
a fictitious host name during port creation.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
self._create_resources(
|
with self._create_resources(host_id='fake_host') as result_status:
|
||||||
host_id='fake_host',
|
self._assertExpectedHTTP(result_status,
|
||||||
expected_exception=c_exc.NexusComputeHostNotConfigured)
|
c_exc.NexusComputeHostNotConfigured)
|
||||||
|
|
||||||
def test_nexus_bind_fail_rollback(self):
|
def test_nexus_missing_fields(self):
|
||||||
"""Test for proper rollback following add Nexus DB binding failure.
|
"""Test handling of a NexusMissingRequiredFields exception.
|
||||||
|
|
||||||
Test that the Cisco Nexus plugin correctly rolls back the vlan
|
Test the Cisco NexusMissingRequiredFields exception by using
|
||||||
configuration on the Nexus switch when add_nexusport_binding fails
|
empty host_id and device_id values during port creation.
|
||||||
within the plugin's create_port() method.
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
with mock.patch.object(nexus_db_v2,
|
with self._create_resources(device_id='', host_id='') as result_status:
|
||||||
'add_nexusport_binding',
|
self._assertExpectedHTTP(result_status,
|
||||||
side_effect=KeyError):
|
c_exc.NexusMissingRequiredFields)
|
||||||
with self._create_resources(expected_exception=KeyError):
|
|
||||||
# Confirm that the last configuration sent to the Nexus
|
|
||||||
# switch was a removal of vlan from the test interface.
|
|
||||||
self.assertTrue(
|
|
||||||
self._is_in_last_nexus_cfg(['<vlan>', '<remove>'])
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_nexus_delete_port_rollback(self):
|
|
||||||
"""Test for proper rollback for nexus plugin delete port failure.
|
|
||||||
|
|
||||||
Test for rollback (i.e. restoration) of a VLAN entry in the
|
|
||||||
nexus database whenever the nexus plugin fails to reconfigure the
|
|
||||||
nexus switch during a delete_port operation.
|
|
||||||
|
|
||||||
"""
|
|
||||||
with self._create_resources() as port:
|
|
||||||
# Check that there is only one binding in the nexus database
|
|
||||||
# for this VLAN/nexus switch.
|
|
||||||
start_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
|
|
||||||
NEXUS_IP_ADDR)
|
|
||||||
self.assertEqual(len(start_rows), 1)
|
|
||||||
|
|
||||||
# Simulate a Nexus switch configuration error during
|
|
||||||
# port deletion.
|
|
||||||
with self._patch_ncclient(
|
|
||||||
'connect.return_value.edit_config.side_effect',
|
|
||||||
AttributeError):
|
|
||||||
self._delete('ports', port['port']['id'],
|
|
||||||
wexc.HTTPInternalServerError.code)
|
|
||||||
|
|
||||||
# Confirm that the binding has been restored (rolled back).
|
|
||||||
end_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
|
|
||||||
NEXUS_IP_ADDR)
|
|
||||||
self.assertEqual(start_rows, end_rows)
|
|
||||||
|
|
||||||
|
|
||||||
class TestCiscoNetworksV2(CiscoML2MechanismTestCase,
|
class TestCiscoNetworksV2(CiscoML2MechanismTestCase,
|
||||||
|
|||||||
@@ -182,6 +182,7 @@ class TestCiscoNexusDevice(base.BaseTestCase):
|
|||||||
port_context = FakePortContext(instance_id, host_name,
|
port_context = FakePortContext(instance_id, host_name,
|
||||||
network_context)
|
network_context)
|
||||||
|
|
||||||
|
self._cisco_mech_driver.update_port_precommit(port_context)
|
||||||
self._cisco_mech_driver.update_port_postcommit(port_context)
|
self._cisco_mech_driver.update_port_postcommit(port_context)
|
||||||
bindings = nexus_db_v2.get_nexusport_binding(nexus_port,
|
bindings = nexus_db_v2.get_nexusport_binding(nexus_port,
|
||||||
vlan_id,
|
vlan_id,
|
||||||
@@ -190,6 +191,7 @@ class TestCiscoNexusDevice(base.BaseTestCase):
|
|||||||
self.assertEqual(len(bindings), 1)
|
self.assertEqual(len(bindings), 1)
|
||||||
|
|
||||||
self._cisco_mech_driver.delete_port_precommit(port_context)
|
self._cisco_mech_driver.delete_port_precommit(port_context)
|
||||||
|
self._cisco_mech_driver.delete_port_postcommit(port_context)
|
||||||
with testtools.ExpectedException(exceptions.NexusPortBindingNotFound):
|
with testtools.ExpectedException(exceptions.NexusPortBindingNotFound):
|
||||||
nexus_db_v2.get_nexusport_binding(nexus_port,
|
nexus_db_v2.get_nexusport_binding(nexus_port,
|
||||||
vlan_id,
|
vlan_id,
|
||||||
|
|||||||
Reference in New Issue
Block a user