From: Sumit Naiksatam Date: Wed, 18 Jul 2012 20:30:21 +0000 (-0700) Subject: Add v2 API support for the Cisco plugin X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=9cc6decb7a554619b565b269f0b29b0b1486847c;p=openstack-build%2Fneutron-build.git Add v2 API support for the Cisco plugin Blueprint cisco-plugin-v2-api-support New meta-plugin which makes use of the Quantum db_plugin and supports the new v2 API Changes to the Cisco DB model, now reusing quantum DB for core attributes Changes to the device sub plugins to access the quantum DB for core resources' state versus Cisco DB Addition of fake/dummy drivers to support testing of the device sub plugins even without actual hardware New v2 unit tests which exercise the meta-plugin and the device sub -plugins as well In general creating new v2 modules such that v1.x code can be deprecated easily by deleting the older modules. The following files are v2 versions of older modules, only the imports have changed, most of the other code is the same as from the older modules (already reviewed): quantum/plugins/cisco/common/cisco_credentials_v2.py quantum/plugins/cisco/db/network_db_v2.py quantum/plugins/cisco/db/network_models_v2.py quantum/plugins/cisco/db/nexus_db_v2.py quantum/plugins/cisco/db/nexus_models_v2.py quantum/plugins/cisco/db/ucs_db_v2.py quantum/plugins/cisco/db/ucs_models_v2.py quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py quantum/plugins/cisco/ucs/cisco_ucs_inventory_v2.py quantum/plugins/cisco/ucs/cisco_ucs_plugin_v2.py quantum/plugins/cisco/segmentation/l2network_vlan_mgr_v2.py All changes are contained with the Cisco plugin. (Sumit & Rohit) Change-Id: Ib82a9f843548c286c84ba63caf5406a773ac85b1 --- diff --git a/etc/quantum/plugins/cisco/cisco_plugins.ini b/etc/quantum/plugins/cisco/cisco_plugins.ini index 2e6308766..d5c7e4191 100644 --- a/etc/quantum/plugins/cisco/cisco_plugins.ini +++ b/etc/quantum/plugins/cisco/cisco_plugins.ini @@ -1,7 +1,8 @@ [PLUGINS] -#ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin.UCSVICPlugin -#nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin +#ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin_v2.UCSVICPlugin +#nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin_v2.NexusPlugin [INVENTORY] -#ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_inventory.UCSInventory +#ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_inventory_v2.UCSInventory +#ucs_plugin=quantum.plugins.cisco.tests.unit.v2.ucs.cisco_ucs_inventory_fake.UCSInventory #nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_inventory.NexusInventory diff --git a/etc/quantum/plugins/cisco/credentials.ini b/etc/quantum/plugins/cisco/credentials.ini index ebb004ba1..4eef1d992 100644 --- a/etc/quantum/plugins/cisco/credentials.ini +++ b/etc/quantum/plugins/cisco/credentials.ini @@ -4,7 +4,7 @@ username= password= #Provide the Nexus credentials, if you are using Nexus -[] -username= -password= +[1.1.1.1] +username=abc +password=def diff --git a/etc/quantum/plugins/cisco/l2network_plugin.ini b/etc/quantum/plugins/cisco/l2network_plugin.ini index 421d301ac..218e1e372 100644 --- a/etc/quantum/plugins/cisco/l2network_plugin.ini +++ b/etc/quantum/plugins/cisco/l2network_plugin.ini @@ -13,7 +13,8 @@ max_port_profiles=65568 max_networks=65568 [MODEL] -model_class=quantum.plugins.cisco.models.l2network_multi_blade.L2NetworkMultiBlade +#model_class=quantum.plugins.cisco.models.l2network_multi_blade.L2NetworkMultiBlade +model_class=quantum.plugins.cisco.models.network_multi_blade_v2.NetworkMultiBladeV2 [SEGMENTATION] -manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr.L2NetworkVLANMgr +manager_class=quantum.plugins.cisco.segmentation.l2network_vlan_mgr_v2.L2NetworkVLANMgr diff --git a/etc/quantum/plugins/cisco/nexus.ini b/etc/quantum/plugins/cisco/nexus.ini index 50d8e54be..c305db4e3 100644 --- a/etc/quantum/plugins/cisco/nexus.ini +++ b/etc/quantum/plugins/cisco/nexus.ini @@ -8,4 +8,5 @@ nexus_second_port= nexus_ssh_port=22 [DRIVER] -name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver +#name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver +name=quantum.plugins.cisco.tests.unit.v2.nexus.fake_nexus_driver.CiscoNEXUSFakeDriver diff --git a/etc/quantum/plugins/cisco/ucs.ini b/etc/quantum/plugins/cisco/ucs.ini index 73c0968b3..44644dae0 100644 --- a/etc/quantum/plugins/cisco/ucs.ini +++ b/etc/quantum/plugins/cisco/ucs.ini @@ -8,4 +8,5 @@ max_ucsm_port_profiles=1024 profile_name_prefix=q- [DRIVER] -name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver +#name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver +name=quantum.plugins.cisco.tests.unit.v2.ucs.fake_ucs_driver.CiscoUCSMFakeDriver diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README index bccbcb555..b81ab269f 100755 --- a/quantum/plugins/cisco/README +++ b/quantum/plugins/cisco/README @@ -1,5 +1,328 @@ ========================================================================================= -README: A Quantum Plugin Framework for Supporting L2 Networks Spannning Multiple Switches +README for Quantum v2.0: +A Plugin Framework for Supporting Quantum Networks Spannning Multiple Switches +========================================================================================= + +Introduction +------------ + +This plugin implementation provides the following capabilities: + +* A reference implementation for a Quantum Plugin Framework +(For details see: http://wiki.openstack.org/quantum-multi-switch-plugin) +* Supports multiple switches in the network +* Supports multiple models of switches concurrently +* Supports use of multiple L2 technologies +* Supports the Cisco Nexus family of switches. +* Supports Cisco UCS blade servers with M81KR Virtual Interface Cards + (aka "Palo adapters") via 802.1Qbh. + +Pre-requisites +-------------- +(The following are necessary only when using the UCS and/or Nexus devices in your system. +If you plan to just leverage the plugin framework, you do not need these.) + +If you are using a Nexus switch in your topology, you'll need the following +NX-OS version and packages to enable Nexus support: +* NX-OS 5.2.1 (Delhi) Build 69 or above. +* paramiko library - SSHv2 protocol library for python +* ncclient v0.3.1 - Python library for NETCONF clients + ** You need a version of ncclient modifed by Cisco Systems. + To get it, from your shell prompt do: + + git clone git@github.com:CiscoSystems/ncclient.git + sudo python ./setup.py install + + ** For more information of ncclient, see: + http://schmizz.net/ncclient/ + +* One or more UCS B200 series blade servers with M81KR VIC (aka + Palo adapters) installed. +* UCSM 2.0 (Capitola) Build 230 or above. +* OS supported: + ** RHEL 6.1 or above + ** Ubuntu 11.10 or above + ** Package: python-configobj-4.6.0-3.el6.noarch (or newer) + ** Package: python-routes-1.12.3-2.el6.noarch (or newer) + ** Package: pip install mysql-python + + +Module Structure: +----------------- +* quantum/plugins/cisco/ - Contains the Network Plugin Framework + /client - CLI module for core and extensions API + /common - Modules common to the entire plugin + /conf - All configuration files + /db - Persistence framework + /models - Class(es) which tie the logical abstractions + to the physical topology + /nova - Scheduler and VIF-driver to be used by Nova + /nexus - Nexus-specific modules + /segmentation - Implementation of segmentation manager, + e.g. VLAN Manager + /services - Set of orchestration libraries to insert + In-path Networking Services + /tests - Tests specific to this plugin + /ucs - UCS-specific modules + + +Plugin Installation Instructions +---------------------------------- +1. Make a backup copy of quantum/etc/quantum.conf + +2. Edit quantum/etc/quantum.conf and edit the "core_plugin" for v2 API + +core_plugin = quantum.plugins.cisco.network_plugin.PluginV2 + +3. MySQL database setup: + 3a. Create quantum_l2network database in mysql with the following command - + +mysql -u -p -e "create database quantum_l2network" + + 3b. Enter the quantum_l2network database configuration info in the + quantum/plugins/cisco/conf/db_conn.ini file. + +4. If you want to turn on support for Cisco Nexus switches: + 4a. Uncomment the nexus_plugin property in + etc/quantum/plugins/cisco/cisco_plugins.ini to read: + +[PLUGINS] +nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin_v2.NexusPlugin + + 4b. Enter the relevant configuration in the + etc/quantum/plugins/cisco/nexus.ini file. Example: + +[SWITCH] +# Change the following to reflect the IP address of the Nexus switch. +# This will be the address at which Quantum sends and receives configuration +# information via SSHv2. +nexus_ip_address=10.0.0.1 +# Port numbers on the Nexus switch to each one of the UCSM 6120s is connected +# Use shortened interface syntax, e.g. "1/10" not "Ethernet1/10". +nexus_first_port=1/10 +nexus_second_port=1/11 +#Port number where SSH will be running on the Nexus switch. Typically this is 22 +#unless you've configured your switch otherwise. +nexus_ssh_port=22 + +[DRIVER] +name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver + + 4c. Make sure that SSH host key of the Nexus switch is known to the + host on which you are running the Quantum service. You can do + this simply by logging in to your Quantum host as the user that + Quantum runs as and SSHing to the switch at least once. If the + host key changes (e.g. due to replacement of the supervisor or + clearing of the SSH config on the switch), you may need to repeat + this step and remove the old hostkey from ~/.ssh/known_hosts. + +5. If your are using UCS blade servers with M81KR Virtual Interface Cards and + want to leverage the VM-FEX features, + + 5a. Uncomment the ucs_plugin propertes in + etc/quantum/plugins/cisco/cisco_plugins.ini to read: + +[PLUGINS] +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin_v2.UCSVICPlugin +[INVENTORY] +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_inventory_v2.UCSInventory + + 5b. Enter the relevant configuration in the + etc/quantum/plugins/cisco/ucs.ini file. Example: + +[UCSM] +#change the following to the appropriate UCSM IP address +#if you have more than one UCSM, enter info from any one +ip_address= +default_vlan_name=default +default_vlan_id=1 +max_ucsm_port_profiles=1024 +profile_name_prefix=q- + +[DRIVER] +name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver + + 5c. Configure the UCS systems' information in your deployment by editing the + quantum/plugins/cisco/conf/ucs_inventory.ini file. You can configure multiple + UCSMs per deployment, multiple chassis per UCSM, and multiple blades per + chassis. Chassis ID and blade ID can be obtained from the UCSM (they will + typically be numbers like 1, 2, 3, etc.). Also make sure that you put the exact + hostname as nova sees it (the host column in the services table of the nova + DB will give you that information). + +[ucsm-1] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = +[[[blade-3]]] +blade_id = +host_name = + +[ucsm-2] +ip_address = +[[chassis-1]] +chassis_id = +[[[blade-1]]] +blade_id = +host_name = +[[[blade-2]]] +blade_id = +host_name = + + 5d. Configure your OpenStack installation to use the 802.1qbh VIF driver and + Quantum-aware scheduler by editing the /etc/nova/nova.conf file with the + following entries: + +scheduler_driver=quantum.plugins.cisco.nova.quantum_port_aware_scheduler.QuantumPortAwareScheduler +quantum_host=127.0.0.1 +quantum_port=9696 +libvirt_vif_driver=quantum.plugins.cisco.nova.vifdirect.Libvirt802dot1QbhDriver +libvirt_vif_type=802.1Qbh + + Note: To be able to bring up a VM on a UCS blade, you should first create a + port for that VM using the Quantum create port API. VM creation will + fail if an unused port is not available. If you have configured your + Nova project with more than one network, Nova will attempt to instantiate + the VM with one network interface (VIF) per configured network. To provide + plugin points for each of these VIFs, you will need to create multiple + Quantum ports, one for each of the networks, prior to starting the VM. + However, in this case you will need to use the Cisco multiport extension + API instead of the Quantum create port API. More details on using the + multiport extension follow in the section on multi NIC support. + + To support the above configuration, you will need some Quantum modules. It's easiest + to copy the entire quantum directory from your quantum installation into: + + /usr/lib/python2.7/site-packages/ + + This needs to be done on each nova compute node. + +7. Verify that you have the correct credentials for each IP address listed + in quantum/plugins/cisco/conf/credentials.ini. Example: + +# Provide the UCSM credentials, create a separte entry for each UCSM used in your system +# UCSM IP address, username and password. +[10.0.0.2] +username=admin +password=mySecretPasswordForUCSM + +# Provide the Nexus credentials, if you are using Nexus switches. +# If not this will be ignored. +[10.0.0.1] +username=admin +password=mySecretPasswordForNexus + + In general, make sure that every UCSM and Nexus switch used in your system, + has a credential entry in the above file. This is required for the system to + be able to communicate with those switches. + + +9. Start the Quantum service. If something doesn't work, verify the + your configuration of each of the above files. + + +Multi NIC support for VMs +------------------------- +As indicated earlier, if your Nova setup has a project with more than one network, +Nova will try to create a virtual network interface (VIF) on the VM for each of those +networks. Before each VM is instantiated, you should create Quantum ports on each of +those networks. These ports need to be created using the following rest call: + +POST /1.0/extensions/csco/tenants/{tenant_id}/multiport/ + +with request body: + +{'multiport': + {'status': 'ACTIVE', + 'net_id_list': net_id_list, + 'ports_desc': {'key': 'value'}}} + +where, + +net_id_list is a list of network IDs: [netid1, netid2, ...]. The "ports_desc" dictionary +is reserved for later use. For now, the same structure in terms of the dictionary name, key +and value should be used. + +The corresponding CLI for this operation is as follows: + +PYTHONPATH=. python quantum/plugins/cisco/client/cli.py create_multiport + + (Note that you should not be using the create port core API in the above case.) + + +How to test the installation +---------------------------- +The unit tests are located at quantum/plugins/cisco/tests/unit/v2. They can be +executed from the top level Quantum directory using the run_tests.sh script. + +1. Testing the core API (without UCS/Nexus/RHEL device sub-plugins configured): + By default all the device sub-plugins are disabled (commented out) in + etc/quantum/plugins/cisco/cisco_plugins.ini + + ./run_tests.sh quantum.plugins.cisco.tests.unit.v2.test_api_v2 + ./run_tests.sh quantum.plugins.cisco.tests.unit.v2.test_network_plugin + +2. For testing the Nexus device sub-plugin perform the following configuration: + + Edit etc/quantum/plugins/cisco/cisco_plugins.ini to add: + In the [PLUGINS] section add: +nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin_v2.NexusPlugin + + Edit the etc/quantum/plugins/cisco/nexus.ini file. + When not using Nexus hardware use the following dummy configuration verbatim: +[SWITCH] +nexus_ip_address=1.1.1.1 +nexus_first_port=1/10 +nexus_second_port=1/11 +nexus_ssh_port=22 +[DRIVER] +name=quantum.plugins.cisco.tests.unit.v2.nexus.fake_nexus_driver.CiscoNEXUSFakeDriver + Or when using Nexus hardware (put the values relevant to your setup): +[SWITCH] +nexus_ip_address=1.1.1.1 +nexus_first_port=1/10 +nexus_second_port=1/11 +nexus_ssh_port=22 +[DRIVER] +name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver + + (Note: Make sure that quantum/plugins/cisco/conf/credentials.ini has an entry for + the nexus_ip_address being used in the above cases) + +3. For testing the UCS device sub-plugin perform the following configuration: + + Edit etc/quantum/plugins/cisco/cisco_plugins.ini to add: + In the [PLUGINS] section add: +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_plugin_v2.UCSVICPlugin + + In the [INVENTORY] section add: + When not using UCS hardware: +ucs_plugin=quantum.plugins.cisco.tests.unit.v2.ucs.cisco_ucs_inventory_fake.UCSInventory + Or when using UCS hardware: +ucs_plugin=quantum.plugins.cisco.ucs.cisco_ucs_inventory_v2.UCSInventory + + Edit the etc/quantum/plugins/cisco/ucs.ini file. + When not using UCS hardware: +[DRIVER] +name=quantum.plugins.cisco.tests.unit.v2.ucs.fake_ucs_driver.CiscoUCSMFakeDriver + Or when using UCS hardware: +[DRIVER] +name=quantum.plugins.cisco.ucs.cisco_ucs_network_driver.CiscoUCSMDriver + + +:Web site: http://wiki.openstack.org/cisco-quantum +:Copyright: 2012 Cisco Systems, Inc. +:Contact: netstack@lists.launchpad.net + +========================================================================================= +README for Quantum v1 and v1.1: +A Quantum Plugin Framework for Supporting L2 Networks Spannning Multiple Switches ========================================================================================= :Author: Sumit Naiksatam, Ram Durairaj, Mark Voelker, Edgar Magana, Shweta Padubidri, @@ -83,12 +406,15 @@ Module Structure: Plugin Installation Instructions ---------------------------------- -1. Make a backup copy of quantum/etc/plugins.ini. +1. Make a backup copy of quantum/etc/quantum.conf + +2. Edit quantum/etc/quantum.conf and edit the "core_plugin" for v2 API + +core_plugin = quantum.plugins.cisco.network_plugin.PluginV2 -2. Edit quantum/etc/plugins.ini and edit the "provider" entry to point - to the L2Network-plugin: + OR for v1.1 API -provider = quantum.plugins.cisco.l2network_plugin.L2Network +core_plugin = quantum.plugins.cisco.l2network_plugin.L2Network 3. Configure your OpenStack installation to use the 802.1qbh VIF driver and Quantum-aware scheduler by editing the /etc/nova/nova.conf file with the @@ -374,7 +700,9 @@ result the run_tests.py script. Device-specific sub-plugins can be disabled by commenting out all the entries in: etc/quantum/plugins/cisco/cisco_plugins.ini - Execute the l2networkApi tests only using: + Execute the v2 API tests only using: + ./run_tests.sh quantum.plugins.cisco.tests.unit.test_api_v2 + Execute the v1.1 API tests only using: ./run_tests.sh quantum.plugins.cisco.tests.unit.test_l2networkApi If just the ucs or both ucs and the nexus plugins are configured then all the tests could be executed by @@ -389,13 +717,16 @@ result the run_tests.py script. Nexus plugins. Device-specific plugins can be disabled by commenting out the entries in: etc/quantum/plugins/cisco/cisco_plugins.ini - Execute the test script as follows: - ./run_tests.sh quantum.plugins.cisco.tests.unit.test_l2networkApi + Execute the v2 API tests only using: + ./run_tests.sh quantum.plugins.cisco.tests.unit.test_api_v2 + or + python run_tests.py quantum.plugins.cisco.tests.unit.test_api_v2 - or - - python run_tests.py quantum.plugins.cisco.tests.unit.test_l2networkApi + Execute the v1.1 API tests only using: + ./run_tests.sh quantum.plugins.cisco.tests.unit.test_l2networkApi + or + python run_tests.py quantum.plugins.cisco.tests.unit.test_l2networkApi 3. Specific Plugin unit test (needs environment setup as indicated in the pre-requisites): diff --git a/quantum/plugins/cisco/common/cisco_constants.py b/quantum/plugins/cisco/common/cisco_constants.py index 696dc17eb..7019fe883 100644 --- a/quantum/plugins/cisco/common/cisco_constants.py +++ b/quantum/plugins/cisco/common/cisco_constants.py @@ -161,3 +161,9 @@ ASSOCIATION_STATUS = 'association_status' ATTACHED = 'attached' DETACHED = 'detached' + +NETWORK = 'network' +PORT = 'port' +BASE_PLUGIN_REF = 'base_plugin_ref' +CONTEXT = 'context' +SUBNET = 'subnet' diff --git a/quantum/plugins/cisco/common/cisco_credentials_v2.py b/quantum/plugins/cisco/common/cisco_credentials_v2.py new file mode 100644 index 000000000..0ba356cb8 --- /dev/null +++ b/quantum/plugins/cisco/common/cisco_credentials_v2.py @@ -0,0 +1,82 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. + +import logging as LOG + +from quantum.common.utils import find_config_file +from quantum.plugins.cisco.common import cisco_configparser as confp +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.db import network_db_v2 as cdb + + +LOG.basicConfig(level=LOG.WARN) +LOG.getLogger(const.LOGGER_COMPONENT_NAME) + +CREDENTIALS_FILE = find_config_file({'plugin': 'cisco'}, + "credentials.ini") +TENANT = const.NETWORK_ADMIN + +cp = confp.CiscoConfigParser(CREDENTIALS_FILE) +_creds_dictionary = cp.walk(cp.dummy) + + +class Store(object): + """Credential Store""" + + @staticmethod + def initialize(): + for id in _creds_dictionary.keys(): + try: + cdb.add_credential(TENANT, id, + _creds_dictionary[id][const.USERNAME], + _creds_dictionary[id][const.PASSWORD]) + except cexc.CredentialAlreadyExists: + # We are quietly ignoring this, since it only happens + # if this class module is loaded more than once, in which + # case, the credentials are already populated + pass + + @staticmethod + def put_credential(cred_name, username, password): + """Set the username and password""" + credential = cdb.add_credential(TENANT, cred_name, username, password) + + @staticmethod + def get_username(cred_name): + """Get the username""" + credential = cdb.get_credential_name(TENANT, cred_name) + return credential[const.CREDENTIAL_USERNAME] + + @staticmethod + def get_password(cred_name): + """Get the password""" + credential = cdb.get_credential_name(TENANT, cred_name) + return credential[const.CREDENTIAL_PASSWORD] + + @staticmethod + def get_credential(cred_name): + """Get the username and password""" + credential = cdb.get_credential_name(TENANT, cred_name) + return {const.USERNAME: const.CREDENTIAL_USERNAME, + const.PASSWORD: const.CREDENTIAL_PASSWORD} + + @staticmethod + def delete_credential(cred_name): + """Delete a credential""" + cdb.remove_credential(TENANT, cred_name) diff --git a/quantum/plugins/cisco/db/network_db_v2.py b/quantum/plugins/cisco/db/network_db_v2.py new file mode 100644 index 000000000..b617290d6 --- /dev/null +++ b/quantum/plugins/cisco/db/network_db_v2.py @@ -0,0 +1,527 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 +from sqlalchemy.orm import exc + +from quantum.common import exceptions as q_exc +from quantum.db import api as db +from quantum.db import models_v2 +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import network_models_v2 +from quantum.plugins.cisco.db import nexus_models_v2 +from quantum.plugins.cisco.db import ucs_models_v2 +from quantum.plugins.cisco import l2network_plugin_configuration as conf + + +def initialize(): + 'Establish database connection and load models' + sql_connection = "mysql://%s:%s@%s/%s" % (conf.DB_USER, conf.DB_PASS, + conf.DB_HOST, conf.DB_NAME) + db.configure_db({'sql_connection': sql_connection, + 'base': network_models_v2.model_base.BASEV2}) + + +def create_vlanids(): + """Prepopulates the vlan_bindings table""" + LOG.debug("create_vlanids() called") + session = db.get_session() + try: + vlanid = session.query(network_models_v2.VlanID).one() + except exc.MultipleResultsFound: + pass + except exc.NoResultFound: + start = int(conf.VLAN_START) + end = int(conf.VLAN_END) + while start <= end: + vlanid = network_models_v2.VlanID(start) + session.add(vlanid) + start += 1 + session.flush() + return + + +def get_all_vlanids(): + """Gets all the vlanids""" + LOG.debug("get_all_vlanids() called") + session = db.get_session() + try: + vlanids = session.query(network_models_v2.VlanID).all() + return vlanids + except exc.NoResultFound: + return [] + + +def is_vlanid_used(vlan_id): + """Checks if a vlanid is in use""" + LOG.debug("is_vlanid_used() called") + session = db.get_session() + try: + vlanid = (session.query(network_models_v2.VlanID). + filter_by(vlan_id=vlan_id).one()) + return vlanid["vlan_used"] + except exc.NoResultFound: + raise c_exc.VlanIDNotFound(vlan_id=vlan_id) + + +def release_vlanid(vlan_id): + """Sets the vlanid state to be unused""" + LOG.debug("release_vlanid() called") + session = db.get_session() + try: + vlanid = (session.query(network_models_v2.VlanID). + filter_by(vlan_id=vlan_id).one()) + vlanid["vlan_used"] = False + session.merge(vlanid) + session.flush() + return vlanid["vlan_used"] + except exc.NoResultFound: + raise c_exc.VlanIDNotFound(vlan_id=vlan_id) + return + + +def delete_vlanid(vlan_id): + """Deletes a vlanid entry from db""" + LOG.debug("delete_vlanid() called") + session = db.get_session() + try: + vlanid = (session.query(network_models_v2.VlanID). + filter_by(vlan_id=vlan_id).one()) + session.delete(vlanid) + session.flush() + return vlanid + except exc.NoResultFound: + pass + + +def reserve_vlanid(): + """Reserves the first unused vlanid""" + LOG.debug("reserve_vlanid() called") + session = db.get_session() + try: + rvlan = (session.query(network_models_v2.VlanID). + filter_by(vlan_used=False).first()) + if not rvlan: + raise exc.NoResultFound + rvlanid = (session.query(network_models_v2.VlanID). + filter_by(vlan_id=rvlan["vlan_id"]).one()) + rvlanid["vlan_used"] = True + session.merge(rvlanid) + session.flush() + return rvlan["vlan_id"] + except exc.NoResultFound: + raise c_exc.VlanIDNotAvailable() + + +def get_all_vlanids_used(): + """Gets all the vlanids used""" + LOG.debug("get_all_vlanids() called") + session = db.get_session() + try: + vlanids = (session.query(network_models_v2.VlanID). + filter_by(vlan_used=True).all()) + return vlanids + except exc.NoResultFound: + return [] + + +def get_all_vlan_bindings(): + """Lists all the vlan to network associations""" + LOG.debug("get_all_vlan_bindings() called") + session = db.get_session() + try: + bindings = session.query(network_models_v2.VlanBinding).all() + return bindings + except exc.NoResultFound: + return [] + + +def get_vlan_binding(netid): + """Lists the vlan given a network_id""" + LOG.debug("get_vlan_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.VlanBinding). + filter_by(network_id=netid).one()) + return binding + except exc.NoResultFound: + raise q_exc.NetworkNotFound(net_id=netid) + + +def add_vlan_binding(vlanid, vlanname, netid): + """Adds a vlan to network association""" + LOG.debug("add_vlan_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.VlanBinding). + filter_by(vlan_id=vlanid).one()) + raise c_exc.NetworkVlanBindingAlreadyExists(vlan_id=vlanid, + network_id=netid) + except exc.NoResultFound: + binding = network_models_v2.VlanBinding(vlanid, vlanname, netid) + session.add(binding) + session.flush() + return binding + + +def remove_vlan_binding(netid): + """Removes a vlan to network association""" + LOG.debug("remove_vlan_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.VlanBinding). + filter_by(network_id=netid).one()) + session.delete(binding) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_vlan_binding(netid, newvlanid=None, newvlanname=None): + """Updates a vlan to network association""" + LOG.debug("update_vlan_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.VlanBinding). + filter_by(network_id=netid).one()) + if newvlanid: + binding["vlan_id"] = newvlanid + if newvlanname: + binding["vlan_name"] = newvlanname + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise q_exc.NetworkNotFound(net_id=netid) + + +def get_all_portprofiles(): + """Lists all the port profiles""" + LOG.debug("get_all_portprofiles() called") + session = db.get_session() + try: + pps = session.query(network_models_v2.PortProfile).all() + return pps + except exc.NoResultFound: + return [] + + +def get_portprofile(tenantid, ppid): + """Lists a port profile""" + LOG.debug("get_portprofile() called") + session = db.get_session() + try: + pp = (session.query(network_models_v2.PortProfile). + filter_by(uuid=ppid).one()) + return pp + except exc.NoResultFound: + raise c_exc.PortProfileNotFound(tenant_id=tenantid, + portprofile_id=ppid) + + +def add_portprofile(tenantid, ppname, vlanid, qos): + """Adds a port profile""" + LOG.debug("add_portprofile() called") + session = db.get_session() + try: + pp = (session.query(network_models_v2.PortProfile). + filter_by(name=ppname).one()) + raise c_exc.PortProfileAlreadyExists(tenant_id=tenantid, + pp_name=ppname) + except exc.NoResultFound: + pp = network_models_v2.PortProfile(ppname, vlanid, qos) + session.add(pp) + session.flush() + return pp + + +def remove_portprofile(tenantid, ppid): + """Removes a port profile""" + LOG.debug("remove_portprofile() called") + session = db.get_session() + try: + pp = (session.query(network_models_v2.PortProfile). + filter_by(uuid=ppid).one()) + session.delete(pp) + session.flush() + return pp + except exc.NoResultFound: + pass + + +def update_portprofile(tenantid, ppid, newppname=None, newvlanid=None, + newqos=None): + """Updates port profile""" + LOG.debug("update_portprofile() called") + session = db.get_session() + try: + pp = (session.query(network_models_v2.PortProfile). + filter_by(uuid=ppid).one()) + if newppname: + pp["name"] = newppname + if newvlanid: + pp["vlan_id"] = newvlanid + if newqos: + pp["qos"] = newqos + session.merge(pp) + session.flush() + return pp + except exc.NoResultFound: + raise c_exc.PortProfileNotFound(tenant_id=tenantid, + portprofile_id=ppid) + + +def get_all_pp_bindings(): + """Lists all the port profiles""" + LOG.debug("get_all_pp_bindings() called") + session = db.get_session() + try: + bindings = session.query(network_models_v2.PortProfileBinding).all() + return bindings + except exc.NoResultFound: + return [] + + +def get_pp_binding(tenantid, ppid): + """Lists a port profile binding""" + LOG.debug("get_pp_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.PortProfileBinding). + filter_by(portprofile_id=ppid).one()) + return binding + except exc.NoResultFound: + return [] + + +def add_pp_binding(tenantid, portid, ppid, default): + """Adds a port profile binding""" + LOG.debug("add_pp_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.PortProfileBinding). + filter_by(portprofile_id=ppid).one()) + raise c_exc.PortProfileBindingAlreadyExists(pp_id=ppid, + port_id=portid) + except exc.NoResultFound: + binding = network_models_v2.PortProfileBinding(tenantid, portid, + ppid, default) + session.add(binding) + session.flush() + return binding + + +def remove_pp_binding(tenantid, portid, ppid): + """Removes a port profile binding""" + LOG.debug("remove_pp_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.PortProfileBinding). + filter_by(portprofile_id=ppid).filter_by(port_id=portid). + one()) + session.delete(binding) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_pp_binding(tenantid, ppid, newtenantid=None, + newportid=None, newdefault=None): + """Updates port profile binding""" + LOG.debug("update_pp_binding() called") + session = db.get_session() + try: + binding = (session.query(network_models_v2.PortProfileBinding). + filter_by(portprofile_id=ppid).one()) + if newtenantid: + binding["tenant_id"] = newtenantid + if newportid: + binding["port_id"] = newportid + if newdefault: + binding["default"] = newdefault + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise c_exc.PortProfileNotFound(tenant_id=tenantid, + portprofile_id=ppid) + + +def get_all_qoss(tenant_id): + """Lists all the qos to tenant associations""" + LOG.debug("get_all_qoss() called") + session = db.get_session() + try: + qoss = (session.query(network_models_v2.QoS). + filter_by(tenant_id=tenant_id).all()) + return qoss + except exc.NoResultFound: + return [] + + +def get_qos(tenant_id, qos_id): + """Lists the qos given a tenant_id and qos_id""" + LOG.debug("get_qos() called") + session = db.get_session() + try: + qos = (session.query(network_models_v2.QoS). + filter_by(tenant_id=tenant_id). + filter_by(qos_id=qos_id).one()) + return qos + except exc.NoResultFound: + raise c_exc.QosNotFound(qos_id=qos_id, + tenant_id=tenant_id) + + +def add_qos(tenant_id, qos_name, qos_desc): + """Adds a qos to tenant association""" + LOG.debug("add_qos() called") + session = db.get_session() + try: + qos = (session.query(network_models_v2.QoS). + filter_by(tenant_id=tenant_id). + filter_by(qos_name=qos_name).one()) + raise c_exc.QosNameAlreadyExists(qos_name=qos_name, + tenant_id=tenant_id) + except exc.NoResultFound: + qos = network_models_v2.QoS(tenant_id, qos_name, qos_desc) + session.add(qos) + session.flush() + return qos + + +def remove_qos(tenant_id, qos_id): + """Removes a qos to tenant association""" + session = db.get_session() + try: + qos = (session.query(network_models_v2.QoS). + filter_by(tenant_id=tenant_id). + filter_by(qos_id=qos_id).one()) + session.delete(qos) + session.flush() + return qos + except exc.NoResultFound: + pass + + +def update_qos(tenant_id, qos_id, new_qos_name=None): + """Updates a qos to tenant association""" + session = db.get_session() + try: + qos = (session.query(network_models_v2.QoS). + filter_by(tenant_id=tenant_id). + filter_by(qos_id=qos_id).one()) + if new_qos_name: + qos["qos_name"] = new_qos_name + session.merge(qos) + session.flush() + return qos + except exc.NoResultFound: + raise c_exc.QosNotFound(qos_id=qos_id, + tenant_id=tenant_id) + + +def get_all_credentials(tenant_id): + """Lists all the creds for a tenant""" + session = db.get_session() + try: + creds = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id).all()) + return creds + except exc.NoResultFound: + return [] + + +def get_credential(tenant_id, credential_id): + """Lists the creds for given a cred_id and tenant_id""" + session = db.get_session() + try: + cred = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id). + filter_by(credential_id=credential_id).one()) + return cred + except exc.NoResultFound: + raise c_exc.CredentialNotFound(credential_id=credential_id, + tenant_id=tenant_id) + + +def get_credential_name(tenant_id, credential_name): + """Lists the creds for given a cred_name and tenant_id""" + session = db.get_session() + try: + cred = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id). + filter_by(credential_name=credential_name).one()) + return cred + except exc.NoResultFound: + raise c_exc.CredentialNameNotFound(credential_name=credential_name, + tenant_id=tenant_id) + + +def add_credential(tenant_id, credential_name, user_name, password): + """Adds a qos to tenant association""" + session = db.get_session() + try: + cred = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id). + filter_by(credential_name=credential_name).one()) + raise c_exc.CredentialAlreadyExists(credential_name=credential_name, + tenant_id=tenant_id) + except exc.NoResultFound: + cred = network_models_v2.Credential(tenant_id, credential_name, + user_name, password) + session.add(cred) + session.flush() + return cred + + +def remove_credential(tenant_id, credential_id): + """Removes a credential from a tenant""" + session = db.get_session() + try: + cred = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id). + filter_by(credential_id=credential_id).one()) + session.delete(cred) + session.flush() + return cred + except exc.NoResultFound: + pass + + +def update_credential(tenant_id, credential_id, + new_user_name=None, new_password=None): + """Updates a credential for a tenant""" + session = db.get_session() + try: + cred = (session.query(network_models_v2.Credential). + filter_by(tenant_id=tenant_id). + filter_by(credential_id=credential_id).one()) + if new_user_name: + cred["user_name"] = new_user_name + if new_password: + cred["password"] = new_password + session.merge(cred) + session.flush() + return cred + except exc.NoResultFound: + raise c_exc.CredentialNotFound(credential_id=credential_id, + tenant_id=tenant_id) diff --git a/quantum/plugins/cisco/db/network_models_v2.py b/quantum/plugins/cisco/db/network_models_v2.py new file mode 100644 index 000000000..3d70ae275 --- /dev/null +++ b/quantum/plugins/cisco/db/network_models_v2.py @@ -0,0 +1,195 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 uuid + +from sqlalchemy import Column, Integer, String, ForeignKey, Boolean +from sqlalchemy.orm import relation, object_mapper + +from quantum.db import model_base +from quantum.db import models_v2 as models + + +class L2NetworkBase(object): + """Base class for L2Network Models.""" + #__table_args__ = {'mysql_engine': 'InnoDB'} + + def __setitem__(self, key, value): + """Internal Dict set method""" + setattr(self, key, value) + + def __getitem__(self, key): + """Internal Dict get method""" + return getattr(self, key) + + def get(self, key, default=None): + """Dict get method""" + return getattr(self, key, default) + + def __iter__(self): + """Iterate over table columns""" + self._i = iter(object_mapper(self).columns) + return self + + def next(self): + """Next method for the iterator""" + n = self._i.next().name + return n, getattr(self, n) + + def update(self, values): + """Make the model object behave like a dict""" + for k, v in values.iteritems(): + setattr(self, k, v) + + def iteritems(self): + """Make the model object behave like a dict" + Includes attributes from joins.""" + local = dict(self) + joined = dict([(k, v) for k, v in self.__dict__.iteritems() + if not k[0] == '_']) + local.update(joined) + return local.iteritems() + + +class VlanID(model_base.BASEV2, L2NetworkBase): + """Represents a vlan_id usage""" + __tablename__ = 'vlan_ids' + + vlan_id = Column(Integer, primary_key=True) + vlan_used = Column(Boolean) + + def __init__(self, vlan_id): + self.vlan_id = vlan_id + self.vlan_used = False + + def __repr__(self): + return "" % (self.vlan_id, self.vlan_used) + + +class VlanBinding(model_base.BASEV2, L2NetworkBase): + """Represents a binding of vlan_id to network_id""" + __tablename__ = 'vlan_bindings' + + vlan_id = Column(Integer, primary_key=True) + vlan_name = Column(String(255)) + network_id = Column(String(255), + nullable=False) + + def __init__(self, vlan_id, vlan_name, network_id): + self.vlan_id = vlan_id + self.vlan_name = vlan_name + self.network_id = network_id + + def __repr__(self): + return "" % (self.vlan_id, + self.vlan_name, + self.network_id) + + +class PortProfile(model_base.BASEV2, L2NetworkBase): + """Represents L2 network plugin level PortProfile for a network""" + __tablename__ = 'portprofiles' + + uuid = Column(String(255), primary_key=True) + name = Column(String(255)) + vlan_id = Column(Integer) + qos = Column(String(255)) + + def __init__(self, name, vlan_id, qos=None): + self.uuid = uuid.uuid4() + self.name = name + self.vlan_id = vlan_id + self.qos = qos + + def __repr__(self): + return "" % (self.uuid, + self.name, + self.vlan_id, + self.qos) + + +class PortProfileBinding(model_base.BASEV2, L2NetworkBase): + """Represents PortProfile binding to tenant and network""" + __tablename__ = 'portprofile_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + tenant_id = Column(String(255)) + + port_id = Column(String(255), ForeignKey("ports.id"), nullable=False) + portprofile_id = Column(String(255), ForeignKey("portprofiles.uuid"), + nullable=False) + default = Column(Boolean) + ports = relation(models.Port) + portprofile = relation(PortProfile, uselist=False) + + def __init__(self, tenant_id, port_id, portprofile_id, default): + self.tenant_id = tenant_id + self.port_id = port_id + self.portprofile_id = portprofile_id + self.default = default + + def __repr__(self): + return "" % (self.tenant_id, + self.port_id, + self.portprofile_id, + self.default) + + +class QoS(model_base.BASEV2, L2NetworkBase): + """Represents QoS for a tenant""" + __tablename__ = 'qoss' + + qos_id = Column(String(255)) + tenant_id = Column(String(255), primary_key=True) + qos_name = Column(String(255), primary_key=True) + qos_desc = Column(String(255)) + + def __init__(self, tenant_id, qos_name, qos_desc): + self.qos_id = str(uuid.uuid4()) + self.tenant_id = tenant_id + self.qos_name = qos_name + self.qos_desc = qos_desc + + def __repr__(self): + return "" % (self.qos_id, self.tenant_id, + self.qos_name, self.qos_desc) + + +class Credential(model_base.BASEV2, L2NetworkBase): + """Represents credentials for a tenant""" + __tablename__ = 'credentials' + + credential_id = Column(String(255)) + tenant_id = Column(String(255), primary_key=True) + credential_name = Column(String(255), primary_key=True) + user_name = Column(String(255)) + password = Column(String(255)) + + def __init__(self, tenant_id, credential_name, user_name, password): + self.credential_id = str(uuid.uuid4()) + self.tenant_id = tenant_id + self.credential_name = credential_name + self.user_name = user_name + self.password = password + + def __repr__(self): + return "" % (self.credential_id, + self.tenant_id, + self.credential_name, + self.user_name, + self.password) diff --git a/quantum/plugins/cisco/db/nexus_db_v2.py b/quantum/plugins/cisco/db/nexus_db_v2.py new file mode 100644 index 000000000..a61ffc4ae --- /dev/null +++ b/quantum/plugins/cisco/db/nexus_db_v2.py @@ -0,0 +1,89 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 + +from sqlalchemy.orm import exc + +import quantum.db.api as db + +from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import nexus_models_v2 + + +def get_all_nexusport_bindings(): + """Lists all the nexusport bindings""" + LOG.debug("get_all_nexusport_bindings() called") + session = db.get_session() + try: + bindings = session.query(nexus_models_v2.NexusPortBinding).all() + return bindings + except exc.NoResultFound: + return [] + + +def get_nexusport_binding(vlan_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()) + return binding + except exc.NoResultFound: + raise c_exc.NexusPortBindingNotFound(vlan_id=vlan_id) + + +def add_nexusport_binding(port_id, vlan_id): + """Adds a nexusport binding""" + LOG.debug("add_nexusport_binding() called") + session = db.get_session() + binding = nexus_models_v2.NexusPortBinding(port_id, vlan_id) + session.add(binding) + session.flush() + return binding + + +def remove_nexusport_binding(vlan_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()) + for bind in binding: + session.delete(bind) + session.flush() + return binding + except exc.NoResultFound: + pass + + +def update_nexusport_binding(port_id, new_vlan_id): + """Updates nexusport binding""" + LOG.debug("update_nexusport_binding called") + session = db.get_session() + try: + binding = (session.query(nexus_models_v2.NexusPortBinding). + filter_by(port_id=port_id).one()) + if new_vlan_id: + binding["vlan_id"] = new_vlan_id + session.merge(binding) + session.flush() + return binding + except exc.NoResultFound: + raise c_exc.NexusPortBindingNotFound() diff --git a/quantum/plugins/cisco/db/nexus_models_v2.py b/quantum/plugins/cisco/db/nexus_models_v2.py new file mode 100644 index 000000000..975d270cd --- /dev/null +++ b/quantum/plugins/cisco/db/nexus_models_v2.py @@ -0,0 +1,37 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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. + +from sqlalchemy import Column, Integer, String + +from quantum.db import model_base +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' + + id = Column(Integer, primary_key=True, autoincrement=True) + port_id = Column(String(255)) + vlan_id = Column(Integer, nullable=False) + + def __init__(self, port_id, vlan_id): + self.port_id = port_id + self.vlan_id = vlan_id + + def __repr__(self): + return "" % (self.port_id, self.vlan_id) diff --git a/quantum/plugins/cisco/db/ucs_db_v2.py b/quantum/plugins/cisco/db/ucs_db_v2.py new file mode 100644 index 000000000..a8c5f5f68 --- /dev/null +++ b/quantum/plugins/cisco/db/ucs_db_v2.py @@ -0,0 +1,155 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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 + +from sqlalchemy.orm import exc + +from quantum.db import api as db + +from quantum.plugins.cisco.common import cisco_exceptions as c_exc +from quantum.plugins.cisco.db import ucs_models_v2 as ucs_models + + +def get_all_portbindings(): + """Lists all the port bindings""" + LOG.debug("db get_all_portbindings() called") + session = db.get_session() + try: + port_bindings = session.query(ucs_models.PortBinding).all() + return port_bindings + except exc.NoResultFound: + return [] + + +def get_portbinding(port_id): + """Lists a port binding""" + LOG.debug("get_portbinding() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def add_portbinding(port_id, blade_intf_dn, portprofile_name, + vlan_name, vlan_id, qos): + """Adds a port binding""" + LOG.debug("add_portbinding() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + raise c_exc.PortVnicBindingAlreadyExists(port_id=port_id) + except exc.NoResultFound: + port_binding = ucs_models.PortBinding(port_id, blade_intf_dn, + portprofile_name, vlan_name, + vlan_id, qos) + session.add(port_binding) + session.flush() + return port_binding + + +def remove_portbinding(port_id): + """Removes a port binding""" + LOG.debug("db remove_portbinding() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + session.delete(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + pass + + +def update_portbinding(port_id, blade_intf_dn=None, portprofile_name=None, + vlan_name=None, vlan_id=None, qos=None, + tenant_id=None, instance_id=None, + vif_id=None): + """Updates port binding""" + LOG.debug("db update_portbinding() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + if blade_intf_dn: + port_binding.blade_intf_dn = blade_intf_dn + if portprofile_name: + port_binding.portprofile_name = portprofile_name + if vlan_name: + port_binding.vlan_name = vlan_name + if vlan_name: + port_binding.vlan_id = vlan_id + if qos: + port_binding.qos = qos + if tenant_id: + port_binding.tenant_id = tenant_id + if instance_id: + port_binding.instance_id = instance_id + if vif_id: + port_binding.vif_id = vif_id + session.merge(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def update_portbinding_instance_id(port_id, instance_id): + """Updates port binding for the instance ID""" + LOG.debug("db update_portbinding_instance_id() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + port_binding.instance_id = instance_id + session.merge(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def update_portbinding_vif_id(port_id, vif_id): + """Updates port binding for the VIF ID""" + LOG.debug("db update_portbinding_vif_id() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(port_id=port_id).one()) + port_binding.vif_id = vif_id + session.merge(port_binding) + session.flush() + return port_binding + except exc.NoResultFound: + raise c_exc.PortVnicNotFound(port_id=port_id) + + +def get_portbinding_dn(blade_intf_dn): + """Lists a port binding""" + LOG.debug("get_portbinding_dn() called") + session = db.get_session() + try: + port_binding = (session.query(ucs_models.PortBinding). + filter_by(blade_intf_dn=blade_intf_dn).one()) + return port_binding + except exc.NoResultFound: + return [] diff --git a/quantum/plugins/cisco/db/ucs_models_v2.py b/quantum/plugins/cisco/db/ucs_models_v2.py new file mode 100644 index 000000000..a6c791920 --- /dev/null +++ b/quantum/plugins/cisco/db/ucs_models_v2.py @@ -0,0 +1,53 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012, Cisco Systems, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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. + +from sqlalchemy import Column, Integer, String +from sqlalchemy.orm import relation + +from quantum.db.model_base import BASEV2 as BASE +from quantum.db import models_v2 as models +from quantum.plugins.cisco.db.network_models_v2 import L2NetworkBase + + +class PortBinding(BASE, L2NetworkBase): + """Represents Port binding to device interface""" + __tablename__ = 'port_bindings' + + id = Column(Integer, primary_key=True, autoincrement=True) + port_id = Column(String(255), nullable=False) + blade_intf_dn = Column(String(255), nullable=False) + portprofile_name = Column(String(255)) + vlan_name = Column(String(255)) + vlan_id = Column(Integer) + qos = Column(String(255)) + tenant_id = Column(String(255)) + instance_id = Column(String(255)) + vif_id = Column(String(255)) + + def __init__(self, port_id, blade_intf_dn, portprofile_name, + vlan_name, vlan_id, qos): + self.port_id = port_id + self.blade_intf_dn = blade_intf_dn + self.portprofile_name = portprofile_name + self.vlan_name = vlan_name + self.vlan_id = vlan_id + self.qos = qos + + def __repr__(self): + return "" % ( + self.port_id, self.blade_intf_dn, self.portprofile_name, + self.vlan_name, self.vlan_id, self.qos) diff --git a/quantum/plugins/cisco/l2device_plugin_base.py b/quantum/plugins/cisco/l2device_plugin_base.py index 46ef83ebe..7e8ff53f7 100644 --- a/quantum/plugins/cisco/l2device_plugin_base.py +++ b/quantum/plugins/cisco/l2device_plugin_base.py @@ -1,6 +1,6 @@ # vim: tabstop=4 shiftwidth=4 softtabstop=4 # -# Copyright 2011 Cisco Systems, Inc. All rights reserved. +# Copyright 2012 Cisco Systems, Inc. All rights reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may # not use this file except in compliance with the License. You may obtain @@ -128,6 +128,42 @@ class L2DevicePluginBase(object): """ pass + def create_subnet(self, tenant_id, net_id, ip_version, + subnet_cidr, **kwargs): + """ + :returns: + :raises: + """ + pass + + def get_subnets(self, tenant_id, net_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + def get_subnet(self, tenant_id, net_id, subnet_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + def update_subnet(self, tenant_id, net_id, subnet_id, **kwargs): + """ + :returns: + :raises: + """ + pass + + def delete_subnet(self, tenant_id, net_id, subnet_id, **kwargs): + """ + :returns: + :raises: + """ + pass + @classmethod def __subclasshook__(cls, klass): """ diff --git a/quantum/plugins/cisco/models/network_multi_blade_v2.py b/quantum/plugins/cisco/models/network_multi_blade_v2.py new file mode 100644 index 000000000..e8f19eda5 --- /dev/null +++ b/quantum/plugins/cisco/models/network_multi_blade_v2.py @@ -0,0 +1,296 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. + +from copy import deepcopy +import inspect +import logging + +from quantum.openstack.common import importutils +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco import l2network_plugin_configuration as conf +from quantum import quantum_plugin_base_v2 + + +LOG = logging.getLogger(__name__) + + +class NetworkMultiBladeV2(quantum_plugin_base_v2.QuantumPluginBaseV2): + """ + This implementation works with UCS and Nexus plugin for the + following topology: + One or more UCSM (each with one or more chasses connected), + All FICs connected to a single Nexus Switch. + """ + _plugins = {} + _inventory = {} + + def __init__(self): + """ + Initialize the segmentation manager, check which device plugins are + configured, and load the inventories those device plugins for which the + inventory is configured + """ + self._vlan_mgr = importutils.import_object(conf.MANAGER_CLASS) + for key in conf.PLUGINS[const.PLUGINS].keys(): + plugin_obj = conf.PLUGINS[const.PLUGINS][key] + self._plugins[key] = importutils.import_object(plugin_obj) + LOG.debug("Loaded device plugin %s\n" % + conf.PLUGINS[const.PLUGINS][key]) + if key in conf.PLUGINS[const.INVENTORY].keys(): + inventory_obj = conf.PLUGINS[const.INVENTORY][key] + self._inventory[key] = importutils.import_object(inventory_obj) + LOG.debug("Loaded device inventory %s\n" % + conf.PLUGINS[const.INVENTORY][key]) + + LOG.debug("%s.%s init done" % (__name__, self.__class__.__name__)) + + def _func_name(self, offset=0): + """Get the name of the calling function""" + return inspect.stack()[1 + offset][3] + + def _invoke_plugin_per_device(self, plugin_key, function_name, args): + """ + Invokes a device plugin's relevant functions (on the it's + inventory and plugin implementation) for completing this operation. + """ + if not plugin_key in self._plugins.keys(): + LOG.info("No %s Plugin loaded" % plugin_key) + LOG.info("%s: %s with args %s ignored" % + (plugin_key, function_name, args)) + return + device_params = self._invoke_inventory(plugin_key, function_name, + args) + device_ips = device_params[const.DEVICE_IP] + if not device_ips: + return [self._invoke_plugin(plugin_key, function_name, args, + device_params)] + else: + output = [] + for device_ip in device_ips: + new_device_params = deepcopy(device_params) + new_device_params[const.DEVICE_IP] = device_ip + output.append(self._invoke_plugin(plugin_key, function_name, + args, new_device_params)) + return output + + def _invoke_inventory(self, plugin_key, function_name, args): + """ + Invokes the relevant function on a device plugin's + inventory for completing this operation. + """ + if not plugin_key in self._inventory.keys(): + LOG.info("No %s inventory loaded" % plugin_key) + LOG.info("%s: %s with args %s ignored" % + (plugin_key, function_name, args)) + return {const.DEVICE_IP: []} + else: + return getattr(self._inventory[plugin_key], function_name)(args) + + def _invoke_plugin(self, plugin_key, function_name, args, kwargs): + """ + Invokes the relevant function on a device plugin's + implementation for completing this operation. + """ + func = getattr(self._plugins[plugin_key], function_name) + func_args_len = int(inspect.getargspec(func).args.__len__()) - 1 + if args.__len__() > func_args_len: + func_args = args[:func_args_len] + extra_args = args[func_args_len:] + for dict_arg in extra_args: + for k, v in dict_arg.iteritems(): + kwargs[k] = v + return func(*func_args, **kwargs) + else: + return func(*args, **kwargs) + + def create_network(self, context, network): + """ + Perform this operation in the context of the configured device + plugins. + """ + n = network + try: + vlan_id = self._vlan_mgr.reserve_segmentation_id(n['tenant_id'], + n['name']) + vlan_name = self._vlan_mgr.get_vlan_name(n['id'], str(vlan_id)) + args = [n['tenant_id'], n['name'], n['id'], vlan_name, vlan_id] + output = [] + ucs_output = self._invoke_plugin_per_device(const.UCS_PLUGIN, + self._func_name(), + args) + nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), + args) + output.extend(ucs_output or []) + output.extend(nexus_output or []) + cdb.add_vlan_binding(vlan_id, vlan_name, n['id']) + return output + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def get_network(self, context, id, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + def get_networks(self, context, filters=None, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + def update_network(self, context, id, network): + """Currently there is no processing required for the device plugins""" + pass + + def delete_network(self, context, id, kwargs): + """ + Perform this operation in the context of the configured device + plugins. + """ + try: + base_plugin_ref = kwargs[const.BASE_PLUGIN_REF] + n = kwargs[const.NETWORK] + tenant_id = n['tenant_id'] + args = [tenant_id, id, {const.CONTEXT:context}, + {const.BASE_PLUGIN_REF:base_plugin_ref}] + # TODO (Sumit): Might first need to check here if there are active + # ports + output = [] + ucs_output = self._invoke_plugin_per_device(const.UCS_PLUGIN, + self._func_name(), + args) + nexus_output = self._invoke_plugin_per_device(const.NEXUS_PLUGIN, + self._func_name(), + args) + output.extend(ucs_output or []) + output.extend(nexus_output or []) + self._vlan_mgr.release_segmentation_id(tenant_id, id) + cdb.remove_vlan_binding(id) + return output + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def create_port(self, context, port): + """ + Perform this operation in the context of the configured device + plugins. + """ + try: + tenant_id = port['tenant_id'] + net_id = port['network_id'] + port_state = port['admin_state_up'] + port_id_string = port['id'] + args = [tenant_id, net_id, port_state, port_id_string] + ret_val = self._invoke_plugin_per_device(const.UCS_PLUGIN, + self._func_name(), args) + new_args = [tenant_id, net_id, port['id'], port['id']] + self._invoke_plugin_per_device(const.UCS_PLUGIN, + "plug_interface", new_args) + return ret_val + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def get_port(self, context, id, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + def get_ports(self, context, filters=None, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + def update_port(self, context, id, port): + """Currently there is no processing required for the device plugins""" + pass + + def delete_port(self, context, id, kwargs): + """ + Perform this operation in the context of the configured device + plugins. + """ + try: + p = kwargs['port'] + args = [p['tenant_id'], p['network_id'], p['id']] + return self._invoke_plugin_per_device(const.UCS_PLUGIN, + self._func_name(), args) + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def create_subnet(self, context, subnet): + """Currently there is no processing required for the device plugins""" + pass + + def update_subnet(self, context, id, subnet): + """Currently there is no processing required for the device plugins""" + pass + + def get_subnet(self, context, id, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + def delete_subnet(self, context, id, kwargs): + """Currently there is no processing required for the device plugins""" + pass + + def get_subnets(self, context, filters=None, fields=None, verbose=None): + """Currently there is no processing required for the device plugins""" + pass + + """ + Extensions' implementation in device plugins + """ + def schedule_host(self, args): + """Provides the hostname on which a dynamic vnic is reserved""" + try: + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def associate_port(self, args): + """Get the portprofile name and the device name for the dynamic vnic""" + try: + return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(), + args) + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def detach_port(self, args): + """Remove the association of the VIF with the dynamic vnic """ + try: + return self._invoke_plugin_per_device(const.UCS_PLUGIN, + self._func_name(), args) + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise + + def create_multiport(self, args): + """ + Makes a call to the UCS device plugin to create ports on the same + host. + """ + try: + self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(), + args) + except: + # TODO (Sumit): Check if we need to perform any rollback here + raise diff --git a/quantum/plugins/cisco/network_plugin.py b/quantum/plugins/cisco/network_plugin.py new file mode 100644 index 000000000..c3d2ac25c --- /dev/null +++ b/quantum/plugins/cisco/network_plugin.py @@ -0,0 +1,455 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. + +import inspect +import logging + +from quantum.common import exceptions as exc +from quantum.db import db_base_plugin_v2 +from quantum.db import models_v2 +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 cisco_utils as cutil +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco import l2network_plugin_configuration as conf +from quantum.quantum_plugin_base import QuantumPluginBase + +LOG = logging.getLogger(__name__) + + +class PluginV2(db_base_plugin_v2.QuantumDbPluginV2): + """ + Plugin with v2 API support for multiple sub-plugins + """ + supported_extension_aliases = ["Cisco Credential", "Cisco Port Profile", + "Cisco qos", "Cisco Nova Tenant", + "Cisco Multiport"] + + """ + Core API implementation + """ + def __init__(self): + """ + Initializes the DB, and credential store. + """ + cdb.initialize() + cred.Store.initialize() + self._model = importutils.import_object(conf.MODEL_CLASS) + super(PluginV2, self).__init__() + LOG.debug("Plugin initialization complete") + + def create_network(self, context, network): + """ + Creates a new Virtual Network, and assigns it + a symbolic name. + """ + LOG.debug("create_network() called\n") + new_network = super(PluginV2, self).create_network(context, network) + try: + self._invoke_device_plugins(self._func_name(), [context, + new_network]) + return new_network + except: + super(PluginV2, self).delete_network(context, new_network['id']) + raise + + def update_network(self, context, id, network): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + LOG.debug("update_network() called\n") + try: + self._invoke_device_plugins(self._func_name(), [context, id, + network]) + return super(PluginV2, self).update_network(context, id, network) + except: + raise + + def delete_network(self, context, id): + """ + Deletes the network with the specified network identifier + belonging to the specified tenant. + """ + LOG.debug("delete_network() called\n") + #We first need to check if there are any ports on this network + with context.session.begin(): + network = self._get_network(context, id) + + filter = {'network_id': [id]} + ports = self.get_ports(context, filters=filter) + if ports: + 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: + raise + + def create_port(self, context, port): + """ + Creates a port on the specified Virtual Network. + """ + LOG.debug("create_port() called\n") + new_port = super(PluginV2, self).create_port(context, port) + try: + self._invoke_device_plugins(self._func_name(), [context, new_port]) + return new_port + except: + super(PluginV2, self).delete_port(context, new_port['id']) + raise + + def delete_port(self, context, id): + """ + Deletes a port + """ + LOG.debug("delete_port() called\n") + port = self._get_port(context, id) + """ + TODO (Sumit): Disabling this check for now, check later + #Allow deleting a port only if the administrative state is down, + #and its operation status is also down + if port['admin_state_up'] or port['status'] == 'ACTIVE': + 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: + raise + + def update_port(self, context, id, port): + """ + Updates the state of a port and returns the updated port + """ + LOG.debug("update_port() called\n") + try: + self._invoke_device_plugins(self._func_name(), [context, id, + port]) + return super(PluginV2, self).update_port(context, id, port) + except: + raise + + def create_subnet(self, context, subnet): + """ + Create a subnet, which represents a range of IP addresses + that can be allocated to devices. + """ + LOG.debug("create_subnet() called\n") + new_subnet = super(PluginV2, self).create_subnet(context, subnet) + try: + self._invoke_device_plugins(self._func_name(), [context, + new_subnet]) + return new_subnet + except: + super(PluginV2, self).delete_subnet(context, new_subnet['id']) + raise + + def update_subnet(self, context, id, subnet): + """ + Updates the state of a subnet and returns the updated subnet + """ + LOG.debug("update_subnet() called\n") + try: + self._invoke_device_plugins(self._func_name(), [context, id, + subnet]) + return super(PluginV2, self).update_subnet(context, id, subnet) + except: + raise + + def delete_subnet(self, context, id): + """ + Deletes a subnet + """ + LOG.debug("delete_subnet() called\n") + with context.session.begin(): + subnet = self._get_subnet(context, id) + # Check if ports are using this subnet + allocated_qry = context.session.query(models_v2.IPAllocation) + allocated = allocated_qry.filter_by(subnet_id=id).all() + if 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: + raise + + """ + Extension API implementation + """ + def get_all_portprofiles(self, tenant_id): + """Get all port profiles""" + LOG.debug("get_all_portprofiles() called\n") + pplist = cdb.get_all_portprofiles() + new_pplist = [] + for portprofile in pplist: + new_pp = cutil.make_portprofile_dict(tenant_id, + portprofile[const.UUID], + portprofile[const.PPNAME], + portprofile[const.PPQOS]) + new_pplist.append(new_pp) + + return new_pplist + + def get_portprofile_details(self, tenant_id, profile_id): + """Get port profile details""" + LOG.debug("get_portprofile_details() called\n") + try: + portprofile = cdb.get_portprofile(tenant_id, profile_id) + except Exception: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + portprofile_id=profile_id) + + new_pp = cutil.make_portprofile_dict(tenant_id, + portprofile[const.UUID], + portprofile[const.PPNAME], + portprofile[const.PPQOS]) + return new_pp + + def create_portprofile(self, tenant_id, profile_name, qos): + """Create port profile""" + LOG.debug("create_portprofile() called\n") + portprofile = cdb.add_portprofile(tenant_id, profile_name, + const.NO_VLAN_ID, qos) + new_pp = cutil.make_portprofile_dict(tenant_id, + portprofile[const.UUID], + portprofile[const.PPNAME], + portprofile[const.PPQOS]) + return new_pp + + def delete_portprofile(self, tenant_id, profile_id): + """Delete portprofile""" + LOG.debug("delete_portprofile() called\n") + try: + portprofile = cdb.get_portprofile(tenant_id, profile_id) + except Exception: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + portprofile_id=profile_id) + + plist = cdb.get_pp_binding(tenant_id, profile_id) + if plist: + raise cexc.PortProfileInvalidDelete(tenant_id=tenant_id, + profile_id=profile_id) + else: + cdb.remove_portprofile(tenant_id, profile_id) + + def rename_portprofile(self, tenant_id, profile_id, new_name): + """Rename port profile""" + LOG.debug("rename_portprofile() called\n") + try: + portprofile = cdb.get_portprofile(tenant_id, profile_id) + except Exception: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + portprofile_id=profile_id) + portprofile = cdb.update_portprofile(tenant_id, profile_id, new_name) + new_pp = cutil.make_portprofile_dict(tenant_id, + portprofile[const.UUID], + portprofile[const.PPNAME], + portprofile[const.PPQOS]) + return new_pp + + def associate_portprofile(self, tenant_id, net_id, + port_id, portprofile_id): + """Associate port profile""" + LOG.debug("associate_portprofile() called\n") + try: + portprofile = cdb.get_portprofile(tenant_id, portprofile_id) + except Exception: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + portprofile_id=portprofile_id) + + cdb.add_pp_binding(tenant_id, port_id, portprofile_id, False) + + def disassociate_portprofile(self, tenant_id, net_id, + port_id, portprofile_id): + """Disassociate port profile""" + LOG.debug("disassociate_portprofile() called\n") + try: + portprofile = cdb.get_portprofile(tenant_id, portprofile_id) + except Exception: + raise cexc.PortProfileNotFound(tenant_id=tenant_id, + portprofile_id=portprofile_id) + + cdb.remove_pp_binding(tenant_id, port_id, portprofile_id) + + def get_all_qoss(self, tenant_id): + """Get all QoS levels""" + LOG.debug("get_all_qoss() called\n") + qoslist = cdb.get_all_qoss(tenant_id) + return qoslist + + def get_qos_details(self, tenant_id, qos_id): + """Get QoS Details""" + LOG.debug("get_qos_details() called\n") + try: + qos_level = cdb.get_qos(tenant_id, qos_id) + except Exception: + raise cexc.QosNotFound(tenant_id=tenant_id, + qos_id=qos_id) + return qos_level + + def create_qos(self, tenant_id, qos_name, qos_desc): + """Create a QoS level""" + LOG.debug("create_qos() called\n") + qos = cdb.add_qos(tenant_id, qos_name, str(qos_desc)) + return qos + + def delete_qos(self, tenant_id, qos_id): + """Delete a QoS level""" + LOG.debug("delete_qos() called\n") + try: + qos_level = cdb.get_qos(tenant_id, qos_id) + except Exception: + raise cexc.QosNotFound(tenant_id=tenant_id, + qos_id=qos_id) + return cdb.remove_qos(tenant_id, qos_id) + + def rename_qos(self, tenant_id, qos_id, new_name): + """Rename QoS level""" + LOG.debug("rename_qos() called\n") + try: + qos_level = cdb.get_qos(tenant_id, qos_id) + except Exception: + raise cexc.QosNotFound(tenant_id=tenant_id, + qos_id=qos_id) + qos = cdb.update_qos(tenant_id, qos_id, new_name) + return qos + + def get_all_credentials(self, tenant_id): + """Get all credentials""" + LOG.debug("get_all_credentials() called\n") + credential_list = cdb.get_all_credentials(tenant_id) + return credential_list + + def get_credential_details(self, tenant_id, credential_id): + """Get a particular credential""" + LOG.debug("get_credential_details() called\n") + try: + credential = cdb.get_credential(tenant_id, credential_id) + except Exception: + raise cexc.CredentialNotFound(tenant_id=tenant_id, + credential_id=credential_id) + return credential + + def create_credential(self, tenant_id, credential_name, user_name, + password): + """Create a new credential""" + LOG.debug("create_credential() called\n") + credential = cdb.add_credential(tenant_id, credential_name, + user_name, password) + return credential + + def delete_credential(self, tenant_id, credential_id): + """Delete a credential""" + LOG.debug("delete_credential() called\n") + try: + credential = cdb.get_credential(tenant_id, credential_id) + except Exception: + raise cexc.CredentialNotFound(tenant_id=tenant_id, + credential_id=credential_id) + credential = cdb.remove_credential(tenant_id, credential_id) + return credential + + def rename_credential(self, tenant_id, credential_id, new_name): + """Rename the particular credential resource""" + LOG.debug("rename_credential() called\n") + try: + credential = cdb.get_credential(tenant_id, credential_id) + except Exception: + raise cexc.CredentialNotFound(tenant_id=tenant_id, + credential_id=credential_id) + credential = cdb.update_credential(tenant_id, credential_id, new_name) + return credential + + def schedule_host(self, tenant_id, instance_id, instance_desc): + """Provides the hostname on which a dynamic vnic is reserved""" + LOG.debug("schedule_host() called\n") + host_list = self._invoke_device_plugins(self._func_name(), + [tenant_id, + instance_id, + instance_desc]) + return host_list + + def associate_port(self, tenant_id, instance_id, instance_desc): + """ + Get the portprofile name and the device name for the dynamic vnic + """ + LOG.debug("associate_port() called\n") + return self._invoke_device_plugins(self._func_name(), [tenant_id, + instance_id, + instance_desc]) + + def detach_port(self, tenant_id, instance_id, instance_desc): + """ + Remove the association of the VIF with the dynamic vnic + """ + LOG.debug("detach_port() called\n") + return self._invoke_device_plugins(self._func_name(), [tenant_id, + instance_id, + instance_desc]) + + def create_multiport(self, tenant_id, net_id_list, port_state, ports_desc): + """ + Creates multiple ports on the specified Virtual Network. + """ + LOG.debug("create_ports() called\n") + ports_num = len(net_id_list) + ports_id_list = [] + ports_dict_list = [] + + for net_id in net_id_list: + db.validate_network_ownership(tenant_id, net_id) + port = db.port_create(net_id, port_state) + ports_id_list.append(port[const.UUID]) + port_dict = {const.PORT_ID: port[const.UUID]} + ports_dict_list.append(port_dict) + + self._invoke_device_plugins(self._func_name(), [tenant_id, + net_id_list, + ports_num, + ports_id_list]) + return ports_dict_list + + """ + Private functions + """ + def _invoke_device_plugins(self, function_name, args): + """ + All device-specific calls are delegated to the model + """ + return getattr(self._model, function_name)(*args) + + def _func_name(self, offset=0): + """Getting the name of the calling funciton""" + return inspect.stack()[1 + offset][3] diff --git a/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py b/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py new file mode 100644 index 000000000..8fcc0601c --- /dev/null +++ b/quantum/plugins/cisco/nexus/cisco_nexus_plugin_v2.py @@ -0,0 +1,203 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Edgar Magana, Cisco Systems, Inc. +# +""" +PlugIn for Nexus OS driver +""" +import logging + +from quantum.common import exceptions as exc +from quantum.db import api as db +from quantum.openstack.common import importutils +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco.db import nexus_db_v2 as nxos_db +from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase +from quantum.plugins.cisco.nexus import cisco_nexus_configuration as conf + + +LOG = logging.getLogger(__name__) + + +class NexusPlugin(L2DevicePluginBase): + """ + Nexus PLugIn Main Class + """ + _networks = {} + + def __init__(self): + """ + Extracts the configuration parameters from the configuration file + """ + 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_first_port = conf.NEXUS_FIRST_PORT + self._nexus_second_port = conf.NEXUS_SECOND_PORT + self._nexus_ssh_port = conf.NEXUS_SSH_PORT + + def get_all_networks(self, tenant_id): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + LOG.debug("NexusPlugin:get_all_networks() called\n") + return self._networks.values() + + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, + **kwargs): + """ + Create a VLAN in the switch, and configure the appropriate interfaces + for this VLAN + """ + LOG.debug("NexusPlugin:create_network() called\n") + self._client.create_vlan( + vlan_name, str(vlan_id), self._nexus_ip, + self._nexus_username, self._nexus_password, + self._nexus_first_port, self._nexus_second_port, + self._nexus_ssh_port) + nxos_db.add_nexusport_binding(self._nexus_first_port, str(vlan_id)) + nxos_db.add_nexusport_binding(self._nexus_second_port, str(vlan_id)) + + new_net_dict = {const.NET_ID: net_id, + const.NET_NAME: net_name, + const.NET_PORTS: {}, + const.NET_VLAN_NAME: vlan_name, + const.NET_VLAN_ID: vlan_id} + self._networks[net_id] = new_net_dict + return new_net_dict + + def delete_network(self, tenant_id, net_id, **kwargs): + """ + Deletes a VLAN in the switch, and removes the VLAN configuration + from the relevant interfaces + """ + LOG.debug("NexusPlugin:delete_network() called\n") + context = kwargs[const.CONTEXT] + base_plugin_ref = kwargs[const.BASE_PLUGIN_REF] + 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) + net = self._get_network(tenant_id, net_id, context, base_plugin_ref) + if net: + self._client.delete_vlan( + str(vlan_id), self._nexus_ip, + self._nexus_username, self._nexus_password, + self._nexus_first_port, self._nexus_second_port, + self._nexus_ssh_port) + return net + # Network not found + raise exc.NetworkNotFound(net_id=net_id) + + def get_network_details(self, tenant_id, net_id, **kwargs): + """ + Returns the details of a particular network + """ + LOG.debug("NexusPlugin:get_network_details() called\n") + network = self._get_network(tenant_id, net_id) + return network + + def update_network(self, tenant_id, net_id, **kwargs): + """ + Updates the properties of a particular + Virtual Network. + """ + LOG.debug("NexusPlugin:update_network() called\n") + network = self._get_network(tenant_id, net_id) + network[const.NET_NAME] = kwargs["name"] + return network + + def get_all_ports(self, tenant_id, net_id, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:get_all_ports() called\n") + + def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:create_port() called\n") + + def delete_port(self, tenant_id, net_id, port_id, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:delete_port() called\n") + + def update_port(self, tenant_id, net_id, port_id, port_state, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:update_port() called\n") + + def get_port_details(self, tenant_id, net_id, port_id, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:get_port_details() called\n") + + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, + **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:plug_interface() called\n") + + def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): + """ + This is probably not applicable to the Nexus plugin. + Delete if not required. + """ + LOG.debug("NexusPlugin:unplug_interface() called\n") + + def _get_vlan_id_for_network(self, tenant_id, network_id, context, + base_plugin_ref): + """ + Obtain the VLAN ID given the Network ID + """ + net = self._get_network(tenant_id, network_id, context, + base_plugin_ref) + vlan_id = net[const.NET_VLAN_ID] + return vlan_id + + def _get_network(self, tenant_id, network_id, context, base_plugin_ref): + """ + Gets the NETWORK ID + """ + network = base_plugin_ref._get_network(context, network_id) + if not network: + raise exc.NetworkNotFound(net_id=network_id) + vlan = cdb.get_vlan_binding(network_id) + return {const.NET_ID: network_id, const.NET_NAME: network.name, + const.NET_PORTS: network.ports, + const.NET_VLAN_NAME: vlan.vlan_name, + const.NET_VLAN_ID: vlan.vlan_id} diff --git a/quantum/plugins/cisco/segmentation/l2network_vlan_mgr_v2.py b/quantum/plugins/cisco/segmentation/l2network_vlan_mgr_v2.py new file mode 100644 index 000000000..90ab050d2 --- /dev/null +++ b/quantum/plugins/cisco/segmentation/l2network_vlan_mgr_v2.py @@ -0,0 +1,52 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging + +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco import l2network_plugin_configuration as conf +from quantum.plugins.cisco.l2network_segmentation_base import ( + L2NetworkSegmentationMgrBase, +) + + +LOG = logging.getLogger(__name__) + + +class L2NetworkVLANMgr(L2NetworkSegmentationMgrBase): + """ + VLAN Manager which gets VLAN ID from DB + """ + def __init__(self): + cdb.create_vlanids() + + def reserve_segmentation_id(self, tenant_id, net_name, **kwargs): + """Get an available VLAN ID""" + return cdb.reserve_vlanid() + + def release_segmentation_id(self, tenant_id, net_id, **kwargs): + """Release the ID""" + vlan_binding = cdb.get_vlan_binding(net_id) + return cdb.release_vlanid(vlan_binding[const.VLANID]) + + def get_vlan_name(self, net_id, vlan): + """Getting the vlan name from the tenant and vlan""" + vlan_name = conf.VLAN_NAME_PREFIX + vlan + return vlan_name diff --git a/quantum/plugins/cisco/tests/unit/v2/__init__.py b/quantum/plugins/cisco/tests/unit/v2/__init__.py new file mode 100644 index 000000000..622dcfb73 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/__init__.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import __builtin__ +import unittest + + +setattr(__builtin__, '_', lambda x: x) + + +class BaseTest(unittest.TestCase): + + def setUp(self): + pass + + +def setUp(): + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/nexus/__init__.py b/quantum/plugins/cisco/tests/unit/v2/nexus/__init__.py new file mode 100644 index 000000000..622dcfb73 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/nexus/__init__.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import __builtin__ +import unittest + + +setattr(__builtin__, '_', lambda x: x) + + +class BaseTest(unittest.TestCase): + + def setUp(self): + pass + + +def setUp(): + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/nexus/fake_nexus_driver.py b/quantum/plugins/cisco/tests/unit/v2/nexus/fake_nexus_driver.py new file mode 100644 index 000000000..1f25cde31 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/nexus/fake_nexus_driver.py @@ -0,0 +1,101 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Rohit Agarwalla, Cisco Systems, Inc. + + +class CiscoNEXUSFakeDriver(): + """ + Nexus Driver Fake Class + """ + def __init__(self): + pass + + def nxos_connect(self, nexus_host, nexus_ssh_port, nexus_user, + nexus_password): + """ + Makes the fake connection to the Nexus Switch + """ + pass + + def create_xml_snippet(self, cutomized_config): + """ + Creates the Proper XML structure for the Nexus Switch Configuration + """ + pass + + def enable_vlan(self, mgr, vlanid, vlanname): + """ + Creates a VLAN on Nexus Switch given the VLAN ID and Name + """ + pass + + def disable_vlan(self, mgr, vlanid): + """ + Delete a VLAN on Nexus Switch given the VLAN ID + """ + pass + + def enable_port_trunk(self, mgr, interface): + """ + Enables trunk mode an interface on Nexus Switch + """ + pass + + def disable_switch_port(self, mgr, interface): + """ + Disables trunk mode an interface on Nexus Switch + """ + pass + + def enable_vlan_on_trunk_int(self, mgr, interface, vlanid): + """ + Enables trunk mode vlan access an interface on Nexus Switch given + VLANID + """ + pass + + def disable_vlan_on_trunk_int(self, mgr, interface, vlanid): + """ + Enables trunk mode vlan access an interface on Nexus Switch given + VLANID + """ + pass + + def create_vlan(self, vlan_name, vlan_id, nexus_host, nexus_user, + nexus_password, nexus_first_interface, + nexus_second_interface, nexus_ssh_port): + """ + Creates a VLAN and Enable on trunk mode an interface on Nexus Switch + given the VLAN ID and Name and Interface Number + """ + pass + + def delete_vlan(self, vlan_id, nexus_host, nexus_user, nexus_password, + nexus_first_interface, nexus_second_interface, + nexus_ssh_port): + """ + Delete a VLAN and Disables trunk mode an interface on Nexus Switch + given the VLAN ID and Interface Number + """ + pass + + def build_vlans_cmd(self): + """ + Builds a string with all the VLANs on the same Switch + """ + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test b/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test new file mode 100644 index 000000000..89ff3e642 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/quantumv2.conf.cisco.test @@ -0,0 +1,20 @@ +[DEFAULT] +# Show more verbose log output (sets INFO log level output) +verbose = True + +# Show debugging output in logs (sets DEBUG log level output) +debug = False + +# Address to bind the API server +bind_host = 0.0.0.0 + +# Port the bind the API server to +bind_port = 9696 + +# Path to the extensions +api_extensions_path = ../../../../extensions + +# Paste configuration file +api_paste_config = api-paste.ini.cisco.test + +core_plugin = quantum.plugins.cisco.network_plugin.PluginV2 diff --git a/quantum/plugins/cisco/tests/unit/v2/test_api_v2.py b/quantum/plugins/cisco/tests/unit/v2/test_api_v2.py new file mode 100644 index 000000000..43d81f2ba --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/test_api_v2.py @@ -0,0 +1,55 @@ +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the spec + +import inspect +import logging +import mock +import os +import webtest + +from quantum.api.v2 import router +from quantum.common import config +from quantum.openstack.common import cfg +from quantum.tests.unit import test_api_v2 + + +LOG = logging.getLogger(__name__) + + +def curdir(*p): + return os.path.join(os.path.dirname(__file__), *p) + + +class APIv2TestCase(test_api_v2.APIv2TestCase): + + def setUp(self): + plugin = 'quantum.plugins.cisco.network_plugin.PluginV2' + # Create the default configurations + args = ['--config-file', curdir('quantumv2.conf.cisco.test')] + config.parse(args=args) + # Update the plugin + cfg.CONF.set_override('core_plugin', plugin) + + self._plugin_patcher = mock.patch(plugin, autospec=True) + self.plugin = self._plugin_patcher.start() + + api = router.APIRouter() + self.api = webtest.TestApp(api) + LOG.debug("%s.%s.%s done" % (__name__, self.__class__.__name__, + inspect.stack()[0][3])) + + +class JSONV2TestCase(APIv2TestCase, test_api_v2.JSONV2TestCase): + + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/test_network_plugin.py b/quantum/plugins/cisco/tests/unit/v2/test_network_plugin.py new file mode 100644 index 000000000..b799207f4 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/test_network_plugin.py @@ -0,0 +1,86 @@ +# Copyright (c) 2012 OpenStack, LLC. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +# implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import inspect +import logging +import mock +import os + +from quantum.api.v2.router import APIRouter +from quantum.common import config +from quantum.db import api as db +from quantum.plugins.cisco.db import network_models_v2 +from quantum.openstack.common import cfg +from quantum.tests.unit import test_db_plugin +from quantum.wsgi import JSONDeserializer + +LOG = logging.getLogger(__name__) + + +def curdir(*p): + return os.path.join(os.path.dirname(__file__), *p) + + +class NetworkPluginV2TestCase(test_db_plugin.QuantumDbPluginV2TestCase): + + def setUp(self): + db._ENGINE = None + db._MAKER = None + + self._tenant_id = 'test-tenant' + + json_deserializer = JSONDeserializer() + self._deserializers = { + 'application/json': json_deserializer, + } + + plugin = 'quantum.plugins.cisco.network_plugin.PluginV2' + # Create the default configurations + args = ['--config-file', curdir('quantumv2.conf.cisco.test')] + config.parse(args=args) + # Update the plugin + cfg.CONF.set_override('core_plugin', plugin) + cfg.CONF.set_override('base_mac', "12:34:56:78:90:ab") + self.api = APIRouter() + LOG.debug("%s.%s.%s done" % (__name__, self.__class__.__name__, + inspect.stack()[0][3])) + + def tearDown(self): + db.clear_db(network_models_v2.model_base.BASEV2) + db._ENGINE = None + db._MAKER = None + + cfg.CONF.reset() + + +class TestV2HTTPResponse(NetworkPluginV2TestCase, + test_db_plugin.TestV2HTTPResponse): + + pass + + +class TestPortsV2(NetworkPluginV2TestCase, test_db_plugin.TestPortsV2): + + pass + + +class TestNetworksV2(NetworkPluginV2TestCase, test_db_plugin.TestNetworksV2): + + pass + + +class TestSubnetsV2(NetworkPluginV2TestCase, test_db_plugin.TestSubnetsV2): + + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/ucs/__init__.py b/quantum/plugins/cisco/tests/unit/v2/ucs/__init__.py new file mode 100644 index 000000000..29f78d1ee --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/ucs/__init__.py @@ -0,0 +1,32 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 OpenStack LLC. +# All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +import __builtin__ +import unittest + + +setattr(__builtin__, '_', lambda x: x) + + +class BaseTest(unittest.TestCase): + + def setUp(self): + pass + + +def setUp(): + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/ucs/cisco_ucs_inventory_fake.py b/quantum/plugins/cisco/tests/unit/v2/ucs/cisco_ucs_inventory_fake.py new file mode 100644 index 000000000..fd436bdc7 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/ucs/cisco_ucs_inventory_fake.py @@ -0,0 +1,59 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. + + +import logging +import os + +from quantum.common.utils import find_config_file +from quantum.openstack.common import importutils +from quantum.plugins.cisco.common import cisco_configparser as confp +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.ucs import ( + cisco_ucs_inventory_configuration as conf, +) +from quantum.plugins.cisco.ucs import cisco_ucs_inventory_v2 + + +LOG = logging.getLogger(__name__) + + +def curdir(*p): + return os.path.join(os.path.dirname(__file__), *p) + + +class UCSInventory(cisco_ucs_inventory_v2.UCSInventory): + """ + Inventory implementation for testing + """ + + def __init__(self): + fake_ucs_driver = "quantum.plugins.cisco.tests.unit.v2.ucs." + \ + "fake_ucs_driver.CiscoUCSMFakeDriver" + self._client = importutils.import_object(fake_ucs_driver) + conf_parser = confp.CiscoConfigParser(curdir("fake_ucs_inventory.ini")) + + conf.INVENTORY = conf_parser.walk(conf_parser.dummy) + for ucsm in conf.INVENTORY.keys(): + ucsm_ip = conf.INVENTORY[ucsm][const.IP_ADDRESS] + try: + cred.Store.put_credential(ucsm_ip, "username", "password") + except: + pass + self._load_inventory() diff --git a/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_driver.py b/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_driver.py new file mode 100644 index 000000000..9ffd7e406 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_driver.py @@ -0,0 +1,96 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. +# @author: Rohit Agarwalla, Cisco Systems, Inc. + +from quantum.plugins.cisco.common import cisco_constants as const + + +class CiscoUCSMFakeDriver(): + """UCSM Fake Driver""" + + def __init__(self): + pass + + def _get_blade_interfaces(self, chassis_number, blade_number, ucsm_ip, + ucsm_username, ucsm_password): + blade_interfaces = {} + for element in range(20): + dist_name = "dn" + str(element) + if dist_name: + order = str(element) + rhel_name = "eth" + str(element) + blade_interface = { + const.BLADE_INTF_DN: dist_name, + const.BLADE_INTF_ORDER: order, + const.BLADE_INTF_LINK_STATE: None, + const.BLADE_INTF_OPER_STATE: None, + const.BLADE_INTF_INST_TYPE: const.BLADE_INTF_DYNAMIC, + const.BLADE_INTF_RHEL_DEVICE_NAME: rhel_name, + } + blade_interfaces[dist_name] = blade_interface + + return blade_interfaces + + def _get_blade_interface_state(self, blade_intf, ucsm_ip, + ucsm_username, ucsm_password): + blade_intf[const.BLADE_INTF_LINK_STATE] = \ + const.BLADE_INTF_STATE_UNKNOWN + blade_intf[const.BLADE_INTF_OPER_STATE] = \ + const.BLADE_INTF_STATE_UNKNOWN + blade_intf[const.BLADE_INTF_INST_TYPE] = \ + const.BLADE_INTF_DYNAMIC + + def create_vlan(self, vlan_name, vlan_id, ucsm_ip, ucsm_username, + ucsm_password): + pass + + def create_profile(self, profile_name, vlan_name, ucsm_ip, ucsm_username, + ucsm_password): + pass + + def change_vlan_in_profile(self, profile_name, old_vlan_name, + new_vlan_name, ucsm_ip, ucsm_username, + ucsm_password): + pass + + def get_blade_data(self, chassis_number, blade_number, ucsm_ip, + ucsm_username, ucsm_password): + """ + Returns only the dynamic interfaces on the blade + """ + blade_interfaces = self._get_blade_interfaces(chassis_number, + blade_number, + ucsm_ip, + ucsm_username, + ucsm_password) + for blade_intf in blade_interfaces.keys(): + self._get_blade_interface_state(blade_interfaces[blade_intf], + ucsm_ip, ucsm_username, + ucsm_password) + if ((blade_interfaces[blade_intf][const.BLADE_INTF_INST_TYPE] != + const.BLADE_INTF_DYNAMIC)): + blade_interfaces.pop(blade_intf) + + return blade_interfaces + + def delete_vlan(self, vlan_name, ucsm_ip, ucsm_username, ucsm_password): + pass + + def delete_profile(self, profile_name, ucsm_ip, ucsm_username, + ucsm_password): + pass diff --git a/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_inventory.ini b/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_inventory.ini new file mode 100644 index 000000000..cd5bc6db9 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/ucs/fake_ucs_inventory.ini @@ -0,0 +1,7 @@ +[ucsm-1] +ip_address = 192.168.100.2 +[[chassis-1]] +chassis_id = 1 +[[[blade-1]]] +blade_id = 1 +host_name = blade1 diff --git a/quantum/plugins/cisco/tests/unit/v2/ucs/test_ucs_inventory_v2.py b/quantum/plugins/cisco/tests/unit/v2/ucs/test_ucs_inventory_v2.py new file mode 100644 index 000000000..b2f1dd034 --- /dev/null +++ b/quantum/plugins/cisco/tests/unit/v2/ucs/test_ucs_inventory_v2.py @@ -0,0 +1,127 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Shubhangi Satras, Cisco Systems, Inc. +# @author: Tyler Smith, Cisco Systems, Inc. + +import logging +import unittest +import uuid + +from quantum.common import exceptions as exc +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials_v2 as creds +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco.tests.unit.v2.ucs.cisco_ucs_inventory_fake import ( + UCSInventory, +) + + +LOG = logging.getLogger(__name__) + +# Set some data to use in tests +tenant = 'shubh' +net_name = 'TestNetwork1' +port_state = const.PORT_UP +interface_id = 'vif-01' + + +class TestUCSInventory(unittest.TestCase): + """ + Tests for the UCS Inventory. Each high-level operation should return + some information about which devices to perform the action on. + """ + + def setUp(self): + """Setup our tests""" + cdb.initialize() + creds.Store.initialize() + + # Create the ucs inventory object + self._ucs_inventory = UCSInventory() + self.inventory = self._ucs_inventory._inventory + + def assertValidUCM(self, ip_address): + """Asserts that the given ip is in the UCS inventory""" + if ip_address in self.inventory.keys(): + assert(1) + return + assert(0) + + def _test_get_all_ucms(self, cmd): + """Runs tests for commands that expect a list of all UCMS""" + LOG.debug("test_%s - START", cmd) + results = getattr(self._ucs_inventory, cmd)([]) + self.assertEqual(results[const.DEVICE_IP], self.inventory.keys()) + LOG.debug("test_%s - END", cmd) + + def _test_with_port_creation(self, cmd, params=None): + """Tests commands that requires a port to exist""" + LOG.debug("test_%s - START", cmd) + net_uuid = str(uuid.uuid4()) + device_params = self._ucs_inventory.create_port(tenant, net_uuid, + port_state, + state=port_state) + + args = [tenant, net_uuid, port[const.PORT_ID]] + if params is not None: + args.extend(params) + + ip_address = getattr(self._ucs_inventory, cmd)(args) + ip_address = ip_address[const.DEVICE_IP][0] + self.assertValidUCM(ip_address) + cdb.clear_db() + + LOG.debug("test_%s - END", cmd) + + def test_create_port(self): + """Test that the UCS Inventory returns the correct devices to use""" + LOG.debug("test_create_port - START") + results = self._ucs_inventory.create_port([]) + results = results[const.LEAST_RSVD_BLADE_DICT] + + ip_address = results[const.LEAST_RSVD_BLADE_UCSM] + chassis = results[const.LEAST_RSVD_BLADE_CHASSIS] + blade = results[const.LEAST_RSVD_BLADE_ID] + + if blade not in self.inventory[ip_address][chassis]: + self.assertEqual(0, 1) + self.assertEqual(1, 1) + LOG.debug("test_create_port - END") + + def test_get_all_networks(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('get_all_networks') + + def test_create_network(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('create_network') + + def test_delete_network(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('delete_network') + + def test_get_network_details(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('get_network_details') + + def test_update_network(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('update_network') + + def test_get_all_ports(self): + """Test that the UCS Inventory returns the correct devices to use""" + self._test_get_all_ucms('get_all_ports') diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_inventory_v2.py b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_v2.py new file mode 100644 index 000000000..19cb9aca2 --- /dev/null +++ b/quantum/plugins/cisco/ucs/cisco_ucs_inventory_v2.py @@ -0,0 +1,709 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. + +""" +The _inventory data strcuture contains a nested disctioary: + {"UCSM_IP: {"Chassis-ID": [Balde-ID, Blade-ID], + "Chassis-ID": [Blade-ID, Blade-ID, Blade-ID]]}, + "UCSM_IP: {"Chassis-ID": [Balde-ID]} + } +""" +""" +_inventory_state data structure is organized as below: +{ucsm_ip: + {chassis_id: + {blade_id: + {'blade-data': + {blade-dn-1: {blade-intf-data}, + blade-dn-2: {blade-intf-data} + } + } + } + } +} +'blade-data': Blade Data dictionary has the following keys: +=========================================================== +const.BLADE_INTF_DATA: This is a dictionary, with the key as the + dn of the interface, and the value as the + Blade Interface Dictionary described next +const.BLADE_UNRESERVED_INTF_COUNT: Number of unreserved interfaces + on this blade + +'blade-intf-data': Blade Interface dictionary has the following keys: +===================================================================== +const.BLADE_INTF_DN +const.BLADE_INTF_ORDER +const.BLADE_INTF_LINK_STATE +const.BLADE_INTF_OPER_STATE +const.BLADE_INTF_INST_TYPE +const.BLADE_INTF_RHEL_DEVICE_NAME +const.BLADE_INTF_RESERVATION +const.TENANTID +const.PORTID +const.PROFILE_ID +const.INSTANCE_ID +const.VIF_ID +""" + +from copy import deepcopy +import logging + +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 cexc +from quantum.plugins.cisco.db import ucs_db_v2 as udb +from quantum.plugins.cisco.l2device_inventory_base import ( + L2NetworkDeviceInventoryBase, +) +from quantum.plugins.cisco.ucs import ( + cisco_ucs_inventory_configuration as conf, +) + + +LOG = logging.getLogger(__name__) + + +class UCSInventory(L2NetworkDeviceInventoryBase): + """ + Manages the state of all the UCS chasses, and blades in + the system + """ + + _inventory = {} + _host_names = {} + _inventory_state = {} + + def __init__(self): + self._client = importutils.import_object(conf.UCSM_DRIVER) + self._load_inventory() + + def _load_inventory(self): + """Load the inventory from a config file""" + inventory = deepcopy(conf.INVENTORY) + LOG.info("Loaded UCS inventory: %s\n" % inventory) + LOG.info("Building UCS inventory state (this may take a while)...") + + for ucsm in inventory.keys(): + ucsm_ip = inventory[ucsm][const.IP_ADDRESS] + inventory[ucsm].pop(const.IP_ADDRESS) + chassis_dict = {} + for chassis in inventory[ucsm].keys(): + chassis_id = inventory[ucsm][chassis][const.CHASSIS_ID] + inventory[ucsm][chassis].pop(const.CHASSIS_ID) + blade_list = [] + for blade in inventory[ucsm][chassis].keys(): + blade_id = ( + inventory[ucsm][chassis][blade][const.BLADE_ID]) + host_name = ( + inventory[ucsm][chassis][blade][const.HOST_NAME]) + host_key = ucsm_ip + "-" + chassis_id + "-" + blade_id + self._host_names[host_key] = host_name + blade_list.append(blade_id) + chassis_dict[chassis_id] = blade_list + self._inventory[ucsm_ip] = chassis_dict + + self._build_inventory_state() + + def _build_inventory_state(self): + """Populate the state of all the blades""" + for ucsm_ip in self._inventory.keys(): + self._inventory_state[ucsm_ip] = {ucsm_ip: {}} + ucsm_username = cred.Store.get_username(ucsm_ip) + ucsm_password = cred.Store.get_password(ucsm_ip) + chasses_state = {} + self._inventory_state[ucsm_ip] = chasses_state + ucsm = self._inventory[ucsm_ip] + for chassis_id in ucsm.keys(): + blades_dict = {} + chasses_state[chassis_id] = blades_dict + for blade_id in ucsm[chassis_id]: + blade_data = self._get_initial_blade_state(chassis_id, + blade_id, + ucsm_ip, + ucsm_username, + ucsm_password) + blades_dict[blade_id] = blade_data + + LOG.debug("UCS Inventory state is: %s\n" % self._inventory_state) + return True + + def _get_host_name(self, ucsm_ip, chassis_id, blade_id): + """Get the hostname based on the blade info""" + host_key = ucsm_ip + "-" + chassis_id + "-" + blade_id + return self._host_names[host_key] + + def _get_initial_blade_state(self, chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password): + """Get the initial blade state""" + blade_intf_data = self._client.get_blade_data(chassis_id, blade_id, + ucsm_ip, ucsm_username, + ucsm_password) + + unreserved_counter = 0 + + for blade_intf in blade_intf_data.keys(): + dist_name = blade_intf_data[blade_intf][const.BLADE_INTF_DN] + # We first make a pass through the state in UCSM + # If a particular interface is showing as being allocated in + # UCSM then it is definitely being used and so should be + # marked as reserved, else we temporarily mark it as unreserved + # based on the UCSM state, but may later change it if a port + # association is found in the DB + if not const.TENANTID in blade_intf_data[blade_intf].keys(): + blade_intf_data[blade_intf][const.TENANTID] = None + if not const.PORTID in blade_intf_data[blade_intf].keys(): + blade_intf_data[blade_intf][const.PORTID] = None + if not const.PROFILE_ID in blade_intf_data[blade_intf].keys(): + blade_intf_data[blade_intf][const.PROFILE_ID] = None + if not const.INSTANCE_ID in blade_intf_data[blade_intf].keys(): + blade_intf_data[blade_intf][const.INSTANCE_ID] = None + if not const.VIF_ID in blade_intf_data[blade_intf].keys(): + blade_intf_data[blade_intf][const.VIF_ID] = None + + if (blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == + const.BLADE_INTF_STATE_UNALLOCATED or + blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == + const.BLADE_INTF_STATE_UNKNOWN) and ( + blade_intf_data[blade_intf][const.BLADE_INTF_OPER_STATE] == + const.BLADE_INTF_STATE_UNKNOWN): + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_UNRESERVED) + unreserved_counter += 1 + else: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_RESERVED) + + port_binding = udb.get_portbinding_dn(dist_name) + if port_binding: + # We have found a port binding for this interface in the DB, + # so we have earlier marked this interface as unreserved, we + # need to change it, and also load the state from the DB for + # other associations + intf_data = blade_intf_data[blade_intf] + if ((intf_data[const.BLADE_INTF_RESERVATION] == const. + BLADE_INTF_UNRESERVED)): + unreserved_counter -= 1 + intf_data[const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_RESERVED) + intf_data[const.TENANTID] = port_binding[const.TENANTID] + intf_data[const.PORTID] = port_binding[const.PORTID] + intf_data[const.PROFILE_ID] = ( + port_binding[const.PORTPROFILENAME]) + intf_data[const.INSTANCE_ID] = port_binding[const.INSTANCE_ID] + intf_data[const.VIF_ID] = port_binding[const.VIF_ID] + host_name = self._get_host_name(ucsm_ip, chassis_id, blade_id) + blade_data = {const.BLADE_INTF_DATA: blade_intf_data, + const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter, + const.HOST_NAME: host_name} + return blade_data + + def _get_blade_state(self, chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password): + """Get the blade state""" + blade_intf_data = self._client.get_blade_data(chassis_id, blade_id, + ucsm_ip, ucsm_username, + ucsm_password) + unreserved_counter = 0 + + for blade_intf in blade_intf_data.keys(): + if (blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == + const.BLADE_INTF_STATE_UNALLOCATED or + blade_intf_data[blade_intf][const.BLADE_INTF_LINK_STATE] == + const.BLADE_INTF_STATE_UNKNOWN) and ( + blade_intf_data[blade_intf][const.BLADE_INTF_OPER_STATE] == + const.BLADE_INTF_STATE_UNKNOWN): + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_UNRESERVED) + unreserved_counter += 1 + else: + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_RESERVED) + + blade_data = {const.BLADE_INTF_DATA: blade_intf_data, + const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter} + return blade_data + + def _get_all_ucsms(self): + """Return a list of the IPs of all the UCSMs in the system""" + return {const.DEVICE_IP: self._inventory.keys()} + + def _get_blade_for_port(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + tenant_id = args[0] + net_id = args[1] + port_id = args[2] + rsvd_info = self._get_rsvd_blade_intf_by_port(tenant_id, port_id) + if not rsvd_info: + raise exc.PortNotFound(net_id=net_id, port_id=port_id) + device_params = {const.DEVICE_IP: [rsvd_info[const.UCSM_IP]]} + return device_params + + def _get_host_name_for_rsvd_intf(self, tenant_id, instance_id): + """ + Return the hostname of the blade with a reserved instance + for this tenant + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + tmp = deepcopy(blade_intf_data[blade_intf]) + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_RESERVED and + intf_data[const.TENANTID] == tenant_id and + intf_data[const.INSTANCE_ID] is None): + intf_data[const.INSTANCE_ID] = instance_id + host_name = self._get_host_name(ucsm_ip, + chassis_id, + blade_id) + port_binding = udb.get_portbinding_dn(blade_intf) + port_id = port_binding[const.PORTID] + udb.update_portbinding(port_id, + instance_id=instance_id) + return host_name + LOG.warn("Could not find a reserved dynamic nic for tenant: %s" % + tenant_id) + return None + + def _get_instance_port(self, tenant_id, instance_id, vif_id): + """ + Return the device name for a reserved interface + """ + found_blade_intf_data = None + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_RESERVED and + intf_data[const.TENANTID] == tenant_id and + intf_data[const.INSTANCE_ID] == instance_id): + found_blade_intf_data = blade_intf_data + LOG.debug(("Found blade %s associated with this" + " instance: %s") % (blade_id, + instance_id)) + break + + if found_blade_intf_data: + blade_intf_data = found_blade_intf_data + for blade_intf in blade_intf_data.keys(): + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_RESERVED and + intf_data[const.TENANTID] == tenant_id and + (not intf_data[const.VIF_ID])): + intf_data[const.VIF_ID] = vif_id + intf_data[const.INSTANCE_ID] = instance_id + port_binding = udb.get_portbinding_dn(blade_intf) + port_id = port_binding[const.PORTID] + udb.update_portbinding(port_id, instance_id=instance_id, + vif_id=vif_id) + device_name = intf_data[const.BLADE_INTF_RHEL_DEVICE_NAME] + profile_name = port_binding[const.PORTPROFILENAME] + dynamicnic_details = { + const.DEVICENAME: device_name, + const.UCSPROFILE: profile_name, + } + LOG.debug(("Found reserved dynamic nic: %s" + "associated with port %s") % + (intf_data, port_id)) + LOG.debug("Returning dynamic nic details: %s" % + dynamicnic_details) + return dynamicnic_details + + LOG.warn("Could not find a reserved dynamic nic for tenant: %s" % + tenant_id) + return None + + def _disassociate_vifid_from_port(self, tenant_id, instance_id, vif_id): + """ + Disassociate a VIF-ID from a port, this happens when a + VM is destroyed + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_RESERVED and + intf_data[const.TENANTID] == tenant_id and + blade_intf_data[blade_intf][const.INSTANCE_ID] + == instance_id and + intf_data[const.VIF_ID][:const.UUID_LENGTH] == + vif_id): + intf_data[const.VIF_ID] = None + intf_data[const.INSTANCE_ID] = None + port_binding = udb.get_portbinding_dn(blade_intf) + port_id = port_binding[const.PORTID] + udb.update_portbinding(port_id, instance_id=None, + vif_id=None) + LOG.debug( + ("Disassociated VIF-ID: %s " + "from port: %s" + "in UCS inventory state for blade: %s") % + (vif_id, port_id, intf_data)) + device_params = {const.DEVICE_IP: [ucsm_ip], + const.PORTID: port_id} + return device_params + LOG.warn(("Disassociating VIF-ID in UCS inventory failed. " + "Could not find a reserved dynamic nic for tenant: %s") % + tenant_id) + return None + + def _get_rsvd_blade_intf_by_port(self, tenant_id, port_id): + """ + Lookup a reserved blade interface based on tenant_id and port_id + and return the blade interface info + """ + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + for blade_intf in blade_intf_data.keys(): + if ((not blade_intf_data[blade_intf][const.PORTID] or + not blade_intf_data[blade_intf][const.TENANTID])): + continue + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_RESERVED and + intf_data[const.TENANTID] == tenant_id and + intf_data[const.PORTID] == port_id): + interface_dn = intf_data[const.BLADE_INTF_DN] + blade_intf_info = {const.UCSM_IP: ucsm_ip, + const.CHASSIS_ID: chassis_id, + const.BLADE_ID: blade_id, + const.BLADE_INTF_DN: + interface_dn} + return blade_intf_info + LOG.warn("Could not find a reserved nic for tenant: %s port: %s" % + (tenant_id, port_id)) + return None + + def _get_least_reserved_blade(self, intf_count=1): + """Return the blade with least number of dynamic nics reserved""" + unreserved_interface_count = 0 + least_reserved_blade_ucsm = None + least_reserved_blade_chassis = None + least_reserved_blade_id = None + least_reserved_blade_data = None + + for ucsm_ip in self._inventory_state.keys(): + ucsm = self._inventory_state[ucsm_ip] + for chassis_id in ucsm.keys(): + for blade_id in ucsm[chassis_id]: + blade_data = ucsm[chassis_id][blade_id] + if ((blade_data[const.BLADE_UNRESERVED_INTF_COUNT] > + unreserved_interface_count)): + unreserved_interface_count = ( + blade_data[const.BLADE_UNRESERVED_INTF_COUNT]) + least_reserved_blade_ucsm = ucsm_ip + least_reserved_blade_chassis = chassis_id + least_reserved_blade_id = blade_id + least_reserved_blade_data = blade_data + + if unreserved_interface_count < intf_count: + LOG.warn(("Not enough dynamic nics available on a single host." + " Requested: %s, Maximum available: %s") % + (intf_count, unreserved_interface_count)) + return False + + least_reserved_blade_dict = { + const.LEAST_RSVD_BLADE_UCSM: least_reserved_blade_ucsm, + const.LEAST_RSVD_BLADE_CHASSIS: least_reserved_blade_chassis, + const.LEAST_RSVD_BLADE_ID: least_reserved_blade_id, + const.LEAST_RSVD_BLADE_DATA: least_reserved_blade_data, + } + LOG.debug("Found dynamic nic %s available for reservation", + least_reserved_blade_dict) + return least_reserved_blade_dict + + def reload_inventory(self): + """Reload the inventory from a conf file""" + self._load_inventory() + + def reserve_blade_interface(self, ucsm_ip, chassis_id, blade_id, + blade_data_dict, tenant_id, port_id, + portprofile_name): + """Reserve an interface on a blade""" + ucsm_username = cred.Store.get_username(ucsm_ip) + ucsm_password = cred.Store.get_password(ucsm_ip) + """ + We are first getting the updated UCSM-specific blade + interface state + """ + blade_data = self._get_blade_state(chassis_id, blade_id, ucsm_ip, + ucsm_username, ucsm_password) + blade_intf_data = blade_data[const.BLADE_INTF_DATA] + chassis_data = self._inventory_state[ucsm_ip][chassis_id] + old_blade_intf_data = chassis_data[blade_id][const.BLADE_INTF_DATA] + + """ + We will now copy the older non-UCSM-specific blade + interface state + """ + for blade_intf in blade_intf_data.keys(): + old_intf_data = old_blade_intf_data[blade_intf] + blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = ( + old_intf_data[const.BLADE_INTF_RESERVATION]) + blade_intf_data[blade_intf][const.TENANTID] = ( + old_intf_data[const.TENANTID]) + blade_intf_data[blade_intf][const.PORTID] = ( + old_intf_data[const.PORTID]) + blade_intf_data[blade_intf][const.PROFILE_ID] = ( + old_intf_data[const.PROFILE_ID]) + blade_intf_data[blade_intf][const.INSTANCE_ID] = ( + old_intf_data[const.INSTANCE_ID]) + blade_intf_data[blade_intf][const.VIF_ID] = ( + old_intf_data[const.VIF_ID]) + + blade_data[const.BLADE_UNRESERVED_INTF_COUNT] = ( + chassis_data[blade_id][const.BLADE_UNRESERVED_INTF_COUNT]) + """ + Now we will reserve an interface if its available + """ + for blade_intf in blade_intf_data.keys(): + intf_data = blade_intf_data[blade_intf] + if (intf_data[const.BLADE_INTF_RESERVATION] == + const.BLADE_INTF_UNRESERVED): + intf_data[const.BLADE_INTF_RESERVATION] = ( + const.BLADE_INTF_RESERVED) + intf_data[const.TENANTID] = tenant_id + intf_data[const.PORTID] = port_id + intf_data[const.INSTANCE_ID] = None + dev_eth_name = intf_data[const.BLADE_INTF_RHEL_DEVICE_NAME] + """ + We are replacing the older blade interface state with new + """ + chassis_data[blade_id][const.BLADE_INTF_DATA] = blade_intf_data + chassis_data[blade_id][const.BLADE_UNRESERVED_INTF_COUNT] -= 1 + host_name = self._get_host_name(ucsm_ip, chassis_id, blade_id) + reserved_nic_dict = { + const.RESERVED_NIC_HOSTNAME: host_name, + const.RESERVED_NIC_NAME: dev_eth_name, + const.BLADE_INTF_DN: blade_intf, + } + port_binding = udb.add_portbinding(port_id, blade_intf, None, + None, None, None) + udb.update_portbinding(port_id, + tenant_id=intf_data[const.TENANTID]) + LOG.debug("Reserved blade interface: %s\n" % reserved_nic_dict) + return reserved_nic_dict + + LOG.warn("Dynamic nic %s could not be reserved for port-id: %s" % + (blade_data, port_id)) + return False + + def unreserve_blade_interface(self, ucsm_ip, chassis_id, blade_id, + interface_dn): + """Unreserve a previously reserved interface on a blade""" + ucsm_username = cred.Store.get_username(ucsm_ip) + ucsm_password = cred.Store.get_password(ucsm_ip) + blade_data = self._inventory_state[ucsm_ip][chassis_id][blade_id] + + blade_data[const.BLADE_UNRESERVED_INTF_COUNT] += 1 + blade_intf = blade_data[const.BLADE_INTF_DATA][interface_dn] + blade_intf[const.BLADE_INTF_RESERVATION] = const.BLADE_INTF_UNRESERVED + blade_intf[const.TENANTID] = None + blade_intf[const.PORTID] = None + blade_intf[const.PROFILE_ID] = None + blade_intf[const.INSTANCE_ID] = None + blade_intf[const.VIF_ID] = None + LOG.debug("Unreserved blade interface %s\n" % interface_dn) + + def add_blade(self, ucsm_ip, chassis_id, blade_id): + """Add a blade to the inventory""" + # TODO (Sumit) + pass + + def get_all_networks(self, args): + """Return all UCSM IPs""" + LOG.debug("get_all_networks() called\n") + return self._get_all_ucsms() + + def create_network(self, args): + """Return all UCSM IPs""" + LOG.debug("create_network() called\n") + return self._get_all_ucsms() + + def delete_network(self, args): + """Return all UCSM IPs""" + LOG.debug("delete_network() called\n") + return self._get_all_ucsms() + + def get_network_details(self, args): + """Return all UCSM IPs""" + LOG.debug("get_network_details() called\n") + return self._get_all_ucsms() + + def update_network(self, args): + """Return all UCSM IPs""" + LOG.debug("update_network() called\n") + return self._get_all_ucsms() + + def get_all_ports(self, args): + """Return all UCSM IPs""" + LOG.debug("get_all_ports() called\n") + return self._get_all_ucsms() + + def create_port(self, args): + """ + Return the a dict with information of the blade + on which a dynamic vnic is available + """ + LOG.debug("create_port() called\n") + least_reserved_blade_dict = self._get_least_reserved_blade() + if not least_reserved_blade_dict: + raise cexc.NoMoreNics() + ucsm_ip = least_reserved_blade_dict[const.LEAST_RSVD_BLADE_UCSM] + device_params = { + const.DEVICE_IP: [ucsm_ip], + const.UCS_INVENTORY: self, + const.LEAST_RSVD_BLADE_DICT: least_reserved_blade_dict, + } + return device_params + + def delete_port(self, args): + """ + Return the a dict with information of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("delete_port() called\n") + tenant_id = args[0] + net_id = args[1] + port_id = args[2] + rsvd_info = self._get_rsvd_blade_intf_by_port(tenant_id, port_id) + if not rsvd_info: + LOG.warn("UCSInventory: Port not found: net_id: %s, port_id: %s" % + (net_id, port_id)) + return {const.DEVICE_IP: []} + device_params = { + const.DEVICE_IP: [rsvd_info[const.UCSM_IP]], + const.UCS_INVENTORY: self, + const.CHASSIS_ID: rsvd_info[const.CHASSIS_ID], + const.BLADE_ID: rsvd_info[const.BLADE_ID], + const.BLADE_INTF_DN: rsvd_info[const.BLADE_INTF_DN], + } + return device_params + + def update_port(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("update_port() called\n") + return self._get_blade_for_port(args) + + def get_port_details(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("get_port_details() called\n") + return self._get_blade_for_port(args) + + def plug_interface(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("plug_interface() called\n") + return self._get_blade_for_port(args) + + def unplug_interface(self, args): + """ + Return the a dict with IP address of the blade + on which a dynamic vnic was reserved for this port + """ + LOG.debug("unplug_interface() called\n") + return self._get_blade_for_port(args) + + def schedule_host(self, args): + """Provides the hostname on which a dynamic vnic is reserved""" + LOG.debug("schedule_host() called\n") + instance_id = args[1] + tenant_id = args[2][const.PROJECT_ID] + host_name = self._get_host_name_for_rsvd_intf(tenant_id, instance_id) + host_list = {const.HOST_LIST: {const.HOST_1: host_name}} + LOG.debug("host_list is: %s" % host_list) + return host_list + + def associate_port(self, args): + """ + Get the portprofile name and the device name for the dynamic vnic + """ + LOG.debug("associate_port() called\n") + instance_id = args[1] + tenant_id = args[2][const.PROJECT_ID] + vif_id = args[2][const.VIF_ID] + vif_info = self._get_instance_port(tenant_id, instance_id, vif_id) + vif_desc = {const.VIF_DESC: vif_info} + + LOG.debug("vif_desc is: %s" % vif_desc) + return vif_desc + + def detach_port(self, args): + """ + Remove the VIF-ID and instance name association + with the port + """ + LOG.debug("detach_port() called\n") + instance_id = args[1] + tenant_id = args[2][const.PROJECT_ID] + vif_id = args[2][const.VIF_ID] + device_params = self._disassociate_vifid_from_port(tenant_id, + instance_id, + vif_id) + return device_params + + def create_multiport(self, args): + """ + Create multiple ports for a VM + """ + LOG.debug("create_ports() called\n") + tenant_id = args[0] + ports_num = args[2] + least_reserved_blade_dict = self._get_least_reserved_blade(ports_num) + if not least_reserved_blade_dict: + raise cexc.NoMoreNics() + ucsm_ip = least_reserved_blade_dict[const.LEAST_RSVD_BLADE_UCSM] + device_params = { + const.DEVICE_IP: [ucsm_ip], + const.UCS_INVENTORY: self, + const.LEAST_RSVD_BLADE_DICT: + least_reserved_blade_dict, + } + return device_params diff --git a/quantum/plugins/cisco/ucs/cisco_ucs_plugin_v2.py b/quantum/plugins/cisco/ucs/cisco_ucs_plugin_v2.py new file mode 100644 index 000000000..b831c1c6e --- /dev/null +++ b/quantum/plugins/cisco/ucs/cisco_ucs_plugin_v2.py @@ -0,0 +1,337 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 +# +# Copyright 2012 Cisco Systems, Inc. All rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# 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: Sumit Naiksatam, Cisco Systems, Inc. +# + +import logging + +from quantum.db import api as db + +from quantum.openstack.common import importutils +from quantum.plugins.cisco.common import cisco_constants as const +from quantum.plugins.cisco.common import cisco_credentials as cred +from quantum.plugins.cisco.common import cisco_exceptions as cexc +from quantum.plugins.cisco.common import cisco_utils as cutil +from quantum.plugins.cisco.db import network_db_v2 as cdb +from quantum.plugins.cisco.db import ucs_db_v2 as udb +from quantum.plugins.cisco.l2device_plugin_base import L2DevicePluginBase +from quantum.plugins.cisco.ucs import cisco_ucs_configuration as conf + + +LOG = logging.getLogger(__name__) + + +class UCSVICPlugin(L2DevicePluginBase): + """UCS Device Plugin""" + + def __init__(self): + self._driver = importutils.import_object(conf.UCSM_DRIVER) + LOG.debug("Loaded driver %s\n" % conf.UCSM_DRIVER) + # TODO (Sumit) Make the counter per UCSM + self._port_profile_counter = 0 + + def get_all_networks(self, tenant_id, **kwargs): + """ + Returns a dictionary containing all + for + the specified tenant. + """ + LOG.debug("UCSVICPlugin:get_all_networks() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + networks_list = db.network_list(tenant_id) + new_networks_list = [] + for network in networks_list: + new_network_dict = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + []) + new_networks_list.append(new_network_dict) + + return new_networks_list + + def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id, + **kwargs): + """ + Creates a new Virtual Network, and assigns it + a symbolic name. + """ + LOG.debug("UCSVICPlugin:create_network() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + self._driver.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + ports_on_net = [] + new_network_dict = cutil.make_net_dict(net_id, + net_name, + ports_on_net) + return new_network_dict + + def delete_network(self, tenant_id, net_id, **kwargs): + """ + Deletes the network with the specified network identifier + belonging to the specified tenant. + """ + LOG.debug("UCSVICPlugin:delete_network() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + vlan_binding = cdb.get_vlan_binding(net_id) + vlan_name = vlan_binding[const.VLANNAME] + self._driver.delete_vlan(vlan_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + #Rohit:passing empty network name, might not need fixing + net_dict = cutil.make_net_dict(net_id, + "", + []) + return net_dict + + def get_network_details(self, tenant_id, net_id, **kwargs): + """ + Deletes the Virtual Network belonging to a the + spec + """ + LOG.debug("UCSVICPlugin:get_network_details() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + ports_list = network[const.NETWORKPORTS] + ports_on_net = [] + for port in ports_list: + new_port = cutil.make_port_dict(port[const.UUID], + port[const.PORTSTATE], + port[const.NETWORKID], + port[const.INTERFACEID]) + ports_on_net.append(new_port) + + new_network = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + ports_on_net) + + return new_network + + def update_network(self, tenant_id, net_id, **kwargs): + """ + Updates the symbolic name belonging to a particular + Virtual Network. + """ + LOG.debug("UCSVICPlugin:update_network() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + net_dict = cutil.make_net_dict(network[const.UUID], + network[const.NETWORKNAME], + []) + return net_dict + + def get_all_ports(self, tenant_id, net_id, **kwargs): + """ + Retrieves all port identifiers belonging to the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:get_all_ports() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + network = db.network_get(net_id) + ports_list = network[const.NETWORKPORTS] + ports_on_net = [] + for port in ports_list: + port_binding = udb.get_portbinding(port[const.UUID]) + ports_on_net.append(port_binding) + + return ports_on_net + + def create_port(self, tenant_id, net_id, port_state, port_id, **kwargs): + """ + Creates a port on the specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:create_port() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + qos = None + ucs_inventory = kwargs[const.UCS_INVENTORY] + least_rsvd_blade_dict = kwargs[const.LEAST_RSVD_BLADE_DICT] + chassis_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_CHASSIS] + blade_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_ID] + blade_data_dict = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_DATA] + new_port_profile = self._create_port_profile(tenant_id, net_id, + port_id, + conf.DEFAULT_VLAN_NAME, + conf.DEFAULT_VLAN_ID) + profile_name = new_port_profile[const.PROFILE_NAME] + rsvd_nic_dict = ucs_inventory.reserve_blade_interface( + self._ucsm_ip, chassis_id, + blade_id, blade_data_dict, + tenant_id, port_id, + profile_name) + port_binding = udb.update_portbinding(port_id, + portprofile_name=profile_name, + vlan_name=conf.DEFAULT_VLAN_NAME, + vlan_id=conf.DEFAULT_VLAN_ID, + qos=qos) + return port_binding + + def delete_port(self, tenant_id, net_id, port_id, **kwargs): + """ + Deletes a port on a specified Virtual Network, + if the port contains a remote interface attachment, + the remote interface should first be un-plugged and + then the port can be deleted. + """ + LOG.debug("UCSVICPlugin:delete_port() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + ucs_inventory = kwargs[const.UCS_INVENTORY] + chassis_id = kwargs[const.CHASSIS_ID] + blade_id = kwargs[const.BLADE_ID] + interface_dn = kwargs[const.BLADE_INTF_DN] + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + self._delete_port_profile(port_id, profile_name) + ucs_inventory.unreserve_blade_interface(self._ucsm_ip, chassis_id, + blade_id, interface_dn) + return udb.remove_portbinding(port_id) + + def update_port(self, tenant_id, net_id, port_id, **kwargs): + """ + Updates the state of a port on the specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:update_port() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + pass + + def get_port_details(self, tenant_id, net_id, port_id, **kwargs): + """ + This method allows the user to retrieve a remote interface + that is attached to this particular port. + """ + LOG.debug("UCSVICPlugin:get_port_details() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + return port_binding + + def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id, + **kwargs): + """ + Attaches a remote interface to the specified port on the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:plug_interface() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + old_vlan_name = port_binding[const.VLANNAME] + new_vlan_name = self._get_vlan_name_for_network(tenant_id, net_id) + new_vlan_id = self._get_vlan_id_for_network(tenant_id, net_id) + self._driver.change_vlan_in_profile(profile_name, old_vlan_name, + new_vlan_name, self._ucsm_ip, + self._ucsm_username, + self._ucsm_password) + return udb.update_portbinding(port_id, vlan_name=new_vlan_name, + vlan_id=new_vlan_id) + + def unplug_interface(self, tenant_id, net_id, port_id, **kwargs): + """ + Detaches a remote interface from the specified port on the + specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:unplug_interface() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + port_binding = udb.get_portbinding(port_id) + profile_name = port_binding[const.PORTPROFILENAME] + old_vlan_name = port_binding[const.VLANNAME] + new_vlan_name = conf.DEFAULT_VLAN_NAME + self._driver.change_vlan_in_profile(profile_name, old_vlan_name, + new_vlan_name, self._ucsm_ip, + self._ucsm_username, + self._ucsm_password) + return udb.update_portbinding(port_id, vlan_name=new_vlan_name, + vlan_id=conf.DEFAULT_VLAN_ID) + + def create_multiport(self, tenant_id, net_id_list, ports_num, + port_id_list, **kwargs): + """ + Creates a port on the specified Virtual Network. + """ + LOG.debug("UCSVICPlugin:create_multiport() called\n") + self._set_ucsm(kwargs[const.DEVICE_IP]) + qos = None + ucs_inventory = kwargs[const.UCS_INVENTORY] + least_rsvd_blade_dict = kwargs[const.LEAST_RSVD_BLADE_DICT] + chassis_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_CHASSIS] + blade_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_ID] + blade_data_dict = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_DATA] + port_binding_list = [] + for port_id, net_id in zip(port_id_list, net_id_list): + new_port_profile = self._create_port_profile( + tenant_id, net_id, port_id, + conf.DEFAULT_VLAN_NAME, + conf.DEFAULT_VLAN_ID) + profile_name = new_port_profile[const.PROFILE_NAME] + rsvd_nic_dict = ucs_inventory.reserve_blade_interface( + self._ucsm_ip, chassis_id, + blade_id, blade_data_dict, + tenant_id, port_id, + profile_name) + port_binding = udb.update_portbinding( + port_id, + portprofile_name=profile_name, + vlan_name=conf.DEFAULT_VLAN_NAME, + vlan_id=conf.DEFAULT_VLAN_ID, + qos=qos) + port_binding_list.append(port_binding) + return port_binding_list + + def detach_port(self, tenant_id, instance_id, instance_desc, **kwargs): + """ + Remove the association of the VIF with the dynamic vnic + """ + LOG.debug("detach_port() called\n") + port_id = kwargs[const.PORTID] + kwargs.pop(const.PORTID) + return self.unplug_interface(tenant_id, None, port_id, **kwargs) + + def _get_profile_name(self, port_id): + """Returns the port profile name based on the port UUID""" + profile_name = conf.PROFILE_NAME_PREFIX + cutil.get16ByteUUID(port_id) + return profile_name + + def _get_vlan_name_for_network(self, tenant_id, network_id): + """Return the VLAN name as set by the L2 network plugin""" + vlan_binding = cdb.get_vlan_binding(network_id) + return vlan_binding[const.VLANNAME] + + def _get_vlan_id_for_network(self, tenant_id, network_id): + """Return the VLAN id as set by the L2 network plugin""" + vlan_binding = cdb.get_vlan_binding(network_id) + return vlan_binding[const.VLANID] + + def _create_port_profile(self, tenant_id, net_id, port_id, vlan_name, + vlan_id): + """Create port profile in UCSM""" + if self._port_profile_counter >= int(conf.MAX_UCSM_PORT_PROFILES): + raise cexc.UCSMPortProfileLimit(net_id=net_id, port_id=port_id) + profile_name = self._get_profile_name(port_id) + self._driver.create_profile(profile_name, vlan_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + self._port_profile_counter += 1 + new_port_profile = {const.PROFILE_NAME: profile_name, + const.PROFILE_VLAN_NAME: vlan_name, + const.PROFILE_VLAN_ID: vlan_id} + return new_port_profile + + def _delete_port_profile(self, port_id, profile_name): + """Delete port profile in UCSM""" + self._driver.delete_profile(profile_name, self._ucsm_ip, + self._ucsm_username, self._ucsm_password) + self._port_profile_counter -= 1 + + def _set_ucsm(self, ucsm_ip): + """Set the UCSM IP, username, and password""" + self._ucsm_ip = ucsm_ip + self._ucsm_username = cred.Store.get_username(conf.UCSM_IP_ADDRESS) + self._ucsm_password = cred.Store.get_password(conf.UCSM_IP_ADDRESS)