Merge "Fix Cisco Nexus plugin failures for vlan IDs 1006-4094"

This commit is contained in:
Jenkins 2013-05-22 06:51:44 +00:00 committed by Gerrit Code Review
commit b28ae84068
3 changed files with 155 additions and 24 deletions

View File

@ -37,22 +37,32 @@ class CiscoNEXUSDriver():
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):
@ -77,11 +87,34 @@ class CiscoNEXUSDriver():
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

View File

@ -45,9 +45,29 @@ CMD_VLAN_CONF_SNIPPET = """
<name>
<vlan-name>%s</vlan-name>
</name>
</__XML__MODE_vlan>
</vlan-id-create-delete>
</vlan>
"""
CMD_VLAN_ACTIVE_SNIPPET = """
<vlan>
<vlan-id-create-delete>
<__XML__PARAM_value>%s</__XML__PARAM_value>
<__XML__MODE_vlan>
<state>
<vstate>active</vstate>
</state>
</__XML__MODE_vlan>
</vlan-id-create-delete>
</vlan>
"""
CMD_VLAN_NO_SHUTDOWN_SNIPPET = """
<vlan>
<vlan-id-create-delete>
<__XML__PARAM_value>%s</__XML__PARAM_value>
<__XML__MODE_vlan>
<no>
<shutdown/>
</no>

View File

@ -18,6 +18,8 @@ import inspect
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
@ -190,9 +192,14 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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
@ -219,8 +226,11 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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:
@ -239,8 +249,11 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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.
@ -273,6 +286,60 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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.
@ -329,10 +396,9 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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):
@ -424,8 +490,11 @@ class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
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:
@ -441,8 +510,11 @@ class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
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,
@ -473,8 +545,11 @@ 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:
@ -493,8 +568,11 @@ class TestCiscoSubnetsV2(CiscoNetworkPluginV2TestCase,
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):