cisco/nexus plugin doesn't create port for router interface

Fixes bug 1234826

This fix adds a "nexus_l3_enable" configuration boolean for the
Cisco Nexus plugin. When this config boolean is set to False (default),
then the Nexus switches are only used for L2 switching/segmentation, and
layer 3 functionality is deferred to the OVS subplugin / network
control node. If this config boolean is set to True, layer 3
functionality, e.g. switch virtual interfaces, are supported on
the Nexus switches. (Note that layer 3 functionality is not supported
on all versions/models Nexus switches.)

Some other things addressed with this fix:
- The l3_port_check keyword argument which is optionally passed to the
  Cisco plugin's delete_port method was not being forwarded on to the
  OVS (sub) plugin. This keyword argument needs to be forwarded to OVS
  e.g. when the delete_port is being done in the context of a
  router interface delete (whereby l3_port_check==False).
- UT test cases are added for new "nexus_l3_enable" config, which
  exercise router interface add/delete.
- The Cisco test_network_plugin.py module is refactored/reorganized
  in order to cleanly add a new router interface test class.
- The test_model_update_port_rollback test case was yielding a false
  positive result (device_owner was not being passed to self.port).

Change-Id: I994b2b82769ea5e10e50dbe3a223d1518e99f714
This commit is contained in:
Dane LeBlanc 2013-10-11 17:07:00 -04:00
parent cbfd690d65
commit 1c3864fb8a
4 changed files with 239 additions and 156 deletions

View File

@ -56,10 +56,15 @@
# With real hardware, use the CiscoNEXUSDriver class:
# nexus_driver = neutron.plugins.cisco.nexus.cisco_nexus_network_driver_v2.CiscoNEXUSDriver
# (BoolOpt) A flag to enable Layer 3 support on the Nexus switches.
# Note: This feature is not supported on all models/versions of Cisco
# Nexus switches. To use this feature, all of the Nexus switches in the
# deployment must support it.
# nexus_l3_enable = False
# (BoolOpt) A flag to enable round robin scheduling of routers for SVI.
# svi_round_robin = False
# Cisco Nexus Switch configurations.
# Each switch to be managed by Openstack Neutron must be configured here.
#

View File

@ -41,6 +41,8 @@ cisco_opts = [
cfg.BoolOpt('provider_vlan_auto_trunk', default=True,
help=_('Provider VLANs are automatically trunked as needed '
'on the ports of the Nexus switch')),
cfg.BoolOpt('nexus_l3_enable', default=False,
help=_("Enable L3 support on the Nexus switches")),
cfg.BoolOpt('svi_round_robin', default=False,
help=_("Distribute SVI interfaces over all switches")),
cfg.StrOpt('model_class',

View File

@ -23,8 +23,6 @@ import inspect
import logging
import sys
from oslo.config import cfg
from neutron.api.v2 import attributes
from neutron.db import api as db_api
from neutron.extensions import portbindings
@ -96,10 +94,10 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
'name': self.__class__.__name__})
# Check whether we have a valid Nexus driver loaded
self.config_nexus = False
nexus_driver = cfg.CONF.CISCO.nexus_driver
self.is_nexus_plugin = False
nexus_driver = conf.CISCO.nexus_driver
if nexus_driver.endswith('CiscoNEXUSDriver'):
self.config_nexus = True
self.is_nexus_plugin = True
def __getattribute__(self, name):
"""Delegate calls to OVS sub-plugin.
@ -130,7 +128,8 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
func_name = frame_record[3]
return func_name
def _invoke_plugin_per_device(self, plugin_key, function_name, args):
def _invoke_plugin_per_device(self, plugin_key, function_name,
args, **kwargs):
"""Invoke plugin per device.
Invokes a device plugin's relevant functions (based on the
@ -143,10 +142,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
{'plugin_key': plugin_key, 'function_name': function_name,
'args': args})
return
device_params = {const.DEVICE_IP: []}
return [self._invoke_plugin(plugin_key, function_name, args,
device_params)]
return [self._invoke_plugin(plugin_key, function_name, args, kwargs)]
def _invoke_plugin(self, plugin_key, function_name, args, kwargs):
"""Invoke plugin.
@ -156,7 +152,6 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
"""
func = getattr(self._plugins[plugin_key], function_name)
func_args_len = int(inspect.getargspec(func).args.__len__()) - 1
fargs, varargs, varkw, defaults = inspect.getargspec(func)
if args.__len__() > func_args_len:
func_args = args[:func_args_len]
extra_args = args[func_args_len:]
@ -165,10 +160,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
kwargs[k] = v
return func(*func_args, **kwargs)
else:
if (varkw == 'kwargs'):
return func(*args, **kwargs)
else:
return func(*args)
def _get_segmentation_id(self, network_id):
binding_seg_id = odb.get_network_binding(None, network_id)
@ -261,7 +253,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
def _invoke_nexus_for_net_create(self, context, tenant_id, net_id,
instance_id, host_id):
if not self.config_nexus:
if not self.is_nexus_plugin:
return False
network = self.get_network(context, net_id)
@ -387,7 +379,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
# Re-raise the original exception
raise exc_info[0], exc_info[1], exc_info[2]
def delete_port(self, context, id):
def delete_port(self, context, id, l3_port_check=True):
"""Delete port.
Perform this operation in the context of the configured device
@ -398,7 +390,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
host_id = self._get_port_host_id_from_bindings(port)
if (self.config_nexus and host_id and
if (self.is_nexus_plugin and host_id and
self._check_valid_port_device_owner(port)):
vlan_id = self._get_segmentation_id(port['network_id'])
n_args = [port['device_id'], vlan_id]
@ -407,9 +399,9 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
n_args)
try:
args = [context, id]
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
args)
ovs_output = self._invoke_plugin_per_device(
const.VSWITCH_PLUGIN, self._func_name(),
args, l3_port_check=l3_port_check)
except Exception:
exc_info = sys.exc_info()
# Roll back the delete port on the Nexus plugin
@ -429,12 +421,12 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
def add_router_interface(self, context, router_id, interface_info):
"""Add a router interface on a subnet.
Only invoke the Nexus plugin to create SVI if a Nexus
plugin is loaded, otherwise send it to the vswitch plugin
Only invoke the Nexus plugin to create SVI if L3 support on
the Nexus switches is enabled and a Nexus plugin is loaded,
otherwise send it to the vswitch plugin
"""
nexus_driver = cfg.CONF.CISCO.nexus_driver
if nexus_driver.endswith('CiscoNEXUSDriver'):
LOG.debug(_("Nexus plugin loaded, creating SVI on switch"))
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
LOG.debug(_("L3 enabled on Nexus plugin, create SVI on switch"))
if 'subnet_id' not in interface_info:
raise cexc.SubnetNotSpecified()
if 'port_id' in interface_info:
@ -454,7 +446,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
self._func_name(),
n_args)
else:
LOG.debug(_("No Nexus plugin, sending to vswitch"))
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
n_args = [context, router_id, interface_info]
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),
@ -463,12 +455,12 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
def remove_router_interface(self, context, router_id, interface_info):
"""Remove a router interface.
Only invoke the Nexus plugin to delete SVI if a Nexus
plugin is loaded, otherwise send it to the vswitch plugin
Only invoke the Nexus plugin to delete SVI if L3 support on
the Nexus switches is enabled and a Nexus plugin is loaded,
otherwise send it to the vswitch plugin
"""
nexus_driver = cfg.CONF.CISCO.nexus_driver
if nexus_driver.endswith('CiscoNEXUSDriver'):
LOG.debug(_("Nexus plugin loaded, deleting SVI from switch"))
if (conf.CISCO.nexus_l3_enable and self.is_nexus_plugin):
LOG.debug(_("L3 enabled on Nexus plugin, delete SVI from switch"))
subnet = self.get_subnet(context, interface_info['subnet_id'])
network_id = subnet['network_id']
@ -479,7 +471,7 @@ class VirtualPhysicalSwitchModelV2(neutron_plugin_base_v2.NeutronPluginBaseV2):
self._func_name(),
n_args)
else:
LOG.debug(_("No Nexus plugin, sending to vswitch"))
LOG.debug(_("L3 disabled or not Nexus plugin, send to vswitch"))
n_args = [context, router_id, interface_info]
return self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
self._func_name(),

View File

@ -18,9 +18,9 @@ import inspect
import logging
import mock
from oslo.config import cfg
import webob.exc as wexc
from neutron.api import extensions
from neutron.api.v2 import base
from neutron.common import exceptions as q_exc
from neutron import context
@ -38,29 +38,88 @@ from neutron.plugins.openvswitch.common import config as ovs_config
from neutron.plugins.openvswitch import ovs_db_v2
from neutron.tests.unit import _test_extension_portbindings as test_bindings
from neutron.tests.unit import test_db_plugin
from neutron.tests.unit import test_extensions
LOG = logging.getLogger(__name__)
CORE_PLUGIN = 'neutron.plugins.cisco.network_plugin.PluginV2'
NEXUS_PLUGIN = 'neutron.plugins.cisco.nexus.cisco_nexus_plugin_v2.NexusPlugin'
NEXUS_DRIVER = ('neutron.plugins.cisco.nexus.'
'cisco_nexus_network_driver_v2.CiscoNEXUSDriver')
PHYS_NET = 'physnet1'
BRIDGE_NAME = 'br-eth1'
VLAN_START = 1000
VLAN_END = 1100
COMP_HOST_NAME = 'testhost'
NEXUS_IP_ADDR = '1.1.1.1'
NEXUS_DEV_ID = 'NEXUS_SWITCH'
NEXUS_USERNAME = 'admin'
NEXUS_PASSWORD = 'mySecretPassword'
NEXUS_SSH_PORT = 22
NEXUS_INTERFACE = '1/1'
NETWORK_NAME = 'test_network'
CIDR_1 = '10.0.0.0/24'
CIDR_2 = '10.0.1.0/24'
DEVICE_ID_1 = '11111111-1111-1111-1111-111111111111'
DEVICE_ID_2 = '22222222-2222-2222-2222-222222222222'
DEVICE_OWNER = 'compute:None'
class CiscoNetworkPluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase):
_plugin_name = 'neutron.plugins.cisco.network_plugin.PluginV2'
def setUp(self):
"""Configure for end-to-end neutron testing using a mock ncclient.
This setup includes:
- Configure the OVS plugin to use VLANs in the range of
VLAN_START-VLAN_END.
- Configure the Cisco plugin model to use the Nexus driver.
- Configure the Nexus driver to use an imaginary switch
at NEXUS_IP_ADDR.
"""
# Configure the OVS and Cisco plugins
phys_bridge = ':'.join([PHYS_NET, BRIDGE_NAME])
phys_vlan_range = ':'.join([PHYS_NET, str(VLAN_START), str(VLAN_END)])
config = {
ovs_config: {
'OVS': {'bridge_mappings': phys_bridge,
'network_vlan_ranges': [phys_vlan_range],
'tenant_network_type': 'vlan'}
},
cisco_config: {
'CISCO': {'nexus_driver': NEXUS_DRIVER},
'CISCO_PLUGINS': {'nexus_plugin': NEXUS_PLUGIN},
}
}
for module in config:
for group in config[module]:
for opt, val in config[module][group].items():
module.cfg.CONF.set_override(opt, val, group)
self.addCleanup(module.cfg.CONF.reset)
# Configure the Nexus switch dictionary
# TODO(Henry): add tests for other devices
nexus_config = {
(NEXUS_DEV_ID, NEXUS_IP_ADDR, 'username'): NEXUS_USERNAME,
(NEXUS_DEV_ID, NEXUS_IP_ADDR, 'password'): NEXUS_PASSWORD,
(NEXUS_DEV_ID, NEXUS_IP_ADDR, 'ssh_port'): NEXUS_SSH_PORT,
(NEXUS_DEV_ID, NEXUS_IP_ADDR, COMP_HOST_NAME): NEXUS_INTERFACE,
}
nexus_patch = mock.patch.dict(cisco_config.device_dictionary,
nexus_config)
nexus_patch.start()
self.addCleanup(nexus_patch.stop)
# Use a mock netconf client
self.mock_ncclient = mock.Mock()
self.patch_obj = mock.patch.dict('sys.modules',
ncclient_patch = mock.patch.dict('sys.modules',
{'ncclient': self.mock_ncclient})
self.patch_obj.start()
ncclient_patch.start()
self.addCleanup(ncclient_patch.stop)
cisco_config.cfg.CONF.set_override('nexus_plugin', NEXUS_PLUGIN,
'CISCO_PLUGINS')
self.addCleanup(cisco_config.cfg.CONF.reset)
super(CiscoNetworkPluginV2TestCase, self).setUp(self._plugin_name)
# Call the parent setUp, start the core plugin
super(CiscoNetworkPluginV2TestCase, self).setUp(CORE_PLUGIN)
self.port_create_status = 'DOWN'
self.addCleanup(self.patch_obj.stop)
def _get_plugin_ref(self):
plugin_obj = NeutronManager.get_plugin()
@ -72,74 +131,6 @@ class CiscoNetworkPluginV2TestCase(test_db_plugin.NeutronDbPluginV2TestCase):
return plugin_ref
class TestCiscoBasicGet(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestBasicGet):
pass
class TestCiscoV2HTTPResponse(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestV2HTTPResponse):
pass
class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestPortsV2,
test_bindings.PortBindingsHostTestCaseMixin):
def setUp(self):
"""Configure for end-to-end neutron testing using a mock ncclient.
This setup includes:
- Configure the OVS plugin to use VLANs in the range of 1000-1100.
- Configure the Cisco plugin model to use the real Nexus driver.
- Configure the Nexus sub-plugin to use an imaginary switch
at 1.1.1.1.
"""
self.addCleanup(mock.patch.stopall)
self.vlan_start = 1000
self.vlan_end = 1100
range_str = 'physnet1:%d:%d' % (self.vlan_start,
self.vlan_end)
nexus_driver = ('neutron.plugins.cisco.nexus.'
'cisco_nexus_network_driver_v2.CiscoNEXUSDriver')
config = {
ovs_config: {
'OVS': {'bridge_mappings': 'physnet1:br-eth1',
'network_vlan_ranges': [range_str],
'tenant_network_type': 'vlan'}
},
cisco_config: {
'CISCO': {'nexus_driver': nexus_driver},
'CISCO_PLUGINS': {'nexus_plugin': NEXUS_PLUGIN},
}
}
for module in config:
for group in config[module]:
for opt in config[module][group]:
module.cfg.CONF.set_override(opt,
config[module][group][opt],
group)
self.addCleanup(module.cfg.CONF.reset)
# TODO(Henry): add tests for other devices
self.dev_id = 'NEXUS_SWITCH'
self.switch_ip = '1.1.1.1'
nexus_config = {
(self.dev_id, self.switch_ip, 'username'): 'admin',
(self.dev_id, self.switch_ip, 'password'): 'mySecretPassword',
(self.dev_id, self.switch_ip, 'ssh_port'): 22,
(self.dev_id, self.switch_ip, 'testhost'): '1/1',
}
mock.patch.dict(cisco_config.device_dictionary, nexus_config).start()
super(TestCiscoPortsV2, self).setUp()
@contextlib.contextmanager
def _patch_ncclient(self, attr, value):
"""Configure an attribute on the mock ncclient module.
@ -160,9 +151,38 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
config = {attr: None}
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.manager.connect.return_value.
edit_config.mock_calls):
configlet = call[2]['config']
if all(word in configlet for word in words):
return True
def _is_in_last_nexus_cfg(self, words):
"""Check if last config sent to Nexus contains all words in a list."""
last_cfg = (self.mock_ncclient.manager.connect.return_value.
edit_config.mock_calls[-1][2]['config'])
return all(word in last_cfg for word in words)
class TestCiscoBasicGet(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestBasicGet):
pass
class TestCiscoV2HTTPResponse(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestV2HTTPResponse):
pass
class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestPortsV2,
test_bindings.PortBindingsHostTestCaseMixin):
@contextlib.contextmanager
def _create_port_res(self, name='myname', cidr='1.0.0.0/24',
do_delete=True, host_id='testhost'):
def _create_port_res(self, name=NETWORK_NAME, cidr=CIDR_1,
do_delete=True, host_id=COMP_HOST_NAME):
"""Create a network, subnet, and port and yield the result.
Create a network, subnet, and port, yield the result,
@ -172,6 +192,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
:param cidr: cidr address of subnetwork to be created
:param do_delete: If set to True, delete the port at the
end of testing
:param host_id: Name of compute host to use for testing
"""
ctx = context.get_admin_context()
@ -180,8 +201,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
net_id = subnet['subnet']['network_id']
args = (portbindings.HOST_ID, 'device_id', 'device_owner')
port_dict = {portbindings.HOST_ID: host_id,
'device_id': 'testdev',
'device_owner': 'compute:None'}
'device_id': DEVICE_ID_1,
'device_owner': DEVICE_OWNER}
res = self._create_port(self.fmt, net_id, arg_list=args,
context=ctx, **port_dict)
port = self.deserialize(self.fmt, res)
@ -208,11 +229,6 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
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
@ -268,7 +284,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
*args, **kwargs)
patched_plugin.side_effect = side_effect
res = self._create_port_bulk(self.fmt, 2, net['network']['id'],
res = self._create_port_bulk(self.fmt, 2,
net['network']['id'],
'test', True, context=ctx)
# We expect an internal server error as we injected a fault
self._validate_behavior_on_bulk_failure(
@ -279,11 +296,11 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
def test_nexus_enable_vlan_cmd(self):
"""Verify the syntax of the command to enable a vlan on an intf."""
# First vlan should be configured without 'add' keyword
with self._create_port_res(name='net1', cidr='1.0.0.0/24'):
with self._create_port_res(name='net1', cidr=CIDR_1):
self.assertTrue(self._is_in_last_nexus_cfg(['allowed', 'vlan']))
self.assertFalse(self._is_in_last_nexus_cfg(['add']))
# Second vlan should be configured with 'add' keyword
with self._create_port_res(name='net2', cidr='1.0.1.0/24'):
with self._create_port_res(name='net2', cidr=CIDR_2):
self.assertTrue(
self._is_in_last_nexus_cfg(['allowed', 'vlan', 'add']))
@ -332,7 +349,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
with self._patch_ncclient(
'manager.connect.return_value.edit_config.side_effect',
mock_edit_config_a):
with self._create_port_res(name='myname') as res:
with self._create_port_res() as res:
self.assertEqual(res.status_int, wexc.HTTPCreated.code)
def mock_edit_config_b(target, config):
@ -342,7 +359,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
with self._patch_ncclient(
'manager.connect.return_value.edit_config.side_effect',
mock_edit_config_b):
with self._create_port_res(name='myname') as res:
with self._create_port_res() as res:
self.assertEqual(res.status_int, wexc.HTTPCreated.code)
def test_nexus_vlan_config_rollback(self):
@ -360,7 +377,7 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
with self._patch_ncclient(
'manager.connect.return_value.edit_config.side_effect',
mock_edit_config):
with self._create_port_res(name='myname', do_delete=False) as res:
with self._create_port_res(do_delete=False) as res:
# Confirm that the last configuration sent to the Nexus
# switch was deletion of the VLAN.
self.assertTrue(
@ -402,7 +419,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
a fictitious host name during port creation.
"""
with self._create_port_res(do_delete=False, host_id='fakehost') as res:
with self._create_port_res(do_delete=False,
host_id='fakehost') as res:
self._assertExpectedHTTP(res.status_int,
c_exc.NexusComputeHostNotConfigured)
@ -431,8 +449,14 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
(restored) by the Cisco plugin model layer when there is a
failure in the Nexus sub-plugin for an update port operation.
The update port operation simulates a port attachment scenario:
first a port is created with no instance (null device_id),
and then a port update is requested with a non-null device_id
to simulate the port attachment.
"""
with self.port(fmt=self.fmt) as orig_port:
with self.port(fmt=self.fmt, device_id='',
device_owner=DEVICE_OWNER) as orig_port:
inserted_exc = ValueError
with mock.patch.object(
@ -440,12 +464,10 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
'_invoke_nexus_for_net_create',
side_effect=inserted_exc):
# Send an update port request with a new device ID
device_id = "00fff4d0-e4a8-4a3a-8906-4c4cdafb59f1"
if orig_port['port']['device_id'] == device_id:
device_id = "600df00d-e4a8-4a3a-8906-feed600df00d"
data = {'port': {'device_id': device_id,
portbindings.HOST_ID: 'testhost'}}
# Send an update port request including a non-null device ID
data = {'port': {'device_id': DEVICE_ID_2,
'device_owner': DEVICE_OWNER,
portbindings.HOST_ID: COMP_HOST_NAME}}
port_id = orig_port['port']['id']
req = self.new_update_request('ports', data, port_id)
res = req.get_response(self.api)
@ -473,8 +495,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
# After port is created, we should have one binding for this
# vlan/nexus switch.
port = self.deserialize(self.fmt, res)
start_rows = nexus_db_v2.get_nexusvlan_binding(self.vlan_start,
self.switch_ip)
start_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
NEXUS_IP_ADDR)
self.assertEqual(len(start_rows), 1)
# Inject an exception in the OVS plugin delete_port
@ -489,8 +511,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
# Confirm that the Cisco model plugin has restored
# the nexus configuration for this port after deletion failure.
end_rows = nexus_db_v2.get_nexusvlan_binding(self.vlan_start,
self.switch_ip)
end_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
NEXUS_IP_ADDR)
self.assertEqual(start_rows, end_rows)
def test_nexus_delete_port_rollback(self):
@ -507,8 +529,8 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
# Check that there is only one binding in the nexus database
# for this VLAN/nexus switch.
start_rows = nexus_db_v2.get_nexusvlan_binding(self.vlan_start,
self.switch_ip)
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
@ -520,24 +542,14 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
base.FAULT_MAP[c_exc.NexusConfigFailed].code)
# Confirm that the binding has been restored (rolled back).
end_rows = nexus_db_v2.get_nexusvlan_binding(self.vlan_start,
self.switch_ip)
end_rows = nexus_db_v2.get_nexusvlan_binding(VLAN_START,
NEXUS_IP_ADDR)
self.assertEqual(start_rows, end_rows)
class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
test_db_plugin.TestNetworksV2):
def setUp(self):
self.physnet = 'testphys1'
self.vlan_range = '100:199'
phys_vrange = ':'.join([self.physnet, self.vlan_range])
cfg.CONF.set_override('tenant_network_type', 'vlan', 'OVS')
cfg.CONF.set_override('network_vlan_ranges', [phys_vrange], 'OVS')
self.addCleanup(cfg.CONF.reset)
super(TestCiscoNetworksV2, self).setUp()
def test_create_networks_bulk_emulated_plugin_failure(self):
real_has_attr = hasattr
@ -587,7 +599,7 @@ class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
def test_create_provider_vlan_network(self):
provider_attrs = {provider.NETWORK_TYPE: 'vlan',
provider.PHYSICAL_NETWORK: self.physnet,
provider.PHYSICAL_NETWORK: PHYS_NET,
provider.SEGMENTATION_ID: '1234'}
arg_list = tuple(provider_attrs.keys())
res = self._create_network(self.fmt, 'pvnet1', True,
@ -598,7 +610,7 @@ class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
('status', 'ACTIVE'),
('shared', False),
(provider.NETWORK_TYPE, 'vlan'),
(provider.PHYSICAL_NETWORK, self.physnet),
(provider.PHYSICAL_NETWORK, PHYS_NET),
(provider.SEGMENTATION_ID, 1234)]
for k, v in expected:
self.assertEqual(net['network'][k], v)
@ -662,6 +674,74 @@ class TestCiscoSubnetsV2(CiscoNetworkPluginV2TestCase,
wexc.HTTPInternalServerError.code)
class TestCiscoRouterInterfacesV2(CiscoNetworkPluginV2TestCase):
def setUp(self):
"""Configure an API extension manager."""
super(TestCiscoRouterInterfacesV2, self).setUp()
ext_mgr = extensions.PluginAwareExtensionManager.get_instance()
self.ext_api = test_extensions.setup_extensions_middleware(ext_mgr)
@contextlib.contextmanager
def _router(self, subnet):
"""Create a virtual router, yield it for testing, then delete it."""
data = {'router': {'tenant_id': 'test_tenant_id'}}
router_req = self.new_create_request('routers', data, self.fmt)
res = router_req.get_response(self.ext_api)
router = self.deserialize(self.fmt, res)
try:
yield router
finally:
self._delete('routers', router['router']['id'])
@contextlib.contextmanager
def _router_interface(self, router, subnet):
"""Create a router interface, yield for testing, then delete it."""
interface_data = {'subnet_id': subnet['subnet']['id']}
req = self.new_action_request('routers', interface_data,
router['router']['id'],
'add_router_interface')
req.get_response(self.ext_api)
try:
yield
finally:
req = self.new_action_request('routers', interface_data,
router['router']['id'],
'remove_router_interface')
req.get_response(self.ext_api)
def test_nexus_l3_enable_config(self):
"""Verify proper operation of the Nexus L3 enable configuration."""
self.addCleanup(cisco_config.CONF.reset)
with self.network() as network:
with self.subnet(network=network) as subnet:
with self._router(subnet) as router:
# With 'nexus_l3_enable' configured to True, confirm that
# a switched virtual interface (SVI) is created/deleted
# on the Nexus switch when a virtual router interface is
# created/deleted.
cisco_config.CONF.set_override('nexus_l3_enable',
True, 'CISCO')
with self._router_interface(router, subnet):
self.assertTrue(self._is_in_last_nexus_cfg(
['interface', 'vlan', 'ip', 'address']))
self.assertTrue(self._is_in_nexus_cfg(
['no', 'interface', 'vlan']))
self.assertTrue(self._is_in_last_nexus_cfg(
['no', 'vlan']))
# With 'nexus_l3_enable' configured to False, confirm
# that no changes are made to the Nexus switch running
# configuration when a virtual router interface is
# created and then deleted.
cisco_config.CONF.set_override('nexus_l3_enable',
False, 'CISCO')
self.mock_ncclient.reset_mock()
self._router_interface(router, subnet)
self.assertFalse(self.mock_ncclient.manager.connect.
return_value.edit_config.called)
class TestCiscoPortsV2XML(TestCiscoPortsV2):
fmt = 'xml'
@ -672,3 +752,7 @@ class TestCiscoNetworksV2XML(TestCiscoNetworksV2):
class TestCiscoSubnetsV2XML(TestCiscoSubnetsV2):
fmt = 'xml'
class TestCiscoRouterInterfacesV2XML(TestCiscoRouterInterfacesV2):
fmt = 'xml'