def __init__(self):
pass
- def _edit_config(self, mgr, target='running', config=''):
+ def _edit_config(self, mgr, target='running', config='',
+ allowed_exc_strs=None):
"""Modify switch config for a target config type.
:param mgr: NetConf client manager
:param target: Target config type
:param config: Configuration string in XML format
+ :param allowed_exc_strs: Exceptions which have any of these strings
+ as a subset of their exception message
+ (str(exception)) can be ignored
:raises: NexusConfigFailed
"""
+ if not allowed_exc_strs:
+ allowed_exc_strs = []
try:
mgr.edit_config(target, config=config)
except Exception as e:
- # Raise a Quantum exception. Include a description of
- # the original ncclient exception.
- raise cexc.NexusConfigFailed(config=config, exc=e)
+ for exc_str in allowed_exc_strs:
+ if exc_str in str(e):
+ break
+ else:
+ # Raise a Quantum exception. Include a description of
+ # the original ncclient exception.
+ raise cexc.NexusConfigFailed(config=config, exc=e)
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
nexus_password):
return conf_xml_snippet
def enable_vlan(self, mgr, vlanid, vlanname):
- """Creates a VLAN on Nexus Switch given the VLAN ID and Name."""
- confstr = snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname)
- confstr = self.create_xml_snippet(confstr)
+ """Create a VLAN on Nexus Switch given the VLAN ID and Name."""
+ confstr = self.create_xml_snippet(
+ snipp.CMD_VLAN_CONF_SNIPPET % (vlanid, vlanname))
self._edit_config(mgr, target='running', config=confstr)
+ # Enable VLAN active and no-shutdown states. Some versions of
+ # Nexus switch do not allow state changes for the extended VLAN
+ # range (1006-4094), but these errors can be ignored (default
+ # values are appropriate).
+ state_config = [snipp.CMD_VLAN_ACTIVE_SNIPPET,
+ snipp.CMD_VLAN_NO_SHUTDOWN_SNIPPET]
+ for snippet in state_config:
+ try:
+ confstr = self.create_xml_snippet(snippet % vlanid)
+ self._edit_config(
+ mgr,
+ target='running',
+ config=confstr,
+ allowed_exc_strs=["Can't modify state for extended",
+ "Command is only allowed on VLAN"])
+ except cexc.NexusConfigFailed as e:
+ # Rollback VLAN creation
+ try:
+ self.disable_vlan(mgr, vlanid)
+ finally:
+ # Re-raise original exception
+ raise e
+
def disable_vlan(self, mgr, vlanid):
"""Delete a VLAN on Nexus Switch given the VLAN ID."""
confstr = snipp.CMD_NO_VLAN_CONF_SNIPPET % vlanid
import logging
import mock
+import webob.exc as wexc
+
from quantum.api.v2 import base
from quantum.common import exceptions as q_exc
from quantum import context
if exc in base.FAULT_MAP:
expected_http = base.FAULT_MAP[exc].code
else:
- expected_http = 500
+ expected_http = wexc.HTTPInternalServerError.code
self.assertEqual(status, expected_http)
+ def _is_in_last_nexus_cfg(self, words):
+ last_cfg = (self.mock_ncclient.manager.connect().
+ edit_config.mock_calls[-1][2]['config'])
+ return all(word in last_cfg for word in words)
+
def test_create_ports_bulk_emulated_plugin_failure(self):
real_has_attr = hasattr
net['network']['id'],
'test',
True)
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'ports', 500)
+ # Expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'ports',
+ wexc.HTTPInternalServerError.code)
def test_create_ports_bulk_native_plugin_failure(self):
if self._skip_native_bulk:
patched_plugin.side_effect = side_effect
res = self._create_port_bulk(self.fmt, 2, net['network']['id'],
'test', True, context=ctx)
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'ports', 500)
+ # We expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'ports',
+ wexc.HTTPInternalServerError.code)
def test_nexus_connect_fail(self):
"""Test failure to connect to a Nexus switch.
self._assertExpectedHTTP(res.status_int,
c_exc.NexusConfigFailed)
+ def test_nexus_extended_vlan_range_failure(self):
+ """Test that extended VLAN range config errors are ignored.
+
+ Some versions of Nexus switch do not allow state changes for
+ the extended VLAN range (1006-4094), but these errors can be
+ ignored (default values are appropriate). Test that such errors
+ are ignored by the Nexus plugin.
+
+ """
+ def mock_edit_config_a(target, config):
+ if all(word in config for word in ['state', 'active']):
+ raise Exception("Can't modify state for extended")
+
+ with self._patch_ncclient(
+ 'manager.connect.return_value.edit_config.side_effect',
+ mock_edit_config_a):
+ with self._create_port_res(self.fmt, name='myname') as res:
+ self.assertEqual(res.status_int, wexc.HTTPCreated.code)
+
+ def mock_edit_config_b(target, config):
+ if all(word in config for word in ['no', 'shutdown']):
+ raise Exception("Command is only allowed on VLAN")
+
+ with self._patch_ncclient(
+ 'manager.connect.return_value.edit_config.side_effect',
+ mock_edit_config_b):
+ with self._create_port_res(self.fmt, name='myname') as res:
+ self.assertEqual(res.status_int, wexc.HTTPCreated.code)
+
+ def test_nexus_vlan_config_rollback(self):
+ """Test rollback following Nexus VLAN state config failure.
+
+ Test that the Cisco Nexus plugin correctly deletes the VLAN
+ on the Nexus switch when the 'state active' command fails (for
+ a reason other than state configuration change is rejected
+ for the extended VLAN range).
+
+ """
+ def mock_edit_config(target, config):
+ if all(word in config for word in ['state', 'active']):
+ raise ValueError
+ with self._patch_ncclient(
+ 'manager.connect.return_value.edit_config.side_effect',
+ mock_edit_config):
+ with self._create_port_res(self.fmt, no_delete=True,
+ name='myname') as res:
+ # Confirm that the last configuration sent to the Nexus
+ # switch was deletion of the VLAN.
+ self.assertTrue(
+ self._is_in_last_nexus_cfg(['<no>', '<vlan>'])
+ )
+ self._assertExpectedHTTP(res.status_int,
+ c_exc.NexusConfigFailed)
+
def test_get_seg_id_fail(self):
"""Test handling of a NetworkSegmentIDNotFound exception.
name='myname') as res:
# Confirm that the last configuration sent to the Nexus
# switch was a removal of vlan from the test interface.
- last_nexus_cfg = (self.mock_ncclient.manager.connect().
- edit_config.mock_calls[-1][2]['config'])
- self.assertTrue('<vlan>' in last_nexus_cfg)
- self.assertTrue('<remove>' in last_nexus_cfg)
+ self.assertTrue(
+ self._is_in_last_nexus_cfg(['<vlan>', '<remove>'])
+ )
self._assertExpectedHTTP(res.status_int, KeyError)
def test_model_delete_port_rollback(self):
patched_plugin.side_effect = side_effect
res = self._create_network_bulk(self.fmt, 2, 'test', True)
LOG.debug("response is %s" % res)
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'networks', 500)
+ # We expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'networks',
+ wexc.HTTPInternalServerError.code)
def test_create_networks_bulk_native_plugin_failure(self):
if self._skip_native_bulk:
patched_plugin.side_effect = side_effect
res = self._create_network_bulk(self.fmt, 2, 'test', True)
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'networks', 500)
+ # We expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'networks',
+ wexc.HTTPInternalServerError.code)
class TestCiscoSubnetsV2(CiscoNetworkPluginV2TestCase,
res = self._create_subnet_bulk(self.fmt, 2,
net['network']['id'],
'test')
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'subnets', 500)
+ # We expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'subnets',
+ wexc.HTTPInternalServerError.code)
def test_create_subnets_bulk_native_plugin_failure(self):
if self._skip_native_bulk:
net['network']['id'],
'test')
- # We expect a 500 as we injected a fault in the plugin
- self._validate_behavior_on_bulk_failure(res, 'subnets', 500)
+ # We expect an internal server error as we injected a fault
+ self._validate_behavior_on_bulk_failure(
+ res,
+ 'subnets',
+ wexc.HTTPInternalServerError.code)
class TestCiscoPortsV2XML(TestCiscoPortsV2):