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:
Rich Curran
2013-11-14 17:20:07 -05:00
committed by Gerrit Code Review
parent 260a9f5935
commit 8d0abf2db7
5 changed files with 257 additions and 226 deletions

View File

@@ -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.")

View File

@@ -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)

View File

@@ -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)

View File

@@ -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,

View File

@@ -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,