merge sumit's branch for lp837752
This commit is contained in:
commit
9b6efba5e9
116
extensions/multiport.py
Normal file
116
extensions/multiport.py
Normal file
@ -0,0 +1,116 @@
|
||||
"""
|
||||
# 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: Ying Liu, Cisco Systems, Inc.
|
||||
#
|
||||
"""
|
||||
import logging
|
||||
|
||||
from webob import exc
|
||||
|
||||
from quantum.api import api_common as common
|
||||
from quantum.api.views import ports as port_view
|
||||
from quantum.common import extensions
|
||||
from quantum.manager import QuantumManager
|
||||
from quantum.plugins.cisco.common import cisco_exceptions as exception
|
||||
from quantum.plugins.cisco.common import cisco_faults as faults
|
||||
|
||||
LOG = logging.getLogger('quantum.api.multiports')
|
||||
|
||||
|
||||
class Multiport(object):
|
||||
"""extension class multiport"""
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def get_name(cls):
|
||||
""" Returns Ext Resource Name """
|
||||
return "Cisco Multiport"
|
||||
|
||||
@classmethod
|
||||
def get_alias(cls):
|
||||
""" Returns Ext Resource Alias """
|
||||
return "Cisco Multiport"
|
||||
|
||||
@classmethod
|
||||
def get_description(cls):
|
||||
""" Returns Ext Resource Description """
|
||||
return "handle multiple ports in one call"
|
||||
|
||||
@classmethod
|
||||
def get_namespace(cls):
|
||||
""" Returns Ext Resource Namespace """
|
||||
return "http://docs.ciscocloud.com/api/ext/multiport/v1.0"
|
||||
|
||||
@classmethod
|
||||
def get_updated(cls):
|
||||
""" Returns Ext Resource Update Time """
|
||||
return "2011-08-25T13:25:27-06:00"
|
||||
|
||||
@classmethod
|
||||
def get_resources(cls):
|
||||
""" Returns Ext Resources """
|
||||
parent_resource = dict(member_name="tenant",
|
||||
collection_name="extensions/csco/tenants")
|
||||
controller = MultiportController(QuantumManager.get_plugin())
|
||||
return [extensions.ResourceExtension('multiport', controller,
|
||||
parent=parent_resource)]
|
||||
|
||||
|
||||
class MultiportController(common.QuantumController):
|
||||
""" multiport API controller
|
||||
based on QuantumController """
|
||||
|
||||
_multiport_ops_param_list = [{
|
||||
'param-name': 'net_id_list',
|
||||
'required': True}, {
|
||||
'param-name': 'status',
|
||||
'required': True}, {
|
||||
'param-name': 'ports_desc',
|
||||
'required': True}]
|
||||
|
||||
_serialization_metadata = {
|
||||
"application/xml": {
|
||||
"attributes": {
|
||||
"multiport": ["id", "name"],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self, plugin):
|
||||
self._resource_name = 'multiport'
|
||||
self._plugin = plugin
|
||||
|
||||
# pylint: disable-msg=E1101,W0613
|
||||
def create(self, request, tenant_id):
|
||||
""" Creates a new multiport for a given tenant """
|
||||
try:
|
||||
req_params = \
|
||||
self._parse_request_params(request,
|
||||
self._multiport_ops_param_list)
|
||||
except exc.HTTPError as exp:
|
||||
return faults.Fault(exp)
|
||||
multiports = self._plugin.\
|
||||
create_multiport(tenant_id,
|
||||
req_params['net_id_list'],
|
||||
req_params['status'],
|
||||
req_params['ports_desc'])
|
||||
builder = port_view.get_view_builder(request)
|
||||
result = [builder.build(port)['port']
|
||||
for port in multiports]
|
||||
return dict(ports=result)
|
@ -40,8 +40,7 @@ If you plan to just leverage the plugin framework, you do not need these.)
|
||||
* One or more UCS B200 series blade servers with M81KR VIC (aka
|
||||
Palo adapters) installed.
|
||||
* UCSM 2.0 (Capitola) Build 230 or above.
|
||||
* OpenStack Cactus release installation (additional patch is required,
|
||||
details follow in this document)
|
||||
* OpenStack Diablo D3 or later (should have VIF-driver support)
|
||||
* RHEL 6.1 (as of this writing, UCS only officially supports RHEL, but
|
||||
it should be noted that Ubuntu support is planned in coming releases as well)
|
||||
** Package: python-configobj-4.6.0-3.el6.noarch (or newer)
|
||||
@ -84,11 +83,13 @@ Then run "yum install python-routes".
|
||||
Module Structure:
|
||||
-----------------
|
||||
* quantum/plugins/cisco/ - Contains the L2-Network Plugin Framework
|
||||
/client - CLI module for core and extensions API
|
||||
/common - Modules common to the entire plugin
|
||||
/conf - All configuration files
|
||||
/db - Persistence framework
|
||||
/models - Class(es) which tie the logical abstractions
|
||||
to the physical topology
|
||||
/nova - Scheduler and VIF-driver to be used by Nova
|
||||
/nexus - Nexus-specific modules
|
||||
/segmentation - Implementation of segmentation manager,
|
||||
e.g. VLAN Manager
|
||||
@ -109,20 +110,37 @@ provider = quantum.plugins.cisco.l2network_plugin.L2Network
|
||||
Quantum-aware scheduler by editing the /etc/nova/nova.conf file with the
|
||||
following entries:
|
||||
|
||||
--scheduler_driver=quantum.plugins.cisco.nova.quantum_aware_scheduler.QuantumScheduler
|
||||
--scheduler_driver=quantum.plugins.cisco.nova.quantum_port_aware_scheduler.QuantumPortAwareScheduler
|
||||
--quantum_host=127.0.0.1
|
||||
--quantum_port=9696
|
||||
--libvirt_vif_driver=quantum.plugins.cisco.nova.vifdirect.Libvirt802dot1QbhDriver
|
||||
--libvirt_vif_type=802.1Qbh
|
||||
|
||||
|
||||
4. If you want to turn on support for Cisco Nexus switches:
|
||||
4a. Uncomment the nexus_plugin property in
|
||||
Note: To be able to bring up a VM on a UCS blade, you should first create a
|
||||
port for that VM using the Quantum create port API. VM creation will
|
||||
fail if an unused port is not available. If you have configured your
|
||||
Nova project with more than one network, Nova will attempt to instantiate
|
||||
the VM with one network interface (VIF) per configured network. To provide
|
||||
plugin points for each of these VIFs, you will need to create multiple
|
||||
Quantum ports, one for each of the networks, prior to starting the VM.
|
||||
However, in this case you will need to use the Cisco multiport extension
|
||||
API instead of the Quantum create port API. More details on using the
|
||||
multiport extension follow in the section on multi NIC support.
|
||||
|
||||
4. To support the above configuration, you will need some Quantum modules. It's easiest
|
||||
to copy the entire quantum directory from your quantum installation into:
|
||||
|
||||
/usr/lib/python2.6/site-packages/
|
||||
|
||||
This needs to be done for each nova compute node.
|
||||
|
||||
5. If you want to turn on support for Cisco Nexus switches:
|
||||
5a. Uncomment the nexus_plugin property in
|
||||
quantum/plugins/cisco/conf/plugins.ini to read:
|
||||
|
||||
nexus_plugin=quantum.plugins.cisco.nexus.cisco_nexus_plugin.NexusPlugin
|
||||
|
||||
4b. Enter the relevant configuration in the
|
||||
5b. Enter the relevant configuration in the
|
||||
quantum/plugins/cisco/conf/nexus.ini file. Example:
|
||||
|
||||
[SWITCH]
|
||||
@ -141,7 +159,7 @@ nexus_ssh_port=22
|
||||
[DRIVER]
|
||||
name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver
|
||||
|
||||
4c. Make sure that SSH host key of the Nexus switch is known to the
|
||||
5c. Make sure that SSH host key of the Nexus switch is known to the
|
||||
host on which you are running the Quantum service. You can do
|
||||
this simply by logging in to your Quantum host as the user that
|
||||
Quantum runs as and SSHing to the switch at least once. If the
|
||||
@ -149,47 +167,45 @@ name=quantum.plugins.cisco.nexus.cisco_nexus_network_driver.CiscoNEXUSDriver
|
||||
clearing of the SSH config on the switch), you may need to repeat
|
||||
this step and remove the old hostkey from ~/.ssh/known_hosts.
|
||||
|
||||
5. Plugin Persistence framework setup:
|
||||
5a. Create quantum_l2network database in mysql with the following command -
|
||||
6. Plugin Persistence framework setup:
|
||||
6a. Create quantum_l2network database in mysql with the following command -
|
||||
|
||||
mysql -u<mysqlusername> -p<mysqlpassword> -e "create database quantum_l2network"
|
||||
|
||||
5b. Enter the quantum_l2network database configuration info in the
|
||||
6b. Enter the quantum_l2network database configuration info in the
|
||||
quantum/plugins/cisco/conf/db_conn.ini file.
|
||||
|
||||
5c. If there is a change in the plugin configuration, service would need
|
||||
6c. If there is a change in the plugin configuration, service would need
|
||||
to be restarted after dropping and re-creating the database using
|
||||
the following commands -
|
||||
|
||||
mysql -u<mysqlusername> -p<mysqlpassword> -e "drop database quantum_l2network"
|
||||
mysql -u<mysqlusername> -p<mysqlpassword> -e "create database quantum_l2network"
|
||||
|
||||
6. Verify that you have the correct credentials for each IP address listed
|
||||
7. Verify that you have the correct credentials for each IP address listed
|
||||
in quantum/plugins/cisco/conf/credentials.ini. Example:
|
||||
|
||||
# Provide the UCSM credentials
|
||||
# Provide the UCSM credentials, create a separte entry for each UCSM used in your system
|
||||
# UCSM IP address, username and password.
|
||||
[10.0.0.2]
|
||||
username=admin
|
||||
password=mySecretPasswordForUCSM
|
||||
|
||||
# Provide the Nova DB credentials.
|
||||
# The IP address should be the same as in nova.ini.
|
||||
[10.0.0.3]
|
||||
username=nova
|
||||
password=mySecretPasswordForNova
|
||||
|
||||
# Provide the Nexus credentials, if you are using Nexus switches.
|
||||
# If not this will be ignored.
|
||||
[10.0.0.1]
|
||||
username=admin
|
||||
password=mySecretPasswordForNexus
|
||||
|
||||
7. Configure the UCS systems' information in your deployment by editing the
|
||||
In general, make sure that every UCSM and Nexus switch used in your system,
|
||||
has a credential entry in the above file. This is required for the system to
|
||||
be able to communicate with those switches.
|
||||
|
||||
8. Configure the UCS systems' information in your deployment by editing the
|
||||
quantum/plugins/cisco/conf/ucs_inventory.ini file. You can configure multiple
|
||||
UCSMs per deployment, multiple chasses per UCSM, and multiple blades per
|
||||
UCSMs per deployment, multiple chassis per UCSM, and multiple blades per
|
||||
chassis. Chassis ID and blade ID can be obtained from the UCSM (they will
|
||||
typically numbers like 1, 2, 3, etc.
|
||||
typically be numbers like 1, 2, 3, etc.)
|
||||
|
||||
[ucsm-1]
|
||||
ip_address = <put_ucsm_ip_address_here>
|
||||
@ -217,11 +233,132 @@ blade_id = <put_blade_id_here>
|
||||
host_name = <put_hostname_here>
|
||||
|
||||
|
||||
8. Start the Quantum service. If something doesn't work, verify that
|
||||
9. Start the Quantum service. If something doesn't work, verify that
|
||||
your configuration of each of the above files hasn't gone a little kaka.
|
||||
Once you've put right what once went wrong, leap on.
|
||||
|
||||
|
||||
Multi NIC support for VMs
|
||||
-------------------------
|
||||
As indicated earlier, if your Nova setup has a project with more than one network,
|
||||
Nova will try to create a vritual network interface (VIF) on the VM for each of those
|
||||
networks. That implies that,
|
||||
|
||||
(1) You should create the same number of networks in Quantum as in your Nova
|
||||
project.
|
||||
|
||||
(2) Before each VM is instantiated, you should create Quantum ports on each of those
|
||||
networks. These ports need to be created using the following rest call:
|
||||
|
||||
POST /v1.0/extensions/csco/tenants/{tenant_id}/multiport/
|
||||
|
||||
with request body:
|
||||
|
||||
{'multiport':
|
||||
{'status': 'ACTIVE',
|
||||
'net_id_list': net_id_list,
|
||||
'ports_desc': {'key': 'value'}}}
|
||||
|
||||
where,
|
||||
|
||||
net_id_list is a list of network IDs: [netid1, netid2, ...]. The "ports_desc" dictionary
|
||||
is reserved for later use. For now, the same structure in terms of the dictionary name, key
|
||||
and value should be used.
|
||||
|
||||
The corresponding CLI for this operation is as follows:
|
||||
|
||||
PYTHONPATH=. python quantum/plugins/cisco/client/cli.py create_multiport <tenant_id> <net_id1,net_id2,...>
|
||||
|
||||
(Note that you should not be using the create port core API in the above case.)
|
||||
|
||||
|
||||
Using the Command Line Client to work with this Plugin
|
||||
------------------------------------------------------
|
||||
A command line client is packaged with this plugin. This module can be used
|
||||
to invoke the core API as well as the extensions API, so that you don't have
|
||||
to switch between different CLI modules (it internally invokes the Quantum
|
||||
CLI module for the core APIs to ensure consistency when using either). This
|
||||
command line client can be invoked as follows:
|
||||
|
||||
PYTHONPATH=. python quantum/plugins/cisco/client/cli.py
|
||||
|
||||
1. Creating the network
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py create_net -H 10.10.2.6 demo net1
|
||||
Created a new Virtual Network with ID: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant demo
|
||||
|
||||
|
||||
2. Listing the networks
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py list_nets -H 10.10.2.6 demo
|
||||
Virtual Networks for Tenant demo
|
||||
Network ID: 0e85e924-6ef6-40c1-9f7a-3520ac6888b3
|
||||
Network ID: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
|
||||
|
||||
3. Creating one port on each of the networks
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py create_multiport -H 10.10.2.6 demo c4a2bea7-a528-4caf-b16e-80397cd1663a,0e85e924-6ef6-40c1-9f7a-3520ac6888b3
|
||||
Created ports: {u'ports': [{u'id': u'118ac473-294d-480e-8f6d-425acbbe81ae'}, {u'id': u'996e84b8-2ed3-40cf-be75-de17ff1214c4'}]}
|
||||
|
||||
|
||||
4. List all the ports on a network
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py list_ports -H 10.10.2.6 demo c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
Ports on Virtual Network: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant: demo
|
||||
Logical Port: 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
|
||||
|
||||
5. Show the details of a port
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py show_port -H 10.10.2.6 demo c4a2bea7-a528-4caf-b16e-80397cd1663a 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
Logical Port ID: 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
administrative State: ACTIVE
|
||||
interface: <none>
|
||||
on Virtual Network: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant: demo
|
||||
|
||||
|
||||
6. Start the VM instance using Nova
|
||||
Note that when using UCS and the 802.1Qbh features, the association of the
|
||||
VIF-ID (also referred to as interface ID) on the VM's NIC with a port will
|
||||
happen automatically when the VM is instantiated. At this point, doing a
|
||||
show_port will reveal the VIF-ID associated with the port.
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py show_port demo c4a2bea7-a528-4caf-b16e-80397cd1663a 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
Logical Port ID: 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
administrative State: ACTIVE
|
||||
interface: b73e3585-d074-4379-8dde-931c0fc4db0e
|
||||
on Virtual Network: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant: demo
|
||||
|
||||
|
||||
7. Plug interface and port into the network
|
||||
Use the interface information obtained in step 6 to plug the interface into
|
||||
the network.
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py plug_iface demo c4a2bea7-a528-4caf-b16e-80397cd1663a 118ac473-294d-480e-8f6d-425acbbe81ae b73e3585-d074-4379-8dde-931c0fc4db0e
|
||||
Plugged interface b73e3585-d074-4379-8dde-931c0fc4db0e
|
||||
into Logical Port: 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
on Virtual Network: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant: demo
|
||||
|
||||
|
||||
8. Unplug an interface and port from the network
|
||||
Note: Before unplugging, make a note of the interface ID (you can use the
|
||||
show_port CLI as before). While the VM, which has a VIF with this interface
|
||||
ID still exists, you can only plug that same interface back into this port.
|
||||
So the subsequent plug interface operation on this port will have to make
|
||||
use of the same interface ID.
|
||||
|
||||
# PYTHONPATH=. python quantum/plugins/cisco/client/cli.py unplug_iface demo c4a2bea7-a528-4caf-b16e-80397cd1663a 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
Unplugged interface from Logical Port: 118ac473-294d-480e-8f6d-425acbbe81ae
|
||||
on Virtual Network: c4a2bea7-a528-4caf-b16e-80397cd1663a
|
||||
for Tenant: demo
|
||||
|
||||
|
||||
How to test the installation
|
||||
----------------------------
|
||||
The unit tests are located at quantum/plugins/cisco/tests/unit. They can be
|
||||
|
225
quantum/plugins/cisco/client/cli.py
Normal file
225
quantum/plugins/cisco/client/cli.py
Normal file
@ -0,0 +1,225 @@
|
||||
"""
|
||||
# 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.
|
||||
#
|
||||
# Initial structure and framework of this CLI has been borrowed from Quantum,
|
||||
# written by the following authors
|
||||
# @author: Somik Behera, Nicira Networks, Inc.
|
||||
# @author: Brad Hall, Nicira Networks, Inc.
|
||||
# @author: Salvatore Orlando, Citrix
|
||||
#
|
||||
# Cisco adaptation for extensions
|
||||
# @author: Sumit Naiksatam, Cisco Systems, Inc.
|
||||
# @author: Ying Liu, Cisco Systems, Inc.
|
||||
#
|
||||
"""
|
||||
|
||||
import gettext
|
||||
import logging
|
||||
import logging.handlers
|
||||
import os
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
from optparse import OptionParser
|
||||
|
||||
POSSIBLE_TOPDIR = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
|
||||
os.pardir,
|
||||
os.pardir))
|
||||
if os.path.exists(os.path.join(POSSIBLE_TOPDIR, 'quantum', '__init__.py')):
|
||||
sys.path.insert(0, POSSIBLE_TOPDIR)
|
||||
|
||||
gettext.install('quantum', unicode=1)
|
||||
|
||||
from quantum.client import Client
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
|
||||
LOG = logging.getLogger('quantum')
|
||||
FORMAT = 'json'
|
||||
ACTION_PREFIX_EXT = '/v1.0'
|
||||
ACTION_PREFIX_CSCO = ACTION_PREFIX_EXT + \
|
||||
'/extensions/csco/tenants/{tenant_id}'
|
||||
TENANT_ID = 'nova'
|
||||
CSCO_EXT_NAME = 'Cisco Nova Tenant'
|
||||
|
||||
|
||||
def help():
|
||||
"""Help for CLI"""
|
||||
print "\nCisco Extension Commands:"
|
||||
for key in COMMANDS.keys():
|
||||
print " %s %s" % (key,
|
||||
" ".join(["<%s>" % y for y in COMMANDS[key]["args"]]))
|
||||
|
||||
|
||||
def build_args(cmd, cmdargs, arglist):
|
||||
"""Building the list of args for a particular CLI"""
|
||||
args = []
|
||||
orig_arglist = arglist[:]
|
||||
try:
|
||||
for cmdarg in cmdargs:
|
||||
args.append(arglist[0])
|
||||
del arglist[0]
|
||||
except:
|
||||
LOG.error("Not enough arguments for \"%s\" (expected: %d, got: %d)" % (
|
||||
cmd, len(cmdargs), len(orig_arglist)))
|
||||
print "Usage:\n %s %s" % (cmd,
|
||||
" ".join(["<%s>" % y for y in COMMANDS[cmd]["args"]]))
|
||||
sys.exit()
|
||||
if len(arglist) > 0:
|
||||
LOG.error("Too many arguments for \"%s\" (expected: %d, got: %d)" % (
|
||||
cmd, len(cmdargs), len(orig_arglist)))
|
||||
print "Usage:\n %s %s" % (cmd,
|
||||
" ".join(["<%s>" % y for y in COMMANDS[cmd]["args"]]))
|
||||
sys.exit()
|
||||
return args
|
||||
|
||||
|
||||
def list_extensions(*args):
|
||||
"""Invoking the action to get the supported extensions"""
|
||||
request_url = "/extensions"
|
||||
client = Client(HOST, PORT, USE_SSL, format='json',
|
||||
action_prefix=ACTION_PREFIX_EXT, tenant="dummy")
|
||||
data = client.do_request('GET', request_url)
|
||||
print("Obtained supported extensions from Quantum: %s" % data)
|
||||
|
||||
|
||||
def schedule_host(tenant_id, instance_id, user_id=None):
|
||||
"""Gets the host name from the Quantum service"""
|
||||
project_id = tenant_id
|
||||
|
||||
instance_data_dict = \
|
||||
{'novatenant': \
|
||||
{'instance_id': instance_id,
|
||||
'instance_desc': \
|
||||
{'user_id': user_id,
|
||||
'project_id': project_id}}}
|
||||
|
||||
request_url = "/novatenants/" + project_id + "/schedule_host"
|
||||
client = Client(HOST, PORT, USE_SSL, format='json', tenant=TENANT_ID,
|
||||
action_prefix=ACTION_PREFIX_CSCO)
|
||||
data = client.do_request('PUT', request_url, body=instance_data_dict)
|
||||
|
||||
hostname = data["host_list"]["host_1"]
|
||||
if not hostname:
|
||||
print("Scheduler was unable to locate a host" + \
|
||||
" for this request. Is the appropriate" + \
|
||||
" service running?")
|
||||
|
||||
print("Quantum service returned host: %s" % hostname)
|
||||
|
||||
|
||||
def create_multiport(tenant_id, net_id_list, *args):
|
||||
"""Creates ports on a single host"""
|
||||
net_list = net_id_list.split(",")
|
||||
ports_info = {'multiport': \
|
||||
{'status': 'ACTIVE',
|
||||
'net_id_list': net_list,
|
||||
'ports_desc': {'key': 'value'}}}
|
||||
|
||||
request_url = "/multiport"
|
||||
client = Client(HOST, PORT, USE_SSL, format='json', tenant=tenant_id,
|
||||
action_prefix=ACTION_PREFIX_CSCO)
|
||||
data = client.do_request('POST', request_url, body=ports_info)
|
||||
|
||||
print("Created ports: %s" % data)
|
||||
|
||||
|
||||
COMMANDS = {
|
||||
"create_multiport": {
|
||||
"func": create_multiport,
|
||||
"args": ["tenant-id",
|
||||
"net-id-list (comma separated list of netword IDs)"]},
|
||||
"list_extensions": {
|
||||
"func": list_extensions,
|
||||
"args": []},
|
||||
"schedule_host": {
|
||||
"func": schedule_host,
|
||||
"args": ["tenant-id", "instance-id"]}, }
|
||||
|
||||
|
||||
class _DynamicModule(object):
|
||||
"""Loading a string as python module"""
|
||||
def load(self, code):
|
||||
execdict = {}
|
||||
exec code in execdict
|
||||
for key in execdict:
|
||||
if not key.startswith('_'):
|
||||
setattr(self, key, execdict[key])
|
||||
|
||||
|
||||
import sys as _sys
|
||||
_ref, _sys.modules[__name__] = _sys.modules[__name__], _DynamicModule()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
import cli
|
||||
FILE_NAME = os.path.join("bin/", "cli")
|
||||
MODULE_CODE = open(FILE_NAME).read()
|
||||
cli.load(MODULE_CODE)
|
||||
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
|
||||
PARSER = OptionParser(usage=usagestr)
|
||||
PARSER.add_option("-H", "--host", dest="host",
|
||||
type="string", default="127.0.0.1", help="ip address of api host")
|
||||
PARSER.add_option("-p", "--port", dest="port",
|
||||
type="int", default=9696, help="api poort")
|
||||
PARSER.add_option("-s", "--ssl", dest="ssl",
|
||||
action="store_true", default=False, help="use ssl")
|
||||
PARSER.add_option("-v", "--verbose", dest="verbose",
|
||||
action="store_true", default=False, help="turn on verbose logging")
|
||||
PARSER.add_option("-f", "--logfile", dest="logfile",
|
||||
type="string", default="syslog", help="log file path")
|
||||
options, args = PARSER.parse_args()
|
||||
|
||||
if options.verbose:
|
||||
LOG.setLevel(logging.DEBUG)
|
||||
else:
|
||||
LOG.setLevel(logging.WARN)
|
||||
|
||||
if options.logfile == "syslog":
|
||||
LOG.addHandler(logging.handlers.SysLogHandler(address='/dev/log'))
|
||||
else:
|
||||
LOG.addHandler(logging.handlers.WatchedFileHandler(options.logfile))
|
||||
os.chmod(options.logfile, 0644)
|
||||
|
||||
if len(args) < 1:
|
||||
PARSER.print_help()
|
||||
cli.help()
|
||||
help()
|
||||
sys.exit(1)
|
||||
|
||||
CMD = args[0]
|
||||
if CMD in cli.commands.keys():
|
||||
args.insert(0, FILE_NAME)
|
||||
subprocess.call(args)
|
||||
sys.exit(1)
|
||||
if CMD not in COMMANDS.keys():
|
||||
LOG.error("Unknown command: %s" % CMD)
|
||||
cli.help()
|
||||
help()
|
||||
sys.exit(1)
|
||||
|
||||
args = build_args(CMD, COMMANDS[CMD]["args"], args[1:])
|
||||
|
||||
LOG.info("Executing command \"%s\" with args: %s" % (CMD, args))
|
||||
|
||||
HOST = options.host
|
||||
PORT = options.port
|
||||
USE_SSL = options.ssl
|
||||
COMMANDS[CMD]["func"](*args)
|
||||
|
||||
LOG.info("Command execution completed")
|
||||
sys.exit(0)
|
@ -149,3 +149,7 @@ LEAST_RSVD_BLADE_DICT = 'least_rsvd_blade_dict'
|
||||
UCSM_IP = 'ucsm_ip_address'
|
||||
|
||||
NETWORK_ADMIN = 'network_admin'
|
||||
|
||||
NETID_LIST = 'net_id_list'
|
||||
|
||||
DELIMITERS = "[,;:\b\s]"
|
||||
|
@ -55,6 +55,12 @@ class PortProfileNotFound(exceptions.QuantumException):
|
||||
"for tenant %(tenant_id)s")
|
||||
|
||||
|
||||
class MultiportNotFound(exceptions.QuantumException):
|
||||
"""Multiport cannot be found"""
|
||||
message = _("Multiports %(port_id)s could not be found " \
|
||||
"for tenant %(tenant_id)s")
|
||||
|
||||
|
||||
class PortProfileInvalidDelete(exceptions.QuantumException):
|
||||
"""Port profile cannot be deleted since its being used"""
|
||||
message = _("Port profile %(profile_id)s could not be deleted " \
|
||||
|
@ -34,6 +34,7 @@ class Fault(webob.exc.HTTPException):
|
||||
451: "CredentialNotFound",
|
||||
452: "QoSNotFound",
|
||||
453: "NovatenantNotFound",
|
||||
454: "MultiportNotFound",
|
||||
470: "serviceUnavailable",
|
||||
471: "pluginFault"}
|
||||
|
||||
@ -96,7 +97,7 @@ class CredentialNotFound(webob.exc.HTTPClientError):
|
||||
This indicates that the server did not find the Credential specified
|
||||
in the HTTP request
|
||||
|
||||
code: 460, title: Credential not Found
|
||||
code: 451, title: Credential not Found
|
||||
"""
|
||||
code = 451
|
||||
title = 'Credential Not Found'
|
||||
@ -111,7 +112,7 @@ class QosNotFound(webob.exc.HTTPClientError):
|
||||
This indicates that the server did not find the QoS specified
|
||||
in the HTTP request
|
||||
|
||||
code: 480, title: QoS not Found
|
||||
code: 452, title: QoS not Found
|
||||
"""
|
||||
code = 452
|
||||
title = 'QoS Not Found'
|
||||
@ -126,7 +127,7 @@ class NovatenantNotFound(webob.exc.HTTPClientError):
|
||||
This indicates that the server did not find the Novatenant specified
|
||||
in the HTTP request
|
||||
|
||||
code: 480, title: Nova tenant not Found
|
||||
code: 453, title: Nova tenant not Found
|
||||
"""
|
||||
code = 453
|
||||
title = 'Nova tenant Not Found'
|
||||
@ -134,6 +135,21 @@ class NovatenantNotFound(webob.exc.HTTPClientError):
|
||||
+ ' the specified identifier.')
|
||||
|
||||
|
||||
class MultiportNotFound(webob.exc.HTTPClientError):
|
||||
"""
|
||||
subclass of :class:`~HTTPClientError`
|
||||
|
||||
This indicates that the server did not find the Multiport specified
|
||||
in the HTTP request
|
||||
|
||||
code: 454, title: Multiport not Found
|
||||
"""
|
||||
code = 454
|
||||
title = 'Multiport Not Found'
|
||||
explanation = ('Unable to find Multiport with'
|
||||
+ ' the specified identifier.')
|
||||
|
||||
|
||||
class RequestedStateInvalid(webob.exc.HTTPClientError):
|
||||
"""
|
||||
subclass of :class:`~HTTPClientError`
|
||||
|
@ -1,37 +0,0 @@
|
||||
"""
|
||||
# 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 os
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_configparser as confp
|
||||
|
||||
CONF_FILE = "../conf/nova.ini"
|
||||
|
||||
CP = confp.CiscoConfigParser(os.path.dirname(os.path.realpath(__file__)) \
|
||||
+ "/" + CONF_FILE)
|
||||
|
||||
SECTION = CP['NOVA']
|
||||
DB_SERVER_IP = SECTION['db_server_ip']
|
||||
DB_NAME = SECTION['db_name']
|
||||
DB_USERNAME = SECTION['db_username']
|
||||
DB_PASSWORD = SECTION['db_password']
|
||||
NOVA_HOST_NAME = SECTION['nova_host_name']
|
||||
NOVA_PROJ_NAME = SECTION['nova_proj_name']
|
@ -25,7 +25,6 @@ import MySQLdb
|
||||
import traceback
|
||||
|
||||
from quantum.plugins.cisco.common import cisco_constants as const
|
||||
from quantum.plugins.cisco.common import cisco_nova_configuration as conf
|
||||
from quantum.plugins.cisco.db import api as db
|
||||
from quantum.plugins.cisco.db import l2network_db as cdb
|
||||
|
||||
@ -77,35 +76,3 @@ def make_portprofile_assc_list(tenant_id, profile_id):
|
||||
assc_list.append(port[const.PORTID])
|
||||
|
||||
return assc_list
|
||||
|
||||
|
||||
class DBUtils(object):
|
||||
"""Utilities to use connect to MySQL DB and execute queries"""
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
def _get_db_connection(self):
|
||||
"""Get a connection to the DB"""
|
||||
db_ip = conf.DB_SERVER_IP
|
||||
db_username = conf.DB_USERNAME
|
||||
db_password = conf.DB_PASSWORD
|
||||
self.db = MySQLdb.connect(db_ip, db_username, db_password,
|
||||
conf.DB_NAME)
|
||||
return self.db
|
||||
|
||||
def execute_db_query(self, sql_query):
|
||||
"""Execute a DB 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)
|
||||
db.close()
|
||||
except:
|
||||
db.rollback()
|
||||
LOG.debug("DB query execution failed: %s" % sql_query)
|
||||
traceback.print_exc()
|
||||
db.close()
|
||||
|
@ -1,13 +1,8 @@
|
||||
#Provide the UCSM credentials
|
||||
#Provide the UCSM credentials, make sure you have a separate entry for every UCSM in your deployment
|
||||
[<put_ucsm_ip_address_here>]
|
||||
username=<put_user_name_here>
|
||||
password=<put_password_here>
|
||||
|
||||
#Provide the Nova DB credentials, the IP address should be the same as in nova.ini
|
||||
[<put_nova_db_ip_here>]
|
||||
username=<put_user_name_here>
|
||||
password=<put_password_here>
|
||||
|
||||
#Provide the Nexus credentials, if you are using Nexus
|
||||
[<put_nexus_ip_address_here>]
|
||||
username=<put_user_name_here>
|
||||
|
@ -1,8 +0,0 @@
|
||||
[NOVA]
|
||||
#Change the following details to reflect your OpenStack Nova configuration. If you are running this service on the same machine as the Nova DB, you do not have to change the IP address.
|
||||
db_server_ip=127.0.0.1
|
||||
db_name=nova
|
||||
db_username=<put_db_user_name_here>
|
||||
db_password=<put_db_password_here>
|
||||
nova_host_name=<put_openstack_cloud_controller_hostname_here>
|
||||
nova_proj_name=<put_openstack_project_name_here>
|
@ -1,5 +1,6 @@
|
||||
[UCSM]
|
||||
#change the following to the appropriate UCSM IP address
|
||||
#if you have more than one UCSM, enter info from any one
|
||||
ip_address=<put_ucsm_ip_address_here>
|
||||
default_vlan_name=default
|
||||
default_vlan_id=1
|
||||
|
@ -21,7 +21,7 @@
|
||||
|
||||
import inspect
|
||||
import logging as LOG
|
||||
import platform
|
||||
import re
|
||||
|
||||
from quantum.common import exceptions as exc
|
||||
from quantum.common import utils
|
||||
@ -41,8 +41,9 @@ LOG.getLogger(const.LOGGER_COMPONENT_NAME)
|
||||
|
||||
class L2Network(QuantumPluginBase):
|
||||
""" L2 Network Framework Plugin """
|
||||
supported_extension_aliases = ["Cisco Credential", "Cisco Port Profile",
|
||||
"Cisco qos", "Cisco Nova Tenant"]
|
||||
supported_extension_aliases = ["Cisco Multiport", "Cisco Credential",
|
||||
"Cisco Port Profile", "Cisco qos",
|
||||
"Cisco Nova Tenant"]
|
||||
|
||||
def __init__(self):
|
||||
cdb.initialize()
|
||||
@ -178,6 +179,7 @@ class L2Network(QuantumPluginBase):
|
||||
Creates a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("create_port() called\n")
|
||||
|
||||
port = db.port_create(net_id, port_state)
|
||||
unique_port_id_string = port[const.UUID]
|
||||
self._invoke_device_plugins(self._func_name(), [tenant_id, net_id,
|
||||
@ -249,10 +251,19 @@ class L2Network(QuantumPluginBase):
|
||||
"""
|
||||
LOG.debug("plug_interface() called\n")
|
||||
network = db.network_get(net_id)
|
||||
self._invoke_device_plugins(self._func_name(), [tenant_id, net_id,
|
||||
port_id,
|
||||
remote_interface_id])
|
||||
db.port_set_attachment(net_id, port_id, remote_interface_id)
|
||||
port = db.port_get(net_id, port_id)
|
||||
attachment_id = port[const.INTERFACEID]
|
||||
if attachment_id and remote_interface_id != attachment_id:
|
||||
raise exc.PortInUse(port_id=port_id, net_id=net_id,
|
||||
att_id=attachment_id)
|
||||
self._invoke_device_plugins(self._func_name(), [tenant_id,
|
||||
net_id, port_id,
|
||||
remote_interface_id])
|
||||
if attachment_id == None:
|
||||
db.port_set_attachment(net_id, port_id, remote_interface_id)
|
||||
#Note: The remote_interface_id gets associated with the port
|
||||
# when the VM is instantiated. The plug interface call results
|
||||
# in putting the port on the VLAN associated with this network
|
||||
|
||||
def unplug_interface(self, tenant_id, net_id, port_id):
|
||||
"""
|
||||
@ -287,7 +298,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("get_portprofile_details() called\n")
|
||||
try:
|
||||
portprofile = cdb.get_portprofile(tenant_id, profile_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=profile_id)
|
||||
|
||||
@ -313,7 +324,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("delete_portprofile() called\n")
|
||||
try:
|
||||
portprofile = cdb.get_portprofile(tenant_id, profile_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=profile_id)
|
||||
|
||||
@ -329,7 +340,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("rename_portprofile() called\n")
|
||||
try:
|
||||
portprofile = cdb.get_portprofile(tenant_id, profile_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=profile_id)
|
||||
portprofile = cdb.update_portprofile(tenant_id, profile_id, new_name)
|
||||
@ -345,7 +356,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("associate_portprofile() called\n")
|
||||
try:
|
||||
portprofile = cdb.get_portprofile(tenant_id, portprofile_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=portprofile_id)
|
||||
|
||||
@ -357,7 +368,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("disassociate_portprofile() called\n")
|
||||
try:
|
||||
portprofile = cdb.get_portprofile(tenant_id, portprofile_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.PortProfileNotFound(tenant_id=tenant_id,
|
||||
portprofile_id=portprofile_id)
|
||||
|
||||
@ -374,7 +385,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("get_qos_details() called\n")
|
||||
try:
|
||||
qos_level = cdb.get_qos(tenant_id, qos_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.QosNotFound(tenant_id=tenant_id,
|
||||
qos_id=qos_id)
|
||||
return qos_level
|
||||
@ -390,7 +401,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("delete_qos() called\n")
|
||||
try:
|
||||
qos_level = cdb.get_qos(tenant_id, qos_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.QosNotFound(tenant_id=tenant_id,
|
||||
qos_id=qos_id)
|
||||
return cdb.remove_qos(tenant_id, qos_id)
|
||||
@ -400,7 +411,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("rename_qos() called\n")
|
||||
try:
|
||||
qos_level = cdb.get_qos(tenant_id, qos_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.QosNotFound(tenant_id=tenant_id,
|
||||
qos_id=qos_id)
|
||||
qos = cdb.update_qos(tenant_id, qos_id, new_name)
|
||||
@ -417,7 +428,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("get_credential_details() called\n")
|
||||
try:
|
||||
credential = cdb.get_credential(tenant_id, credential_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.CredentialNotFound(tenant_id=tenant_id,
|
||||
credential_id=credential_id)
|
||||
return credential
|
||||
@ -435,7 +446,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("delete_credential() called\n")
|
||||
try:
|
||||
credential = cdb.get_credential(tenant_id, credential_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.CredentialNotFound(tenant_id=tenant_id,
|
||||
credential_id=credential_id)
|
||||
credential = cdb.remove_credential(tenant_id, credential_id)
|
||||
@ -446,7 +457,7 @@ class L2Network(QuantumPluginBase):
|
||||
LOG.debug("rename_credential() called\n")
|
||||
try:
|
||||
credential = cdb.get_credential(tenant_id, credential_id)
|
||||
except Exception, excp:
|
||||
except Exception:
|
||||
raise cexc.CredentialNotFound(tenant_id=tenant_id,
|
||||
credential_id=credential_id)
|
||||
credential = cdb.update_credential(tenant_id, credential_id, new_name)
|
||||
@ -469,6 +480,27 @@ class L2Network(QuantumPluginBase):
|
||||
instance_id,
|
||||
instance_desc])
|
||||
|
||||
def create_multiport(self, tenant_id, net_id_list, port_state, ports_desc):
|
||||
"""
|
||||
Creates multiple ports on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("create_ports() called\n")
|
||||
ports_num = len(net_id_list)
|
||||
ports_id_list = []
|
||||
ports_dict_list = []
|
||||
|
||||
for net_id in net_id_list:
|
||||
port = db.port_create(net_id, port_state)
|
||||
ports_id_list.append(port[const.UUID])
|
||||
port_dict = {const.PORT_ID: port[const.UUID]}
|
||||
ports_dict_list.append(port_dict)
|
||||
|
||||
self._invoke_device_plugins(self._func_name(), [tenant_id,
|
||||
net_id_list,
|
||||
ports_num,
|
||||
ports_id_list])
|
||||
return ports_dict_list
|
||||
|
||||
"""
|
||||
Private functions
|
||||
"""
|
||||
|
@ -171,3 +171,8 @@ class L2NetworkMultiBlade(L2NetworkModelBase):
|
||||
LOG.debug("associate_port() called\n")
|
||||
return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(),
|
||||
args)
|
||||
|
||||
def create_multiport(self, args):
|
||||
"""Support for extension API call"""
|
||||
self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(),
|
||||
args)
|
||||
|
@ -162,3 +162,8 @@ class L2NetworkSingleBlade(L2NetworkModelBase):
|
||||
LOG.debug("associate_port() called\n")
|
||||
return self._invoke_inventory(const.UCS_PLUGIN, self._func_name(),
|
||||
args)
|
||||
|
||||
def create_multiport(self, args):
|
||||
"""Support for extension API call"""
|
||||
self._invoke_plugin_per_device(const.UCS_PLUGIN, self._func_name(),
|
||||
args)
|
||||
|
@ -43,7 +43,7 @@ CSCO_EXT_NAME = 'Cisco Nova Tenant'
|
||||
ACTION = '/schedule_host'
|
||||
|
||||
|
||||
class QuantumScheduler(driver.Scheduler):
|
||||
class QuantumPortAwareScheduler(driver.Scheduler):
|
||||
"""
|
||||
Quantum network service dependent scheduler
|
||||
Obtains the hostname from Quantum using an extension API
|
@ -28,6 +28,7 @@ from extensions import credential
|
||||
from extensions import portprofile
|
||||
from extensions import novatenant
|
||||
from extensions import qos
|
||||
from extensions import multiport
|
||||
from quantum.plugins.cisco.db import api as db
|
||||
from quantum.common import wsgi
|
||||
from quantum.common import config
|
||||
@ -1022,6 +1023,122 @@ class CredentialExtensionTest(unittest.TestCase):
|
||||
db.clear_db()
|
||||
|
||||
|
||||
class MultiPortExtensionTest(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
|
||||
""" Set up function """
|
||||
|
||||
parent_resource = dict(member_name="tenant",
|
||||
collection_name="extensions/csco/tenants")
|
||||
controller = multiport.MultiportController(
|
||||
QuantumManager.get_plugin())
|
||||
res_ext = extensions.ResourceExtension('multiport', controller,
|
||||
parent=parent_resource)
|
||||
self.test_app = setup_extensions_test_app(
|
||||
SimpleExtensionManager(res_ext))
|
||||
self.contenttype = 'application/json'
|
||||
self.multiport_path = '/extensions/csco/tenants/tt/multiport'
|
||||
self.multiport_path2 = '/extensions/csco/tenants/tt/multiport/'
|
||||
self.test_multi_port = {'multiport':
|
||||
{'net_id_list': '1',
|
||||
'status': 'test-qos1',
|
||||
'ports_desc': 'Port Descr'}}
|
||||
self.tenant_id = "test_tenant"
|
||||
self.network_name = "test_network"
|
||||
options = {}
|
||||
options['plugin_provider'] = 'quantum.plugins.cisco.l2network_plugin'\
|
||||
'.L2Network'
|
||||
self.api = server.APIRouterV1(options)
|
||||
self._l2network_plugin = l2network_plugin.L2Network()
|
||||
|
||||
def create_request(self, path, body, content_type, method='GET'):
|
||||
|
||||
""" Test create request"""
|
||||
|
||||
LOG.debug("test_create_request - START")
|
||||
req = webob.Request.blank(path)
|
||||
req.method = method
|
||||
req.headers = {}
|
||||
req.headers['Accept'] = content_type
|
||||
req.body = body
|
||||
LOG.debug("test_create_request - END")
|
||||
return req
|
||||
|
||||
def _create_network(self, name=None):
|
||||
|
||||
""" Test create network"""
|
||||
|
||||
LOG.debug("Creating network - START")
|
||||
if name:
|
||||
net_name = name
|
||||
else:
|
||||
net_name = self.network_name
|
||||
net_path = "/tenants/tt/networks"
|
||||
net_data = {'network': {'name': '%s' % net_name}}
|
||||
req_body = wsgi.Serializer().serialize(net_data, self.contenttype)
|
||||
network_req = self.create_request(net_path, req_body,
|
||||
self.contenttype, 'POST')
|
||||
network_res = network_req.get_response(self.api)
|
||||
network_data = wsgi.Serializer().deserialize(network_res.body,
|
||||
self.contenttype)
|
||||
LOG.debug("Creating network - END")
|
||||
return network_data['network']['id']
|
||||
|
||||
def _delete_network(self, network_id):
|
||||
""" Delete network """
|
||||
LOG.debug("Deleting network %s - START", network_id)
|
||||
network_path = "/tenants/tt/networks/%s" % network_id
|
||||
network_req = self.create_request(network_path, None,
|
||||
self.contenttype, 'DELETE')
|
||||
network_req.get_response(self.api)
|
||||
LOG.debug("Deleting network - END")
|
||||
|
||||
def test_create_multiport(self):
|
||||
|
||||
""" Test create MultiPort"""
|
||||
|
||||
LOG.debug("test_create_multiport - START")
|
||||
|
||||
net_id = self._create_network('net1')
|
||||
net_id2 = self._create_network('net2')
|
||||
test_multi_port = {'multiport':
|
||||
{'net_id_list': [net_id, net_id2],
|
||||
'status': 'ACTIVE',
|
||||
'ports_desc': {'key': 'value'}}}
|
||||
req_body = json.dumps(test_multi_port)
|
||||
index_response = self.test_app.post(self.multiport_path, req_body,
|
||||
content_type=self.contenttype)
|
||||
resp_body = wsgi.Serializer().deserialize(index_response.body,
|
||||
self.contenttype)
|
||||
self.assertEqual(200, index_response.status_int)
|
||||
self.assertEqual(len(test_multi_port['multiport']['net_id_list']),
|
||||
len(resp_body['ports']))
|
||||
# Clean Up - Delete the Port Profile
|
||||
self._delete_network(net_id)
|
||||
self._delete_network(net_id2)
|
||||
LOG.debug("test_create_multiport - END")
|
||||
|
||||
def test_create_multiportBADRequest(self):
|
||||
|
||||
""" Test create MultiPort Bad Request"""
|
||||
|
||||
LOG.debug("test_create_multiportBADRequest - START")
|
||||
net_id = self._create_network('net1')
|
||||
net_id2 = self._create_network('net2')
|
||||
index_response = self.test_app.post(self.multiport_path, 'BAD_REQUEST',
|
||||
content_type=self.contenttype,
|
||||
status='*')
|
||||
self.assertEqual(400, index_response.status_int)
|
||||
# Clean Up - Delete the Port Profile
|
||||
self._delete_network(net_id)
|
||||
self._delete_network(net_id2)
|
||||
LOG.debug("test_create_multiportBADRequest - END")
|
||||
|
||||
def tearDown(self):
|
||||
db.clear_db()
|
||||
|
||||
|
||||
def app_factory(global_conf, **local_conf):
|
||||
conf = global_conf.copy()
|
||||
conf.update(local_conf)
|
||||
|
@ -506,13 +506,14 @@ class CoreAPITestFunc(unittest.TestCase):
|
||||
"""
|
||||
|
||||
LOG.debug("test_plug_interface_portInUse - START")
|
||||
current_interface = "current_interface"
|
||||
new_net_dict = self._l2network_plugin.create_network(
|
||||
tenant_id, self.network_name)
|
||||
port_dict = self._l2network_plugin.create_port(
|
||||
tenant_id, new_net_dict[const.NET_ID], self.port_state)
|
||||
self._l2network_plugin.plug_interface(
|
||||
tenant_id, new_net_dict[const.NET_ID],
|
||||
port_dict[const.PORT_ID], remote_interface)
|
||||
port_dict[const.PORT_ID], current_interface)
|
||||
self.assertRaises(exc.PortInUse,
|
||||
self._l2network_plugin.plug_interface, tenant_id,
|
||||
new_net_dict[const.NET_ID],
|
||||
|
@ -121,7 +121,30 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
chassis_dict[chassis_id] = blade_list
|
||||
self._inventory[ucsm_ip] = chassis_dict
|
||||
|
||||
self.build_inventory_state()
|
||||
self._build_inventory_state()
|
||||
|
||||
def _build_inventory_state(self):
|
||||
"""Populate the state of all the blades"""
|
||||
for ucsm_ip in self._inventory.keys():
|
||||
self._inventory_state[ucsm_ip] = {ucsm_ip: {}}
|
||||
ucsm_username = cred.Store.getUsername(ucsm_ip)
|
||||
ucsm_password = cred.Store.getPassword(ucsm_ip)
|
||||
chasses_state = {}
|
||||
self._inventory_state[ucsm_ip] = chasses_state
|
||||
ucsm = self._inventory[ucsm_ip]
|
||||
for chassis_id in ucsm.keys():
|
||||
blades_dict = {}
|
||||
chasses_state[chassis_id] = blades_dict
|
||||
for blade_id in ucsm[chassis_id]:
|
||||
blade_data = self._get_initial_blade_state(chassis_id,
|
||||
blade_id,
|
||||
ucsm_ip,
|
||||
ucsm_username,
|
||||
ucsm_password)
|
||||
blades_dict[blade_id] = blade_data
|
||||
|
||||
LOG.debug("UCS Inventory state is: %s\n" % self._inventory_state)
|
||||
return True
|
||||
|
||||
def _get_host_name(self, ucsm_ip, chassis_id, blade_id):
|
||||
"""Get the hostname based on the blade info"""
|
||||
@ -187,8 +210,10 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
blade_intf_data[blade_intf][const.VIF_ID] = \
|
||||
port_binding[const.VIF_ID]
|
||||
|
||||
host_name = self._get_host_name(ucsm_ip, chassis_id, blade_id)
|
||||
blade_data = {const.BLADE_INTF_DATA: blade_intf_data,
|
||||
const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter}
|
||||
const.BLADE_UNRESERVED_INTF_COUNT: unreserved_counter,
|
||||
const.HOST_NAME: host_name}
|
||||
return blade_data
|
||||
|
||||
def _get_blade_state(self, chassis_id, blade_id, ucsm_ip,
|
||||
@ -229,7 +254,7 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
tenant_id = args[0]
|
||||
net_id = args[1]
|
||||
port_id = args[2]
|
||||
rsvd_info = self.get_rsvd_blade_intf_by_port(tenant_id, port_id)
|
||||
rsvd_info = self._get_rsvd_blade_intf_by_port(tenant_id, port_id)
|
||||
if not rsvd_info:
|
||||
raise exc.PortNotFound(net_id=net_id, port_id=port_id)
|
||||
device_params = {const.DEVICE_IP: [rsvd_info[const.UCSM_IP]]}
|
||||
@ -361,39 +386,46 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
(vif_id, port_id,
|
||||
blade_intf_data[blade_intf]))
|
||||
return
|
||||
LOG.warn("Disassociating VIF-ID %s in UCS inventory failed. " \
|
||||
LOG.warn("Disassociating VIF-ID in UCS inventory failed. " \
|
||||
"Could not find a reserved dynamic nic for tenant: %s" %
|
||||
(vif_id, tenant_id))
|
||||
tenant_id)
|
||||
return None
|
||||
|
||||
def reload_inventory(self):
|
||||
"""Reload the inventory from a conf file"""
|
||||
self._load_inventory()
|
||||
|
||||
def build_inventory_state(self):
|
||||
"""Populate the state of all the blades"""
|
||||
for ucsm_ip in self._inventory.keys():
|
||||
self._inventory_state[ucsm_ip] = {ucsm_ip: {}}
|
||||
ucsm_username = cred.Store.getUsername(ucsm_ip)
|
||||
ucsm_password = cred.Store.getPassword(ucsm_ip)
|
||||
chasses_state = {}
|
||||
self._inventory_state[ucsm_ip] = chasses_state
|
||||
ucsm = self._inventory[ucsm_ip]
|
||||
def _get_rsvd_blade_intf_by_port(self, tenant_id, port_id):
|
||||
"""
|
||||
Lookup a reserved blade interface based on tenant_id and port_id
|
||||
and return the blade interface info
|
||||
"""
|
||||
for ucsm_ip in self._inventory_state.keys():
|
||||
ucsm = self._inventory_state[ucsm_ip]
|
||||
for chassis_id in ucsm.keys():
|
||||
blades_dict = {}
|
||||
chasses_state[chassis_id] = blades_dict
|
||||
for blade_id in ucsm[chassis_id]:
|
||||
blade_data = self._get_initial_blade_state(chassis_id,
|
||||
blade_id,
|
||||
ucsm_ip,
|
||||
ucsm_username,
|
||||
ucsm_password)
|
||||
blades_dict[blade_id] = blade_data
|
||||
blade_data = ucsm[chassis_id][blade_id]
|
||||
blade_intf_data = blade_data[const.BLADE_INTF_DATA]
|
||||
for blade_intf in blade_intf_data.keys():
|
||||
if not blade_intf_data[blade_intf][const.PORTID] or \
|
||||
not blade_intf_data[blade_intf][const.TENANTID]:
|
||||
continue
|
||||
if blade_intf_data[blade_intf]\
|
||||
[const.BLADE_INTF_RESERVATION] == \
|
||||
const.BLADE_INTF_RESERVED and \
|
||||
blade_intf_data[blade_intf]\
|
||||
[const.TENANTID] == tenant_id and \
|
||||
blade_intf_data[blade_intf]\
|
||||
[const.PORTID] == port_id:
|
||||
interface_dn = blade_intf_data[blade_intf]\
|
||||
[const.BLADE_INTF_DN]
|
||||
blade_intf_info = {const.UCSM_IP: ucsm_ip,
|
||||
const.CHASSIS_ID: chassis_id,
|
||||
const.BLADE_ID: blade_id,
|
||||
const.BLADE_INTF_DN:
|
||||
interface_dn}
|
||||
return blade_intf_info
|
||||
LOG.warn("Could not find a reserved nic for tenant: %s port: %s" %
|
||||
(tenant_id, port_id))
|
||||
return None
|
||||
|
||||
LOG.debug("UCS Inventory state is: %s\n" % self._inventory_state)
|
||||
return True
|
||||
|
||||
def get_least_reserved_blade(self):
|
||||
def _get_least_reserved_blade(self, intf_count=1):
|
||||
"""Return the blade with least number of dynamic nics reserved"""
|
||||
unreserved_interface_count = 0
|
||||
least_reserved_blade_ucsm = None
|
||||
@ -415,8 +447,10 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
least_reserved_blade_id = blade_id
|
||||
least_reserved_blade_data = blade_data
|
||||
|
||||
if unreserved_interface_count == 0:
|
||||
LOG.warn("No more dynamic nics available for reservation")
|
||||
if unreserved_interface_count < intf_count:
|
||||
LOG.warn("Not enough dynamic nics available on a single host." \
|
||||
" Requested: %s, Maximum available: %s" %
|
||||
(intf_count, unreserved_interface_count))
|
||||
return False
|
||||
|
||||
least_reserved_blade_dict = \
|
||||
@ -428,6 +462,10 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
least_reserved_blade_dict)
|
||||
return least_reserved_blade_dict
|
||||
|
||||
def reload_inventory(self):
|
||||
"""Reload the inventory from a conf file"""
|
||||
self._load_inventory()
|
||||
|
||||
def reserve_blade_interface(self, ucsm_ip, chassis_id, blade_id,
|
||||
blade_data_dict, tenant_id, port_id,
|
||||
portprofile_name):
|
||||
@ -435,15 +473,19 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
ucsm_username = cred.Store.getUsername(ucsm_ip)
|
||||
ucsm_password = cred.Store.getPassword(ucsm_ip)
|
||||
"""
|
||||
We are first getting the updated blade interface state
|
||||
We are first getting the updated UCSM-specific blade
|
||||
interface state
|
||||
"""
|
||||
blade_data = self._get_blade_state(chassis_id, blade_id, ucsm_ip,
|
||||
ucsm_username, ucsm_password)
|
||||
blade_intf_data = blade_data[const.BLADE_INTF_DATA]
|
||||
old_blade_intf_data = blade_data_dict[const.BLADE_INTF_DATA]
|
||||
old_blade_intf_data = \
|
||||
self._inventory_state[ucsm_ip][chassis_id]\
|
||||
[blade_id][const.BLADE_INTF_DATA]
|
||||
|
||||
"""
|
||||
We will now copy the older blade interface state
|
||||
We will now copy the older non-UCSM-specific blade
|
||||
interface state
|
||||
"""
|
||||
for blade_intf in blade_intf_data.keys():
|
||||
blade_intf_data[blade_intf][const.BLADE_INTF_RESERVATION] = \
|
||||
@ -461,7 +503,8 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
old_blade_intf_data[blade_intf][const.VIF_ID]
|
||||
|
||||
blade_data[const.BLADE_UNRESERVED_INTF_COUNT] = \
|
||||
blade_data_dict[const.BLADE_UNRESERVED_INTF_COUNT]
|
||||
self._inventory_state[ucsm_ip][chassis_id]\
|
||||
[blade_id][const.BLADE_UNRESERVED_INTF_COUNT]
|
||||
"""
|
||||
Now we will reserve an interface if its available
|
||||
"""
|
||||
@ -498,7 +541,7 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
return reserved_nic_dict
|
||||
|
||||
LOG.warn("Dynamic nic %s could not be reserved for port-id: %s" %
|
||||
(blade_data_dict, port_id))
|
||||
(blade_data, port_id))
|
||||
return False
|
||||
|
||||
def unreserve_blade_interface(self, ucsm_ip, chassis_id, blade_id,
|
||||
@ -518,40 +561,6 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
blade_intf[const.VIF_ID] = None
|
||||
LOG.debug("Unreserved blade interface %s\n" % interface_dn)
|
||||
|
||||
def get_rsvd_blade_intf_by_port(self, tenant_id, port_id):
|
||||
"""
|
||||
Lookup a reserved blade interface based on tenant_id and port_id
|
||||
and return the blade interface info
|
||||
"""
|
||||
for ucsm_ip in self._inventory_state.keys():
|
||||
ucsm = self._inventory_state[ucsm_ip]
|
||||
for chassis_id in ucsm.keys():
|
||||
for blade_id in ucsm[chassis_id]:
|
||||
blade_data = ucsm[chassis_id][blade_id]
|
||||
blade_intf_data = blade_data[const.BLADE_INTF_DATA]
|
||||
for blade_intf in blade_intf_data.keys():
|
||||
if not blade_intf_data[blade_intf][const.PORTID] or \
|
||||
not blade_intf_data[blade_intf][const.TENANTID]:
|
||||
continue
|
||||
if blade_intf_data[blade_intf]\
|
||||
[const.BLADE_INTF_RESERVATION] == \
|
||||
const.BLADE_INTF_RESERVED and \
|
||||
blade_intf_data[blade_intf]\
|
||||
[const.TENANTID] == tenant_id and \
|
||||
blade_intf_data[blade_intf]\
|
||||
[const.PORTID] == port_id:
|
||||
interface_dn = blade_intf_data[blade_intf]\
|
||||
[const.BLADE_INTF_DN]
|
||||
blade_intf_info = {const.UCSM_IP: ucsm_ip,
|
||||
const.CHASSIS_ID: chassis_id,
|
||||
const.BLADE_ID: blade_id,
|
||||
const.BLADE_INTF_DN:
|
||||
interface_dn}
|
||||
return blade_intf_info
|
||||
LOG.warn("Could not find a reserved nic for tenant: %s port: %s" %
|
||||
(tenant_id, port_id))
|
||||
return None
|
||||
|
||||
def add_blade(self, ucsm_ip, chassis_id, blade_id):
|
||||
"""Add a blade to the inventory"""
|
||||
# TODO (Sumit)
|
||||
@ -593,7 +602,7 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
on which a dynamic vnic is available
|
||||
"""
|
||||
LOG.debug("create_port() called\n")
|
||||
least_reserved_blade_dict = self.get_least_reserved_blade()
|
||||
least_reserved_blade_dict = self._get_least_reserved_blade()
|
||||
if not least_reserved_blade_dict:
|
||||
raise cexc.NoMoreNics()
|
||||
ucsm_ip = least_reserved_blade_dict[const.LEAST_RSVD_BLADE_UCSM]
|
||||
@ -612,9 +621,11 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
tenant_id = args[0]
|
||||
net_id = args[1]
|
||||
port_id = args[2]
|
||||
rsvd_info = self.get_rsvd_blade_intf_by_port(tenant_id, port_id)
|
||||
rsvd_info = self._get_rsvd_blade_intf_by_port(tenant_id, port_id)
|
||||
if not rsvd_info:
|
||||
raise exc.PortNotFound(net_id=net_id, port_id=port_id)
|
||||
LOG.warn("UCSInventory: Port not found: net_id: %s, port_id: %s" %
|
||||
(net_id, port_id))
|
||||
return {const.DEVICE_IP: []}
|
||||
device_params = \
|
||||
{const.DEVICE_IP: [rsvd_info[const.UCSM_IP]],
|
||||
const.UCS_INVENTORY: self,
|
||||
@ -680,3 +691,20 @@ class UCSInventory(L2NetworkDeviceInventoryBase):
|
||||
vif_desc = {const.VIF_DESC: vif_info}
|
||||
LOG.debug("vif_desc is: %s" % vif_desc)
|
||||
return vif_desc
|
||||
|
||||
def create_multiport(self, args):
|
||||
"""
|
||||
Create multiple ports for a VM
|
||||
"""
|
||||
LOG.debug("create_ports() called\n")
|
||||
tenant_id = args[0]
|
||||
ports_num = args[2]
|
||||
least_reserved_blade_dict = self._get_least_reserved_blade(ports_num)
|
||||
if not least_reserved_blade_dict:
|
||||
raise cexc.NoMoreNics()
|
||||
ucsm_ip = least_reserved_blade_dict[const.LEAST_RSVD_BLADE_UCSM]
|
||||
device_params = {const.DEVICE_IP: [ucsm_ip],
|
||||
const.UCS_INVENTORY: self,
|
||||
const.LEAST_RSVD_BLADE_DICT:\
|
||||
least_reserved_blade_dict}
|
||||
return device_params
|
||||
|
@ -145,7 +145,7 @@ class CiscoUCSMDriver():
|
||||
|
||||
def _post_data(self, ucsm_ip, ucsm_username, ucsm_password, data):
|
||||
"""Send command to UCSM in http request"""
|
||||
conn = httplib.HTTPConnection(ucsm_ip)
|
||||
conn = httplib.HTTPSConnection(ucsm_ip)
|
||||
login_data = "<aaaLogin inName=\"" + ucsm_username + \
|
||||
"\" inPassword=\"" + ucsm_password + "\" />"
|
||||
conn.request(METHOD, URL, login_data, HEADERS)
|
||||
|
@ -296,3 +296,36 @@ class UCSVICPlugin(L2DevicePluginBase):
|
||||
self._ucsm_ip = ucsm_ip
|
||||
self._ucsm_username = cred.Store.getUsername(conf.UCSM_IP_ADDRESS)
|
||||
self._ucsm_password = cred.Store.getPassword(conf.UCSM_IP_ADDRESS)
|
||||
|
||||
def create_multiport(self, tenant_id, net_id_list, ports_num, port_id_list,
|
||||
**kwargs):
|
||||
"""
|
||||
Creates a port on the specified Virtual Network.
|
||||
"""
|
||||
LOG.debug("UCSVICPlugin:create_multiport() called\n")
|
||||
self._set_ucsm(kwargs[const.DEVICE_IP])
|
||||
qos = None
|
||||
ucs_inventory = kwargs[const.UCS_INVENTORY]
|
||||
least_rsvd_blade_dict = kwargs[const.LEAST_RSVD_BLADE_DICT]
|
||||
chassis_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_CHASSIS]
|
||||
blade_id = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_ID]
|
||||
blade_data_dict = least_rsvd_blade_dict[const.LEAST_RSVD_BLADE_DATA]
|
||||
port_binding_list = []
|
||||
for port_id, net_id in zip(port_id_list, net_id_list):
|
||||
new_port_profile = \
|
||||
self._create_port_profile(tenant_id, net_id, port_id,
|
||||
conf.DEFAULT_VLAN_NAME,
|
||||
conf.DEFAULT_VLAN_ID)
|
||||
profile_name = new_port_profile[const.PROFILE_NAME]
|
||||
rsvd_nic_dict = ucs_inventory.\
|
||||
reserve_blade_interface(self._ucsm_ip, chassis_id,
|
||||
blade_id, blade_data_dict,
|
||||
tenant_id, port_id,
|
||||
profile_name)
|
||||
port_binding = udb.update_portbinding(port_id,
|
||||
portprofile_name=profile_name,
|
||||
vlan_name=conf.DEFAULT_VLAN_NAME,
|
||||
vlan_id=conf.DEFAULT_VLAN_ID,
|
||||
qos=qos)
|
||||
port_binding_list.append(port_binding)
|
||||
return port_binding_list
|
||||
|
Loading…
x
Reference in New Issue
Block a user