]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Initial checkin for the L2-Network Plugin with all the associated modules and artifacts.
authorSumit Naiksatam <snaiksat@cisco.com>
Fri, 8 Jul 2011 16:34:04 +0000 (09:34 -0700)
committerSumit Naiksatam <snaiksat@cisco.com>
Fri, 8 Jul 2011 16:34:04 +0000 (09:34 -0700)
13 files changed:
quantum/plugins.ini
quantum/plugins/cisco/README [new file with mode: 0644]
quantum/plugins/cisco/cisco_configuration.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_constants.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_credentials.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_exceptions.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_nexus_plugin.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_ucs.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_ucs_network_driver.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_ucs_plugin.py [new file with mode: 0644]
quantum/plugins/cisco/cisco_utils.py [new file with mode: 0644]
quantum/plugins/cisco/get-vif.sh [new file with mode: 0755]
quantum/plugins/cisco/l2network_plugin.py [new file with mode: 0644]

index 307d2b48d2c7b167a10112cf1990eb0441ff579f..60db782d0c41133d94ee39b6a5024cbd1527d1e8 100644 (file)
@@ -1,3 +1,3 @@
 [PLUGIN]
 # Quantum plugin provider module
-provider = quantum.plugins.SamplePlugin.FakePlugin
+provider = quantum.plugins.cisco.l2network_plugin.L2Network
diff --git a/quantum/plugins/cisco/README b/quantum/plugins/cisco/README
new file mode 100644 (file)
index 0000000..fcc5001
--- /dev/null
@@ -0,0 +1,78 @@
+ L2 Network Plugin
+==================
+
+*** Current support for UCS (blade servers) with M81KR VIC (Palo) for 802.1Qbh ***
+
+** Pre-requisities
+* UCS B200 series blades with M81KR VIC installed.
+* UCSM 2.0 (Capitola) Build 230
+* RHEL 6.1
+* UCS & VIC installation (support for KVM) - please consult the accompanying installation guide available at:
+http://wikicentral.cisco.com/display/GROUP/SAVBU+Palo+VM-FEX+for+Linux+KVM
+* To run Quantum on RHEL, you will need to have the correct version of python-routes (version 1.12.3 or later). The RHEL 6.1 package contains an older version. Do the following and check your python-routes version:
+rpm -qav | grep "python-routes"
+
+If it's an older version, you will need to upgrade to 1.12.3 or later. One quick way to do it as by adding the following to your /etc/yum.repos.d/openstack.repo (assuming that you had installed OpenStack on this host, and hence had this repo; else you could add to any other operational repo config), and then update the python-routes package. That should get you the python-routes-1.12.3-2.el6.noarch package.
+
+[openstack-deps]
+name=OpenStack Nova Compute Dependencies
+baseurl=http://yum.griddynamics.net/yum/cactus/deps
+enabled=1
+gpgcheck=1
+gpgkey=file:///etc/pki/rpm-gpg/RPM-GPG-KEY-OPENSTACK
+
+
+** Plugin Installation Instructions:
+* Make a backup copy of quantum/quantum/plugins.ini, and edit the file to remove all exisiting entries and add the following entry:
+provider = quantum.plugins.cisco.l2network_plugin.L2Network
+* You should have the following files in quantum/quantum/plugins/cisco directory (if you have pulled the Cisco Quantum branch, you will already have them):
+l2network_plugin.py
+cisco_configuration.py
+cisco_constants.py
+cisco_credentials.py
+cisco_exceptions.py
+cisco_nexus_network_driver.py
+cisco_ucs_network_driver.py
+cisco_ucs_plugin.py
+cisco_utils.py
+__init__.py
+get-vif.sh
+* Configure the L2 Network Pllugin:
+       + In cisco_configuration.py, 
+               - change the UCSM IP in the following statement to your UCSM IP
+                       flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of UCSM')
+               - change the Nova MySQL DB IP if you are running Quantum on a different host than the OpenStack Cloud Controller (in other words you do not need to change the IP if Quantum is running on the same host on which the Nova DB is running). DB IP is changed in the following statement:
+                       flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server')
+               - change the hostname of the OpenStack Cloud Controller below
+                       flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname')
+               - change the name of the OpenStack project
+                       flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova')
+               - change the start range of the VLAN (if you are not sure about this number, leave this unchanged)
+                       flags.DEFINE_string('vlan_start', "100", 'This is the start value of the allowable VLANs')
+               - change the end range of the VLAN (if you are not sure about this number, leave this unchanged)
+                       flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the allowable VLANs')
+               - unless you have VLANs created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters.
+                       flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given to the VLAN')
+               - unless you have Port Profiles created in UCSM which start with the name "q-", you do not need to change the following property. If you do need to change it, change "q-" to some other string. Do not use more than 6 characters.
+                       flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name given to the port profile')
+               - Change the path to reflect the location of the get-vif.sh script, if you have followed the instructions in this README, this location should be the same as that of your other plugin modules
+                        flags.DEFINE_string('get_next_vif', "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh", 'This is the location of the script to get the next available dynamic nic')
+       + In cisco_credentials.py, 
+               - Change the following stucture to reflect the correct UCS and Nova DB details. Your UCSM_IP_ADDRESS has to match the ucsmm_ip_addresss  which you provided in the cisco_configuration file earlier. Similarly, your NOVA_DATABSE_IP has to match the db_server_ip which you provided earlier. DB_USERNAME and DB_PASSWORD are those which you provided for the Nova MySQL DB when you setup OpenStack
+                       _creds_dictionary = {
+                               'UCSM_IP_ADDRESS':["UCSM_USERNAME", "UCSM_PASSWORD"],
+                               'NOVA_DATABASE_IP':["DB_USERNAME", "DB_PASSWORD"]
+                       }
+* Start the Quantum service
+
+** Additional installation required on Nova Compute:
+* Create DB Table in Nova DB (On the Cloud Controller)
+mysql -uroot -p<mysql_password_here> nova -e 'create table ports (port_id VARCHAR(255) primary key, profile_name VARCHAR(255), dynamic_vnic VARCHAR(255), host VARCHAR(255), instance_name VARCHAR(255), instance_nic_name VARCHAR(255), used tinyint(1));'
+
+* Replace the following files with the files from the Cisco Nova branch:
+/usr/lib/python2.6/site-packages/nova/virt/libvirt_conn.py
+
+* Add the following files from the Cisco Nova branch:
+/usr/lib/python2.6/site-packages/nova/virt/cisco_ucs.py
+
+* Restart nova-compute service
diff --git a/quantum/plugins/cisco/cisco_configuration.py b/quantum/plugins/cisco/cisco_configuration.py
new file mode 100644 (file)
index 0000000..5ab2aaf
--- /dev/null
@@ -0,0 +1,87 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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 quantum.common import flags
+
+# Note: All configuration values defined here are strings
+FLAGS = flags.FLAGS
+#
+# TODO (Sumit): The following are defaults, but we also need to add to config
+# file
+#
+flags.DEFINE_string('ucsm_ip_address', "172.20.231.27", 'IP address of \
+                    UCSM')
+flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB \
+                    server')
+flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud \
+                    controller hostname')
+
+flags.DEFINE_string('db_name', "nova", 'DB name')
+flags.DEFINE_string('vlan_name_prefix', "q-", 'Prefix of the name given \
+                    to the VLAN')
+flags.DEFINE_string('profile_name_prefix', "q-", 'Prefix of the name \
+                    given to the port profile')
+flags.DEFINE_string('vlan_start', "100", 'This is the start value of the \
+                    allowable VLANs')
+flags.DEFINE_string('vlan_end', "3000", 'This is the end value of the \
+                    allowable VLANs')
+flags.DEFINE_string('default_vlan_name', "default", 'This is the name of \
+                    the VLAN which will be associated with the port profile \
+                    when it is created, by default the VMs will be on this \
+                    VLAN, until attach is called')
+flags.DEFINE_string('default_vlan_id', "1", 'This is the name of the VLAN \
+                    which will be associated with the port profile when it \
+                    is created, by default the VMs will be on this VLAN, \
+                    until attach is called')
+flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova')
+#
+# TODO (Sumit): SAVBU to provide the accurate number below
+#
+flags.DEFINE_string('max_ucsm_port_profiles', "1024", 'This is the maximum \
+                    number port profiles that can be handled by one UCSM.')
+flags.DEFINE_string('max_port_profiles', "65568", 'This is the maximum \
+                    number port profiles that can be handled by Cisco \
+                    plugin. Currently this is just an arbitrary number.')
+flags.DEFINE_string('max_networks', "65568", 'This is the maximum number \
+                    of networks that can be handled by Cisco plugin. \
+                    Currently this is just an arbitrary number.')
+
+flags.DEFINE_string('get_next_vif',
+                    "/root/sumit/quantum/quantum/plugins/cisco/get-vif.sh",
+                    'This is the location of the script to get the next \
+                    next available dynamic nic')
+
+# Inventory items
+UCSM_IP_ADDRESS = FLAGS.ucsm_ip_address
+DB_SERVER_IP = FLAGS.db_server_ip
+NOVA_HOST_NAME = FLAGS.nova_host_name
+
+# General configuration items
+DB_NAME = FLAGS.db_name
+VLAN_NAME_PREFIX = FLAGS.vlan_name_prefix
+PROFILE_NAME_PREFIX = FLAGS.profile_name_prefix
+VLAN_START = FLAGS.vlan_start
+VLAN_END = FLAGS.vlan_end
+DEFAULT_VLAN_NAME = FLAGS.default_vlan_name
+DEFAULT_VLAN_ID = FLAGS.default_vlan_id
+NOVA_PROJ_NAME = FLAGS.nova_proj_name
+MAX_UCSM_PORT_PROFILES = FLAGS.max_ucsm_port_profiles
+MAX_PORT_PROFILES = FLAGS.max_port_profiles
+MAX_NETWORKS = FLAGS.max_networks
+
+GET_NEXT_VIF_SCRIPT = FLAGS.get_next_vif
diff --git a/quantum/plugins/cisco/cisco_constants.py b/quantum/plugins/cisco/cisco_constants.py
new file mode 100644 (file)
index 0000000..115addf
--- /dev/null
@@ -0,0 +1,46 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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.
+#
+
+PORT_STATE = 'port-state'
+PORT_UP = "UP"
+PORT_DOWN = "DOWN"
+
+ATTACHMENT = 'attachment'
+PORT_ID = 'port-id'
+
+NET_ID = 'net-id'
+NET_NAME = 'net-name'
+NET_PORTS = 'net-ports'
+NET_VLAN_NAME = 'net-vlan-name'
+NET_VLAN_ID = 'net-vlan-id'
+NET_TENANTS = 'net-tenants'
+
+TENANT_ID = 'tenant-id'
+TENANT_NETWORKS = 'tenant-networks'
+TENANT_NAME = 'tenant-name'
+TENANT_PORTPROFILES = 'tenant-portprofiles'
+
+PORT_PROFILE = 'port-profile'
+PROFILE_ID = 'profile-id'
+PROFILE_NAME = 'profile-name'
+PROFILE_VLAN_NAME = 'profile-vlan-name'
+PROFILE_VLAN_ID = 'profile-vlan-id'
+PROFILE_QOS = 'profile-qos'
+
+LOGGER_COMPONENT_NAME = "cisco_plugin"
diff --git a/quantum/plugins/cisco/cisco_credentials.py b/quantum/plugins/cisco/cisco_credentials.py
new file mode 100644 (file)
index 0000000..c37d00f
--- /dev/null
@@ -0,0 +1,73 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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.plugins.cisco import cisco_constants as const
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+_creds_dictionary = {
+    '172.20.231.27': ["admin", "c3l12345"],
+    '127.0.0.1': ["root", "nova"]
+}
+
+
+class Store(object):
+    # The format for this store is {"ip-address" :{"username", "password"}}
+    def __init__(self):
+        pass
+
+    @staticmethod
+    def putId(id):
+        _creds_dictionary[id] = []
+
+    @staticmethod
+    def putUsername(id, username):
+        creds = _creds_dictionary.get(id)
+        creds.insert(0, username)
+
+    @staticmethod
+    def putPassword(id, password):
+        creds = _creds_dictionary.get(id)
+        creds.insert(1, password)
+
+    @staticmethod
+    def getUsername(id):
+        creds = _creds_dictionary.get(id)
+        return creds[0]
+
+    @staticmethod
+    def getPassword(id):
+        creds = _creds_dictionary.get(id)
+        return creds[1]
+
+
+def main():
+    LOG.debug("username %s\n" % Store.getUsername("172.20.231.27"))
+    LOG.debug("password %s\n" % Store.getPassword("172.20.231.27"))
+    Store.putId("192.168.1.1")
+    Store.putUsername("192.168.1.1", "guest-username")
+    Store.putPassword("192.168.1.1", "guest-password")
+    LOG.debug("username %s\n" % Store.getUsername("192.168.1.1"))
+    LOG.debug("password %s\n" % Store.getPassword("192.168.1.1"))
+
+if __name__ == '__main__':
+    main()
diff --git a/quantum/plugins/cisco/cisco_exceptions.py b/quantum/plugins/cisco/cisco_exceptions.py
new file mode 100644 (file)
index 0000000..2829329
--- /dev/null
@@ -0,0 +1,52 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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.
+#
+
+"""
+Exceptions used by the Cisco plugin
+"""
+
+from quantum.common import exceptions
+
+
+class NoMoreNics(exceptions.QuantumException):
+    message = _("Unable to complete operation on port %(port_id)s " \
+                "for network %(net_id)s. No more dynamic nics are available" \
+                "in the system.")
+
+
+class PortProfileLimit(exceptions.QuantumException):
+    message = _("Unable to complete operation on port %(port_id)s " \
+                "for network %(net_id)s. The system has reached the maximum" \
+                "limit of allowed port profiles.")
+
+
+class UCSMPortProfileLimit(exceptions.QuantumException):
+    message = _("Unable to complete operation on port %(port_id)s " \
+                "for network %(net_id)s. The system has reached the maximum" \
+                "limit of allowed UCSM port profiles.")
+
+
+class NetworksLimit(exceptions.QuantumException):
+    message = _("Unable to create new network. Number of networks" \
+                "for the system has exceeded the limit")
+
+
+class PortProfileNotFound(exceptions.QuantumException):
+    message = _("Port profile %(port_id)s could not be found " \
+                "for tenant %(tenant_id)s")
diff --git a/quantum/plugins/cisco/cisco_nexus_plugin.py b/quantum/plugins/cisco/cisco_nexus_plugin.py
new file mode 100644 (file)
index 0000000..545499f
--- /dev/null
@@ -0,0 +1,152 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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 import exceptions as exc
+from quantum.plugins.cisco import cisco_configuration as conf
+from quantum.plugins.cisco import cisco_constants as const
+from quantum.plugins.cisco import cisco_credentials as cred
+from quantum.plugins.cisco import cisco_exceptions as cexc
+from quantum.plugins.cisco import cisco_utils as cutil
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+
+class NexusPlugin(object):
+    _networks = {}
+
+    def __init__(self):
+        """
+        Initialize the Nexus driver here
+        """
+        pass
+
+    def get_all_networks(self, tenant_id):
+        """
+        Returns a dictionary containing all
+        <network_uuid, network_name> 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):
+        """
+        Create a VLAN in the switch, and configure the appropriate interfaces
+        for this VLAN
+        """
+        LOG.debug("NexusPlugin:create_network() called\n")
+        # TODO (Sumit): Call the nexus driver here to create the VLAN, and
+        # configure the appropriate interfaces
+        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):
+        """
+        Deletes a VLAN in the switch, and removes the VLAN configuration
+        from the relevant interfaces
+        """
+        LOG.debug("NexusPlugin:delete_network() called\n")
+        net = self._networks.get(net_id)
+        if net:
+            # TODO (Sumit): Call the nexus driver here to create the VLAN,
+            # and configure the appropriate interfaces
+            self._networks.pop(net_id)
+            return net
+        # Network not found
+        raise exc.NetworkNotFound(net_id=net_id)
+
+    def get_network_details(self, tenant_id, net_id):
+        """
+        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 rename_network(self, tenant_id, net_id, new_name):
+        """
+        Updates the symbolic name belonging to a particular
+        Virtual Network.
+        """
+        LOG.debug("NexusPlugin:rename_network() called\n")
+        network = self._get_network(tenant_id, net_id)
+        network[const.NET_NAME] = new_name
+        return network
+
+    def get_all_ports(self, tenant_id, net_id):
+        """
+        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):
+        """
+        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):
+        """
+        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):
+        """
+        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):
+        """
+        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):
+        """
+        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):
+        """
+        This is probably not applicable to the Nexus plugin.
+        Delete if not required.
+        """
+        LOG.debug("NexusPlugin:unplug_interface() called\n")
+
+    def _get_network(self, tenant_id, network_id):
+        network = self._networks.get(network_id)
+        if not network:
+            raise exc.NetworkNotFound(net_id=network_id)
+        return network
diff --git a/quantum/plugins/cisco/cisco_ucs.py b/quantum/plugins/cisco/cisco_ucs.py
new file mode 100644 (file)
index 0000000..0723da9
--- /dev/null
@@ -0,0 +1,102 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+#
+# @author: Sumit Naiksatam, Cisco Systems, Inc.
+#
+#
+
+import MySQLdb
+import sys, traceback
+
+from nova import flags
+from nova import log as logging
+
+FLAGS = flags.FLAGS
+LOG = logging.getLogger('nova.virt.libvirt_conn')
+#
+# TODO (Sumit): The following are defaults, but we might need to make it conf file driven as well
+#
+
+flags.DEFINE_string('db_server_ip', "127.0.0.1", 'IP address of nova DB server')
+flags.DEFINE_string('db_username', "root", 'DB username')
+flags.DEFINE_string('db_password', "nova", 'DB paswwprd')
+flags.DEFINE_string('db_name', "nova", 'DB name')
+flags.DEFINE_string('nova_proj_name', "demo", 'project created in nova')
+flags.DEFINE_string('nova_host_name', "openstack-0203", 'nova cloud controller hostname')
+
+class CiscoUCSComputeDriver(object):
+    def __init__(self):
+       pass
+    
+    def _get_db_connection(self):
+       self.db = MySQLdb.connect(FLAGS.db_server_ip, FLAGS.db_username, FLAGS.db_password, FLAGS.db_name)
+       return self.db
+       
+    def _execute_db_query(self, sql_query):
+       db = self._get_db_connection()
+       cursor = db.cursor()
+       try:
+               cursor.execute(sql_query)
+               results = cursor.fetchall()
+               db.commit()
+               print "DB query execution succeeded: %s" % sql_query
+       except:
+               db.rollback()
+               print "DB query execution failed: %s" % sql_query
+               traceback.print_exc()
+       db.close()
+       return results
+
+    def reserve_port(self, instance_name, instance_nic_name):
+       sql_query = "SELECT * from ports WHERE used='0'"
+       results = self._execute_db_query(sql_query)
+       if len(results) == 0:
+               print "No ports available/n"
+               return 0
+       else:
+               for row in results:
+                       port_id = row[0];
+                       sql_query = "UPDATE ports SET instance_name = '%s', instance_nic_name = '%s' WHERE port_id = '%s'" % (instance_name, instance_nic_name, port_id)
+                       results = self._execute_db_query(sql_query)
+                       return port_id;
+       return 0
+
+    def get_port_details(self, port_id):
+       port_details = {}
+       sql_query = "SELECT * from ports WHERE port_id='%s'" % (port_id)
+       results = self._execute_db_query(sql_query)
+       if len(results) == 0:
+               print "Could not fetch port from DB for port_id = %s/n" % port_id
+               return 
+       else:
+               for row in results:
+                       profile_name = row[1];
+                       dynamic_vnic = row[2];
+                       sql_query = "UPDATE ports SET used = %d WHERE port_id = '%s'" % (1, port_id)
+                       results = self._execute_db_query(sql_query)
+                       port_details = {'profile_name':profile_name, 'dynamic_vnic':dynamic_vnic}
+                       return port_details;
+
+    def release_port(self, instance_name, instance_nic_name):
+       sql_query = "SELECT * from ports WHERE instance_name='%s' and instance_nic_name='%s'" % (instance_name, instance_nic_name)
+       results = self._execute_db_query(sql_query)
+       if len(results) == 0:
+               print "No matching ports found for releasing/n"
+               return 0
+       else:
+               for row in results:
+                       port_id = row[0];
+                       sql_query = "UPDATE ports SET instance_name = NULL, instance_nic_name = NULL, used = 0  WHERE port_id = '%s'" % (port_id)
+                       results = self._execute_db_query(sql_query)
+                       return port_id;
+       return 0
+
+def main():
+       client = CiscoUCSComputeDriver()
+       port_id = client.reserve_port("instance-1", "eth1")
+       port_details = client.get_port_details(port_id)
+       print "profile_name %s dynamic_vnic %s\n" % (port_details['profile_name'], port_details['dynamic_vnic'])
+       port_id = client.release_port("instance-1", "eth1")
+
+if __name__ == '__main__': 
+       main() 
diff --git a/quantum/plugins/cisco/cisco_ucs_network_driver.py b/quantum/plugins/cisco/cisco_ucs_network_driver.py
new file mode 100644 (file)
index 0000000..ca912ed
--- /dev/null
@@ -0,0 +1,256 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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.
+#
+"""
+Implements a UCSM XML API Client
+"""
+
+import httplib
+import logging as LOG
+import string
+import subprocess
+from xml.etree import ElementTree as et
+import urllib
+
+from quantum.plugins.cisco import cisco_configuration as conf
+from quantum.plugins.cisco import cisco_constants as const
+from quantum.plugins.cisco import cisco_exceptions as cexc
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+COOKIE_VALUE = "cookie_placeholder"
+PROFILE_NAME = "profilename_placeholder"
+PROFILE_CLIENT = "profileclient_placeholder"
+VLAN_NAME = "vlanname_placeholder"
+VLAN_ID = "vlanid_placeholder"
+OLD_VLAN_NAME = "old_vlanname_placeholder"
+DYNAMIC_NIC_PREFIX = "eth"
+
+# The following are standard strings, messages used to communicate with UCSM,
+#only place holder values change for each message
+HEADERS = {"Content-Type": "text/xml"}
+METHOD = "POST"
+URL = "/nuova"
+
+CREATE_VLAN = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"true\"> <inConfigs>" \
+"<pair key=\"fabric/lan/net-" + VLAN_NAME + \
+"\"> <fabricVlan defaultNet=\"no\" " \
+"dn=\"fabric/lan/net-" + VLAN_NAME + \
+"\" id=\"" + VLAN_ID + "\" name=\"" + \
+VLAN_NAME + "\" status=\"created\">" \
+"</fabricVlan> </pair> </inConfigs> </configConfMos>"
+
+CREATE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"true\"> <inConfigs>" \
+"<pair key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
+"\"> <vnicProfile descr=\"Profile created by " \
+"Cisco OpenStack Quantum Plugin\" " \
+"dn=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
+"\" maxPorts=\"64\" name=\"" + PROFILE_NAME + \
+"\" nwCtrlPolicyName=\"\" pinToGroupName=\"\" " \
+"qosPolicyName=\"\" status=\"created\"> " \
+"<vnicEtherIf defaultNet=\"yes\" name=\"" + VLAN_NAME + \
+"\" rn=\"if-" + VLAN_NAME + "\" > </vnicEtherIf> " \
+"</vnicProfile> </pair> </inConfigs> </configConfMos>"
+
+ASSOCIATE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"true\"> <inConfigs> <pair " \
+"key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
+"/cl-" + PROFILE_CLIENT + "\"> <vmVnicProfCl dcName=\".*\" " \
+"descr=\"\" dn=\"fabric/lan/profiles/vnic-" + \
+PROFILE_NAME + "/cl-" + PROFILE_CLIENT + \
+"\"name=\"" + PROFILE_CLIENT + "\" orgPath=\".*\" " \
+"status=\"created\" swName=\"default$\"> </vmVnicProfCl>" \
+"</pair> </inConfigs> </configConfMos>"
+
+CHANGE_VLAN_IN_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"true\"> <inConfigs>" \
+"<pair key=\"fabric/lan/profiles/vnic-" + \
+PROFILE_NAME + "\"> <vnicProfile descr=\"Profile " \
+"created by Cisco OpenStack Quantum Plugin\" " \
+"dn=\"fabric/lan/profiles/vnic-" + \
+PROFILE_NAME + "\" maxPorts=\"64\" name=\"" + \
+PROFILE_NAME + "\" nwCtrlPolicyName=\"\" " \
+"pinToGroupName=\"\" qosPolicyName=\"\" " \
+"status=\"created,modified\">" \
+"<vnicEtherIf rn=\"if-" + OLD_VLAN_NAME + \
+"\" status=\"deleted\"> </vnicEtherIf> <vnicEtherIf " \
+"defaultNet=\"yes\" name=\"" + \
+VLAN_NAME + "\" rn=\"if-" + VLAN_NAME + \
+"\" > </vnicEtherIf> </vnicProfile> </pair>" \
+"</inConfigs> </configConfMos>"
+
+DELETE_VLAN = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"true\"> <inConfigs>" \
+"<pair key=\"fabric/lan/net-" + VLAN_NAME + \
+"\"> <fabricVlan dn=\"fabric/lan/net-" + VLAN_NAME + \
+"\" status=\"deleted\"> </fabricVlan> </pair> </inConfigs>" \
+"</configConfMos>"
+
+DELETE_PROFILE = "<configConfMos cookie=\"" + COOKIE_VALUE + \
+"\" inHierarchical=\"false\"> <inConfigs>" \
+"<pair key=\"fabric/lan/profiles/vnic-" + PROFILE_NAME + \
+"\"> <vnicProfile dn=\"fabric/lan/profiles/vnic-" + \
+PROFILE_NAME + "\" status=\"deleted\"> </vnicProfile>" \
+"</pair> </inConfigs> </configConfMos>"
+
+
+class CiscoUCSMDriver():
+
+    def __init__(self):
+        pass
+
+    def _post_data(self, ucsm_ip, ucsm_username, ucsm_password, data):
+        conn = httplib.HTTPConnection(ucsm_ip)
+        login_data = "<aaaLogin inName=\"" + ucsm_username + \
+        "\" inPassword=\"" + ucsm_password + "\" />"
+        conn.request(METHOD, URL, login_data, HEADERS)
+        response = conn.getresponse()
+        response_data = response.read()
+        LOG.debug(response.status)
+        LOG.debug(response.reason)
+        LOG.debug(response_data)
+        # TODO (Sumit): If login is not successful, throw exception
+        xmlTree = et.XML(response_data)
+        cookie = xmlTree.attrib["outCookie"]
+
+        data = data.replace(COOKIE_VALUE, cookie)
+        LOG.debug("POST: %s" % data)
+        conn.request(METHOD, URL, data, HEADERS)
+        response = conn.getresponse()
+        response_data = response.read()
+        LOG.debug(response.status)
+        LOG.debug(response.reason)
+        LOG.debug("UCSM Response: %s" % response_data)
+
+        logout_data = "<aaaLogout inCookie=\"" + cookie + "\" />"
+        conn.request(METHOD, URL, logout_data, HEADERS)
+        response = conn.getresponse()
+        response_data = response.read()
+        LOG.debug(response.status)
+        LOG.debug(response.reason)
+        LOG.debug(response_data)
+
+    def _create_vlan_post_data(self, vlan_name, vlan_id):
+        data = CREATE_VLAN.replace(VLAN_NAME, vlan_name)
+        data = data.replace(VLAN_ID, vlan_id)
+        return data
+
+    def _create_profile_post_data(self, profile_name, vlan_name):
+        data = CREATE_PROFILE.replace(PROFILE_NAME, profile_name)
+        data = data.replace(VLAN_NAME, vlan_name)
+        return data
+
+    def _create_profile_client_post_data(self, profile_name):
+        data = ASSOCIATE_PROFILE.replace(PROFILE_NAME, profile_name)
+        data = data.replace(PROFILE_CLIENT, profile_name)
+        return data
+
+    def _change_vlan_in_profile_post_data(self, profile_name, old_vlan_name,
+                                          new_vlan_name):
+        data = CHANGE_VLAN_IN_PROFILE.replace(PROFILE_NAME, profile_name)
+        data = data.replace(OLD_VLAN_NAME, old_vlan_name)
+        data = data.replace(VLAN_NAME, new_vlan_name)
+        return data
+
+    def _delete_vlan_post_data(self, vlan_name):
+        data = DELETE_VLAN.replace(VLAN_NAME, vlan_name)
+        return data
+
+    def _delete_profile_post_data(self, profile_name):
+        data = DELETE_PROFILE.replace(PROFILE_NAME, profile_name)
+        return data
+
+    def _get_next_dynamic_nic(self):
+        # TODO (Sumit): following should be a call to a python module
+        # (which will in turn eliminate the reference to the path and script)
+        dynamic_nic_id = string.strip(subprocess.Popen(
+            conf.GET_NEXT_VIF_SCRIPT,
+            stdout=subprocess.PIPE).communicate()[0])
+        if len(dynamic_nic_id) > 0:
+            return dynamic_nic_id
+        else:
+            raise cisco_exceptions.NoMoreNics(net_id=net_id, port_id=port_id)
+
+    def create_vlan(self, vlan_name, vlan_id, ucsm_ip, ucsm_username,
+                    ucsm_password):
+        data = self._create_vlan_post_data(vlan_name, vlan_id)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+
+    def create_profile(self, profile_name, vlan_name, ucsm_ip, ucsm_username,
+                       ucsm_password):
+        data = self._create_profile_post_data(profile_name, vlan_name)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+        data = self._create_profile_client_post_data(profile_name)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+
+    def change_vlan_in_profile(self, profile_name, old_vlan_name,
+                               new_vlan_name, ucsm_ip, ucsm_username,
+                               ucsm_password):
+        data = self._change_vlan_in_profile_post_data(profile_name,
+                                                      old_vlan_name,
+                                                      new_vlan_name)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+
+    def get_dynamic_nic(self, host):
+        # TODO (Sumit): Check availability per host
+        # TODO (Sumit): If not available raise exception
+        # TODO (Sumit): This simple logic assumes that create-port and
+        #               spawn-VM happens in lock-step
+        #               But we should support multiple create-port calls,
+        #               followed by spawn-VM calls
+        #               That would require managing a pool of available
+        #               dynamic vnics per host
+        dynamic_nic_name = self._get_next_dynamic_nic()
+        LOG.debug("Reserving dynamic nic %s" % dynamic_nic_name)
+        return dynamic_nic_name
+
+    def delete_vlan(self, vlan_name, ucsm_ip, ucsm_username, ucsm_password):
+        data = self._delete_vlan_post_data(vlan_name)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+
+    def delete_profile(self, profile_name, ucsm_ip, ucsm_username,
+                       ucsm_password):
+        data = self._delete_profile_post_data(profile_name)
+        self._post_data(ucsm_ip, ucsm_username, ucsm_password, data)
+
+    def release_dynamic_nic(self, host):
+        # TODO (Sumit): Release on a specific host
+        pass
+
+
+def main():
+    client = CiscoUCSMDriver()
+    #client.create_vlan("quantum-vlan-3", "3","172.20.231.27","admin",
+    #                   "c3l12345")
+    #client.create_profile("q-prof-3", "quantum-vlan-3","172.20.231.27",
+    #                      "admin", "c3l12345")
+    #client.get_dynamic_nic("dummy")
+    #client.get_dynamic_nic("dummy")
+    #client.release_dynamic_nic("dummy")
+    #client.get_dynamic_nic("dummy")
+    #client.change_vlan_in_profile("br100", "default", "test-2",
+    #                              "172.20.231.27","admin",
+    #                              "c3l12345")
+    client.change_vlan_in_profile("br100", "test-2", "default",
+                                  "172.20.231.27", "admin", "c3l12345")
+
+if __name__ == '__main__':
+    main()
diff --git a/quantum/plugins/cisco/cisco_ucs_plugin.py b/quantum/plugins/cisco/cisco_ucs_plugin.py
new file mode 100644 (file)
index 0000000..52195a5
--- /dev/null
@@ -0,0 +1,294 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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 import exceptions as exc
+from quantum.plugins.cisco import cisco_configuration as conf
+from quantum.plugins.cisco import cisco_constants as const
+from quantum.plugins.cisco import cisco_credentials as cred
+from quantum.plugins.cisco import cisco_exceptions as cexc
+from quantum.plugins.cisco import cisco_ucs_network_driver
+from quantum.plugins.cisco import cisco_utils as cutil
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+
+class UCSVICPlugin(object):
+    _networks = {}
+
+    def __init__(self):
+        self._client = cisco_ucs_network_driver.CiscoUCSMDriver()
+        self._utils = cutil.DBUtils()
+        # TODO (Sumit) This is for now, when using only one chassis
+        self._ucsm_ip = conf.UCSM_IP_ADDRESS
+        self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS)
+        self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS)
+        # TODO (Sumit) Make the counter per UCSM
+        self._port_profile_counter = 0
+
+    def get_all_networks(self, tenant_id):
+        """
+        Returns a dictionary containing all
+        <network_uuid, network_name> for
+        the specified tenant.
+        """
+        LOG.debug("UCSVICPlugin:get_all_networks() called\n")
+        return self._networks.values()
+
+    def create_network(self, tenant_id, net_name, net_id, vlan_name, vlan_id):
+        """
+        Creates a new Virtual Network, and assigns it
+        a symbolic name.
+        """
+        LOG.debug("UCSVICPlugin:create_network() called\n")
+        self._client.create_vlan(vlan_name, str(vlan_id), self._ucsm_ip,
+                                 self._ucsm_username, self._ucsm_password)
+        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):
+        """
+        Deletes the network with the specified network identifier
+        belonging to the specified tenant.
+        """
+        LOG.debug("UCSVICPlugin:delete_network() called\n")
+        net = self._networks.get(net_id)
+        # TODO (Sumit) : Verify that no attachments are plugged into the
+        # network
+        if net:
+            # TODO (Sumit) : Before deleting the network, make sure all the
+            # ports associated with this network are also deleted
+            self._client.delete_vlan(net[const.NET_VLAN_NAME], self._ucsm_ip,
+                                     self._ucsm_username, self._ucsm_password)
+            self._networks.pop(net_id)
+            return net
+        raise exc.NetworkNotFound(net_id=net_id)
+
+    def get_network_details(self, tenant_id, net_id):
+        """
+        Deletes the Virtual Network belonging to a the
+        spec
+        """
+        LOG.debug("UCSVICPlugin:get_network_details() called\n")
+        network = self._get_network(tenant_id, net_id)
+        return network
+
+    def rename_network(self, tenant_id, net_id, new_name):
+        """
+        Updates the symbolic name belonging to a particular
+        Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:rename_network() called\n")
+        network = self._get_network(tenant_id, net_id)
+        network[const.NET_NAME] = new_name
+        return network
+
+    def get_all_ports(self, tenant_id, net_id):
+        """
+        Retrieves all port identifiers belonging to the
+        specified Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:get_all_ports() called\n")
+        network = self._get_network(tenant_id, net_id)
+        ports_on_net = network[const.NET_PORTS].values()
+        return ports_on_net
+
+    def create_port(self, tenant_id, net_id, port_state, port_id):
+        """
+        Creates a port on the specified Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:create_port() called\n")
+        net = self._get_network(tenant_id, net_id)
+        ports = net[const.NET_PORTS]
+        # TODO (Sumit): This works on a single host deployment,
+        # in multi-host environment, dummy needs to be replaced with the
+        # hostname
+        dynamic_nic_name = self._client.get_dynamic_nic("dummy")
+        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]
+        sql_query = "INSERT INTO ports (port_id, profile_name, dynamic_vnic," \
+        "host, instance_name, instance_nic_name, used) VALUES" \
+        "('%s', '%s', '%s', 'dummy', NULL, NULL, 0)" % \
+        (port_id, profile_name, dynamic_nic_name)
+        self._utils.execute_db_query(sql_query)
+        new_port_dict = {const.PORT_ID: port_id,
+                         const.PORT_STATE: const.PORT_UP,
+                         const.ATTACHMENT: None,
+                         const.PORT_PROFILE: new_port_profile}
+        ports[port_id] = new_port_dict
+        return new_port_dict
+
+    def delete_port(self, tenant_id, net_id, port_id):
+        """
+        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")
+        port = self._get_port(tenant_id, net_id, port_id)
+        if port[const.ATTACHMENT]:
+            raise exc.PortInUse(net_id=net_id, port_id=port_id,
+                                att_id=port[const.ATTACHMENT])
+        try:
+            #TODO (Sumit): Before deleting port profile make sure that there
+            # is no VM using this port profile
+            self._client.release_dynamic_nic("dummy")
+            port_profile = port[const.PORT_PROFILE]
+            self._delete_port_profile(port_id,
+                                      port_profile[const.PROFILE_NAME])
+            sql_query = "delete from ports where port_id = \"%s\"" % \
+            (port[const.PORT_ID])
+            self._utils.execute_db_query(sql_query)
+            net = self._get_network(tenant_id, net_id)
+            net[const.NET_PORTS].pop(port_id)
+        except KeyError:
+            raise exc.PortNotFound(net_id=net_id, port_id=port_id)
+
+    def update_port(self, tenant_id, net_id, port_id, port_state):
+        """
+        Updates the state of a port on the specified Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:update_port() called\n")
+        port = self._get_port(tenant_id, net_id, port_id)
+        self._validate_port_state(port_state)
+        port[const.PORT_STATE] = port_state
+        return port
+
+    def get_port_details(self, tenant_id, net_id, port_id):
+        """
+        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")
+        return self._get_port(tenant_id, net_id, port_id)
+
+    def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
+        """
+        Attaches a remote interface to the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:plug_interface() called\n")
+        self._validate_attachment(tenant_id, net_id, port_id,
+                                  remote_interface_id)
+        port = self._get_port(tenant_id, net_id, port_id)
+        if port[const.ATTACHMENT]:
+            raise exc.PortInUse(net_id=net_id, port_id=port_id,
+                                att_id=port[const.ATTACHMENT])
+        port[const.ATTACHMENT] = remote_interface_id
+        port_profile = port[const.PORT_PROFILE]
+        profile_name = port_profile[const.PROFILE_NAME]
+        old_vlan_name = port_profile[const.PROFILE_VLAN_NAME]
+        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._client.change_vlan_in_profile(profile_name, old_vlan_name,
+                                            new_vlan_name, self._ucsm_ip,
+                                            self._ucsm_username,
+                                            self._ucsm_password)
+        port_profile[const.PROFILE_VLAN_NAME] = new_vlan_name
+        port_profile[const.PROFILE_VLAN_ID] = new_vlan_id
+
+    def unplug_interface(self, tenant_id, net_id, port_id):
+        """
+        Detaches a remote interface from the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("UCSVICPlugin:unplug_interface() called\n")
+        port = self._get_port(tenant_id, net_id, port_id)
+        port[const.ATTACHMENT] = None
+        port_profile = port[const.PORT_PROFILE]
+        profile_name = port_profile[const.PROFILE_NAME]
+        old_vlan_name = port_profile[const.PROFILE_VLAN_NAME]
+        new_vlan_name = conf.DEFAULT_VLAN_NAME
+        self._client.change_vlan_in_profile(profile_name, old_vlan_name,
+                                            new_vlan_name, self._ucsm_ip,
+                                            self._ucsm_username,
+                                            self._ucsm_password)
+        port_profile[const.PROFILE_VLAN_NAME] = conf.DEFAULT_VLAN_NAME
+        port_profile[const.PROFILE_VLAN_ID] = conf.DEFAULT_VLAN_ID
+
+    def _get_profile_name(self, port_id):
+        profile_name = conf.PROFILE_NAME_PREFIX + port_id
+        return profile_name
+
+    def _validate_port_state(self, port_state):
+        if port_state.upper() not in (const.PORT_UP, const.PORT_DOWN):
+            raise exc.StateInvalid(port_state=port_state)
+        return True
+
+    def _validate_attachment(self, tenant_id, network_id, port_id,
+                             remote_interface_id):
+        network = self._get_network(tenant_id, network_id)
+        for port in network[const.NET_PORTS].values():
+            if port[const.ATTACHMENT] == remote_interface_id:
+                raise exc.AlreadyAttached(net_id=network_id,
+                                          port_id=port_id,
+                                          att_id=port[const.ATTACHMENT],
+                                          att_port_id=port[const.PORT_ID])
+
+    def _get_network(self, tenant_id, network_id):
+        network = self._networks.get(network_id)
+        if not network:
+            raise exc.NetworkNotFound(net_id=network_id)
+        return network
+
+    def _get_vlan_name_for_network(self, tenant_id, network_id):
+        net = self._get_network(tenant_id, network_id)
+        vlan_name = net[const.NET_VLAN_NAME]
+        return vlan_name
+
+    def _get_vlan_id_for_network(self, tenant_id, network_id):
+        net = self._get_network(tenant_id, network_id)
+        vlan_id = net[const.NET_VLAN_ID]
+        return vlan_id
+
+    def _get_port(self, tenant_id, network_id, port_id):
+        net = self._get_network(tenant_id, network_id)
+        port = net[const.NET_PORTS].get(port_id)
+        if not port:
+            raise exc.PortNotFound(net_id=network_id, port_id=port_id)
+        return port
+
+    def _create_port_profile(self, tenant_id, net_id, port_id, vlan_name,
+                             vlan_id):
+        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._client.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):
+        self._client.delete_profile(profile_name, self._ucsm_ip,
+                                    self._ucsm_username, self._ucsm_password)
+        self._port_profile_counter -= 1
diff --git a/quantum/plugins/cisco/cisco_utils.py b/quantum/plugins/cisco/cisco_utils.py
new file mode 100644 (file)
index 0000000..bd0b257
--- /dev/null
@@ -0,0 +1,59 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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 MySQLdb
+import logging as LOG
+import sys
+import traceback
+
+from quantum.common import exceptions as exc
+from quantum.plugins.cisco import cisco_configuration as conf
+from quantum.plugins.cisco import cisco_constants as const
+from quantum.plugins.cisco import cisco_credentials as cred
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+
+class DBUtils(object):
+
+    def __init__(self):
+        pass
+
+    def _get_db_connection(self):
+        db_ip = conf.DB_SERVER_IP
+        db_username = cred.Store.getUsername(db_ip)
+        db_password = cred.Store.getPassword(db_ip)
+        self.db = MySQLdb.connect(db_ip, db_username, db_password,
+                                  conf.DB_NAME)
+        return self.db
+
+    def execute_db_query(self, sql_query):
+        db = self._get_db_connection()
+        cursor = db.cursor()
+        try:
+            cursor.execute(sql_query)
+            results = cursor.fetchall()
+            db.commit()
+            LOG.debug("DB query execution succeeded: %s" % sql_query)
+        except:
+            db.rollback()
+            LOG.debug("DB query execution failed: %s" % sql_query)
+            traceback.print_exc()
+            db.close()
diff --git a/quantum/plugins/cisco/get-vif.sh b/quantum/plugins/cisco/get-vif.sh
new file mode 100755 (executable)
index 0000000..d424b5f
--- /dev/null
@@ -0,0 +1,15 @@
+#!/bin/bash
+eths=`ifconfig -a | grep eth | cut -f1 -d " "`
+for eth in $eths; do
+        bdf=`ethtool -i $eth | grep bus-info | cut -f2 -d " "`
+        deviceid=`lspci -n -s $bdf | cut -f4 -d ":" | cut -f1 -d " "`
+        if [ $deviceid = "0044" ]; then
+                used=`/sbin/ip link show $eth | grep "UP"`
+                avail=$?
+                if [ $avail -eq 1 ]; then
+                        echo $eth
+                        exit
+                fi
+        fi
+done
+
diff --git a/quantum/plugins/cisco/l2network_plugin.py b/quantum/plugins/cisco/l2network_plugin.py
new file mode 100644 (file)
index 0000000..e98344e
--- /dev/null
@@ -0,0 +1,348 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2011 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 import exceptions as exc
+from quantum.plugins.cisco import cisco_configuration as conf
+from quantum.plugins.cisco import cisco_constants as const
+from quantum.plugins.cisco import cisco_credentials as cred
+from quantum.plugins.cisco import cisco_exceptions as cexc
+from quantum.plugins.cisco import cisco_nexus_plugin
+from quantum.plugins.cisco import cisco_ucs_plugin
+from quantum.plugins.cisco import cisco_utils as cutil
+
+LOG.basicConfig(level=LOG.WARN)
+LOG.getLogger(const.LOGGER_COMPONENT_NAME)
+
+
+class L2Network(object):
+    _networks = {}
+    _tenants = {}
+    _portprofiles = {}
+
+    def __init__(self):
+        self._net_counter = 0
+        self._portprofile_counter = 0
+        self._vlan_counter = int(conf.VLAN_START) - 1
+        self._ucs_plugin = cisco_ucs_plugin.UCSVICPlugin()
+        self._nexus_plugin = cisco_nexus_plugin.NexusPlugin()
+
+    """
+    Core API implementation
+    """
+    def get_all_networks(self, tenant_id):
+        """
+        Returns a dictionary containing all
+        <network_uuid, network_name> for
+        the specified tenant.
+        """
+        LOG.debug("get_all_networks() called\n")
+        return self._networks.values()
+
+    def create_network(self, tenant_id, net_name):
+        """
+        Creates a new Virtual Network, and assigns it
+        a symbolic name.
+        """
+        LOG.debug("create_network() called\n")
+        new_net_id = self._get_unique_net_id(tenant_id)
+        vlan_id = self._get_vlan_for_tenant(tenant_id, net_name)
+        vlan_name = self._get_vlan_name(new_net_id, str(vlan_id))
+        self._nexus_plugin.create_network(tenant_id, net_name, new_net_id,
+                                          vlan_name, vlan_id)
+        self._ucs_plugin.create_network(tenant_id, net_name, new_net_id,
+                                        vlan_name, vlan_id)
+        new_net_dict = {const.NET_ID: new_net_id,
+                        const.NET_NAME: net_name,
+                        const.NET_PORTS: {},
+                        const.NET_VLAN_NAME: vlan_name,
+                        const.NET_VLAN_ID: vlan_id,
+                        const.NET_TENANTS: [tenant_id]}
+        self._networks[new_net_id] = new_net_dict
+        tenant = self._get_tenant(tenant_id)
+        tenant_networks = tenant[const.TENANT_NETWORKS]
+        tenant_networks[new_net_id] = new_net_dict
+        return new_net_dict
+
+    def delete_network(self, tenant_id, net_id):
+        """
+        Deletes the network with the specified network identifier
+        belonging to the specified tenant.
+        """
+        LOG.debug("delete_network() called\n")
+        net = self._networks.get(net_id)
+        # TODO (Sumit) : Verify that no attachments are plugged into the
+        # network
+        if net:
+            # TODO (Sumit) : Before deleting the network, make sure all the
+            # ports associated with this network are also deleted
+            self._nexus_plugin.delete_network(tenant_id, net_id)
+            self._ucs_plugin.delete_network(tenant_id, net_id)
+            self._networks.pop(net_id)
+            tenant = self._get_tenant(tenant_id)
+            tenant_networks = tenant[const.TENANT_NETWORKS]
+            tenant_networks.pop(net_id)
+            return net
+        # Network not found
+        raise exc.NetworkNotFound(net_id=net_id)
+
+    def get_network_details(self, tenant_id, net_id):
+        """
+        Deletes the Virtual Network belonging to a the
+        spec
+        """
+        LOG.debug("get_network_details() called\n")
+        network = self._get_network(tenant_id, net_id)
+        return network
+
+    def rename_network(self, tenant_id, net_id, new_name):
+        """
+        Updates the symbolic name belonging to a particular
+        Virtual Network.
+        """
+        LOG.debug("rename_network() called\n")
+        self._nexus_plugin.rename_network(tenant_id, net_id)
+        self._ucs_plugin.rename_network(tenant_id, net_id)
+        network = self._get_network(tenant_id, net_id)
+        network[const.NET_NAME] = new_name
+        return network
+
+    def get_all_ports(self, tenant_id, net_id):
+        """
+        Retrieves all port identifiers belonging to the
+        specified Virtual Network.
+        """
+        LOG.debug("get_all_ports() called\n")
+        network = self._get_network(tenant_id, net_id)
+        ports_on_net = network[const.NET_PORTS].values()
+        return ports_on_net
+
+    def create_port(self, tenant_id, net_id, port_state=None):
+        """
+        Creates a port on the specified Virtual Network.
+        """
+        LOG.debug("create_port() called\n")
+        net = self._get_network(tenant_id, net_id)
+        ports = net[const.NET_PORTS]
+        unique_port_id_string = self._get_unique_port_id(tenant_id, net_id)
+        self._ucs_plugin.create_port(tenant_id, net_id, port_state,
+                                     unique_port_id_string)
+        new_port_dict = {const.PORT_ID: unique_port_id_string,
+                         const.PORT_STATE: const.PORT_UP,
+                         const.ATTACHMENT: None}
+        ports[unique_port_id_string] = new_port_dict
+        return new_port_dict
+
+    def delete_port(self, tenant_id, net_id, port_id):
+        """
+        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("delete_port() called\n")
+        port = self._get_port(tenant_id, net_id, port_id)
+        if port[const.ATTACHMENT]:
+            raise exc.PortInUse(net_id=net_id, port_id=port_id,
+                                att_id=port[const.ATTACHMENT])
+        try:
+            #TODO (Sumit): Before deleting port profile make sure that there
+            # is no VM using this port profile
+            self._ucs_plugin.delete_port(tenant_id, net_id, port_id)
+            net = self._get_network(tenant_id, net_id)
+            net[const.NET_PORTS].pop(port_id)
+        except KeyError:
+            raise exc.PortNotFound(net_id=net_id, port_id=port_id)
+
+    def update_port(self, tenant_id, net_id, port_id, port_state):
+        """
+        Updates the state of a port on the specified Virtual Network.
+        """
+        LOG.debug("update_port() called\n")
+        port = self._get_port(tenant_id, net_id, port_id)
+        self._validate_port_state(port_state)
+        port[const.PORT_STATE] = port_state
+        return port
+
+    def get_port_details(self, tenant_id, net_id, port_id):
+        """
+        This method allows the user to retrieve a remote interface
+        that is attached to this particular port.
+        """
+        LOG.debug("get_port_details() called\n")
+        return self._get_port(tenant_id, net_id, port_id)
+
+    def plug_interface(self, tenant_id, net_id, port_id,
+                       remote_interface_id):
+        """
+        Attaches a remote interface to the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("plug_interface() called\n")
+        self._validate_attachment(tenant_id, net_id, port_id,
+                                  remote_interface_id)
+        port = self._get_port(tenant_id, net_id, port_id)
+        if port[const.ATTACHMENT]:
+            raise exc.PortInUse(net_id=net_id, port_id=port_id,
+                                att_id=port[const.ATTACHMENT])
+        self._ucs_plugin.plug_interface(tenant_id, net_id, port_id,
+                                        remote_interface_id)
+        port[const.ATTACHMENT] = remote_interface_id
+
+    def unplug_interface(self, tenant_id, net_id, port_id):
+        """
+        Detaches a remote interface from the specified port on the
+        specified Virtual Network.
+        """
+        LOG.debug("unplug_interface() called\n")
+        port = self._get_port(tenant_id, net_id, port_id)
+        self._ucs_plugin.unplug_interface(tenant_id, net_id,
+                                          port_id)
+        port[const.ATTACHMENT] = None
+
+    """
+    Extension API implementation
+    """
+    def get_all_portprofiles(self, tenant_id):
+        return self._portprofiles.values()
+
+    def get_portprofile_details(self, tenant_id, profile_id):
+        return self._get_portprofile(tenant_id, profile_id)
+
+    def create_portprofile(self, tenant_id, profile_name, vlan_id):
+        profile_id = self._get_unique_profile_id(tenant_id)
+        new_port_profile_dict = {const.PROFILE_ID: profile_id,
+                                 const.PROFILE_NAME: profile_name,
+                                 const.PROFILE_VLAN_ID: vlan_id,
+                                 const.PROFILE_QOS: None}
+        self._portprofiles[profile_id] = new_port_profile_dict
+        tenant = self._get_tenant(tenant_id)
+        portprofiles = tenant[const.TENANT_PORTPROFILES]
+        portprofiles[profile_id] = new_port_profile_dict
+        return new_profile_dict
+
+    def delete_portprofile(self, tenant_id, profile_id):
+        portprofile = self._get_portprofile(tenant_id, profile_id)
+        self._portprofile.pop(profile_id)
+        tenant = self._get_tenant(tenant_id)
+        tenant[const.TENANT_PORTPROFILES].pop(profile_id)
+
+    def rename_portprofile(self, tenant_id, profile_id, new_name):
+        portprofile = self._get_portprofile(tenant_id, profile_id)
+        portprofile[const.PROFILE_NAME] = new_name
+        return portprofile
+
+    """
+    Private functions
+    """
+    def _get_vlan_for_tenant(self, tenant_id, net_name):
+        # TODO (Sumit):
+        # The VLAN ID for a tenant might need to be obtained from
+        # somewhere (from Donabe/Melange?)
+        # Also need to make sure that the VLAN ID is not being used already
+        # Currently, just a wrap-around counter ranging from VLAN_START to
+        # VLAN_END
+        self._vlan_counter += 1
+        self._vlan_counter %= int(conf.VLAN_END)
+        if self._vlan_counter < int(conf.VLAN_START):
+            self._vlan_counter = int(conf.VLAN_START)
+        return self._vlan_counter
+
+    def _get_vlan_name(self, net_id, vlan):
+        vlan_name = conf.VLAN_NAME_PREFIX + net_id + "-" + vlan
+        return vlan_name
+
+    def _validate_port_state(self, port_state):
+        if port_state.upper() not in (const.PORT_UP, const.PORT_DOWN):
+            raise exc.StateInvalid(port_state=port_state)
+        return True
+
+    def _validate_attachment(self, tenant_id, network_id, port_id,
+                             remote_interface_id):
+        network = self._get_network(tenant_id, network_id)
+        for port in network[const.NET_PORTS].values():
+            if port[const.ATTACHMENT] == remote_interface_id:
+                raise exc.AlreadyAttached(net_id=network_id,
+                                          port_id=port_id,
+                                          att_id=port[const.ATTACHMENT],
+                                          att_port_id=port[const.PORT_ID])
+
+    def _get_network(self, tenant_id, network_id):
+        network = self._networks.get(network_id)
+        if not network:
+            raise exc.NetworkNotFound(net_id=network_id)
+        return network
+
+    def _get_tenant(self, tenant_id):
+        tenant = self._tenants.get(tenant_id)
+        if not tenant:
+            LOG.debug("Creating new tenant record with tenant id %s\n" %
+                      tenant_id)
+            tenant = {const.TENANT_ID: tenant_id,
+                      const.TENANT_NAME: tenant_id,
+                      const.TENANT_NETWORKS: {},
+                      const.TENANT_PORTPROFILES: {}}
+            self._tenants[tenant_id] = tenant
+        return tenant
+
+    def _get_port(self, tenant_id, network_id, port_id):
+        net = self._get_network(tenant_id, network_id)
+        port = net[const.NET_PORTS].get(port_id)
+        if not port:
+            raise exc.PortNotFound(net_id=network_id, port_id=port_id)
+        return port
+
+    def _get_portprofile(self, tenant_id, portprofile_id):
+        portprofile = self._portprofiles.get(portprofile_id)
+        if not portprofile:
+            raise cexc.PortProfileNotFound(tenant_id=tenant_id,
+                                           profile_id=portprofile_id)
+        return portprofile
+
+    def _get_unique_net_id(self, tenant_id):
+        self._net_counter += 1
+        self._net_counter %= int(conf.MAX_NETWORKS)
+        id = tenant_id[:3] + \
+        "-n-" + ("0" * (6 - len(str(self._net_counter)))) + \
+        str(self._net_counter)
+        # TODO (Sumit): Need to check if the ID has already been allocated
+        return id
+
+    def _get_unique_port_id(self, tenant_id, net_id):
+        net = self._get_network(tenant_id, net_id)
+        ports = net[const.NET_PORTS]
+        if len(ports) == 0:
+            new_port_id = 1
+        else:
+            new_port_id = max(ports.keys()) + 1
+        id = net_id + "-p-" + str(new_port_id)
+        # TODO (Sumit): Need to check if the ID has already been allocated
+        return id
+
+    def _get_unique_profile_id(self, tenant_id):
+        self._portprofile_counter += 1
+        self._portprofile_counter %= int(conf.MAX_PORT_PROFILES)
+        id = tenant_id[:3] + "-pp-" + \
+        ("0" * (6 - len(str(self._net_counter)))) + str(self._net_counter)
+        # TODO (Sumit): Need to check if the ID has already been allocated
+        return id
+
+# TODO (Sumit):
+    # (1) Persistent storage