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 "
"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 "
"for tenant %(tenant_id)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 NexusComputeHostNotConfigured(exceptions.QuantumException):
+ """Connection to compute host is not configured."""
+ message = _("Connection to %(host)s is not configured.")
-class UcsmBindingNotFound(exceptions.QuantumException):
- """Ucsm Binding is not present."""
- message = _("Ucsm Binding with ip %(ucsm_ip)s is not present")
+class NexusConnectFailed(exceptions.QuantumException):
+ """Failed to connect to Nexus switch."""
+ message = _("Unable to connect to Nexus %(nexus_host)s. Reason: %(exc)s.")
-class UcsmBindingAlreadyExists(exceptions.QuantumException):
- """Ucsm Binding already exists."""
- message = _("Ucsm Binding with ip %(ucsm_ip)s already exists")
+class NexusConfigFailed(exceptions.QuantumException):
+ """Failed to configure Nexus switch."""
+ message = _("Failed to configure Nexus: %(config)s. Reason: %(exc)s.")
-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")
+class NexusPortBindingNotFound(exceptions.QuantumException):
+ """NexusPort Binding is not present."""
+ message = _("Nexus Port Binding %(port_id)s is not present.")
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.")
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.'
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:
# 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 = {}
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
+ )
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
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],
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):
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.
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):
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."""
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.
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."""
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']:
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):
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."""
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
'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)
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):
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
"""
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."""
# 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.
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"))
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
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__)
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):
"""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.
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.
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,
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
"""
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,)
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,
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: {},
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']
# 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__)
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)
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
# 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):