Merge trunk

Updating names for APIRouter in CLI and CLI tests
This commit is contained in:
Salvatore Orlando
2011-08-30 17:00:11 +01:00
25 changed files with 1139 additions and 502 deletions

165
bin/cli Executable file
View File

@@ -0,0 +1,165 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# Copyright 2011 Citrix Systems
#
# 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: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Salvatore Orlando, Citrix
import Cheetah.Template as cheetah_template
import gettext
import logging
import logging.handlers
import os
import sys
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 import cli_lib
from quantum.client import Client
#Configure logger for client - cli logger is a child of it
#NOTE(salvatore-orlando): logger name does not map to package
#this is deliberate. Simplifies logger configuration
LOG = logging.getLogger('quantum')
FORMAT = 'json'
commands = {
"list_nets": {
"func": cli_lib.list_nets,
"args": ["tenant-id"]},
"create_net": {
"func": cli_lib.create_net,
"args": ["tenant-id", "net-name"]},
"delete_net": {
"func": cli_lib.delete_net,
"args": ["tenant-id", "net-id"]},
"show_net": {
"func": cli_lib.show_net,
"args": ["tenant-id", "net-id"]},
"rename_net": {
"func": cli_lib.rename_net,
"args": ["tenant-id", "net-id", "new-name"]},
"list_ports": {
"func": cli_lib.list_ports,
"args": ["tenant-id", "net-id"]},
"create_port": {
"func": cli_lib.create_port,
"args": ["tenant-id", "net-id"]},
"delete_port": {
"func": cli_lib.delete_port,
"args": ["tenant-id", "net-id", "port-id"]},
"set_port_state": {
"func": cli_lib.set_port_state,
"args": ["tenant-id", "net-id", "port-id", "new_state"]},
"show_port": {
"func": cli_lib.show_port,
"args": ["tenant-id", "net-id", "port-id"]},
"plug_iface": {
"func": cli_lib.plug_iface,
"args": ["tenant-id", "net-id", "port-id", "iface-id"]},
"unplug_iface": {
"func": cli_lib.unplug_iface,
"args": ["tenant-id", "net-id", "port-id"]}, }
def help():
print "\nCommands:"
for k in commands.keys():
print " %s %s" % (k,
" ".join(["<%s>" % y for y in commands[k]["args"]]))
def build_args(cmd, cmdargs, arglist):
args = []
orig_arglist = arglist[:]
try:
for x 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"]]))
return None
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"]]))
return None
return args
if __name__ == "__main__":
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)
#logging.handlers.WatchedFileHandler
if options.logfile == "syslog":
LOG.addHandler(logging.handlers.SysLogHandler(address='/dev/log'))
else:
LOG.addHandler(logging.handlers.WatchedFileHandler(options.logfile))
# Set permissions on log file
os.chmod(options.logfile, 0644)
if len(args) < 1:
parser.print_help()
help()
sys.exit(1)
cmd = args[0]
if cmd not in commands.keys():
LOG.error("Unknown command: %s" % cmd)
help()
sys.exit(1)
args = build_args(cmd, commands[cmd]["args"], args[1:])
if not args:
sys.exit(1)
LOG.info("Executing command \"%s\" with args: %s" % (cmd, args))
client = Client(options.host, options.port, options.ssl,
args[0], FORMAT)
commands[cmd]["func"](client, *args)
LOG.info("Command execution completed")
sys.exit(0)

View File

@@ -28,7 +28,7 @@ def get_view_builder(req):
class ViewBuilder(object):
"""
ViewBuilder for Credential,
ViewBuilder for Credential,
derived from quantum.views.networks
"""
def __init__(self, base_url):
@@ -39,20 +39,18 @@ class ViewBuilder(object):
def build(self, credential_data, is_detail=False):
"""Generic method used to generate a credential entity."""
if is_detail:
credential = self._build_detail(credential_data)
else:
credential = self._build_simple(credential_data)
return credential
def _build_simple(self, credential_data):
"""Return a simple description of credential."""
return dict(credential=dict(id=credential_data['credential_id']))
def _build_detail(self, credential_data):
"""Return a detailed description of credential."""
return dict(credential=dict(id=credential_data['credential_id'],
name=credential_data['user_name'],
password=credential_data['password']))

View File

@@ -29,7 +29,7 @@ def get_view_builder(req):
class ViewBuilder(object):
"""
ViewBuilder for novatenant,
ViewBuilder for novatenant,
derived from quantum.views.networks
"""
def __init__(self, base_url):
@@ -37,11 +37,11 @@ class ViewBuilder(object):
:param base_url: url of the root wsgi application
"""
self.base_url = base_url
def build_host(self, host_data):
"""Return host description."""
return dict(host_list=host_data[const.HOST_LIST])
def build_vif(self, vif_data):
"""Return VIF description."""
return dict(vif_desc=vif_data[const.VIF_DESC])
return dict(vif_desc=vif_data[const.VIF_DESC])

View File

@@ -28,7 +28,7 @@ def get_view_builder(req):
class ViewBuilder(object):
"""
ViewBuilder for Portprofile,
ViewBuilder for Portprofile,
derived from quantum.views.networks
"""
def __init__(self, base_url):
@@ -39,17 +39,16 @@ class ViewBuilder(object):
def build(self, portprofile_data, is_detail=False):
"""Generic method used to generate a portprofile entity."""
if is_detail:
portprofile = self._build_detail(portprofile_data)
else:
portprofile = self._build_simple(portprofile_data)
return portprofile
def _build_simple(self, portprofile_data):
"""Return a simple description of a portprofile"""
return dict(portprofile=dict(id=portprofile_data['profile_id']))
def _build_detail(self, portprofile_data):
"""Return a detailed info of a portprofile."""
if (portprofile_data['assignment'] == None):

View File

@@ -28,7 +28,7 @@ def get_view_builder(req):
class ViewBuilder(object):
"""
ViewBuilder for QoS,
ViewBuilder for QoS,
derived from quantum.views.networks
"""
def __init__(self, base_url):
@@ -44,11 +44,11 @@ class ViewBuilder(object):
else:
qos = self._build_simple(qos_data)
return qos
def _build_simple(self, qos_data):
"""Return a simple description of qos."""
return dict(qos=dict(id=qos_data['qos_id']))
def _build_detail(self, qos_data):
"""Return a detailed description of qos."""
return dict(qos=dict(id=qos_data['qos_id'],

View File

@@ -125,7 +125,7 @@ class CredentialController(common.QuantumController):
""" Creates a new credential for a given tenant """
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._credential_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)
@@ -142,7 +142,7 @@ class CredentialController(common.QuantumController):
""" Updates the name for the credential with the given id """
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._credential_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)

View File

@@ -35,33 +35,33 @@ class Novatenant(object):
@classmethod
def get_name(cls):
""" Returns Ext Resource Name """
""" Returns Ext Resource Name """
return "Cisco Nova Tenant"
@classmethod
def get_alias(cls):
""" Returns Ext Resource alias"""
return "Cisco Nova Tenant"
@classmethod
def get_description(cls):
""" Returns Ext Resource Description """
return "novatenant resource is used by nova side to invoke quantum api"
@classmethod
def get_namespace(cls):
""" Returns Ext Resource Namespace """
return "http://docs.ciscocloud.com/api/ext/novatenant/v1.0"
@classmethod
def get_updated(cls):
""" Returns Ext Resource Updated Time """
return "2011-08-09T13:25:27-06:00"
@classmethod
def get_resources(cls):
""" Returns Ext Resource """
parent_resource = dict(member_name="tenant",
parent_resource = dict(member_name="tenant",
collection_name="extensions/csco/tenants")
member_actions = {'get_host': "PUT",
'get_instance_port': "PUT"}
@@ -78,13 +78,13 @@ class NovatenantsController(common.QuantumController):
_Novatenant_ops_param_list = [{
'param-name': 'novatenant_name',
'required': True}]
_get_host_ops_param_list = [{
'param-name': 'instance_id',
'required': True}, {
'param-name': 'instance_desc',
'required': True}]
_serialization_metadata = {
"application/xml": {
"attributes": {
@@ -96,13 +96,13 @@ class NovatenantsController(common.QuantumController):
def __init__(self, plugin):
self._resource_name = 'novatenant'
self._plugin = plugin
#added for cisco's extension
# pylint: disable-msg=E1101,W0613
def get_host(self, request, tenant_id, id):
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try:
req_params = \
self._parse_request_params(request,
@@ -110,7 +110,6 @@ class NovatenantsController(common.QuantumController):
except exc.HTTPError as exp:
return faults.Fault(exp)
instance_id = req_params['instance_id']
instance_desc = req_params['instance_desc']
try:
host = self._plugin.get_host(tenant_id, instance_id, instance_desc)
@@ -119,11 +118,10 @@ class NovatenantsController(common.QuantumController):
return result
except qexception.PortNotFound as exp:
return faults.Fault(faults.PortNotFound(exp))
def get_instance_port(self, request, tenant_id, id):
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try:
req_params = \
self._parse_request_params(request,
@@ -131,7 +129,6 @@ class NovatenantsController(common.QuantumController):
except exc.HTTPError as exp:
return faults.Fault(exp)
instance_id = req_params['instance_id']
instance_desc = req_params['instance_desc']
try:
vif = self._plugin. \
@@ -139,6 +136,5 @@ class NovatenantsController(common.QuantumController):
builder = novatenant_view.get_view_builder(request)
result = builder.build_vif(vif)
return result
except qexception.PortNotFound as exp:
return faults.Fault(faults.PortNotFound(exp))

View File

@@ -34,36 +34,36 @@ class Portprofile(object):
"""extension class Portprofile"""
def __init__(self):
pass
@classmethod
def get_name(cls):
""" Returns Ext Resource Name """
return "Cisco Port Profile"
@classmethod
def get_alias(cls):
""" Returns Ext Resource alias """
return "Cisco Port Profile"
@classmethod
def get_description(cls):
""" Returns Ext Resource Description """
return "Portprofile include QoS information"
@classmethod
def get_namespace(cls):
""" Returns Ext Resource Namespace """
return "http://docs.ciscocloud.com/api/ext/portprofile/v1.0"
@classmethod
def get_updated(cls):
""" Returns Ext Resource Updated time """
return "2011-07-23T13:25:27-06:00"
@classmethod
def get_resources(cls):
""" Returns all defined resources """
parent_resource = dict(member_name="tenant",
parent_resource = dict(member_name="tenant",
collection_name="extensions/csco/tenants")
member_actions = {'associate_portprofile': "PUT",
'disassociate_portprofile': "PUT"}
@@ -71,16 +71,16 @@ class Portprofile(object):
return [extensions.ResourceExtension('portprofiles', controller,
parent=parent_resource,
member_actions=member_actions)]
class PortprofilesController(common.QuantumController):
""" portprofile API controller
based on QuantumController """
def __init__(self, plugin):
self._resource_name = 'portprofile'
self._plugin = plugin
self._portprofile_ops_param_list = [{
'param-name': 'portprofile_name',
'required': True}, {
@@ -88,13 +88,13 @@ class PortprofilesController(common.QuantumController):
'required': True}, {
'param-name': 'assignment',
'required': False}]
self._assignprofile_ops_param_list = [{
'param-name': 'network-id',
'required': True}, {
'param-name': 'port-id',
'required': True}]
self._serialization_metadata = {
"application/xml": {
"attributes": {
@@ -102,7 +102,7 @@ class PortprofilesController(common.QuantumController):
},
},
}
def index(self, request, tenant_id):
""" Returns a list of portprofile ids """
return self._items(request, tenant_id, is_detail=False)
@@ -114,7 +114,7 @@ class PortprofilesController(common.QuantumController):
result = [builder.build(portprofile, is_detail)['portprofile']
for portprofile in portprofiles]
return dict(portprofiles=result)
# pylint: disable-msg=E1101
def show(self, request, tenant_id, id):
""" Returns portprofile details for the given portprofile id """
@@ -133,7 +133,7 @@ class PortprofilesController(common.QuantumController):
#look for portprofile name in request
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._portprofile_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)
@@ -149,7 +149,7 @@ class PortprofilesController(common.QuantumController):
""" Updates the name for the portprofile with the given id """
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._portprofile_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)
@@ -171,12 +171,12 @@ class PortprofilesController(common.QuantumController):
return exc.HTTPAccepted()
except exception.PortProfileNotFound as exp:
return faults.Fault(faults.PortprofileNotFound(exp))
def associate_portprofile(self, request, tenant_id, id):
""" associate a portprofile to the port """
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try:
req_params = \
self._parse_request_params(request,
@@ -194,12 +194,11 @@ class PortprofilesController(common.QuantumController):
return faults.Fault(faults.PortprofileNotFound(exp))
except qexception.PortNotFound as exp:
return faults.Fault(faults.PortNotFound(exp))
def disassociate_portprofile(self, request, tenant_id, id):
""" Disassociate a portprofile from a port """
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try:
req_params = \
self._parse_request_params(request,

View File

@@ -36,7 +36,7 @@ class Qos(object):
"""Qos extension file"""
def __init__(self):
pass
@classmethod
def get_name(cls):
""" Returns Ext Resource Name """
@@ -56,7 +56,7 @@ class Qos(object):
def get_namespace(cls):
""" Returns Ext Resource Namespace """
return "http://docs.ciscocloud.com/api/ext/qos/v1.0"
@classmethod
def get_updated(cls):
""" Returns Ext Resource update """
@@ -65,9 +65,9 @@ class Qos(object):
@classmethod
def get_resources(cls):
""" Returns Ext Resources """
parent_resource = dict(member_name="tenant",
parent_resource = dict(member_name="tenant",
collection_name="extensions/csco/tenants")
controller = QosController(QuantumManager.get_plugin())
return [extensions.ResourceExtension('qoss', controller,
parent=parent_resource)]
@@ -93,7 +93,7 @@ class QosController(common.QuantumController):
def __init__(self, plugin):
self._resource_name = 'qos'
self._plugin = plugin
def index(self, request, tenant_id):
""" Returns a list of qos ids """
return self._items(request, tenant_id, is_detail=False)
@@ -124,7 +124,7 @@ class QosController(common.QuantumController):
#look for qos name in request
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._qos_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)
@@ -140,7 +140,7 @@ class QosController(common.QuantumController):
""" Updates the name for the qos with the given id """
try:
req_params = \
self._parse_request_params(request,
self._parse_request_params(request,
self._qos_ops_param_list)
except exc.HTTPError as exp:
return faults.Fault(exp)

View File

@@ -63,6 +63,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
LOG.debug("PLUGGING INTERFACE:%s", request_params['id'])
self._plugin.plug_interface(tenant_id, network_id, id,
request_params['id'])
return exc.HTTPNoContent()

View File

@@ -23,7 +23,7 @@ def get_view_builder(req):
class ViewBuilder(object):
def __init__(self, base_url):
def __init__(self, base_url=None):
"""
:param base_url: url of the root wsgi application
"""

View File

@@ -23,7 +23,7 @@ def get_view_builder(req):
class ViewBuilder(object):
def __init__(self, base_url):
def __init__(self, base_url=None):
"""
:param base_url: url of the root wsgi application
"""

View File

@@ -1,392 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# Copyright 2011 Citrix Systems
#
# 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: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
import httplib
import logging as LOG
import json
import socket
import sys
import urllib
from manager import QuantumManager
from optparse import OptionParser
from client import Client
FORMAT = "json"
### -- Core CLI functions
def list_nets(manager, *args):
tenant_id = args[0]
networks = manager.get_all_networks(tenant_id)
print "Virtual Networks on Tenant:%s\n" % tenant_id
for net in networks:
id = net["net-id"]
name = net["net-name"]
print "\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name)
def api_list_nets(client, *args):
tenant_id = args[0]
res = client.list_networks()
LOG.debug(res)
print "Virtual Networks on Tenant:%s\n" % tenant_id
for n in res["networks"]:
net_id = n["id"]
print "\tNetwork ID:%s\n" % (net_id)
# TODO(bgh): we should make this call pass back the name too
# name = n["net-name"]
# LOG.info("\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name))
def create_net(manager, *args):
tid, name = args
new_net_id = manager.create_network(tid, name)
print "Created a new Virtual Network with ID:%s\n" % new_net_id
def api_create_net(client, *args):
tid, name = args
data = {'network': {'net-name': '%s' % name}}
res = client.create_network(data)
LOG.debug(res)
nid = None
try:
nid = res["network"]["id"]
except Exception, e:
print "Failed to create network"
# TODO(bgh): grab error details from ws request result
return
print "Created a new Virtual Network with ID:%s\n" % nid
def delete_net(manager, *args):
tid, nid = args
manager.delete_network(tid, nid)
print "Deleted Virtual Network with ID:%s" % nid
def api_delete_net(client, *args):
tid, nid = args
try:
res = client.delete_network(nid)
print "Deleted Virtual Network with ID:%s" % nid
except Exception, e:
print "Failed to delete network"
LOG.error("Failed to delete network: %s" % e)
def detail_net(manager, *args):
tid, nid = args
iface_list = manager.get_network_details(tid, nid)
print "Remote Interfaces on Virtual Network:%s\n" % nid
for iface in iface_list:
print "\tRemote interface:%s" % iface
def api_detail_net(client, *args):
tid, nid = args
try:
res = client.show_network_details(nid)["network"]
except Exception, e:
LOG.error("Failed to get network details: %s" % e)
return
try:
ports = client.list_ports(nid)
except Exception, e:
LOG.error("Failed to list ports: %s" % e)
return
print "Network %s (%s)" % (res['name'], res['id'])
print "Remote Interfaces on Virtual Network:%s\n" % nid
for port in ports["ports"]:
pid = port["id"]
res = client.show_port_attachment(nid, pid)
LOG.debug(res)
remote_iface = res["attachment"]["id"]
print "\tRemote interface:%s" % remote_iface
def rename_net(manager, *args):
tid, nid, name = args
manager.rename_network(tid, nid, name)
print "Renamed Virtual Network with ID:%s" % nid
def api_rename_net(client, *args):
tid, nid, name = args
data = {'network': {'name': '%s' % name}}
try:
res = client.update_network(nid, data)
except Exception, e:
LOG.error("Failed to rename network %s: %s" % (nid, e))
return
LOG.debug(res)
print "Renamed Virtual Network with ID:%s" % nid
def list_ports(manager, *args):
tid, nid = args
ports = manager.get_all_ports(tid, nid)
print "Ports on Virtual Network:%s\n" % nid
for port in ports:
print "\tVirtual Port:%s" % port["port-id"]
def api_list_ports(client, *args):
tid, nid = args
try:
ports = client.list_ports(nid)
except Exception, e:
LOG.error("Failed to list ports: %s" % e)
return
LOG.debug(ports)
print "Ports on Virtual Network:%s\n" % nid
for port in ports["ports"]:
print "\tVirtual Port:%s" % port["id"]
def create_port(manager, *args):
tid, nid = args
new_port = manager.create_port(tid, nid)
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port, nid)
def api_create_port(client, *args):
tid, nid = args
try:
res = client.create_port(nid)
except Exception, e:
LOG.error("Failed to create port: %s" % e)
return
new_port = res["port"]["id"]
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port, nid)
def delete_port(manager, *args):
tid, nid, pid = args
manager.delete_port(tid, nid, pid)
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
def api_delete_port(client, *args):
tid, nid, pid = args
try:
res = client.delete_port(nid, pid)
except Exception, e:
LOG.error("Failed to delete port: %s" % e)
return
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
print "Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)
def detail_port(manager, *args):
tid, nid, pid = args
port_detail = manager.get_port_details(tid, nid, pid)
print "Virtual Port:%s on Virtual Network:%s " \
"contains remote interface:%s" % (pid, nid, port_detail)
def api_detail_port(client, *args):
tid, nid, pid = args
try:
port = client.show_port_details(nid, pid)["port"]
att = client.show_port_attachment(nid, pid)
except Exception, e:
LOG.error("Failed to get port details: %s" % e)
return
id = port['id']
interface_id = att['id']
LOG.debug(port)
print "Virtual Port:%s on Virtual Network:%s " \
"contains remote interface:%s" % (pid, nid, interface_id)
def plug_iface(manager, *args):
tid, nid, pid, vid = args
manager.plug_interface(tid, nid, pid, vid)
print "Plugged remote interface:%s " \
"into Virtual Network:%s" % (vid, nid)
def api_plug_iface(client, *args):
tid, nid, pid, vid = args
try:
data = {'attachment': {'id': '%s' % vid}}
res = client.attach_resource(nid, pid, data)
except Exception, e:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,
pid, e))
return
LOG.debug(res)
print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid)
def unplug_iface(manager, *args):
tid, nid, pid = args
manager.unplug_interface(tid, nid, pid)
print "UnPlugged remote interface " \
"from Virtual Port:%s Virtual Network:%s" % (pid, nid)
def api_unplug_iface(client, *args):
tid, nid, pid = args
try:
res = client.detach_resource(nid, pid)
except Exception, e:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (pid, e))
return
LOG.debug(res)
print "Unplugged interface from port:%s on network:%s" % (pid, nid)
commands = {
"list_nets": {
"func": list_nets,
"api_func": api_list_nets,
"args": ["tenant-id"]},
"create_net": {
"func": create_net,
"api_func": api_create_net,
"args": ["tenant-id", "net-name"]},
"delete_net": {
"func": delete_net,
"api_func": api_delete_net,
"args": ["tenant-id", "net-id"]},
"detail_net": {
"func": detail_net,
"api_func": api_detail_net,
"args": ["tenant-id", "net-id"]},
"rename_net": {
"func": rename_net,
"api_func": api_rename_net,
"args": ["tenant-id", "net-id", "new-name"]},
"list_ports": {
"func": list_ports,
"api_func": api_list_ports,
"args": ["tenant-id", "net-id"]},
"create_port": {
"func": create_port,
"api_func": api_create_port,
"args": ["tenant-id", "net-id"]},
"delete_port": {
"func": delete_port,
"api_func": api_delete_port,
"args": ["tenant-id", "net-id", "port-id"]},
"detail_port": {
"func": detail_port,
"api_func": api_detail_port,
"args": ["tenant-id", "net-id", "port-id"]},
"plug_iface": {
"func": plug_iface,
"api_func": api_plug_iface,
"args": ["tenant-id", "net-id", "port-id", "iface-id"]},
"unplug_iface": {
"func": unplug_iface,
"api_func": api_unplug_iface,
"args": ["tenant-id", "net-id", "port-id"]}, }
def help():
print "\nCommands:"
for k in commands.keys():
print " %s %s" % (k,
" ".join(["<%s>" % y for y in commands[k]["args"]]))
def build_args(cmd, cmdargs, arglist):
args = []
orig_arglist = arglist[:]
try:
for x in cmdargs:
args.append(arglist[0])
del arglist[0]
except Exception, e:
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"]]))
return None
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"]]))
return None
return args
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)
parser.add_option("-l", "--load-plugin", dest="load_plugin",
action="store_true", default=False,
help="Load plugin directly instead of using WS API")
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")
options, args = parser.parse_args()
if options.verbose:
LOG.basicConfig(level=LOG.DEBUG)
else:
LOG.basicConfig(level=LOG.WARN)
if len(args) < 1:
parser.print_help()
help()
sys.exit(1)
cmd = args[0]
if cmd not in commands.keys():
LOG.error("Unknown command: %s" % cmd)
help()
sys.exit(1)
args = build_args(cmd, commands[cmd]["args"], args[1:])
if not args:
sys.exit(1)
LOG.debug("Executing command \"%s\" with args: %s" % (cmd, args))
if not options.load_plugin:
client = Client(options.host, options.port, options.ssl,
args[0], FORMAT)
if "api_func" not in commands[cmd]:
LOG.error("API version of \"%s\" is not yet implemented" % cmd)
sys.exit(1)
commands[cmd]["api_func"](client, *args)
else:
quantum = QuantumManager()
manager = quantum.get_plugin()
commands[cmd]["func"](manager, *args)
sys.exit(0)

228
quantum/cli_lib.py Executable file
View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# Copyright 2011 Citrix Systems
#
# 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: Somik Behera, Nicira Networks, Inc.
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Salvatore Orlando, Citrix
import Cheetah.Template as cheetah_template
import logging
import os
import sys
FORMAT = "json"
CLI_TEMPLATE = "cli_output.template"
LOG = logging.getLogger('quantum.cli_lib')
def _handle_exception(ex):
LOG.exception(sys.exc_info())
print "Exception:%s - %s" % (sys.exc_info()[0], sys.exc_info()[1])
status_code = None
message = None
# Retrieve dict at 1st element of tuple at last argument
if ex.args and isinstance(ex.args[-1][0], dict):
status_code = ex.args[-1][0].get('status_code', None)
message = ex.args[-1][0].get('message', None)
msg_1 = "Command failed with error code: %s" \
% (status_code or '<missing>')
msg_2 = "Error message:%s" % (message or '<missing>')
LOG.exception(msg_1 + "-" + msg_2)
print msg_1
print msg_2
def prepare_output(cmd, tenant_id, response):
""" Fills a cheetah template with the response """
#add command and tenant to response for output generation
LOG.debug("Preparing output for response:%s", response)
response['cmd'] = cmd
response['tenant_id'] = tenant_id
template_path = os.path.join(os.path.dirname(__file__), CLI_TEMPLATE)
template_file = open(template_path).read()
output = str(cheetah_template.Template(template_file,
searchList=response))
LOG.debug("Finished preparing output for command:%s", cmd)
return output
def list_nets(client, *args):
tenant_id = args[0]
res = client.list_networks()
LOG.debug("Operation 'list_networks' executed.")
output = prepare_output("list_nets", tenant_id, res)
print output
def create_net(client, *args):
tenant_id, name = args
data = {'network': {'name': name}}
new_net_id = None
try:
res = client.create_network(data)
new_net_id = res["network"]["id"]
LOG.debug("Operation 'create_network' executed.")
output = prepare_output("create_net", tenant_id,
dict(network_id=new_net_id))
print output
except Exception as ex:
_handle_exception(ex)
def delete_net(client, *args):
tenant_id, network_id = args
try:
client.delete_network(network_id)
LOG.debug("Operation 'delete_network' executed.")
output = prepare_output("delete_net", tenant_id,
dict(network_id=network_id))
print output
except Exception as ex:
_handle_exception(ex)
def show_net(client, *args):
tenant_id, network_id = args
try:
#NOTE(salvatore-orlando) changed for returning exclusively
# output for GET /networks/{net-id} API operation
res = client.show_network_details(network_id)["network"]
LOG.debug("Operation 'show_network_details' executed.")
output = prepare_output("show_net", tenant_id, dict(network=res))
print output
except Exception as ex:
_handle_exception(ex)
def rename_net(client, *args):
tenant_id, network_id, name = args
data = {'network': {'name': '%s' % name}}
try:
client.update_network(network_id, data)
LOG.debug("Operation 'update_network' executed.")
# Response has no body. Use data for populating output
data['network']['id'] = network_id
output = prepare_output("rename_net", tenant_id, data)
print output
except Exception as ex:
_handle_exception(ex)
def list_ports(client, *args):
tenant_id, network_id = args
try:
ports = client.list_ports(network_id)
LOG.debug("Operation 'list_ports' executed.")
data = ports
data['network_id'] = network_id
output = prepare_output("list_ports", tenant_id, data)
print output
except Exception as ex:
_handle_exception(ex)
def create_port(client, *args):
tenant_id, network_id = args
try:
res = client.create_port(network_id)
LOG.debug("Operation 'create_port' executed.")
new_port_id = res["port"]["id"]
output = prepare_output("create_port", tenant_id,
dict(network_id=network_id,
port_id=new_port_id))
print output
except Exception as ex:
_handle_exception(ex)
def delete_port(client, *args):
tenant_id, network_id, port_id = args
try:
client.delete_port(network_id, port_id)
LOG.debug("Operation 'delete_port' executed.")
output = prepare_output("delete_port", tenant_id,
dict(network_id=network_id,
port_id=port_id))
print output
except Exception as ex:
_handle_exception(ex)
return
def show_port(client, *args):
tenant_id, network_id, port_id = args
try:
port = client.show_port_details(network_id, port_id)["port"]
LOG.debug("Operation 'list_port_details' executed.")
#NOTE(salvatore-orland): current API implementation does not
#return attachment with GET operation on port. Once API alignment
#branch is merged, update client to use the detail action.
# (danwent) Until then, just make additonal webservice call.
attach = client.show_port_attachment(network_id, port_id)['attachment']
if "id" in attach:
port['attachment'] = attach['id']
else:
port['attachment'] = '<none>'
output = prepare_output("show_port", tenant_id,
dict(network_id=network_id,
port=port))
print output
except Exception as ex:
_handle_exception(ex)
def set_port_state(client, *args):
tenant_id, network_id, port_id, new_state = args
data = {'port': {'state': '%s' % new_state}}
try:
client.set_port_state(network_id, port_id, data)
LOG.debug("Operation 'set_port_state' executed.")
# Response has no body. Use data for populating output
data['network_id'] = network_id
data['port']['id'] = port_id
output = prepare_output("set_port_state", tenant_id, data)
print output
except Exception as ex:
_handle_exception(ex)
def plug_iface(client, *args):
tenant_id, network_id, port_id, attachment = args
try:
data = {'attachment': {'id': '%s' % attachment}}
client.attach_resource(network_id, port_id, data)
LOG.debug("Operation 'attach_resource' executed.")
output = prepare_output("plug_iface", tenant_id,
dict(network_id=network_id,
port_id=port_id,
attachment=attachment))
print output
except Exception as ex:
_handle_exception(ex)
def unplug_iface(client, *args):
tenant_id, network_id, port_id = args
try:
client.detach_resource(network_id, port_id)
LOG.debug("Operation 'detach_resource' executed.")
output = prepare_output("unplug_iface", tenant_id,
dict(network_id=network_id,
port_id=port_id))
print output
except Exception as ex:
_handle_exception(ex)

View File

@@ -0,0 +1,56 @@
## Cheetah template for cli output
#if $cmd == 'list_nets'
Virtual Networks for Tenant $tenant_id
#for $network in $networks
Network ID: $network.id
#end for
#elif $cmd == 'create_net'
Created a new Virtual Network with ID: $network_id
for Tenant $tenant_id
#elif $cmd == 'delete_net'
Deleted Virtual Network with ID: $network_id
for Tenant $tenant_id
#elif $cmd == 'show_net'
Network ID: $network.id
Network Name: $network.name
for Tenant: $tenant_id
#elif $cmd == 'rename_net'
Renamed Virtual Network with ID: $network.id
New name is: $network.name
for Tenant $tenant_id,
#elif $cmd == 'list_ports'
Ports on Virtual Network: $network_id
for Tenant: $tenant_id
#for $port in $ports
Logical Port: $port.id
#end for
#elif $cmd == 'create_port'
Created new Logical Port with ID: $port_id
on Virtual Network: $network_id
for Tenant: $tenant_id
#elif $cmd == 'delete_port'
Deleted Logical Port with ID: $port_id
on Virtual Network: $network_id
for Tenant: $tenant_id
#elif $cmd == 'set_port_state'
Updated state for Logical Port with ID: $port.id
new state is: $port.state
on Virtual Network: $network_id
for tenant: $tenant_id
#elif $cmd == 'show_port'
Logical Port ID: $port.id
administrative State: $port.state
interface: $port.attachment
on Virtual Network: $network_id
for Tenant: $tenant_id
#elif $cmd == 'plug_iface'
Plugged interface $attachment
into Logical Port: $port_id
on Virtual Network: $network_id
for Tenant: $tenant_id
#elif $cmd == 'unplug_iface'
Unplugged interface from Logical Port: $port_id
on Virtual Network: $network_id
for Tenant: $tenant_id
#end if

View File

@@ -16,12 +16,15 @@
# under the License.
# @author: Tyler Smith, Cisco Systems
import logging
import httplib
import socket
import urllib
from quantum.common.wsgi import Serializer
from quantum.common import exceptions
from quantum.common import exceptions
from quantum.common.wsgi import Serializer
LOG = logging.getLogger('quantum.client')
EXCEPTIONS = {
400: exceptions.BadInputError,
401: exceptions.NotAuthorized,
@@ -29,8 +32,8 @@ EXCEPTIONS = {
421: exceptions.NetworkInUse,
430: exceptions.PortNotFound,
431: exceptions.StateInvalid,
432: exceptions.PortInUse,
440: exceptions.AlreadyAttached}
432: exceptions.PortInUseClient,
440: exceptions.AlreadyAttachedClient}
class ApiCall(object):
@@ -60,6 +63,17 @@ class Client(object):
"""A base client class - derived from Glance.BaseClient"""
#Metadata for deserializing xml
_serialization_metadata = {
"application/xml": {
"attributes": {
"network": ["id", "name"],
"port": ["id", "state"],
"attachment": ["id"]},
"plurals": {"networks": "network",
"ports": "port"}},
}
# Action query strings
networks_path = "/networks"
network_path = "/networks/%s"
@@ -105,8 +119,19 @@ class Client(object):
else:
return httplib.HTTPConnection
def _send_request(self, conn, method, action, body, headers):
# Salvatore: Isolating this piece of code in its own method to
# facilitate stubout for testing
if self.logger:
self.logger.debug("Quantum Client Request:\n" \
+ method + " " + action + "\n")
if body:
self.logger.debug(body)
conn.request(method, action, body, headers)
return conn.getresponse()
def do_request(self, method, action, body=None,
headers=None, params=None):
headers=None, params=None, exception_args={}):
"""
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
@@ -119,7 +144,7 @@ class Client(object):
to action
"""
LOG.debug("Client issuing request: %s", action)
# Ensure we have a tenant id
if not self.tenant:
raise Exception("Tenant ID not set")
@@ -131,7 +156,6 @@ class Client(object):
if type(params) is dict:
action += '?' + urllib.urlencode(params)
if body:
body = self.serialize(body)
@@ -139,7 +163,6 @@ class Client(object):
connection_type = self.get_connection_type()
headers = headers or {"Content-Type":
"application/%s" % self.format}
# Open connection and send request, handling SSL certs
certs = {'key_file': self.key_file, 'cert_file': self.cert_file}
certs = dict((x, certs[x]) for x in certs if certs[x] != None)
@@ -148,15 +171,7 @@ class Client(object):
conn = connection_type(self.host, self.port, **certs)
else:
conn = connection_type(self.host, self.port)
if self.logger:
self.logger.debug("Quantum Client Request:\n" \
+ method + " " + action + "\n")
if body:
self.logger.debug(body)
conn.request(method, action, body, headers)
res = conn.getresponse()
res = self._send_request(conn, method, action, body, headers)
status_code = self.get_status_code(res)
data = res.read()
@@ -170,13 +185,21 @@ class Client(object):
httplib.NO_CONTENT):
return self.deserialize(data, status_code)
else:
error_message = res.read()
LOG.debug("Server returned error: %s", status_code)
LOG.debug("Error message: %s", error_message)
# Create exception with HTTP status code and message
if res.status in EXCEPTIONS:
raise EXCEPTIONS[res.status]()
raise Exception("Server returned error: %s" % res.read())
raise EXCEPTIONS[res.status](**exception_args)
# Add error code and message to exception arguments
ex = Exception("Server returned error: %s" % status_code)
ex.args = ([dict(status_code=status_code,
message=error_message)],)
raise ex
except (socket.error, IOError), e:
raise Exception("Unable to connect to "
"server. Got error: %s" % e)
msg = "Unable to connect to server. Got error: %s" % e
LOG.exception(msg)
raise Exception(msg)
def get_status_code(self, response):
"""
@@ -205,9 +228,10 @@ class Client(object):
"""
Deserializes a an xml or json string into a dictionary
"""
if status_code == 202:
if status_code in (202, 204):
return data
return Serializer().deserialize(data, self.content_type())
return Serializer(self._serialization_metadata).\
deserialize(data, self.content_type())
def content_type(self, format=None):
"""
@@ -230,7 +254,8 @@ class Client(object):
"""
Fetches the details of a certain network
"""
return self.do_request("GET", self.network_path % (network))
return self.do_request("GET", self.network_path % (network),
exception_args={"net_id": network})
@ApiCall
def create_network(self, body=None):
@@ -244,14 +269,16 @@ class Client(object):
"""
Updates a network
"""
return self.do_request("PUT", self.network_path % (network), body=body)
return self.do_request("PUT", self.network_path % (network), body=body,
exception_args={"net_id": network})
@ApiCall
def delete_network(self, network):
"""
Deletes the specified network
"""
return self.do_request("DELETE", self.network_path % (network))
return self.do_request("DELETE", self.network_path % (network),
exception_args={"net_id": network})
@ApiCall
def list_ports(self, network):
@@ -265,7 +292,8 @@ class Client(object):
"""
Fetches the details of a certain port
"""
return self.do_request("GET", self.port_path % (network, port))
return self.do_request("GET", self.port_path % (network, port),
exception_args={"net_id": network, "port_id": port})
@ApiCall
def create_port(self, network, body=None):
@@ -273,14 +301,16 @@ class Client(object):
Creates a new port on a given network
"""
body = self.serialize(body)
return self.do_request("POST", self.ports_path % (network), body=body)
return self.do_request("POST", self.ports_path % (network), body=body,
exception_args={"net_id": network})
@ApiCall
def delete_port(self, network, port):
"""
Deletes the specified port from a network
"""
return self.do_request("DELETE", self.port_path % (network, port))
return self.do_request("DELETE", self.port_path % (network, port),
exception_args={"net_id": network, "port_id": port})
@ApiCall
def set_port_state(self, network, port, body=None):
@@ -288,14 +318,18 @@ class Client(object):
Sets the state of the specified port
"""
return self.do_request("PUT",
self.port_path % (network, port), body=body)
self.port_path % (network, port), body=body,
exception_args={"net_id": network,
"port_id": port,
"port_state": str(body)})
@ApiCall
def show_port_attachment(self, network, port):
"""
Fetches the attachment-id associated with the specified port
"""
return self.do_request("GET", self.attachment_path % (network, port))
return self.do_request("GET", self.attachment_path % (network, port),
exception_args={"net_id": network, "port_id": port})
@ApiCall
def attach_resource(self, network, port, body=None):
@@ -303,7 +337,10 @@ class Client(object):
Sets the attachment-id of the specified port
"""
return self.do_request("PUT",
self.attachment_path % (network, port), body=body)
self.attachment_path % (network, port), body=body,
exception_args={"net_id": network,
"port_id": port,
"attach_id": str(body)})
@ApiCall
def detach_resource(self, network, port):
@@ -311,4 +348,5 @@ class Client(object):
Removes the attachment-id of the specified port
"""
return self.do_request("DELETE",
self.attachment_path % (network, port))
self.attachment_path % (network, port),
exception_args={"net_id": network, "port_id": port})

View File

@@ -111,6 +111,21 @@ class AlreadyAttached(QuantumException):
"already plugged into port %(att_port_id)s")
# NOTE: on the client side, we often do not know all of the information
# that is known on the server, thus, we create separate exception for
# those scenarios
class PortInUseClient(QuantumException):
message = _("Unable to complete operation on port %(port_id)s " \
"for network %(net_id)s. An attachment " \
"is plugged into the logical port.")
class AlreadyAttachedClient(QuantumException):
message = _("Unable to plug the attachment %(att_id)s into port " \
"%(port_id)s for network %(net_id)s. The attachment is " \
"already plugged into another port.")
class Duplicate(Error):
pass

View File

@@ -142,6 +142,13 @@ def network_destroy(net_id):
net = session.query(models.Network).\
filter_by(uuid=net_id).\
one()
ports = session.query(models.Port).\
filter_by(network_id=net_id).\
all()
for p in ports:
session.delete(p)
session.delete(net)
session.flush()
return net

View File

@@ -71,7 +71,7 @@ class PortprofileNotFound(webob.exc.HTTPClientError):
"""
code = 450
title = 'Portprofile Not Found'
explanation = ('Unable to find a Portprofile with'
explanation = ('Unable to find a Portprofile with'
+ ' the specified identifier.')
@@ -87,8 +87,8 @@ class PortNotFound(webob.exc.HTTPClientError):
code = 430
title = 'Port not Found'
explanation = ('Unable to find a port with the specified identifier.')
class CredentialNotFound(webob.exc.HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
@@ -100,10 +100,10 @@ class CredentialNotFound(webob.exc.HTTPClientError):
"""
code = 451
title = 'Credential Not Found'
explanation = ('Unable to find a Credential with'
explanation = ('Unable to find a Credential with'
+ ' the specified identifier.')
class QosNotFound(webob.exc.HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
@@ -115,10 +115,10 @@ class QosNotFound(webob.exc.HTTPClientError):
"""
code = 452
title = 'QoS Not Found'
explanation = ('Unable to find a QoS with'
explanation = ('Unable to find a QoS with'
+ ' the specified identifier.')
class NovatenantNotFound(webob.exc.HTTPClientError):
"""
subclass of :class:`~HTTPClientError`
@@ -130,7 +130,7 @@ class NovatenantNotFound(webob.exc.HTTPClientError):
"""
code = 453
title = 'Nova tenant Not Found'
explanation = ('Unable to find a Novatenant with'
explanation = ('Unable to find a Novatenant with'
+ ' the specified identifier.')

View File

@@ -0,0 +1,15 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

View File

@@ -0,0 +1,65 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
""" Stubs for client tools unit tests """
from quantum import api as server
from tests.unit import testlib_api
class FakeStdout:
def __init__(self):
self.content = []
def write(self, text):
self.content.append(text)
def make_string(self):
result = ''
for line in self.content:
result = result + line
return result
class FakeHTTPConnection:
""" stub HTTP connection class for CLI testing """
def __init__(self, _1, _2):
# Ignore host and port parameters
self._req = None
options = \
dict(plugin_provider='quantum.plugins.SamplePlugin.FakePlugin')
self._api = server.APIRouterV1(options)
def request(self, method, action, body, headers):
# TODO: remove version prefix from action!
parts = action.split('/', 2)
path = '/' + parts[2]
self._req = testlib_api.create_request(path, body, "application/json",
method)
def getresponse(self):
res = self._req.get_response(self._api)
def _fake_read():
""" Trick for making a webob.Response look like a
httplib.Response
"""
return res.body
setattr(res, 'read', _fake_read)
return res

View File

@@ -261,6 +261,26 @@ class APITest(unittest.TestCase):
self.assertEqual(delete_network_res.status_int, 421)
LOG.debug("_test_delete_network_in_use - format:%s - END", format)
def _test_delete_network_with_unattached_port(self, format):
LOG.debug("_test_delete_network_with_unattached_port "\
"- format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
port_id = self._create_port(network_id, port_state, format)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_network_req = testlib.network_delete_request(self.tenant_id,
network_id,
format)
delete_network_res = delete_network_req.get_response(self.api)
self.assertEqual(delete_network_res.status_int, 204)
LOG.debug("_test_delete_network_with_unattached_port "\
"- format:%s - END", format)
def _test_list_ports(self, format):
LOG.debug("_test_list_ports - format:%s - START", format)
content_type = "application/%s" % format
@@ -848,6 +868,12 @@ class APITest(unittest.TestCase):
def test_delete_network_in_use_xml(self):
self._test_delete_network_in_use('xml')
def test_delete_network_with_unattached_port_xml(self):
self._test_delete_network_with_unattached_port('xml')
def test_delete_network_with_unattached_port_json(self):
self._test_delete_network_with_unattached_port('json')
def test_list_ports_json(self):
self._test_list_ports('json')

420
tests/unit/test_cli.py Normal file
View File

@@ -0,0 +1,420 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 ????
# 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: Salvatore Orlando, Citrix Systems
""" Module containing unit tests for Quantum
command line interface
"""
import logging
import sys
import unittest
from quantum import api as server
from quantum import cli_lib as cli
from quantum.client import Client
from quantum.db import api as db
from tests.unit.client_tools import stubs as client_stubs
LOG = logging.getLogger('quantum.tests.test_cli')
FORMAT = 'json'
class CLITest(unittest.TestCase):
def setUp(self):
"""Prepare the test environment"""
options = {}
options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin'
self.api = server.APIRouterV1(options)
self.tenant_id = "test_tenant"
self.network_name_1 = "test_network_1"
self.network_name_2 = "test_network_2"
# Prepare client and plugin manager
self.client = Client(tenant=self.tenant_id, format=FORMAT,
testingStub=client_stubs.FakeHTTPConnection)
# Redirect stdout
self.fake_stdout = client_stubs.FakeStdout()
sys.stdout = self.fake_stdout
def tearDown(self):
"""Clear the test environment"""
db.clear_db()
sys.stdout = sys.__stdout__
def _verify_list_networks(self):
# Verification - get raw result from db
nw_list = db.network_list(self.tenant_id)
networks = [dict(id=nw.uuid, name=nw.name) for nw in nw_list]
# Fill CLI template
output = cli.prepare_output('list_nets', self.tenant_id,
dict(networks=networks))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_create_network(self):
# Verification - get raw result from db
nw_list = db.network_list(self.tenant_id)
if len(nw_list) != 1:
self.fail("No network created")
network_id = nw_list[0].uuid
# Fill CLI template
output = cli.prepare_output('create_net', self.tenant_id,
dict(network_id=network_id))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_delete_network(self, network_id):
# Verification - get raw result from db
nw_list = db.network_list(self.tenant_id)
if len(nw_list) != 0:
self.fail("DB should not contain any network")
# Fill CLI template
output = cli.prepare_output('delete_net', self.tenant_id,
dict(network_id=network_id))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_rename_network(self):
# Verification - get raw result from db
nw_list = db.network_list(self.tenant_id)
network_data = {'id': nw_list[0].uuid,
'name': nw_list[0].name}
# Fill CLI template
output = cli.prepare_output('rename_net', self.tenant_id,
dict(network=network_data))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_show_network(self):
# Verification - get raw result from db
nw = db.network_list(self.tenant_id)[0]
network = dict(id=nw.uuid, name=nw.name)
# Fill CLI template
output = cli.prepare_output('show_net', self.tenant_id,
dict(network=network))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_list_ports(self, network_id):
# Verification - get raw result from db
port_list = db.port_list(network_id)
ports = [dict(id=port.uuid, state=port.state)
for port in port_list]
# Fill CLI template
output = cli.prepare_output('list_ports', self.tenant_id,
dict(network_id=network_id,
ports=ports))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_create_port(self, network_id):
# Verification - get raw result from db
port_list = db.port_list(network_id)
if len(port_list) != 1:
self.fail("No port created")
port_id = port_list[0].uuid
# Fill CLI template
output = cli.prepare_output('create_port', self.tenant_id,
dict(network_id=network_id,
port_id=port_id))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_delete_port(self, network_id, port_id):
# Verification - get raw result from db
port_list = db.port_list(network_id)
if len(port_list) != 0:
self.fail("DB should not contain any port")
# Fill CLI template
output = cli.prepare_output('delete_port', self.tenant_id,
dict(network_id=network_id,
port_id=port_id))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_set_port_state(self, network_id, port_id):
# Verification - get raw result from db
port = db.port_get(port_id, network_id)
port_data = {'id': port.uuid, 'state': port.state}
# Fill CLI template
output = cli.prepare_output('set_port_state', self.tenant_id,
dict(network_id=network_id,
port=port_data))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_show_port(self, network_id, port_id):
# Verification - get raw result from db
# TODO(salvatore-orlando): Must resolve this issue with
# attachment in separate bug fix.
port = db.port_get(port_id, network_id)
port_data = {'id': port.uuid, 'state': port.state,
'attachment': "<none>"}
if port.interface_id is not None:
port_data['attachment'] = port.interface_id
# Fill CLI template
output = cli.prepare_output('show_port', self.tenant_id,
dict(network_id=network_id,
port=port_data))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_plug_iface(self, network_id, port_id):
# Verification - get raw result from db
port = db.port_get(port_id, network_id)
# Fill CLI template
output = cli.prepare_output("plug_iface", self.tenant_id,
dict(network_id=network_id,
port_id=port['uuid'],
attachment=port['interface_id']))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def _verify_unplug_iface(self, network_id, port_id):
# Verification - get raw result from db
port = db.port_get(port_id, network_id)
# Fill CLI template
output = cli.prepare_output("unplug_iface", self.tenant_id,
dict(network_id=network_id,
port_id=port['uuid']))
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
def test_list_networks(self):
try:
# Pre-populate data for testing using db api
db.network_create(self.tenant_id, self.network_name_1)
db.network_create(self.tenant_id, self.network_name_2)
cli.list_nets(self.client, self.tenant_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_list_networks failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_list_networks()
def test_create_network(self):
try:
cli.create_net(self.client, self.tenant_id, "test")
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_create_network failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_create_network()
def test_delete_network(self):
try:
db.network_create(self.tenant_id, self.network_name_1)
network_id = db.network_list(self.tenant_id)[0]['uuid']
cli.delete_net(self.client, self.tenant_id, network_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_delete_network failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_delete_network(network_id)
def test_show_network(self):
try:
# Load some data into the datbase
net = db.network_create(self.tenant_id, self.network_name_1)
cli.show_net(self.client, self.tenant_id, net['uuid'])
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_detail_network failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_show_network()
def test_rename_network(self):
try:
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
cli.rename_net(self.client, self.tenant_id,
network_id, self.network_name_2)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_rename_network failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_rename_network()
def test_list_ports(self):
try:
# Pre-populate data for testing using db api
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
db.port_create(network_id)
db.port_create(network_id)
cli.list_ports(self.client, self.tenant_id, network_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_list_ports failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_list_ports(network_id)
def test_create_port(self):
network_id = None
try:
# Pre-populate data for testing using db api
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
cli.create_port(self.client, self.tenant_id, network_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_create_port failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_create_port(network_id)
def test_delete_port(self):
network_id = None
port_id = None
try:
# Pre-populate data for testing using db api
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(network_id)
port_id = port['uuid']
cli.delete_port(self.client, self.tenant_id, network_id, port_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_delete_port failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_delete_port(network_id, port_id)
def test_set_port_state(self):
try:
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(network_id)
port_id = port['uuid']
# Default state is DOWN - change to ACTIVE.
cli.set_port_state(self.client, self.tenant_id, network_id,
port_id, 'ACTIVE')
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_set_port_state failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_set_port_state(network_id, port_id)
def test_show_port_no_attach(self):
network_id = None
port_id = None
try:
# Pre-populate data for testing using db api
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(network_id)
port_id = port['uuid']
cli.show_port(self.client, self.tenant_id, network_id, port_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_show_port_no_attach failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_show_port(network_id, port_id)
def test_show_port_with_attach(self):
network_id = None
port_id = None
iface_id = "flavor crystals"
try:
# Pre-populate data for testing using db api
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(network_id)
port_id = port['uuid']
db.port_set_attachment(port_id, network_id, iface_id)
cli.show_port(self.client, self.tenant_id, network_id, port_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_show_port_with_attach failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_show_port(network_id, port_id)
def test_plug_iface(self):
network_id = None
port_id = None
try:
# Load some data into the datbase
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(net['uuid'])
port_id = port['uuid']
cli.plug_iface(self.client, self.tenant_id, network_id,
port_id, "test_iface_id")
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_plug_iface failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_plug_iface(network_id, port_id)
def test_unplug_iface(self):
network_id = None
port_id = None
try:
# Load some data into the datbase
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
port = db.port_create(net['uuid'])
port_id = port['uuid']
db.port_set_attachment(port_id, network_id, "test_iface_id")
cli.unplug_iface(self.client, self.tenant_id, network_id, port_id)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_plug_iface failed due to an exception")
LOG.debug("Operation completed. Verifying result")
LOG.debug(self.fake_stdout.content)
self._verify_unplug_iface(network_id, port_id)

View File

@@ -72,10 +72,10 @@ class ResourceExtensionTest(unittest.TestCase):
index_response = test_app.get("/tweedles")
self.assertEqual(200, index_response.status_int)
self.assertEqual("resource index", index_response.body)
show_response = test_app.get("/tweedles/25266")
self.assertEqual({'data': {'id': "25266"}}, show_response.json)
def test_resource_extension_with_custom_member_action(self):
controller = self.ResourceExtensionController()
member = {'custom_member_action': "GET"}

View File

@@ -1,5 +1,6 @@
eventlet>=0.9.12
Routes>=1.12.3
Cheetah>=2.0.1
nose
Paste
PasteDeploy