blueprint cisco-plugin-exception-handling
Improvements to exception handling in the Cisco plugins. Changes include: - Added mapping of Cisco exceptions to HTTP codes (extension to FAULT_MAP). - Removed several unused Cisco exception definitions. - Added several new Cisco exceptions for fault conditions. - Added several rollbacks for various sequential operations, e.g.: * Create port: Nexus sub-plugin fails after OVS sub-plugin success * Create port: Nexus switch conig fails after adding binding to Nexus binding database * Delete port: OVS sub-plugin fails after Nexus sub-plugin success - Delete Port: Reversed order of OVS/Nexus sub-plugin calls so that it is done in the reverse order as is done for create port. - Removed several empty except/raise blocks - Delete network: Removed call to Nexus sub-plugin delete_network, since that is a no-op. - Removed a block of unused code in model's create_network method. - Added several unit testcases, including patching of OVS, Cisco and Nexus config. - Remove CISCO_TEST configuration from cisco plugin config .ini file. Change-Id: Iabdf4842aa2f0b090a90e2c565848290832b5197
This commit is contained in:
parent
f66035c131
commit
8ac8222eaa
@ -22,18 +22,17 @@
|
||||
from quantum.common import exceptions
|
||||
|
||||
|
||||
class NetworkSegmentIDNotFound(exceptions.QuantumException):
|
||||
"""Segmentation ID for network is not found."""
|
||||
message = _("Segmentation ID for network %(net_id)s is not found.")
|
||||
|
||||
|
||||
class NoMoreNics(exceptions.QuantumException):
|
||||
"""No more dynamic nics are available in the system."""
|
||||
message = _("Unable to complete operation. No more dynamic nics are "
|
||||
"available in the system.")
|
||||
|
||||
|
||||
class NetworksLimit(exceptions.QuantumException):
|
||||
"""Total number of network objects limit has been hit."""
|
||||
message = _("Unable to create new network. Number of networks"
|
||||
"for the system has exceeded the limit")
|
||||
|
||||
|
||||
class NetworkVlanBindingAlreadyExists(exceptions.QuantumException):
|
||||
"""Binding cannot be created, since it already exists."""
|
||||
message = _("NetworkVlanBinding for %(vlan_id)s and network "
|
||||
@ -56,12 +55,6 @@ class QosNotFound(exceptions.QuantumException):
|
||||
"for tenant %(tenant_id)s")
|
||||
|
||||
|
||||
class QoSLevelInvalidDelete(exceptions.QuantumException):
|
||||
"""QoS is associated with a port profile, hence cannot be deleted."""
|
||||
message = _("QoS level %(qos_id)s could not be deleted "
|
||||
"for tenant %(tenant_id)s since association exists")
|
||||
|
||||
|
||||
class QosNameAlreadyExists(exceptions.QuantumException):
|
||||
"""QoS Name already exists."""
|
||||
message = _("QoS level with name %(qos_name)s already exists "
|
||||
@ -86,44 +79,24 @@ class CredentialAlreadyExists(exceptions.QuantumException):
|
||||
"for tenant %(tenant_id)s")
|
||||
|
||||
|
||||
class NexusComputeHostNotConfigured(exceptions.QuantumException):
|
||||
"""Connection to compute host is not configured."""
|
||||
message = _("Connection to %(host)s is not configured.")
|
||||
|
||||
|
||||
class NexusConnectFailed(exceptions.QuantumException):
|
||||
"""Failed to connect to Nexus switch."""
|
||||
message = _("Unable to connect to Nexus %(nexus_host)s. Reason: %(exc)s.")
|
||||
|
||||
|
||||
class NexusConfigFailed(exceptions.QuantumException):
|
||||
"""Failed to configure Nexus switch."""
|
||||
message = _("Failed to configure Nexus: %(config)s. Reason: %(exc)s.")
|
||||
|
||||
|
||||
class NexusPortBindingNotFound(exceptions.QuantumException):
|
||||
"""NexusPort Binding is not present."""
|
||||
message = _("Nexus Port Binding %(port_id)s is not present")
|
||||
|
||||
|
||||
class NexusPortBindingAlreadyExists(exceptions.QuantumException):
|
||||
"""NexusPort Binding alredy exists."""
|
||||
message = _("Nexus Port Binding %(port_id)s already exists")
|
||||
|
||||
|
||||
class UcsmBindingNotFound(exceptions.QuantumException):
|
||||
"""Ucsm Binding is not present."""
|
||||
message = _("Ucsm Binding with ip %(ucsm_ip)s is not present")
|
||||
|
||||
|
||||
class UcsmBindingAlreadyExists(exceptions.QuantumException):
|
||||
"""Ucsm Binding already exists."""
|
||||
message = _("Ucsm Binding with ip %(ucsm_ip)s already exists")
|
||||
|
||||
|
||||
class DynamicVnicNotFound(exceptions.QuantumException):
|
||||
"""Ucsm Binding is not present."""
|
||||
message = _("Dyanmic Vnic %(vnic_id)s is not present")
|
||||
|
||||
|
||||
class DynamicVnicAlreadyExists(exceptions.QuantumException):
|
||||
"""Ucsm Binding already exists."""
|
||||
message = _("Dynamic Vnic with name %(device_name)s already exists")
|
||||
|
||||
|
||||
class BladeNotFound(exceptions.QuantumException):
|
||||
"""Blade is not present."""
|
||||
message = _("Blade %(blade_id)s is not present")
|
||||
|
||||
|
||||
class BladeAlreadyExists(exceptions.QuantumException):
|
||||
"""Blade already exists."""
|
||||
message = _("Blade with mgmt_ip %(mgmt_ip)s already exists")
|
||||
message = _("Nexus Port Binding %(port_id)s is not present.")
|
||||
|
||||
|
||||
class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
||||
@ -134,17 +107,3 @@ class PortVnicBindingAlreadyExists(exceptions.QuantumException):
|
||||
class PortVnicNotFound(exceptions.QuantumException):
|
||||
"""PortVnic Binding is not present."""
|
||||
message = _("PortVnic Binding %(port_id)s is not present")
|
||||
|
||||
|
||||
class InvalidAttach(exceptions.QuantumException):
|
||||
message = _("Unable to plug the attachment %(att_id)s into port "
|
||||
"%(port_id)s for network %(net_id)s. Association of "
|
||||
"attachment ID with port ID happens implicitly when "
|
||||
"VM is instantiated; attach operation can be "
|
||||
"performed subsequently.")
|
||||
|
||||
|
||||
class InvalidDetach(exceptions.QuantumException):
|
||||
message = _("Unable to unplug the attachment %(att_id)s from port "
|
||||
"%(port_id)s for network %(net_id)s. The attachment "
|
||||
"%(att_id)s does not exist.")
|
||||
|
@ -19,12 +19,6 @@ from oslo.config import cfg
|
||||
from quantum.agent.common import config
|
||||
|
||||
|
||||
cisco_test_opts = [
|
||||
cfg.StrOpt('host',
|
||||
default=None,
|
||||
help=_("Cisco test host option.")),
|
||||
]
|
||||
|
||||
cisco_plugins_opts = [
|
||||
cfg.StrOpt('vswitch_plugin',
|
||||
default='quantum.plugins.openvswitch.ovs_quantum_plugin.'
|
||||
@ -66,13 +60,12 @@ cisco_opts = [
|
||||
|
||||
cfg.CONF.register_opts(cisco_opts, "CISCO")
|
||||
cfg.CONF.register_opts(cisco_plugins_opts, "CISCO_PLUGINS")
|
||||
cfg.CONF.register_opts(cisco_test_opts, "CISCO_TEST")
|
||||
config.register_root_helper(cfg.CONF)
|
||||
|
||||
# shortcuts
|
||||
CONF = cfg.CONF
|
||||
CISCO = cfg.CONF.CISCO
|
||||
CISCO_PLUGINS = cfg.CONF.CISCO_PLUGINS
|
||||
CISCO_TEST = cfg.CONF.CISCO_TEST
|
||||
|
||||
#
|
||||
# When populated the nexus_dictionary format is:
|
||||
@ -81,6 +74,7 @@ CISCO_TEST = cfg.CONF.CISCO_TEST
|
||||
# Example:
|
||||
# {('1.1.1.1', 'username'): 'admin',
|
||||
# ('1.1.1.1', 'password'): 'mySecretPassword',
|
||||
# ('1.1.1.1', 'ssh_port'): 22,
|
||||
# ('1.1.1.1', 'compute1'): '1/1', ...}
|
||||
#
|
||||
nexus_dictionary = {}
|
||||
|
@ -41,3 +41,11 @@ class NexusPortBinding(model_base.BASEV2, L2NetworkBase):
|
||||
def __repr__(self):
|
||||
return "<NexusPortBinding (%s,%d, %s, %s)>" % \
|
||||
(self.port_id, self.vlan_id, self.switch_ip, self.instance_id)
|
||||
|
||||
def __eq__(self, other):
|
||||
return (
|
||||
self.port_id == other.port_id and
|
||||
self.vlan_id == other.vlan_id and
|
||||
self.switch_ip == other.switch_ip and
|
||||
self.instance_id == other.instance_id
|
||||
)
|
||||
|
@ -26,10 +26,10 @@ from novaclient.v1_1 import client as nova_client
|
||||
from oslo.config import cfg
|
||||
|
||||
from quantum.db import api as db_api
|
||||
from quantum.manager import QuantumManager
|
||||
from quantum.openstack.common import importutils
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_credentials_v2 as cred
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco.common import config as conf
|
||||
from quantum.plugins.cisco.db import network_db_v2 as cdb
|
||||
from quantum.plugins.openvswitch import ovs_db_v2 as odb
|
||||
@ -69,7 +69,8 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
for key in conf.CISCO_PLUGINS.keys():
|
||||
plugin_obj = conf.CISCO_PLUGINS[key]
|
||||
self._plugins[key] = importutils.import_object(plugin_obj)
|
||||
LOG.debug(_("Loaded device plugin %s\n"), conf.CISCO_PLUGINS[key])
|
||||
LOG.debug(_("Loaded device plugin %s\n"),
|
||||
conf.CISCO_PLUGINS[key])
|
||||
|
||||
if ((const.VSWITCH_PLUGIN in self._plugins) and
|
||||
hasattr(self._plugins[const.VSWITCH_PLUGIN],
|
||||
@ -161,6 +162,8 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
|
||||
def _get_segmentation_id(self, network_id):
|
||||
binding_seg_id = odb.get_network_binding(None, network_id)
|
||||
if not binding_seg_id:
|
||||
raise cexc.NetworkSegmentIDNotFound(net_id=network_id)
|
||||
return binding_seg_id.segmentation_id
|
||||
|
||||
def _get_all_segmentation_ids(self):
|
||||
@ -199,23 +202,11 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
plugins.
|
||||
"""
|
||||
LOG.debug(_("create_network() called"))
|
||||
try:
|
||||
args = [context, network]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
vlan_id = self._get_segmentation_id(ovs_output[0]['id'])
|
||||
if not self._validate_vlan_id(vlan_id):
|
||||
return ovs_output[0]
|
||||
vlan_name = conf.CISCO.vlan_name_prefix + str(vlan_id)
|
||||
vlanids = self._get_all_segmentation_ids()
|
||||
args = [ovs_output[0]['tenant_id'], ovs_output[0]['name'],
|
||||
ovs_output[0]['id'], vlan_name, vlan_id,
|
||||
{'vlan_ids': vlanids}]
|
||||
return ovs_output[0]
|
||||
except Exception:
|
||||
# TODO(Sumit): Check if we need to perform any rollback here
|
||||
raise
|
||||
args = [context, network]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
return ovs_output[0]
|
||||
|
||||
def update_network(self, context, id, network):
|
||||
"""Update network.
|
||||
@ -228,16 +219,25 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
vlan_id = self._get_segmentation_id(ovs_output[0]['id'])
|
||||
if not self._validate_vlan_id(vlan_id):
|
||||
return ovs_output[0]
|
||||
vlanids = self._get_all_segmentation_ids()
|
||||
args = [ovs_output[0]['tenant_id'], id, {'vlan_id': vlan_id},
|
||||
{'net_admin_state': ovs_output[0]['admin_state_up']},
|
||||
{'vlan_ids': vlanids}]
|
||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
try:
|
||||
vlan_id = self._get_segmentation_id(ovs_output[0]['id'])
|
||||
if not self._validate_vlan_id(vlan_id):
|
||||
return ovs_output[0]
|
||||
vlan_ids = self._get_all_segmentation_ids()
|
||||
args = [ovs_output[0]['tenant_id'], id, {'vlan_id': vlan_id},
|
||||
{'net_admin_state': ovs_output[0]['admin_state_up']},
|
||||
{'vlan_ids': vlan_ids}]
|
||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
except Exception:
|
||||
# TODO(dane): The call to the nexus plugin update network
|
||||
# failed, so the OVS plugin should be rolled back, that is,
|
||||
# "re-updated" back to the original network config.
|
||||
LOG.exception(_("Unable to update network '%s' on Nexus switch"),
|
||||
network['network']['name'])
|
||||
raise
|
||||
|
||||
return ovs_output[0]
|
||||
|
||||
def delete_network(self, context, id):
|
||||
@ -246,24 +246,11 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
Perform this operation in the context of the configured device
|
||||
plugins.
|
||||
"""
|
||||
try:
|
||||
base_plugin_ref = QuantumManager.get_plugin()
|
||||
n = base_plugin_ref.get_network(context, id)
|
||||
tenant_id = n['tenant_id']
|
||||
vlan_id = self._get_segmentation_id(id)
|
||||
args = [context, id]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
args = [tenant_id, id, {const.VLANID: vlan_id},
|
||||
{const.CONTEXT: context},
|
||||
{const.BASE_PLUGIN_REF: base_plugin_ref}]
|
||||
if self._validate_vlan_id(vlan_id):
|
||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(), args)
|
||||
return ovs_output[0]
|
||||
except Exception:
|
||||
raise
|
||||
args = [context, id]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
return ovs_output[0]
|
||||
|
||||
def get_network(self, context, id, fields=None):
|
||||
"""For this model this method will be delegated to vswitch plugin."""
|
||||
@ -292,6 +279,10 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
|
||||
return nexus_output
|
||||
|
||||
@staticmethod
|
||||
def _should_call_create_net(device_owner, instance_id):
|
||||
return (instance_id and device_owner != 'network:dhcp')
|
||||
|
||||
def create_port(self, context, port):
|
||||
"""Create port.
|
||||
|
||||
@ -299,28 +290,34 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
plugins.
|
||||
"""
|
||||
LOG.debug(_("create_port() called"))
|
||||
args = [context, port]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
try:
|
||||
args = [context, port]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
|
||||
instance_id = port['port']['device_id']
|
||||
device_owner = port['port']['device_owner']
|
||||
|
||||
create_net = (conf.CISCO_TEST.host is None and
|
||||
device_owner != 'network:dhcp' and
|
||||
instance_id)
|
||||
if create_net:
|
||||
if self._should_call_create_net(device_owner, instance_id):
|
||||
net_id = port['port']['network_id']
|
||||
tenant_id = port['port']['tenant_id']
|
||||
self._invoke_nexus_for_net_create(
|
||||
context, tenant_id, net_id, instance_id)
|
||||
|
||||
return ovs_output[0]
|
||||
except Exception:
|
||||
# TODO(asomya): Check if we need to perform any rollback here
|
||||
raise
|
||||
except Exception as e:
|
||||
# Create network on the Nexus plugin has failed, so we need
|
||||
# to rollback the port creation on the VSwitch plugin.
|
||||
try:
|
||||
id = ovs_output[0]['id']
|
||||
args = [context, id]
|
||||
ovs_output = self._invoke_plugin_per_device(
|
||||
const.VSWITCH_PLUGIN,
|
||||
'delete_port',
|
||||
args)
|
||||
finally:
|
||||
# Re-raise the original exception
|
||||
raise e
|
||||
return ovs_output[0]
|
||||
|
||||
def get_port(self, context, id, fields=None):
|
||||
"""For this model this method will be delegated to vswitch plugin."""
|
||||
@ -337,16 +334,13 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
plugins.
|
||||
"""
|
||||
LOG.debug(_("update_port() called"))
|
||||
old_port = self.get_port(context, id)
|
||||
old_device = old_port['device_id']
|
||||
args = [context, id, port]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
try:
|
||||
# Get port
|
||||
old_port = self.get_port(context, id)
|
||||
# Check old port device_id
|
||||
old_device = old_port['device_id']
|
||||
# Update port with vswitch plugin
|
||||
args = [context, id, port]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
net_id = old_port['network_id']
|
||||
instance_id = ''
|
||||
if 'device_id' in port['port']:
|
||||
@ -360,6 +354,11 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
|
||||
return ovs_output[0]
|
||||
except Exception:
|
||||
# TODO(dane): The call to the nexus plugin create network
|
||||
# failed, so the OVS plugin should be rolled back, that is,
|
||||
# "re-updated" back to the original port config.
|
||||
LOG.exception(_("Unable to update port '%s' on Nexus switch"),
|
||||
port['port']['name'])
|
||||
raise
|
||||
|
||||
def delete_port(self, context, id):
|
||||
@ -369,21 +368,30 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2):
|
||||
plugins.
|
||||
"""
|
||||
LOG.debug(_("delete_port() called"))
|
||||
port = self.get_port(context, id)
|
||||
vlan_id = self._get_segmentation_id(port['network_id'])
|
||||
n_args = [port['device_id'], vlan_id]
|
||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
try:
|
||||
args = [context, id]
|
||||
port = self.get_port(context, id)
|
||||
vlan_id = self._get_segmentation_id(port['network_id'])
|
||||
n_args = [port['device_id'], vlan_id]
|
||||
ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN,
|
||||
self._func_name(),
|
||||
args)
|
||||
self._invoke_plugin_per_device(const.NEXUS_PLUGIN,
|
||||
self._func_name(),
|
||||
n_args)
|
||||
return ovs_output[0]
|
||||
except Exception:
|
||||
# TODO(asomya): Check if we need to perform any rollback here
|
||||
raise
|
||||
except Exception as e:
|
||||
# Roll back the delete port on the Nexus plugin
|
||||
try:
|
||||
tenant_id = port['tenant_id']
|
||||
net_id = port['network_id']
|
||||
instance_id = port['device_id']
|
||||
self._invoke_nexus_for_net_create(context, tenant_id,
|
||||
net_id, instance_id)
|
||||
finally:
|
||||
# Raise the original exception.
|
||||
raise e
|
||||
|
||||
return ovs_output[0]
|
||||
|
||||
def create_subnet(self, context, subnet):
|
||||
"""For this model this method will be delegated to vswitch plugin."""
|
||||
|
@ -20,7 +20,9 @@ import inspect
|
||||
import logging
|
||||
|
||||
from sqlalchemy import orm
|
||||
import webob.exc as wexc
|
||||
|
||||
from quantum.api.v2 import base
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.db import db_base_plugin_v2
|
||||
from quantum.db import models_v2
|
||||
@ -47,6 +49,24 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
'get_subnet', 'get_subnets', ]
|
||||
_master = True
|
||||
|
||||
CISCO_FAULT_MAP = {
|
||||
cexc.NetworkSegmentIDNotFound: wexc.HTTPNotFound,
|
||||
cexc.NoMoreNics: wexc.HTTPBadRequest,
|
||||
cexc.NetworkVlanBindingAlreadyExists: wexc.HTTPBadRequest,
|
||||
cexc.VlanIDNotFound: wexc.HTTPNotFound,
|
||||
cexc.VlanIDNotAvailable: wexc.HTTPNotFound,
|
||||
cexc.QosNotFound: wexc.HTTPNotFound,
|
||||
cexc.QosNameAlreadyExists: wexc.HTTPBadRequest,
|
||||
cexc.CredentialNotFound: wexc.HTTPNotFound,
|
||||
cexc.CredentialNameNotFound: wexc.HTTPNotFound,
|
||||
cexc.CredentialAlreadyExists: wexc.HTTPBadRequest,
|
||||
cexc.NexusComputeHostNotConfigured: wexc.HTTPNotFound,
|
||||
cexc.NexusConnectFailed: wexc.HTTPServiceUnavailable,
|
||||
cexc.NexusConfigFailed: wexc.HTTPBadRequest,
|
||||
cexc.NexusPortBindingNotFound: wexc.HTTPNotFound,
|
||||
cexc.PortVnicBindingAlreadyExists: wexc.HTTPBadRequest,
|
||||
cexc.PortVnicNotFound: wexc.HTTPNotFound}
|
||||
|
||||
def __init__(self):
|
||||
"""Load the model class."""
|
||||
self._model = importutils.import_object(config.CISCO.model_class)
|
||||
@ -62,6 +82,9 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
self.supported_extension_aliases.extend(
|
||||
self._model.supported_extension_aliases)
|
||||
|
||||
# Extend the fault map
|
||||
self._extend_fault_map()
|
||||
|
||||
LOG.debug(_("Plugin initialization complete"))
|
||||
|
||||
def __getattribute__(self, name):
|
||||
@ -94,6 +117,15 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
raise AttributeError("'%s' object has no attribute '%s'" %
|
||||
(self._model, name))
|
||||
|
||||
def _extend_fault_map(self):
|
||||
"""Extend the Quantum Fault Map for Cisco exceptions.
|
||||
|
||||
Map exceptions which are specific to the Cisco Plugin
|
||||
to standard HTTP exceptions.
|
||||
|
||||
"""
|
||||
base.FAULT_MAP.update(self.CISCO_FAULT_MAP)
|
||||
|
||||
"""
|
||||
Core API implementation
|
||||
"""
|
||||
@ -143,15 +175,12 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
raise exc.NetworkInUse(net_id=id)
|
||||
context.session.close()
|
||||
#Network does not have any ports, we can proceed to delete
|
||||
try:
|
||||
network = self._get_network(context, id)
|
||||
kwargs = {const.NETWORK: network,
|
||||
const.BASE_PLUGIN_REF: self}
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_network(context, id)
|
||||
except Exception:
|
||||
raise
|
||||
network = self._get_network(context, id)
|
||||
kwargs = {const.NETWORK: network,
|
||||
const.BASE_PLUGIN_REF: self}
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_network(context, id)
|
||||
|
||||
def get_network(self, context, id, fields=None):
|
||||
"""Get a particular network."""
|
||||
@ -186,24 +215,18 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
# raise exc.PortInUse(port_id=id, net_id=port['network_id'],
|
||||
# att_id=port['device_id'])
|
||||
"""
|
||||
try:
|
||||
kwargs = {const.PORT: port}
|
||||
# TODO(Sumit): Might first need to check here if port is active
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_port(context, id)
|
||||
except Exception:
|
||||
raise
|
||||
kwargs = {const.PORT: port}
|
||||
# TODO(Sumit): Might first need to check here if port is active
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_port(context, id)
|
||||
|
||||
def update_port(self, context, id, port):
|
||||
"""Update the state of a port and return the updated port."""
|
||||
LOG.debug(_("update_port() called"))
|
||||
try:
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
port])
|
||||
return super(PluginV2, self).update_port(context, id, port)
|
||||
except Exception:
|
||||
raise
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
port])
|
||||
return super(PluginV2, self).update_port(context, id, port)
|
||||
|
||||
def create_subnet(self, context, subnet):
|
||||
"""Create subnet.
|
||||
@ -224,12 +247,9 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
def update_subnet(self, context, id, subnet):
|
||||
"""Updates the state of a subnet and returns the updated subnet."""
|
||||
LOG.debug(_("update_subnet() called"))
|
||||
try:
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
subnet])
|
||||
return super(PluginV2, self).update_subnet(context, id, subnet)
|
||||
except Exception:
|
||||
raise
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
subnet])
|
||||
return super(PluginV2, self).update_subnet(context, id, subnet)
|
||||
|
||||
def delete_subnet(self, context, id):
|
||||
LOG.debug(_("delete_subnet() called"))
|
||||
@ -245,13 +265,10 @@ class PluginV2(db_base_plugin_v2.QuantumDbPluginV2):
|
||||
for a in allocated):
|
||||
raise exc.SubnetInUse(subnet_id=id)
|
||||
context.session.close()
|
||||
try:
|
||||
kwargs = {const.SUBNET: subnet}
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_subnet(context, id)
|
||||
except Exception:
|
||||
raise
|
||||
kwargs = {const.SUBNET: subnet}
|
||||
self._invoke_device_plugins(self._func_name(), [context, id,
|
||||
kwargs])
|
||||
return super(PluginV2, self).delete_subnet(context, id)
|
||||
|
||||
"""
|
||||
Extension API implementation
|
||||
|
@ -25,10 +25,10 @@ import logging
|
||||
|
||||
from ncclient import manager
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cexc
|
||||
from quantum.plugins.cisco.db import network_db_v2 as cdb
|
||||
from quantum.plugins.cisco.nexus import cisco_nexus_snippets as snipp
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@ -37,11 +37,35 @@ class CiscoNEXUSDriver():
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _edit_config(self, mgr, target='running', config=''):
|
||||
"""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
|
||||
|
||||
:raises: NexusConfigFailed
|
||||
|
||||
"""
|
||||
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)
|
||||
|
||||
def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user,
|
||||
nexus_password):
|
||||
"""Make SSH connection to the Nexus Switch."""
|
||||
man = manager.connect(host=nexus_host, port=nexus_ssh_port,
|
||||
username=nexus_user, password=nexus_password)
|
||||
try:
|
||||
man = manager.connect(host=nexus_host, port=nexus_ssh_port,
|
||||
username=nexus_user,
|
||||
password=nexus_password)
|
||||
except Exception as e:
|
||||
# Raise a Quantum exception. Include a description of
|
||||
# the original ncclient exception.
|
||||
raise cexc.NexusConnectFailed(nexus_host=nexus_host, exc=e)
|
||||
|
||||
return man
|
||||
|
||||
def create_xml_snippet(self, cutomized_config):
|
||||
@ -56,27 +80,27 @@ class CiscoNEXUSDriver():
|
||||
"""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)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def disable_vlan(self, mgr, vlanid):
|
||||
"""Delete a VLAN on Nexus Switch given the VLAN ID."""
|
||||
confstr = snipp.CMD_NO_VLAN_CONF_SNIPPET % vlanid
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def enable_port_trunk(self, mgr, interface):
|
||||
"""Enable trunk mode an interface on Nexus Switch."""
|
||||
confstr = snipp.CMD_PORT_TRUNK % (interface)
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def disable_switch_port(self, mgr, interface):
|
||||
"""Disable trunk mode an interface on Nexus Switch."""
|
||||
confstr = snipp.CMD_NO_SWITCHPORT % (interface)
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def enable_vlan_on_trunk_int(self, mgr, interface, vlanid):
|
||||
"""Enable vlan in trunk interface.
|
||||
@ -87,7 +111,7 @@ class CiscoNEXUSDriver():
|
||||
confstr = snipp.CMD_VLAN_INT_SNIPPET % (interface, vlanid)
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def disable_vlan_on_trunk_int(self, mgr, interface, vlanid):
|
||||
"""Disable VLAN.
|
||||
@ -98,7 +122,7 @@ class CiscoNEXUSDriver():
|
||||
confstr = snipp.CMD_NO_VLAN_INT_SNIPPET % (interface, vlanid)
|
||||
confstr = self.create_xml_snippet(confstr)
|
||||
LOG.debug(_("NexusDriver: %s"), confstr)
|
||||
mgr.edit_config(target='running', config=confstr)
|
||||
self._edit_config(mgr, target='running', config=confstr)
|
||||
|
||||
def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user,
|
||||
nexus_password, nexus_ports,
|
||||
|
@ -30,6 +30,7 @@ from quantum.common import exceptions as exc
|
||||
from quantum.openstack.common import importutils
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_credentials_v2 as cred
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as cisco_exc
|
||||
from quantum.plugins.cisco.common import config as conf
|
||||
from quantum.plugins.cisco.db import network_db_v2 as cdb
|
||||
from quantum.plugins.cisco.db import nexus_db_v2 as nxos_db
|
||||
@ -78,16 +79,18 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
"""
|
||||
LOG.debug(_("NexusPlugin:create_network() called"))
|
||||
# Grab the switch IP and port for this host
|
||||
switch_ip = ''
|
||||
port_id = ''
|
||||
for keys in self._nexus_switches.keys():
|
||||
if str(keys[1]) == str(host):
|
||||
switch_ip = keys[0]
|
||||
port_id = self._nexus_switches[keys[0], keys[1]]
|
||||
for switch_ip, attr in self._nexus_switches:
|
||||
if str(attr) == str(host):
|
||||
port_id = self._nexus_switches[switch_ip, attr]
|
||||
break
|
||||
else:
|
||||
raise cisco_exc.NexusComputeHostNotConfigured(host=host)
|
||||
|
||||
# Check if this network is already in the DB
|
||||
binding = nxos_db.get_port_vlan_switch_binding(
|
||||
port_id, vlan_id, switch_ip)
|
||||
vlan_created = False
|
||||
vlan_enabled = False
|
||||
if not binding:
|
||||
_nexus_ip = switch_ip
|
||||
_nexus_ports = (port_id,)
|
||||
@ -104,6 +107,7 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
vlan_name, str(vlan_id), _nexus_ip,
|
||||
_nexus_username, _nexus_password,
|
||||
_nexus_ports, _nexus_ssh_port, vlan_id)
|
||||
vlan_created = True
|
||||
else:
|
||||
# Only trunk vlan on the port
|
||||
man = self._client.nxos_connect(_nexus_ip,
|
||||
@ -113,9 +117,27 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
self._client.enable_vlan_on_trunk_int(man,
|
||||
port_id,
|
||||
vlan_id)
|
||||
vlan_enabled = True
|
||||
|
||||
try:
|
||||
nxos_db.add_nexusport_binding(port_id, str(vlan_id),
|
||||
switch_ip, instance)
|
||||
except Exception as e:
|
||||
try:
|
||||
# Add binding failed, roll back any vlan creation/enabling
|
||||
if vlan_created:
|
||||
self._client.delete_vlan(
|
||||
str(vlan_id), _nexus_ip,
|
||||
_nexus_username, _nexus_password,
|
||||
_nexus_ports, _nexus_ssh_port)
|
||||
if vlan_enabled:
|
||||
self._client.disable_vlan_on_trunk_int(man,
|
||||
port_id,
|
||||
vlan_id)
|
||||
finally:
|
||||
# Raise the original exception
|
||||
raise e
|
||||
|
||||
nxos_db.add_nexusport_binding(port_id, str(vlan_id),
|
||||
switch_ip, instance)
|
||||
new_net_dict = {const.NET_ID: net_id,
|
||||
const.NET_NAME: net_name,
|
||||
const.NET_PORTS: {},
|
||||
@ -176,18 +198,32 @@ class NexusPlugin(L2DevicePluginBase):
|
||||
row['vlan_id'], row['switch_ip'])
|
||||
|
||||
if not bindings:
|
||||
# Delete this vlan from this switch
|
||||
_nexus_ip = row['switch_ip']
|
||||
_nexus_ports = (row['port_id'],)
|
||||
_nexus_ssh_port = \
|
||||
self._nexus_switches[_nexus_ip, 'ssh_port']
|
||||
_nexus_creds = self.get_credential(_nexus_ip)
|
||||
_nexus_username = _nexus_creds['username']
|
||||
_nexus_password = _nexus_creds['password']
|
||||
self._client.delete_vlan(
|
||||
str(row['vlan_id']), _nexus_ip,
|
||||
_nexus_username, _nexus_password,
|
||||
_nexus_ports, _nexus_ssh_port)
|
||||
try:
|
||||
# Delete this vlan from this switch
|
||||
_nexus_ip = row['switch_ip']
|
||||
_nexus_ports = (row['port_id'],)
|
||||
_nexus_ssh_port = (self._nexus_switches[_nexus_ip,
|
||||
'ssh_port'])
|
||||
_nexus_creds = self.get_credential(_nexus_ip)
|
||||
_nexus_username = _nexus_creds['username']
|
||||
_nexus_password = _nexus_creds['password']
|
||||
self._client.delete_vlan(
|
||||
str(row['vlan_id']), _nexus_ip,
|
||||
_nexus_username, _nexus_password,
|
||||
_nexus_ports, _nexus_ssh_port)
|
||||
except Exception as e:
|
||||
# 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.
|
||||
try:
|
||||
nxos_db.add_nexusport_binding(row['port_id'],
|
||||
row['vlan_id'],
|
||||
row['switch_ip'],
|
||||
row['instance_id'])
|
||||
finally:
|
||||
# Raise the original exception
|
||||
raise e
|
||||
|
||||
return row['instance_id']
|
||||
|
||||
|
@ -13,13 +13,23 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import contextlib
|
||||
import inspect
|
||||
import logging
|
||||
import mock
|
||||
|
||||
from quantum.api.v2 import base
|
||||
from quantum.common import exceptions as q_exc
|
||||
from quantum import context
|
||||
from quantum.db import l3_db
|
||||
from quantum.manager import QuantumManager
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.db import network_db_v2 # noqa
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as c_exc
|
||||
from quantum.plugins.cisco.common import config as cisco_config
|
||||
from quantum.plugins.cisco.db import nexus_db_v2
|
||||
from quantum.plugins.cisco.models import virt_phy_sw_v2
|
||||
from quantum.plugins.openvswitch.common import config as ovs_config
|
||||
from quantum.plugins.openvswitch import ovs_db_v2
|
||||
from quantum.tests.unit import test_db_plugin
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
@ -31,9 +41,9 @@ class CiscoNetworkPluginV2TestCase(test_db_plugin.QuantumDbPluginV2TestCase):
|
||||
|
||||
def setUp(self):
|
||||
# Use a mock netconf client
|
||||
mock_ncclient = mock.Mock()
|
||||
self.mock_ncclient = mock.Mock()
|
||||
self.patch_obj = mock.patch.dict('sys.modules',
|
||||
{'ncclient': mock_ncclient})
|
||||
{'ncclient': self.mock_ncclient})
|
||||
self.patch_obj.start()
|
||||
|
||||
super(CiscoNetworkPluginV2TestCase, self).setUp(self._plugin_name)
|
||||
@ -65,6 +75,124 @@ class TestCiscoV2HTTPResponse(CiscoNetworkPluginV2TestCase,
|
||||
class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
|
||||
test_db_plugin.TestPortsV2):
|
||||
|
||||
def setUp(self):
|
||||
"""Configure for end-to-end quantum 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 = ('quantum.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},
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
self.switch_ip = '1.1.1.1'
|
||||
nexus_config = {(self.switch_ip, 'username'): 'admin',
|
||||
(self.switch_ip, 'password'): 'mySecretPassword',
|
||||
(self.switch_ip, 'ssh_port'): 22,
|
||||
(self.switch_ip, 'testhost'): '1/1'}
|
||||
mock.patch.dict(cisco_config.nexus_dictionary, nexus_config).start()
|
||||
|
||||
patches = {
|
||||
'_should_call_create_net': True,
|
||||
'_get_instance_host': 'testhost'
|
||||
}
|
||||
for func in patches:
|
||||
mock_sw = mock.patch.object(
|
||||
virt_phy_sw_v2.VirtualPhysicalSwitchModelV2,
|
||||
func).start()
|
||||
mock_sw.return_value = patches[func]
|
||||
|
||||
super(TestCiscoPortsV2, self).setUp()
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _patch_ncclient(self, attr, value):
|
||||
"""Configure an attribute on the mock ncclient module.
|
||||
|
||||
This method can be used to inject errors by setting a side effect
|
||||
or a return value for an ncclient method.
|
||||
|
||||
:param attr: ncclient attribute (typically method) to be configured.
|
||||
:param value: Value to be configured on the attribute.
|
||||
|
||||
"""
|
||||
# Configure attribute.
|
||||
config = {attr: value}
|
||||
self.mock_ncclient.configure_mock(**config)
|
||||
# Continue testing
|
||||
yield
|
||||
# Unconfigure attribute
|
||||
config = {attr: None}
|
||||
self.mock_ncclient.configure_mock(**config)
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _create_port_res(self, fmt=None, no_delete=False,
|
||||
**kwargs):
|
||||
"""Create a network, subnet, and port and yield the result.
|
||||
|
||||
Create a network, subnet, and port, yield the result,
|
||||
then delete the port, subnet, and network.
|
||||
|
||||
:param fmt: Format to be used for API requests.
|
||||
:param no_delete: If set to True, don't delete the port at the
|
||||
end of testing.
|
||||
:param kwargs: Keyword args to be passed to self._create_port.
|
||||
|
||||
"""
|
||||
with self.subnet() as subnet:
|
||||
net_id = subnet['subnet']['network_id']
|
||||
res = self._create_port(fmt, net_id, **kwargs)
|
||||
port = self.deserialize(fmt, res)
|
||||
try:
|
||||
yield res
|
||||
finally:
|
||||
if not no_delete:
|
||||
self._delete('ports', port['port']['id'])
|
||||
|
||||
def _assertExpectedHTTP(self, status, exc):
|
||||
"""Confirm that an HTTP status corresponds to an expected exception.
|
||||
|
||||
Confirm that an HTTP status which has been returned for an
|
||||
quantum API request matches the HTTP status corresponding
|
||||
to an expected exception.
|
||||
|
||||
:param status: HTTP status
|
||||
:param exc: Expected exception
|
||||
|
||||
"""
|
||||
if exc in base.FAULT_MAP:
|
||||
expected_http = base.FAULT_MAP[exc].code
|
||||
else:
|
||||
expected_http = 500
|
||||
self.assertEqual(status, expected_http)
|
||||
|
||||
def test_create_ports_bulk_emulated_plugin_failure(self):
|
||||
real_has_attr = hasattr
|
||||
|
||||
@ -114,6 +242,163 @@ class TestCiscoPortsV2(CiscoNetworkPluginV2TestCase,
|
||||
# We expect a 500 as we injected a fault in the plugin
|
||||
self._validate_behavior_on_bulk_failure(res, 'ports', 500)
|
||||
|
||||
def test_nexus_connect_fail(self):
|
||||
"""Test failure to connect to a Nexus switch.
|
||||
|
||||
While creating a network, subnet, and port, simulate a connection
|
||||
failure to a nexus switch. Confirm that the expected HTTP code
|
||||
is returned for the create port operation.
|
||||
|
||||
"""
|
||||
with self._patch_ncclient('manager.connect.side_effect',
|
||||
AttributeError):
|
||||
with self._create_port_res(self.fmt, no_delete=True,
|
||||
name='myname') as res:
|
||||
self._assertExpectedHTTP(res.status_int,
|
||||
c_exc.NexusConnectFailed)
|
||||
|
||||
def test_nexus_config_fail(self):
|
||||
"""Test a Nexus switch configuration failure.
|
||||
|
||||
While creating a network, subnet, and port, simulate a nexus
|
||||
switch configuration error. Confirm that the expected HTTP code
|
||||
is returned for the create port operation.
|
||||
|
||||
"""
|
||||
with self._patch_ncclient(
|
||||
'manager.connect.return_value.edit_config.side_effect',
|
||||
AttributeError):
|
||||
with self._create_port_res(self.fmt, no_delete=True,
|
||||
name='myname') as res:
|
||||
self._assertExpectedHTTP(res.status_int,
|
||||
c_exc.NexusConfigFailed)
|
||||
|
||||
def test_get_seg_id_fail(self):
|
||||
"""Test handling of a NetworkSegmentIDNotFound exception.
|
||||
|
||||
Test the Cisco NetworkSegmentIDNotFound exception by simulating
|
||||
a return of None by the OVS DB get_network_binding method
|
||||
during port creation.
|
||||
|
||||
"""
|
||||
orig = ovs_db_v2.get_network_binding
|
||||
|
||||
def _return_none_if_nexus_caller(self, *args, **kwargs):
|
||||
def _calling_func_name(offset=0):
|
||||
"""Get name of the calling function 'offset' frames back."""
|
||||
return inspect.stack()[1 + offset][3]
|
||||
if (_calling_func_name(1) == '_get_segmentation_id' and
|
||||
_calling_func_name(2) == '_invoke_nexus_for_net_create'):
|
||||
return None
|
||||
else:
|
||||
return orig(self, *args, **kwargs)
|
||||
|
||||
with mock.patch.object(ovs_db_v2, 'get_network_binding',
|
||||
new=_return_none_if_nexus_caller):
|
||||
with self._create_port_res(self.fmt, no_delete=True,
|
||||
name='myname') as res:
|
||||
self._assertExpectedHTTP(res.status_int,
|
||||
c_exc.NetworkSegmentIDNotFound)
|
||||
|
||||
def test_nexus_host_non_configured(self):
|
||||
"""Test handling of a NexusComputeHostNotConfigured exception.
|
||||
|
||||
Test the Cisco NexusComputeHostNotConfigured exception by using
|
||||
a fictitious host name during port creation.
|
||||
|
||||
"""
|
||||
with mock.patch.object(virt_phy_sw_v2.VirtualPhysicalSwitchModelV2,
|
||||
'_get_instance_host') as mock_get_instance:
|
||||
mock_get_instance.return_value = 'fictitious_host'
|
||||
with self._create_port_res(self.fmt, no_delete=True,
|
||||
name='myname') as res:
|
||||
self._assertExpectedHTTP(res.status_int,
|
||||
c_exc.NexusComputeHostNotConfigured)
|
||||
|
||||
def test_nexus_bind_fail_rollback(self):
|
||||
"""Test for proper rollback following add Nexus DB binding failure.
|
||||
|
||||
Test that the Cisco Nexus plugin correctly rolls back the vlan
|
||||
configuration on the Nexus switch when add_nexusport_binding fails
|
||||
within the plugin's create_port() method.
|
||||
|
||||
"""
|
||||
with mock.patch.object(nexus_db_v2, 'add_nexusport_binding',
|
||||
side_effect=KeyError):
|
||||
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 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._assertExpectedHTTP(res.status_int, KeyError)
|
||||
|
||||
def test_model_delete_port_rollback(self):
|
||||
"""Test for proper rollback for OVS plugin delete port failure.
|
||||
|
||||
Test that the nexus port configuration is rolled back (restored)
|
||||
by the Cisco model plugin when there is a failure in the OVS
|
||||
plugin for a delete port operation.
|
||||
|
||||
"""
|
||||
with self._create_port_res(self.fmt, name='myname') as res:
|
||||
|
||||
# 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)
|
||||
self.assertEqual(len(start_rows), 1)
|
||||
|
||||
# Inject an exception in the OVS plugin delete_port
|
||||
# processing, and attempt a port deletion.
|
||||
inserted_exc = q_exc.Conflict
|
||||
expected_http = base.FAULT_MAP[inserted_exc].code
|
||||
with mock.patch.object(l3_db.L3_NAT_db_mixin,
|
||||
'disassociate_floatingips',
|
||||
side_effect=inserted_exc):
|
||||
self._delete('ports', port['port']['id'],
|
||||
expected_code=expected_http)
|
||||
|
||||
# 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)
|
||||
self.assertEqual(start_rows, end_rows)
|
||||
|
||||
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_port_res(self.fmt, name='myname') as res:
|
||||
|
||||
port = self.deserialize(self.fmt, res)
|
||||
|
||||
# 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)
|
||||
self.assertEqual(len(start_rows), 1)
|
||||
|
||||
# Simulate a Nexus switch configuration error during
|
||||
# port deletion.
|
||||
with self._patch_ncclient(
|
||||
'manager.connect.return_value.edit_config.side_effect',
|
||||
AttributeError):
|
||||
self._delete('ports', port['port']['id'],
|
||||
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)
|
||||
self.assertEqual(start_rows, end_rows)
|
||||
|
||||
|
||||
class TestCiscoNetworksV2(CiscoNetworkPluginV2TestCase,
|
||||
test_db_plugin.TestNetworksV2):
|
||||
|
Loading…
x
Reference in New Issue
Block a user