Merge trunk
Updating names for APIRouter in CLI and CLI tests
This commit is contained in:
165
bin/cli
Executable file
165
bin/cli
Executable 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)
|
||||
@@ -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']))
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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'],
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
@@ -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
|
||||
"""
|
||||
|
||||
392
quantum/cli.py
392
quantum/cli.py
@@ -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
228
quantum/cli_lib.py
Executable 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)
|
||||
56
quantum/cli_output.template
Normal file
56
quantum/cli_output.template
Normal 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
|
||||
|
||||
@@ -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})
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.')
|
||||
|
||||
|
||||
|
||||
15
tests/unit/client_tools/__init__.py
Normal file
15
tests/unit/client_tools/__init__.py
Normal 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.
|
||||
65
tests/unit/client_tools/stubs.py
Normal file
65
tests/unit/client_tools/stubs.py
Normal 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
|
||||
@@ -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
420
tests/unit/test_cli.py
Normal 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)
|
||||
@@ -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"}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
eventlet>=0.9.12
|
||||
Routes>=1.12.3
|
||||
Cheetah>=2.0.1
|
||||
nose
|
||||
Paste
|
||||
PasteDeploy
|
||||
|
||||
Reference in New Issue
Block a user