From 830820184ae761597b743dd596a4e40dff8796ef Mon Sep 17 00:00:00 2001 From: Arvind Somya Date: Mon, 7 Jan 2013 18:40:59 -0500 Subject: [PATCH] Adding multi switch support to the Cisco Nexus plugin This commit adds intelligent multiple nexus physical switch support for the Cisco plugin. The plugin also has been modified to scan for the host when an instance is created and selectively trunk VLAN's for the port for that host only. It also deletes VLANs from nexus switches when no longer required. Implements: blueprint cisco-plugin-enhancements Change-Id: I6275eb1815310d0d5a8123ca2edbc0a0937718e9 --- etc/quantum/plugins/cisco/credentials.ini | 12 +- .../plugins/cisco/l2network_plugin.ini | 4 + etc/quantum/plugins/cisco/nexus.ini | 15 +- quantum/plugins/cisco/db/nexus_db_v2.py | 67 ++++++-- quantum/plugins/cisco/db/nexus_models_v2.py | 13 +- .../cisco/l2network_plugin_configuration.py | 4 +- .../plugins/cisco/models/virt_phy_sw_v2.py | 104 +++++++++--- .../cisco/nexus/cisco_nexus_configuration.py | 5 +- .../nexus/cisco_nexus_network_driver_v2.py | 47 +++--- .../cisco/nexus/cisco_nexus_plugin_v2.py | 148 ++++++++++-------- quantum/tests/unit/cisco/test_nexus_plugin.py | 132 ++++------------ tools/pip-requires | 4 + 12 files changed, 321 insertions(+), 234 deletions(-) diff --git a/etc/quantum/plugins/cisco/credentials.ini b/etc/quantum/plugins/cisco/credentials.ini index 4eef1d992..a33e16c35 100644 --- a/etc/quantum/plugins/cisco/credentials.ini +++ b/etc/quantum/plugins/cisco/credentials.ini @@ -4,7 +4,13 @@ username= password= #Provide the Nexus credentials, if you are using Nexus -[1.1.1.1] -username=abc -password=def +[] +username= +password= +# Provide credentials and endpoint +# for keystone here +[keystone] +auth_url= +username= +password= diff --git a/etc/quantum/plugins/cisco/l2network_plugin.ini b/etc/quantum/plugins/cisco/l2network_plugin.ini index 848ab2183..501ff3814 100644 --- a/etc/quantum/plugins/cisco/l2network_plugin.ini +++ b/etc/quantum/plugins/cisco/l2network_plugin.ini @@ -18,3 +18,7 @@ model_class=quantum.plugins.cisco.models.virt_phy_sw_v2.VirtualPhysicalSwitchMod [SEGMENTATION] manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr_v2.L2NetworkVLANMgr + +# IMPORTANT: Comment the following lines for production deployments +[TEST] +host=testhost diff --git a/etc/quantum/plugins/cisco/nexus.ini b/etc/quantum/plugins/cisco/nexus.ini index 54bb967e0..b01cd0aa8 100644 --- a/etc/quantum/plugins/cisco/nexus.ini +++ b/etc/quantum/plugins/cisco/nexus.ini @@ -1,10 +1,13 @@ [SWITCH] -# Change the following to reflect the Nexus switch details -nexus_ip_address= -#Interfaces connected from the Nexus Switch to the compute hosts ports, e.g.: 1/10 and 1/11 -ports= -#Port number where the SSH will be running at the Nexus Switch, e.g.: 22 (Default) -nexus_ssh_port=22 +# Ip address of the switch +[[]] +# Hostname of the node +[[[]]] +# Port this node is connected to on the nexus switch +ports= +# Port number where the SSH will be running at the Nexus Switch, e.g.: 22 (Default) +[[[ssh_port]]] +ssh_port= [DRIVER] #name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver_v2.CiscoNEXUSDriver diff --git a/quantum/plugins/cisco/db/nexus_db_v2.py b/quantum/plugins/cisco/db/nexus_db_v2.py index a61ffc4ae..b5f35dd8f 100644 --- a/quantum/plugins/cisco/db/nexus_db_v2.py +++ b/quantum/plugins/cisco/db/nexus_db_v2.py @@ -13,18 +13,22 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. +# # @author: Rohit Agarwalla, Cisco Systems, Inc. - -import logging as LOG +# @author: Arvind Somya, Cisco Systems, Inc. (asomya@cisco.com) +# from sqlalchemy.orm import exc import quantum.db.api as db - +from quantum.openstack.common import log as logging from quantum.plugins.cisco.common import cisco_exceptions as c_exc from quantum.plugins.cisco.db import nexus_models_v2 +LOG = logging.getLogger(__name__) + + def get_all_nexusport_bindings(): """Lists all the nexusport bindings""" LOG.debug("get_all_nexusport_bindings() called") @@ -36,35 +40,54 @@ def get_all_nexusport_bindings(): return [] -def get_nexusport_binding(vlan_id): +def get_nexusport_binding(port_id, vlan_id, switch_ip, instance_id): """Lists a nexusport binding""" LOG.debug("get_nexusport_binding() called") session = db.get_session() try: binding = (session.query(nexus_models_v2.NexusPortBinding). - filter_by(vlan_id=vlan_id).all()) + filter_by(vlan_id=vlan_id).filter_by(switch_ip=switch_ip). + filter_by(port_id=port_id). + filter_by(instance_id=instance_id).all()) return binding except exc.NoResultFound: raise c_exc.NexusPortBindingNotFound(vlan_id=vlan_id) -def add_nexusport_binding(port_id, vlan_id): +def get_nexusvlan_binding(vlan_id, switch_ip): + """Lists a vlan and switch binding""" + LOG.debug("get_nexusvlan_binding() called") + session = db.get_session() + try: + binding = (session.query(nexus_models_v2.NexusPortBinding). + filter_by(vlan_id=vlan_id).filter_by(switch_ip=switch_ip). + all()) + return binding + except exc.NoResultFound: + raise c_exc.NexusPortBindingNotFound(vlan_id=vlan_id) + + +def add_nexusport_binding(port_id, vlan_id, switch_ip, instance_id): """Adds a nexusport binding""" LOG.debug("add_nexusport_binding() called") session = db.get_session() - binding = nexus_models_v2.NexusPortBinding(port_id, vlan_id) + binding = nexus_models_v2.NexusPortBinding( + port_id, vlan_id, switch_ip, instance_id) session.add(binding) session.flush() return binding -def remove_nexusport_binding(vlan_id): +def remove_nexusport_binding(port_id, vlan_id, switch_ip, instance_id): """Removes a nexusport binding""" LOG.debug("remove_nexusport_binding() called") session = db.get_session() try: binding = (session.query(nexus_models_v2.NexusPortBinding). - filter_by(vlan_id=vlan_id).all()) + filter_by(vlan_id=vlan_id).filter_by(switch_ip=switch_ip). + filter_by(port_id=port_id). + filter_by(instance_id=instance_id).all()) + for bind in binding: session.delete(bind) session.flush() @@ -87,3 +110,29 @@ def update_nexusport_binding(port_id, new_vlan_id): return binding except exc.NoResultFound: raise c_exc.NexusPortBindingNotFound() + + +def get_nexusvm_binding(vlan_id, instance_id): + """Lists nexusvm bindings""" + LOG.debug("get_nexusvm_binding() called") + session = db.get_session() + try: + binding = (session.query(nexus_models_v2.NexusPortBinding). + filter_by(instance_id=instance_id). + filter_by(vlan_id=vlan_id).first()) + return binding + except exc.NoResultFound: + raise c_exc.NexusPortBindingNotFound(vlan_id=vlan_id) + + +def get_port_vlan_switch_binding(port_id, vlan_id, switch_ip): + """Lists nexusvm bindings""" + LOG.debug("get_port_vlan_switch_binding() called") + session = db.get_session() + try: + binding = (session.query(nexus_models_v2.NexusPortBinding). + filter_by(port_id=port_id).filter_by(switch_ip=switch_ip). + filter_by(vlan_id=vlan_id).all()) + return binding + except exc.NoResultFound: + raise c_exc.NexusPortBindingNotFound(vlan_id=vlan_id) diff --git a/quantum/plugins/cisco/db/nexus_models_v2.py b/quantum/plugins/cisco/db/nexus_models_v2.py index 975d270cd..e1cfd4b3c 100644 --- a/quantum/plugins/cisco/db/nexus_models_v2.py +++ b/quantum/plugins/cisco/db/nexus_models_v2.py @@ -22,16 +22,21 @@ from quantum.plugins.cisco.db.l2network_models import L2NetworkBase class NexusPortBinding(model_base.BASEV2, L2NetworkBase): - """Represents a binding of nexus port to vlan_id""" - __tablename__ = 'nexusport_bindings' + """Represents a binding of VM's to nexus ports""" + __tablename__ = "nexusport_bindings" id = Column(Integer, primary_key=True, autoincrement=True) port_id = Column(String(255)) vlan_id = Column(Integer, nullable=False) + switch_ip = Column(String(255)) + instance_id = Column(String(255)) - def __init__(self, port_id, vlan_id): + def __init__(self, port_id, vlan_id, switch_ip, instance_id): self.port_id = port_id self.vlan_id = vlan_id + self.switch_ip = switch_ip + self.instance_id = instance_id def __repr__(self): - return "" % (self.port_id, self.vlan_id) + return "" % \ + (self.port_id, self.vlan_id, self.switch_ip, self.instance_id) diff --git a/quantum/plugins/cisco/l2network_plugin_configuration.py b/quantum/plugins/cisco/l2network_plugin_configuration.py index fdd4191bc..a1c35f059 100644 --- a/quantum/plugins/cisco/l2network_plugin_configuration.py +++ b/quantum/plugins/cisco/l2network_plugin_configuration.py @@ -43,6 +43,9 @@ MAX_NETWORKS = SECTION_CONF['max_networks'] SECTION_CONF = CONF_PARSER_OBJ['MODEL'] MODEL_CLASS = SECTION_CONF['model_class'] +if 'TEST' in CONF_PARSER_OBJ.keys(): + TEST = CONF_PARSER_OBJ['TEST'] + CONF_FILE = find_config_file({'plugin': 'cisco'}, "cisco_plugins.ini") SECTION_CONF = CONF_PARSER_OBJ['SEGMENTATION'] @@ -51,7 +54,6 @@ MANAGER_CLASS = SECTION_CONF['manager_class'] CONF_PARSER_OBJ = confp.CiscoConfigParser(CONF_FILE) - # Read the config for the device plugins PLUGINS = CONF_PARSER_OBJ.walk(CONF_PARSER_OBJ.dummy) diff --git a/quantum/plugins/cisco/models/virt_phy_sw_v2.py b/quantum/plugins/cisco/models/virt_phy_sw_v2.py index b46aa4bc1..49c26b5a3 100644 --- a/quantum/plugins/cisco/models/virt_phy_sw_v2.py +++ b/quantum/plugins/cisco/models/virt_phy_sw_v2.py @@ -21,6 +21,10 @@ from copy import deepcopy import inspect import logging +from keystoneclient.v2_0 import client as keystone_client +from novaclient.v1_1 import client as nova_client + +from quantum.db import l3_db from quantum.manager import QuantumManager from quantum.openstack.common import importutils from quantum.plugins.cisco.common import cisco_constants as const @@ -30,7 +34,6 @@ from quantum.plugins.cisco import l2network_plugin_configuration as conf from quantum.plugins.openvswitch import ovs_db_v2 as odb from quantum import quantum_plugin_base_v2 - LOG = logging.getLogger(__name__) @@ -46,11 +49,11 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): _plugins = {} _inventory = {} _methods_to_delegate = ['get_network', 'get_networks', - 'create_port', 'create_port_bulk', 'delete_port', - 'update_port', 'get_port', 'get_ports', + 'create_port_bulk', 'update_port', + 'get_port', 'get_ports', 'create_subnet', 'create_subnet_bulk', - 'delete_subnet', 'update_subnet', 'get_subnet', - 'get_subnets', ] + 'delete_subnet', 'update_subnet', + 'get_subnet', 'get_subnets', ] def __init__(self): """ @@ -181,6 +184,25 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): else: return False + def _get_instance_host(self, tenant_id, instance_id): + keystone = cred._creds_dictionary['keystone'] + kc = keystone_client.Client(username=keystone['username'], + password=keystone['password'], + tenant_id=tenant_id, + auth_url=keystone['auth_url']) + tenant = kc.tenants.get(tenant_id) + tenant_name = tenant.name + + nc = nova_client.Client(keystone['username'], + keystone['password'], + tenant_name, + keystone['auth_url'], + no_cache=True) + serv = nc.servers.get(instance_id) + host = serv.__getattr__('OS-EXT-SRV-ATTR:host') + + return host + def create_network(self, context, network): """ Perform this operation in the context of the configured device @@ -200,9 +222,6 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): args = [ovs_output[0]['tenant_id'], ovs_output[0]['name'], ovs_output[0]['id'], vlan_name, vlan_id, {'vlan_ids': vlanids}] - nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN, - self._func_name(), - args) return ovs_output[0] except: # TODO (Sumit): Check if we need to perform any rollback here @@ -221,14 +240,6 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): LOG.debug("ovs_output: %s\n " % ovs_output) vlanids = self._get_all_segmentation_ids() ovs_networks = ovs_output - for ovs_network in ovs_networks: - vlan_id = self._get_segmentation_id(ovs_network['id']) - vlan_name = conf.VLAN_NAME_PREFIX + str(vlan_id) - args = [ovs_network['tenant_id'], ovs_network['name'], - ovs_network['id'], vlan_name, vlan_id, - {'vlan_ids': vlanids}] - nexus_output = self._invoke_plugin_per_device( - const.NEXUS_PLUGIN, "create_network", args) return ovs_output except: # TODO (Sumit): Check if we need to perform any rollback here @@ -289,8 +300,41 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): pass def create_port(self, context, port): - """For this model this method will be delegated to vswitch plugin""" - pass + """ + Perform this operation in the context of the configured device + plugins. + """ + LOG.debug("create_port() called\n") + try: + args = [context, port] + ovs_output = self._invoke_plugin_per_device(const.VSWITCH_PLUGIN, + self._func_name(), + args) + net_id = port['port']['network_id'] + instance_id = port['port']['device_id'] + tenant_id = port['port']['tenant_id'] + + net_dict = self.get_network(context, net_id) + net_name = net_dict['name'] + + vlan_id = self._get_segmentation_id(net_id) + host = '' + if hasattr(conf, 'TEST'): + host = conf.TEST['host'] + else: + host = self._get_instance_host(tenant_id, instance_id) + + # Trunk segmentation id for only this host + vlan_name = conf.VLAN_NAME_PREFIX + str(vlan_id) + n_args = [tenant_id, net_name, net_id, + vlan_name, vlan_id, host, instance_id] + nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + 'create_network', + n_args) + return ovs_output[0] + except: + # TODO (asomya): Check if we need to perform any rollback here + raise def get_port(self, context, id, fields=None): """For this model this method will be delegated to vswitch plugin""" @@ -304,9 +348,27 @@ class VirtualPhysicalSwitchModelV2(quantum_plugin_base_v2.QuantumPluginBaseV2): """For this model this method will be delegated to vswitch plugin""" pass - def delete_port(self, context, id, kwargs): - """For this model this method will be delegated to vswitch plugin""" - pass + def delete_port(self, context, id): + """ + Perform this operation in the context of the configured device + plugins. + """ + LOG.debug("delete_port() called\n") + 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) + nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), + n_args) + return ovs_output[0] + except: + # TODO (asomya): Check if we need to perform any rollback here + raise def create_subnet(self, context, subnet): """For this model this method will be delegated to vswitch plugin""" diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py index 4d648d93d..d862a39a1 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_configuration.py @@ -30,10 +30,7 @@ from quantum.plugins.cisco.common import cisco_configparser as confp CP = confp.CiscoConfigParser(find_config_file({'plugin': 'cisco'}, "nexus.ini")) -SECTION = CP['SWITCH'] -NEXUS_IP_ADDRESS = SECTION['nexus_ip_address'] -NEXUS_PORTS = SECTION['ports'] -NEXUS_SSH_PORT = SECTION['nexus_ssh_port'] +NEXUS_DETAILS = CP['SWITCH'] SECTION = CP['DRIVER'] NEXUS_DRIVER = SECTION['name'] diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py index 6f30c80bf..0e10f025b 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_network_driver_v2.py @@ -21,6 +21,7 @@ Implements a Nexus-OS NETCONF over SSHv2 API Client """ +import eventlet import logging from ncclient import manager @@ -116,14 +117,14 @@ class CiscoNEXUSDriver(): Creates a VLAN and Enable on trunk mode an interface on Nexus Switch given the VLAN ID and Name and Interface Number """ - with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, - nexus_password) as man: - self.enable_vlan(man, vlan_id, vlan_name) - if vlan_ids is '': - vlan_ids = self.build_vlans_cmd() - LOG.debug("NexusDriver VLAN IDs: %s" % vlan_ids) - for ports in nexus_ports: - self.enable_vlan_on_trunk_int(man, ports, vlan_ids) + man = self.nxos_connect(nexus_host, int(nexus_ssh_port), + nexus_user, nexus_password) + self.enable_vlan(man, vlan_id, vlan_name) + if vlan_ids is '': + vlan_ids = self.build_vlans_cmd() + LOG.debug("NexusDriver VLAN IDs: %s" % vlan_ids) + for ports in nexus_ports: + self.enable_vlan_on_trunk_int(man, ports, vlan_ids) def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password, nexus_ports, nexus_ssh_port): @@ -131,11 +132,11 @@ class CiscoNEXUSDriver(): Delete a VLAN and Disables trunk mode an interface on Nexus Switch given the VLAN ID and Interface Number """ - with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, - nexus_password) as man: - self.disable_vlan(man, vlan_id) - for ports in nexus_ports: - self.disable_vlan_on_trunk_int(man, ports, vlan_id) + man = self.nxos_connect(nexus_host, int(nexus_ssh_port), + nexus_user, nexus_password) + self.disable_vlan(man, vlan_id) + for ports in nexus_ports: + self.disable_vlan_on_trunk_int(man, ports, vlan_id) def build_vlans_cmd(self): """ @@ -154,19 +155,19 @@ class CiscoNEXUSDriver(): """ Adds a vlan from interfaces on the Nexus switch given the VLAN ID """ - with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, - nexus_password) as man: - if not vlan_ids: - vlan_ids = self.build_vlans_cmd() - for ports in nexus_ports: - self.enable_vlan_on_trunk_int(man, ports, vlan_ids) + man = self.nxos_connect(nexus_host, int(nexus_ssh_port), + nexus_user, nexus_password) + if not vlan_ids: + vlan_ids = self.build_vlans_cmd() + for ports in nexus_ports: + self.enable_vlan_on_trunk_int(man, ports, vlan_ids) def remove_vlan_int(self, vlan_id, nexus_host, nexus_user, nexus_password, nexus_ports, nexus_ssh_port): """ Removes a vlan from interfaces on the Nexus switch given the VLAN ID """ - with self.nxos_connect(nexus_host, int(nexus_ssh_port), nexus_user, - nexus_password) as man: - for ports in nexus_ports: - self.disable_vlan_on_trunk_int(man, ports, vlan_id) + man = self.nxos_connect(nexus_host, int(nexus_ssh_port), + nexus_user, nexus_password) + for ports in nexus_ports: + self.disable_vlan_on_trunk_int(man, ports, vlan_id) diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py index 447faf466..57bc3c3a2 100644 --- a/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py @@ -16,6 +16,7 @@ # # @author: Sumit Naiksatam, Cisco Systems, Inc. # @author: Edgar Magana, Cisco Systems, Inc. +# @author: Arvind Somya, Cisco Systems, Inc. (asomya@cisco.com) # """ PlugIn for Nexus OS driver @@ -48,11 +49,18 @@ class NexusPlugin(L2DevicePluginBase): """ self._client = importutils.import_object(conf.NEXUS_DRIVER) LOG.debug("Loaded driver %s\n" % conf.NEXUS_DRIVER) - self._nexus_ip = conf.NEXUS_IP_ADDRESS - self._nexus_username = cred.Store.get_username(conf.NEXUS_IP_ADDRESS) - self._nexus_password = cred.Store.get_password(conf.NEXUS_IP_ADDRESS) - self._nexus_ports = conf.NEXUS_PORTS - self._nexus_ssh_port = conf.NEXUS_SSH_PORT + self._nexus_switches = conf.NEXUS_DETAILS + self.credentials = {} + + def get_credential(self, nexus_ip): + if not nexus_ip in self.credentials.keys(): + _nexus_username = cred.Store.get_username(nexus_ip) + _nexus_password = cred.Store.get_password(nexus_ip) + self.credentials[nexus_ip] = { + 'username': _nexus_username, + 'password': _nexus_password + } + return self.credentials[nexus_ip] def get_all_networks(self, tenant_id): """ @@ -64,26 +72,52 @@ class NexusPlugin(L2DevicePluginBase): return self._networks.values() def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, - **kwargs): + host, instance): """ - Create a VLAN in the switch, and configure the appropriate interfaces + Create a VLAN in the appropriate switch/port, + and configure the appropriate interfaces for this VLAN """ LOG.debug("NexusPlugin:create_network() called\n") - vlan_ids = '' - for key in kwargs: - if key == 'vlan_ids': - vlan_ids = kwargs['vlan_ids'] - self._client.create_vlan( - vlan_name, str(vlan_id), self._nexus_ip, - self._nexus_username, self._nexus_password, - self._nexus_ports, self._nexus_ssh_port, vlan_ids) - for ports in self._nexus_ports: - try: - nxos_db.add_nexusport_binding(ports, str(vlan_id)) - except: - raise excep.NexusPortBindingAlreadyExists(port_id=ports) - + # Grab the switch IP and port for this host + switch_ip = '' + port_id = '' + for switch in self._nexus_switches.keys(): + for hostname in self._nexus_switches[switch].keys(): + if str(hostname) == str(host): + switch_ip = switch + port_id = self._nexus_switches[switch][hostname]['ports'] + # Check if this network is already in the DB + binding = nxos_db.get_port_vlan_switch_binding( + port_id, vlan_id, switch_ip) + if not binding: + _nexus_ip = switch_ip + _nexus_ports = (port_id,) + _nexus_ssh_port = \ + self._nexus_switches[switch_ip]['ssh_port']['ssh_port'] + _nexus_creds = self.get_credential(_nexus_ip) + _nexus_username = _nexus_creds['username'] + _nexus_password = _nexus_creds['password'] + # Check for vlan/switch binding + vbinding = nxos_db.get_nexusvlan_binding(vlan_id, switch_ip) + if not vbinding: + # Create vlan and trunk vlan on the port + self._client.create_vlan( + vlan_name, str(vlan_id), _nexus_ip, + _nexus_username, _nexus_password, + _nexus_ports, _nexus_ssh_port, vlan_id) + else: + # Only trunk vlan on the port + man = self._client.nxos_connect(_nexus_ip, + int(_nexus_ssh_port), + _nexus_username, + _nexus_password) + self._client.enable_vlan_on_trunk_int(man, + port_id, + vlan_id) + + 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: {}, @@ -94,32 +128,10 @@ class NexusPlugin(L2DevicePluginBase): def delete_network(self, tenant_id, net_id, **kwargs): """ - Deletes a VLAN in the switch, and removes the VLAN configuration + Deletes the VLAN in all switches, and removes the VLAN configuration from the relevant interfaces """ LOG.debug("NexusPlugin:delete_network() called\n") - vlan_id = None - for key in kwargs: - if key == const.CONTEXT: - context = kwargs[const.CONTEXT] - elif key == const.BASE_PLUGIN_REF: - base_plugin_ref = kwargs[const.BASE_PLUGIN_REF] - elif key == 'vlan_id': - vlan_id = kwargs['vlan_id'] - if vlan_id is None: - vlan_id = self._get_vlan_id_for_network(tenant_id, net_id, - context, base_plugin_ref) - ports_id = nxos_db.get_nexusport_binding(vlan_id) - LOG.debug("NexusPlugin: Interfaces to be disassociated: %s" % ports_id) - nxos_db.remove_nexusport_binding(vlan_id) - if net_id: - self._client.delete_vlan( - str(vlan_id), self._nexus_ip, - self._nexus_username, self._nexus_password, - self._nexus_ports, self._nexus_ssh_port) - return net_id - # Network not found - raise exc.NetworkNotFound(net_id=net_id) def get_network_details(self, tenant_id, net_id, **kwargs): """ @@ -135,22 +147,6 @@ class NexusPlugin(L2DevicePluginBase): Virtual Network. """ LOG.debug("NexusPlugin:update_network() called\n") - if 'net_admin_state' in kwargs: - net_admin_state = kwargs['net_admin_state'] - vlan_id = kwargs['vlan_id'] - vlan_ids = kwargs['vlan_ids'] - if not net_admin_state: - self._client.remove_vlan_int( - str(vlan_id), self._nexus_ip, - self._nexus_username, self._nexus_password, - self._nexus_ports, self._nexus_ssh_port) - else: - self._client.add_vlan_int( - str(vlan_id), self._nexus_ip, - self._nexus_username, self._nexus_password, - self._nexus_ports, self._nexus_ssh_port, - vlan_ids) - return net_id def get_all_ports(self, tenant_id, net_id, **kwargs): """ @@ -166,12 +162,38 @@ class NexusPlugin(L2DevicePluginBase): """ LOG.debug("NexusPlugin:create_port() called\n") - def delete_port(self, tenant_id, net_id, port_id, **kwargs): + def delete_port(self, device_id, vlan_id): """ - This is probably not applicable to the Nexus plugin. - Delete if not required. + Delete port bindings from the database and scan + whether the network is still required on + the interfaces trunked """ LOG.debug("NexusPlugin:delete_port() called\n") + # Delete DB row for this port + row = nxos_db.get_nexusvm_binding(vlan_id, device_id) + if row: + nxos_db.remove_nexusport_binding(row['port_id'], row['vlan_id'], + row['switch_ip'], + row['instance_id']) + # Check for any other bindings with the same vlan_id and switch_ip + bindings = nxos_db.get_nexusvlan_binding( + 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']['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) + + return row['instance_id'] def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): """ diff --git a/quantum/tests/unit/cisco/test_nexus_plugin.py b/quantum/tests/unit/cisco/test_nexus_plugin.py index 74ab64fb9..f4d9d206d 100644 --- a/quantum/tests/unit/cisco/test_nexus_plugin.py +++ b/quantum/tests/unit/cisco/test_nexus_plugin.py @@ -27,7 +27,9 @@ from quantum.plugins.cisco.nexus import cisco_nexus_plugin_v2 NEXUS_IP_ADDRESS = '1.1.1.1' NEXUS_USERNAME = 'username' NEXUS_PASSWORD = 'password' -NEXUS_PORTS = ['1/10'] +HOSTNAME = 'testhost' +INSTANCE = 'testvm' +NEXUS_PORTS = '1/10' NEXUS_SSH_PORT = '22' NEXUS_DRIVER = ('quantum.plugins.cisco.tests.unit.v2.nexus.' 'fake_nexus_driver.CiscoNEXUSFakeDriver') @@ -48,6 +50,17 @@ class TestCiscoNexusPlugin(unittest.TestCase): self.second_net_id = 000005 self.second_vlan_name = "q-" + str(self.second_net_id) + "vlan" self.second_vlan_id = 265 + self._nexus_switches = { + NEXUS_IP_ADDRESS: { + HOSTNAME: { + 'ports': NEXUS_PORTS, + }, + 'ssh_port': { + 'ssh_port': NEXUS_SSH_PORT + } + } + } + self._hostname = HOSTNAME def new_cdb_init(): db.configure_db() @@ -59,14 +72,21 @@ class TestCiscoNexusPlugin(unittest.TestCase): self._nexus_password = NEXUS_PASSWORD self._nexus_ports = NEXUS_PORTS self._nexus_ssh_port = NEXUS_SSH_PORT + self.credentials = { + self._nexus_ip: { + 'username': self._nexus_username, + 'password': self._nexus_password + } + } with mock.patch.object(cdb, 'initialize', new=new_cdb_init): cdb.initialize() with mock.patch.object(cisco_nexus_plugin_v2.NexusPlugin, '__init__', new=new_nexus_init): self._cisco_nexus_plugin = cisco_nexus_plugin_v2.NexusPlugin() + self._cisco_nexus_plugin._nexus_switches = self._nexus_switches - def test_a_create_delete_network(self): + def test_a_create_network(self): """ Tests creation of two new Virtual Network. Tests deletion of one Virtual Network. @@ -90,18 +110,16 @@ class TestCiscoNexusPlugin(unittest.TestCase): new_net_dict = self._cisco_nexus_plugin.create_network( tenant_id, net_name, net_id, - vlan_name, vlan_id, vlan_ids=str(vlan_id)) - + vlan_name, vlan_id, self._hostname, INSTANCE) self.assertEqual(new_net_dict[const.NET_ID], net_id) self.assertEqual(new_net_dict[const.NET_NAME], self.net_name) self.assertEqual(new_net_dict[const.NET_VLAN_NAME], self.vlan_name) self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.vlan_id) - vlan_ids = str(vlan_id) + "," + str(second_vlan_id) new_net_dict = self._cisco_nexus_plugin.create_network( tenant_id, second_net_name, second_net_id, - second_vlan_name, second_vlan_id, - vlan_ids=vlan_ids) + second_vlan_name, second_vlan_id, self._hostname, + INSTANCE) self.assertEqual(new_net_dict[const.NET_ID], second_net_id) self.assertEqual(new_net_dict[const.NET_NAME], self.second_net_name) @@ -109,106 +127,20 @@ class TestCiscoNexusPlugin(unittest.TestCase): self.second_vlan_name) self.assertEqual(new_net_dict[const.NET_VLAN_ID], self.second_vlan_id) - expected_net_id = self._cisco_nexus_plugin.delete_network( - tenant_id, net_id, vlan_id=str(vlan_id)) - - self.assertEqual(expected_net_id, net_id) - - def test_b_nexus_clear_vlan(self): + def test_b_nexus_delete_port(self): """ Test to clean up second vlan of nexus device created by test_create_delete_network. This test will fail if it is run individually. """ - tenant_id = self.tenant_id - second_net_id = self.second_net_id - second_vlan_id = self.second_vlan_id - - expected_second_net_id = self._cisco_nexus_plugin.delete_network( - tenant_id, second_net_id, - vlan_id=str(second_vlan_id)) - - self.assertEqual(expected_second_net_id, second_net_id) - - def test_c_update_network_False(self): - """ - Test to update a network state to False - resulting in disabling a vlan corresponding to - that network from the configured nexus interfaces - """ - tenant_id = self.tenant_id - net_name = self.net_name - net_id = self.net_id - vlan_name = self.vlan_name - vlan_id = self.vlan_id - second_net_name = self.second_net_name - second_net_id = self.second_net_id - second_vlan_name = self.second_vlan_name - second_vlan_id = self.second_vlan_id - - new_net_dict = self._cisco_nexus_plugin.create_network( - tenant_id, net_name, net_id, - vlan_name, vlan_id, vlan_ids=str(vlan_id)) - - vlan_ids = str(vlan_id) + "," + str(second_vlan_id) - new_net_dict = self._cisco_nexus_plugin.create_network( - tenant_id, second_net_name, second_net_id, - second_vlan_name, second_vlan_id, - vlan_ids=vlan_ids) - - expected_net_id = self._cisco_nexus_plugin.update_network( - tenant_id, net_id, net_admin_state=False, - vlan_id=vlan_id, vlan_ids=str(vlan_id)) - - self.assertEqual(expected_net_id, net_id) - - def test_d_nexus_clean_vlan_update(self): - """ - Cleans up vlans on the nexus for the two - created networks - """ - tenant_id = self.tenant_id - net_id = self.net_id - vlan_id = self.vlan_id - second_net_id = self.second_net_id - second_vlan_id = self.second_vlan_id - - netid = self._cisco_nexus_plugin.delete_network( - tenant_id, net_id, vlan_id=str(vlan_id)) - - self.assertEqual(netid, net_id) - - expected_second_net_id = self._cisco_nexus_plugin.delete_network( - tenant_id, second_net_id, - vlan_id=str(second_vlan_id)) - - self.assertEqual(expected_second_net_id, second_net_id) - - def test_e_update_network_True(self): - """ - Test to update a disabled network state to True - resulting in enabling a vlan corresponding to - that network to the configured nexus interfaces - """ - tenant_id = self.tenant_id - net_name = self.net_name - net_id = self.net_id - vlan_name = self.vlan_name - vlan_id = self.vlan_id - second_vlan_id = self.second_vlan_id - - self.test_c_update_network_False() - - vlan_ids = str(vlan_id) + "," + str(second_vlan_id) - expected_net_id = self._cisco_nexus_plugin.update_network( - tenant_id, net_id, net_admin_state=True, - vlan_id=vlan_id, vlan_ids=str(vlan_ids)) - - self.assertEqual(expected_net_id, net_id) + expected_instance_id = self._cisco_nexus_plugin.delete_port( + INSTANCE, self.second_vlan_id + ) - self.test_d_nexus_clean_vlan_update() + self.assertEqual(expected_instance_id, INSTANCE) def tearDown(self): """Clear the test environment""" + pass # Remove database contents - db.clear_db(network_models_v2.model_base.BASEV2) + #db.clear_db(network_models_v2.model_base.BASEV2) diff --git a/tools/pip-requires b/tools/pip-requires index 331b5beb9..c9c40cbc0 100644 --- a/tools/pip-requires +++ b/tools/pip-requires @@ -17,3 +17,7 @@ sqlalchemy==0.7.9 webob>=1.0.8 python-keystoneclient>=0.2.0 alembic>=0.4.1 + +# Cisco plugin dependencies +python-novaclient +# End Cisco dependencies -- 2.45.2