Initial checkin for the L2-Network Plugin with all the associated modules and artifacts.

This commit is contained in:
Sumit Naiksatam
2011-07-08 09:34:04 -07:00
parent 5d23ba2598
commit d365ae3ac1
13 changed files with 1563 additions and 1 deletions

View File

@@ -1,3 +1,3 @@
[PLUGIN]
# Quantum plugin provider module
provider = quantum.plugins.SamplePlugin.FakePlugin
provider = quantum.plugins.cisco.l2network_plugin.L2Network

View File

@@ -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

View File

@@ -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

View File

@@ -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"

View File

@@ -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()

View File

@@ -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")

View File

@@ -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

View File

@@ -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()

View File

@@ -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()

View File

@@ -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

View File

@@ -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()

View File

@@ -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

View File

@@ -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