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/plugins.ini and edit the "provider" entry to point - to the L2Network-plugin: +2. Edit quantum/etc/quantum.conf and edit the "core_plugin" for v2 API -provider = quantum.plugins.cisco.l2network_plugin.L2Network +core_plugin = quantum.plugins.cisco.network_plugin.PluginV2 + + OR for v1.1 API + +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)