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:
parent
cbfd690d65
commit
1c3864fb8a
@ -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.
|
||||
#
|
||||
|
@ -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',
|
||||
|
@ -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)
|
||||
return func(*args, **kwargs)
|
||||
|
||||
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(),
|
||||
|
@ -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'
|
||||
|
Loading…
Reference in New Issue
Block a user