bp/quantum-client-1.1

- Adds supports for 1.1 API
- Handles new error codes
- Support filters for 1.1 API

Addressing Dan's comments

Change-Id: I230657de1621ddf08573bbeaceeb26cafbee1a00
This commit is contained in:
Salvatore Orlando 2012-02-13 14:38:17 +00:00
parent 880cde716e
commit f6d5087698
6 changed files with 615 additions and 222 deletions

@ -25,19 +25,104 @@ from quantum.common import exceptions
from quantum.common.serializer import Serializer
LOG = logging.getLogger('quantum.client')
EXCEPTIONS = {
400: exceptions.BadInputError,
401: exceptions.NotAuthorized,
420: exceptions.NetworkNotFound,
421: exceptions.NetworkInUse,
430: exceptions.PortNotFound,
431: exceptions.StateInvalid,
432: exceptions.PortInUseClient,
440: exceptions.AlreadyAttachedClient,
501: exceptions.NotImplementedError}
AUTH_TOKEN_HEADER = "X-Auth-Token"
def exception_handler_v10(status_code, error_content):
""" Exception handler for API v1.0 client
This routine generates the appropriate
Quantum exception according to the contents of the
response body
:param status_code: HTTP error status code
:param error_content: deserialized body of error response
"""
quantum_error_types = {
420: 'networkNotFound',
421: 'networkInUse',
430: 'portNotFound',
431: 'requestedStateInvalid',
432: 'portInUse',
440: 'alreadyAttached'
}
quantum_errors = {
400: exceptions.BadInputError,
401: exceptions.NotAuthorized,
404: exceptions.NotFound,
420: exceptions.NetworkNotFoundClient,
421: exceptions.NetworkInUseClient,
430: exceptions.PortNotFoundClient,
431: exceptions.StateInvalidClient,
432: exceptions.PortInUseClient,
440: exceptions.AlreadyAttachedClient,
501: NotImplementedError
}
# Find real error type
error_type = None
if isinstance(error_content, dict):
error_type = quantum_error_types.get(status_code)
if error_type:
error_dict = error_content[error_type]
error_message = error_dict['message'] + "\n" +\
error_dict['detail']
else:
error_message = error_content
# raise the appropriate error!
ex = quantum_errors[status_code](message=error_message)
ex.args = ([dict(status_code=status_code,
message=error_message)],)
raise ex
def exception_handler_v11(status_code, error_content):
""" Exception handler for API v1.1 client
This routine generates the appropriate
Quantum exception according to the contents of the
response body
:param status_code: HTTP error status code
:param error_content: deserialized body of error response
"""
quantum_errors = {
'NetworkNotFound': exceptions.NetworkNotFoundClient,
'NetworkInUse': exceptions.NetworkInUseClient,
'PortNotFound': exceptions.PortNotFoundClient,
'RequestedStateInvalid': exceptions.StateInvalidClient,
'PortInUse': exceptions.PortInUseClient,
'AlreadyAttached': exceptions.AlreadyAttachedClient,
}
error_dict = None
if isinstance(error_content, dict):
error_dict = error_content.get('QuantumError')
# Find real error type
if error_dict:
# If QuantumError key is found, it will definitely contain
# a 'message' and 'type' keys
error_type = error_dict['type']
error_message = (error_dict['message'] + "\n" +
error_dict['detail'])
# raise the appropriate error!
ex = quantum_errors[error_type](message=error_message)
ex.args = ([dict(status_code=status_code,
message=error_message)],)
raise ex
# If we end up here the exception was not a quantum error
raise exceptions.QuantumClientException(status_code + "-" + error_content)
EXCEPTION_HANDLERS = {
'1.0': exception_handler_v10,
'1.1': exception_handler_v11
}
class ApiCall(object):
"""A Decorator to add support for format and tenant overriding"""
def __init__(self, function):
@ -86,7 +171,8 @@ class Client(object):
def __init__(self, host="127.0.0.1", port=9696, use_ssl=False, tenant=None,
format="xml", testingStub=None, key_file=None, cert_file=None,
auth_token=None, logger=None,
action_prefix="/v1.0/tenants/{tenant_id}"):
version="1.1",
uri_prefix="/tenants/{tenant_id}"):
"""
Creates a new client to some service.
@ -113,7 +199,24 @@ class Client(object):
self.cert_file = cert_file
self.logger = logger
self.auth_token = auth_token
self.action_prefix = action_prefix
self.version = version
self.action_prefix = "/v%s%s" % (version, uri_prefix)
def _handle_fault_response(self, status_code, response_body):
# Create exception with HTTP status code and message
error_message = response_body
LOG.debug("Server returned error: %s", status_code)
LOG.debug("Error message: %s", error_message)
# Add deserialized error message to exception arguments
try:
des_error_body = Serializer().deserialize(error_message,
self.content_type())
except:
# If unable to deserialized body it is probably not a
# Quantum error
des_error_body = {'message': error_message}
# Raise the appropriate exception
EXCEPTION_HANDLERS[self.version](status_code, des_error_body)
def get_connection_type(self):
"""
@ -138,7 +241,7 @@ class Client(object):
return conn.getresponse()
def do_request(self, method, action, body=None,
headers=None, params=None, exception_args={}):
headers=None, params=None):
"""
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
@ -155,7 +258,6 @@ class Client(object):
# Ensure we have a tenant id
if not self.tenant:
raise Exception("Tenant ID not set")
# Add format and tenant_id
action += ".%s" % self.format
action = self.action_prefix + action
@ -165,7 +267,6 @@ class Client(object):
action += '?' + urllib.urlencode(params)
if body:
body = self.serialize(body)
try:
connection_type = self.get_connection_type()
headers = headers or {"Content-Type":
@ -176,7 +277,6 @@ class Client(object):
# 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)
if self.use_ssl and len(certs):
conn = connection_type(self.host, self.port, **certs)
else:
@ -184,7 +284,6 @@ class Client(object):
res = self._send_request(conn, method, action, body, headers)
status_code = self.get_status_code(res)
data = res.read()
if self.logger:
self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
% (str(status_code), data))
@ -194,17 +293,7 @@ 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](**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
self._handle_fault_response(status_code, data)
except (socket.error, IOError), e:
msg = "Unable to connect to server. Got error: %s" % e
LOG.exception(msg)
@ -263,8 +352,7 @@ class Client(object):
"""
Fetches the details of a certain network
"""
return self.do_request("GET", self.network_path % (network),
exception_args={"net_id": network})
return self.do_request("GET", self.network_path % (network))
@ApiCall
def create_network(self, body=None):
@ -278,16 +366,14 @@ class Client(object):
"""
Updates a network
"""
return self.do_request("PUT", self.network_path % (network), body=body,
exception_args={"net_id": network})
return self.do_request("PUT", self.network_path % (network), body=body)
@ApiCall
def delete_network(self, network):
"""
Deletes the specified network
"""
return self.do_request("DELETE", self.network_path % (network),
exception_args={"net_id": network})
return self.do_request("DELETE", self.network_path % (network))
@ApiCall
def list_ports(self, network):
@ -301,24 +387,21 @@ class Client(object):
"""
Fetches the details of a certain port
"""
return self.do_request("GET", self.port_path % (network, port),
exception_args={"net_id": network, "port_id": port})
return self.do_request("GET", self.port_path % (network, port))
@ApiCall
def create_port(self, network, body=None):
"""
Creates a new port on a given network
"""
return self.do_request("POST", self.ports_path % (network), body=body,
exception_args={"net_id": network})
return self.do_request("POST", self.ports_path % (network), body=body)
@ApiCall
def delete_port(self, network, port):
"""
Deletes the specified port from a network
"""
return self.do_request("DELETE", self.port_path % (network, port),
exception_args={"net_id": network, "port_id": port})
return self.do_request("DELETE", self.port_path % (network, port))
@ApiCall
def update_port(self, network, port, body=None):
@ -326,17 +409,14 @@ class Client(object):
Sets the attributes of the specified port
"""
return self.do_request("PUT",
self.port_path % (network, port), body=body,
exception_args={"net_id": network,
"port_id": port})
self.port_path % (network, port), body=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),
exception_args={"net_id": network, "port_id": port})
return self.do_request("GET", self.attachment_path % (network, port))
@ApiCall
def attach_resource(self, network, port, body=None):
@ -344,10 +424,7 @@ class Client(object):
Sets the attachment-id of the specified port
"""
return self.do_request("PUT",
self.attachment_path % (network, port), body=body,
exception_args={"net_id": network,
"port_id": port,
"attach_id": str(body)})
self.attachment_path % (network, port), body=body)
@ApiCall
def detach_resource(self, network, port):
@ -355,5 +432,28 @@ class Client(object):
Removes the attachment-id of the specified port
"""
return self.do_request("DELETE",
self.attachment_path % (network, port),
exception_args={"net_id": network, "port_id": port})
self.attachment_path % (network, port))
class ClientV11(Client):
"""
This class overiddes some methods of the Client class in order to deal with
features specific to API v1.1 such as filters
"""
@ApiCall
def list_networks(self, **filters):
"""
Fetches a list of all networks for a tenant
"""
# Pass filters in "params" argument to do_request
return self.do_request("GET", self.networks_path, params=filters)
@ApiCall
def list_ports(self, network, **filters):
"""
Fetches a list of ports on a given network
"""
# Pass filters in "params" argument to do_request
return self.do_request("GET", self.ports_path % (network),
params=filters)

@ -1,7 +1,7 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# Copyright 2011 Citrix Systems
# Copyright 2012 Nicira Networks, Inc.
# Copyright 2012 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
@ -26,6 +26,7 @@ import sys
from optparse import OptionParser
from quantum.common import exceptions
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
@ -37,13 +38,16 @@ gettext.install('quantum', unicode=1)
from quantum.client import cli_lib
from quantum.client import Client
from quantum.client import ClientV11
from quantum.common import utils
#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')
DEFAULT_QUANTUM_VERSION = '1.1'
FORMAT = 'json'
commands = {
commands_v10 = {
"list_nets": {
"func": cli_lib.list_nets,
"args": ["tenant-id"]},
@ -81,36 +85,122 @@ commands = {
"func": cli_lib.unplug_iface,
"args": ["tenant-id", "net-id", "port-id"]}, }
commands_v11 = commands_v10.copy()
commands_v11.update({
"list_nets": {
"func": cli_lib.list_nets_v11,
"args": ["tenant-id"],
"filters": ["name", "op-status", "port-op-status", "port-state",
"has-attachment", "attachment", "port"]},
"list_ports": {
"func": cli_lib.list_ports_v11,
"args": ["tenant-id", "net-id"],
"filters": ["name", "op-status", "has-attachment", "attachment"]},
})
commands = {
'1.0': commands_v10,
'1.1': commands_v11
}
clients = {
'1.0': Client,
'1.1': ClientV11
}
def help():
def help(version):
print "\nCommands:"
for k in commands.keys():
print " %s %s" % (k,
" ".join(["<%s>" % y for y in commands[k]["args"]]))
cmds = commands[version]
for k in cmds.keys():
print " %s %s %s" % (k,
" ".join(["<%s>" % y for y in cmds[k]["args"]]),
'filters' in cmds[k] and "[filterspec ...]" or "")
def print_usage(cmd, version):
cmds = commands[version]
print "Usage:\n %s %s" % (cmd,
" ".join(["<%s>" % y for y in cmds[cmd]["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
arglist_len = len(arglist)
cmdargs_len = len(cmdargs)
if arglist_len < cmdargs_len:
message = "Not enough arguments for \"%s\" (expected: %d, got: %d)"\
% (cmd, len(cmdargs), arglist_len)
raise exceptions.QuantumCLIError(message=message)
args = arglist[:cmdargs_len]
return args
def build_filters(cmd, cmd_filters, filter_list, version):
filters = {}
# Each filter is expected to be in the <key>=<value> format
for flt in filter_list:
split_filter = flt.split("=")
if len(split_filter) != 2:
message = "Invalid filter argument detected (%s)" % flt
raise exceptions.QuantumCLIError(message=message)
filter_key, filter_value = split_filter
# Ensure the filter is allowed
if not filter_key in cmd_filters:
message = "Invalid filter key (%s)" % filter_key
raise exceptions.QuantumCLIError(message=message)
filters[filter_key] = filter_value
return filters
def build_cmd(cmd, cmd_args, cmd_filters, arglist, version):
"""
Builds arguments and filters to be passed to the cli library routines
:param cmd: Command to be executed
:param cmd_args: List of arguments required by the command
:param cmd_filters: List of filters allowed by the command
:param arglist: Command line arguments (includes both arguments and
filter specifications)
:param version: API version
"""
arglist_len = len(arglist)
try:
# Parse arguments
args = build_args(cmd, cmd_args, arglist)
# Parse filters
filters = None
if cmd_filters:
# Pop consumed arguments
arglist = arglist[len(args):]
filters = build_filters(cmd, cmd_filters, arglist, version)
except exceptions.QuantumCLIError as cli_ex:
LOG.error(cli_ex.message)
print " Error in command line:%s" % cli_ex.message
print_usage(cmd, version)
return None, None
filter_len = (filters is not None) and len(filters) or 0
if len(arglist) - len(args) - filter_len > 0:
message = "Too many arguments for \"%s\" (expected: %d, got: %d)"\
% (cmd, len(cmd_args), arglist_len)
LOG.error(message)
print "Error in command line: %s " % message
print "Usage:\n %s %s" % (cmd,
" ".join(["<%s>" % y for y in commands[version][cmd]["args"]]))
return None, None
# Append version to arguments for cli functions
args.append(version)
return args, filters
def instantiate_client(host, port, ssl, tenant, token, version):
client = clients[version](host,
port,
ssl,
tenant,
FORMAT,
auth_token=token,
version=version)
return client
def main():
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)
@ -126,13 +216,15 @@ def main():
type="string", default="syslog", help="log file path")
parser.add_option("-t", "--token", dest="token",
type="string", default=None, help="authentication token")
parser.add_option('--version',
default=utils.env('QUANTUM_VERSION', default=DEFAULT_QUANTUM_VERSION),
help='Accepts 1.1 and 1.0, defaults to env[QUANTUM_VERSION].')
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'))
@ -141,29 +233,45 @@ def main():
# Set permissions on log file
os.chmod(options.logfile, 0644)
version = options.version
if not version in commands:
LOG.error("Unknown API version specified:%s", version)
print "Unknown API version: %s" % version
if len(args) < 1:
parser.print_help()
help()
help(version)
sys.exit(1)
cmd = args[0]
if cmd not in commands.keys():
if cmd not in commands[version].keys():
LOG.error("Unknown command: %s" % cmd)
help()
help(version)
sys.exit(1)
args = build_args(cmd, commands[cmd]["args"], args[1:])
# Build argument list for CLI command
# The argument list will include the version number as well
args, filters = build_cmd(cmd,
commands[version][cmd]["args"],
commands[version][cmd].get("filters", None),
args[1:],
options.version)
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,
auth_token=options.token)
commands[cmd]["func"](client, *args)
client = instantiate_client(options.host,
options.port,
options.ssl,
args[0],
options.token,
options.version)
# append filters to arguments
# this will allow for using the same prototype for v10 and v11
# TODO: Use **kwargs instead of *args (keyword is better than positional)
if filters:
args.append(filters)
commands[version][cmd]["func"](client, *args)
LOG.info("Command execution completed")
sys.exit(0)
if __name__ == "__main__":
main()

@ -91,7 +91,7 @@ class CmdOutputTemplate(OutputTemplate):
Extends OutputTemplate loading a different template for each command.
"""
_templates = {
_templates_v10 = {
"list_nets": "Virtual Networks for Tenant %(tenant_id)s\n" +
"%(networks|\tNetwork ID: %(id)s)s",
"show_net": "Network ID: %(network.id)s\n" +
@ -130,13 +130,31 @@ class CmdOutputTemplate(OutputTemplate):
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s"}
def __init__(self, cmd, data):
super(CmdOutputTemplate, self).__init__(self._templates[cmd], data)
_templates_v11 = _templates_v10.copy()
_templates_v11.update({
"show_net": "Network ID: %(network.id)s\n" +
"network Name: %(network.name)s\n" +
"operational Status: %(network.op-status)s",
"show_port": "Logical Port ID: %(port.id)s\n" +
"administrative state: %(port.state)s\n" +
"operational status: %(port.op-status)s\n" +
"interface: %(port.attachment)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
})
_templates = {
'1.0': _templates_v10,
'1.1': _templates_v11
}
def __init__(self, cmd, data, version):
super(CmdOutputTemplate, self).__init__(
self._templates[version][cmd], data)
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
@ -149,67 +167,94 @@ def _handle_exception(ex):
LOG.exception(msg_1 + "-" + msg_2)
print msg_1
print msg_2
else:
print "An unexpected exception occured:%s (%s)" % (sys.exc_info()[1],
sys.exc_info()[0])
def prepare_output(cmd, tenant_id, response):
LOG.debug("Preparing output for response:%s", response)
def prepare_output(cmd, tenant_id, response, version):
LOG.debug("Preparing output for response:%s, version:%s"
% (response, version))
response['tenant_id'] = tenant_id
output = str(CmdOutputTemplate(cmd, response))
output = str(CmdOutputTemplate(cmd, response, version))
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
tenant_id, version = args
try:
res = client.list_networks()
LOG.debug("Operation 'list_networks' executed.")
output = prepare_output("list_nets", tenant_id, res, version)
print output
except Exception as ex:
_handle_exception(ex)
def list_nets_v11(client, *args):
filters = {}
tenant_id, version = args[:2]
if len(args) > 2:
filters = args[2]
try:
res = client.list_networks(**filters)
LOG.debug("Operation 'list_networks' executed.")
output = prepare_output("list_nets", tenant_id, res, version)
print output
except Exception as ex:
_handle_exception(ex)
def create_net(client, *args):
tenant_id, name = args
tenant_id, name, version = 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))
output = prepare_output("create_net",
tenant_id,
dict(network_id=new_net_id),
version)
print output
except Exception as ex:
_handle_exception(ex)
def delete_net(client, *args):
tenant_id, network_id = args
tenant_id, network_id, version = 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))
output = prepare_output("delete_net",
tenant_id,
dict(network_id=network_id),
version)
print output
except Exception as ex:
_handle_exception(ex)
def show_net(client, *args):
tenant_id, network_id = args
tenant_id, network_id, version = 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))
output = prepare_output("show_net",
tenant_id,
dict(network=res),
version)
print output
except Exception as ex:
_handle_exception(ex)
def update_net(client, *args):
tenant_id, network_id, param_data = args
tenant_id, network_id, param_data, version = args
data = {'network': {}}
for kv in param_data.split(","):
k, v = kv.split("=")
@ -219,47 +264,67 @@ def update_net(client, *args):
client.update_network(network_id, data)
LOG.debug("Operation 'update_network' executed.")
# Response has no body. Use data for populating output
output = prepare_output("update_net", tenant_id, data)
output = prepare_output("update_net", tenant_id, data, version)
print output
except Exception as ex:
_handle_exception(ex)
def list_ports(client, *args):
tenant_id, network_id = args
tenant_id, network_id, version = 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)
output = prepare_output("list_ports", tenant_id, data, version)
print output
except Exception as ex:
_handle_exception(ex)
def list_ports_v11(client, *args):
filters = {}
tenant_id, network_id, version = args[:3]
if len(args) > 3:
filters = args[3]
try:
ports = client.list_ports(network_id, **filters)
LOG.debug("Operation 'list_ports' executed.")
data = ports
data['network_id'] = network_id
output = prepare_output("list_ports", tenant_id, data, version)
print output
except Exception as ex:
_handle_exception(ex)
def create_port(client, *args):
tenant_id, network_id = args
tenant_id, network_id, version = 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,
output = prepare_output("create_port",
tenant_id,
dict(network_id=network_id,
port_id=new_port_id))
port_id=new_port_id),
version)
print output
except Exception as ex:
_handle_exception(ex)
def delete_port(client, *args):
tenant_id, network_id, port_id = args
tenant_id, network_id, port_id, version = args
try:
client.delete_port(network_id, port_id)
LOG.debug("Operation 'delete_port' executed.")
output = prepare_output("delete_port", tenant_id,
output = prepare_output("delete_port",
tenant_id,
dict(network_id=network_id,
port_id=port_id))
port_id=port_id),
version)
print output
except Exception as ex:
_handle_exception(ex)
@ -267,7 +332,7 @@ def delete_port(client, *args):
def show_port(client, *args):
tenant_id, network_id, port_id = args
tenant_id, network_id, port_id, version = args
try:
port = client.show_port_details(network_id, port_id)["port"]
LOG.debug("Operation 'list_port_details' executed.")
@ -280,16 +345,18 @@ def show_port(client, *args):
port['attachment'] = attach['id']
else:
port['attachment'] = '<none>'
output = prepare_output("show_port", tenant_id,
output = prepare_output("show_port",
tenant_id,
dict(network_id=network_id,
port=port))
port=port),
version)
print output
except Exception as ex:
_handle_exception(ex)
def update_port(client, *args):
tenant_id, network_id, port_id, param_data = args
tenant_id, network_id, port_id, param_data, version = args
data = {'port': {}}
for kv in param_data.split(","):
k, v = kv.split("=")
@ -300,14 +367,14 @@ def update_port(client, *args):
client.update_port(network_id, port_id, data)
LOG.debug("Operation 'udpate_port' executed.")
# Response has no body. Use data for populating output
output = prepare_output("update_port", tenant_id, data)
output = prepare_output("update_port", tenant_id, data, version)
print output
except Exception as ex:
_handle_exception(ex)
def plug_iface(client, *args):
tenant_id, network_id, port_id, attachment = args
tenant_id, network_id, port_id, attachment, version = args
try:
data = {'attachment': {'id': '%s' % attachment}}
client.attach_resource(network_id, port_id, data)
@ -315,20 +382,23 @@ def plug_iface(client, *args):
output = prepare_output("plug_iface", tenant_id,
dict(network_id=network_id,
port_id=port_id,
attachment=attachment))
attachment=attachment),
version)
print output
except Exception as ex:
_handle_exception(ex)
def unplug_iface(client, *args):
tenant_id, network_id, port_id = args
tenant_id, network_id, port_id, version = args
try:
client.detach_resource(network_id, port_id)
LOG.debug("Operation 'detach_resource' executed.")
output = prepare_output("unplug_iface", tenant_id,
output = prepare_output("unplug_iface",
tenant_id,
dict(network_id=network_id,
port_id=port_id))
port_id=port_id),
version)
print output
except Exception as ex:
_handle_exception(ex)

@ -33,6 +33,7 @@ from quantum.db import api as db
from quantum.tests.unit.client_tools import stubs as client_stubs
LOG = logging.getLogger('quantum.tests.test_cli')
API_VERSION = "1.1"
FORMAT = 'json'
@ -49,9 +50,12 @@ class CLITest(unittest.TestCase):
self.tenant_id = "test_tenant"
self.network_name_1 = "test_network_1"
self.network_name_2 = "test_network_2"
self.version = API_VERSION
# Prepare client and plugin manager
self.client = Client(tenant=self.tenant_id, format=FORMAT,
testingStub=client_stubs.FakeHTTPConnection)
self.client = Client(tenant=self.tenant_id,
format=FORMAT,
testingStub=client_stubs.FakeHTTPConnection,
version=self.version)
# Redirect stdout
self.fake_stdout = client_stubs.FakeStdout()
sys.stdout = self.fake_stdout
@ -64,10 +68,13 @@ class CLITest(unittest.TestCase):
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]
networks = [{'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))
output = cli.prepare_output('list_nets',
self.tenant_id,
dict(networks=networks),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -79,8 +86,10 @@ class CLITest(unittest.TestCase):
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))
output = cli.prepare_output('create_net',
self.tenant_id,
dict(network_id=network_id),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -91,8 +100,10 @@ class CLITest(unittest.TestCase):
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))
output = cli.prepare_output('delete_net',
self.tenant_id,
dict(network_id=network_id),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -101,10 +112,13 @@ class CLITest(unittest.TestCase):
# 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}
'name': nw_list[0].name,
'op-status': nw_list[0].op_status}
# Fill CLI template
output = cli.prepare_output('update_net', self.tenant_id,
dict(network=network_data))
output = cli.prepare_output('update_net',
self.tenant_id,
dict(network=network_data),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -112,10 +126,14 @@ class CLITest(unittest.TestCase):
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)
network = {'id': nw.uuid,
'name': nw.name,
'op-status': nw.op_status}
# Fill CLI template
output = cli.prepare_output('show_net', self.tenant_id,
dict(network=network))
output = cli.prepare_output('show_net',
self.tenant_id,
dict(network=network),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -126,9 +144,11 @@ class CLITest(unittest.TestCase):
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,
output = cli.prepare_output('list_ports',
self.tenant_id,
dict(network_id=network_id,
ports=ports))
ports=ports),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -140,9 +160,11 @@ class CLITest(unittest.TestCase):
self.fail("No port created")
port_id = port_list[0].uuid
# Fill CLI template
output = cli.prepare_output('create_port', self.tenant_id,
output = cli.prepare_output('create_port',
self.tenant_id,
dict(network_id=network_id,
port_id=port_id))
port_id=port_id),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -153,9 +175,11 @@ class CLITest(unittest.TestCase):
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,
output = cli.prepare_output('delete_port',
self.tenant_id,
dict(network_id=network_id,
port_id=port_id))
port_id=port_id),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -163,11 +187,15 @@ class CLITest(unittest.TestCase):
def _verify_update_port(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}
port_data = {'id': port.uuid,
'state': port.state,
'op-status': port.op_status}
# Fill CLI template
output = cli.prepare_output('update_port', self.tenant_id,
output = cli.prepare_output('update_port',
self.tenant_id,
dict(network_id=network_id,
port=port_data))
port=port_data),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -177,15 +205,19 @@ class CLITest(unittest.TestCase):
# 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>"}
port_data = {'id': port.uuid,
'state': port.state,
'attachment': "<none>",
'op-status': port.op_status}
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,
output = cli.prepare_output('show_port',
self.tenant_id,
dict(network_id=network_id,
port=port_data))
port=port_data),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -194,10 +226,12 @@ class CLITest(unittest.TestCase):
# 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,
output = cli.prepare_output("plug_iface",
self.tenant_id,
dict(network_id=network_id,
port_id=port['uuid'],
attachment=port['interface_id']))
attachment=port['interface_id']),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -206,9 +240,11 @@ class CLITest(unittest.TestCase):
# 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,
output = cli.prepare_output("unplug_iface",
self.tenant_id,
dict(network_id=network_id,
port_id=port['uuid']))
port_id=port['uuid']),
self.version)
# Verify!
# Must add newline at the end to match effect of print call
self.assertEquals(self.fake_stdout.make_string(), output + '\n')
@ -219,7 +255,9 @@ class CLITest(unittest.TestCase):
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)
cli.list_nets(self.client,
self.tenant_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_list_networks failed due to an exception")
@ -230,7 +268,10 @@ class CLITest(unittest.TestCase):
def test_create_network(self):
try:
cli.create_net(self.client, self.tenant_id, "test")
cli.create_net(self.client,
self.tenant_id,
"test",
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_create_network failed due to an exception")
@ -243,7 +284,10 @@ class CLITest(unittest.TestCase):
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)
cli.delete_net(self.client,
self.tenant_id,
network_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_delete_network failed due to an exception")
@ -256,7 +300,10 @@ class CLITest(unittest.TestCase):
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'])
cli.show_net(self.client,
self.tenant_id,
net['uuid'],
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_detail_network failed due to an exception")
@ -269,8 +316,11 @@ class CLITest(unittest.TestCase):
try:
net = db.network_create(self.tenant_id, self.network_name_1)
network_id = net['uuid']
cli.update_net(self.client, self.tenant_id,
network_id, 'name=%s' % self.network_name_2)
cli.update_net(self.client,
self.tenant_id,
network_id,
'name=%s' % self.network_name_2,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_update_network failed due to an exception")
@ -286,7 +336,10 @@ class CLITest(unittest.TestCase):
network_id = net['uuid']
db.port_create(network_id)
db.port_create(network_id)
cli.list_ports(self.client, self.tenant_id, network_id)
cli.list_ports(self.client,
self.tenant_id,
network_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_list_ports failed due to an exception")
@ -301,7 +354,10 @@ class CLITest(unittest.TestCase):
# 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)
cli.create_port(self.client,
self.tenant_id,
network_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_create_port failed due to an exception")
@ -319,7 +375,11 @@ class CLITest(unittest.TestCase):
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)
cli.delete_port(self.client,
self.tenant_id,
network_id,
port_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_delete_port failed due to an exception")
@ -335,8 +395,12 @@ class CLITest(unittest.TestCase):
port = db.port_create(network_id)
port_id = port['uuid']
# Default state is DOWN - change to ACTIVE.
cli.update_port(self.client, self.tenant_id, network_id,
port_id, 'state=ACTIVE')
cli.update_port(self.client,
self.tenant_id,
network_id,
port_id,
'state=ACTIVE',
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_update_port failed due to an exception")
@ -354,7 +418,11 @@ class CLITest(unittest.TestCase):
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)
cli.show_port(self.client,
self.tenant_id,
network_id,
port_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_show_port_no_attach failed due to an exception")
@ -374,7 +442,11 @@ class CLITest(unittest.TestCase):
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)
cli.show_port(self.client,
self.tenant_id,
network_id,
port_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_show_port_with_attach failed due to an exception")
@ -392,8 +464,12 @@ class CLITest(unittest.TestCase):
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")
cli.plug_iface(self.client,
self.tenant_id,
network_id,
port_id,
"test_iface_id",
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_plug_iface failed due to an exception")
@ -412,7 +488,11 @@ class CLITest(unittest.TestCase):
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)
cli.unplug_iface(self.client,
self.tenant_id,
network_id,
port_id,
self.version)
except:
LOG.exception("Exception caught: %s", sys.exc_info())
self.fail("test_plug_iface failed due to an exception")

@ -49,30 +49,6 @@ class QuantumException(Exception):
return self._error_string
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
description = "Unexpected error while running command."
if exit_code is None:
exit_code = '-'
message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % (
description, cmd, exit_code, stdout, stderr)
IOError.__init__(self, message)
class Error(Exception):
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
super(ApiError, self).__init__('%s: %s' % (code, message))
class NotFound(QuantumException):
pass
@ -111,34 +87,87 @@ 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 QuantumClientException(QuantumException):
def __init__(self, **kwargs):
self.message = kwargs.get('message', "")
super(QuantumClientException, self).__init__(**kwargs)
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.")
# NOTE: on the client side, we use different exception types in order
# to allow client library users to handle server exceptions in try...except
# blocks. The actual error message is the one generated on the server side
class NetworkNotFoundClient(QuantumClientException):
pass
class MalformedRequestBody(QuantumException):
message = _("Malformed request body: %(reason)s")
class PortNotFoundClient(QuantumClientException):
pass
class MalformedResponseBody(QuantumException):
message = _("Malformed response body: %(reason)s")
class Duplicate(Error):
class StateInvalidClient(QuantumClientException):
pass
class NotAuthorized(Error):
class NetworkInUseClient(QuantumClientException):
pass
class PortInUseClient(QuantumClientException):
pass
class AlreadyAttachedClient(QuantumClientException):
pass
class NotAuthorized(QuantumClientException):
pass
class QuantumCLIError(QuantumClientException):
""" Exception raised when command line parsing fails """
pass
class BadInputError(Exception):
"""Error resulting from a client sending bad input to a server"""
pass
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
if description is None:
description = "Unexpected error while running command."
if exit_code is None:
exit_code = '-'
message = "%s\nCommand: %s\nExit code: %s\nStdout: %r\nStderr: %r" % (
description, cmd, exit_code, stdout, stderr)
IOError.__init__(self, message)
class Error(Exception):
def __init__(self, message=None):
super(Error, self).__init__(message)
class ApiError(Error):
def __init__(self, message='Unknown', code='Unknown'):
self.message = message
self.code = code
super(ApiError, self).__init__('%s: %s' % (code, message))
class MalformedRequestBody(QuantumException):
message = _("Malformed request body: %(reason)s")
class Duplicate(Error):
pass
@ -154,11 +183,6 @@ class InvalidContentType(Invalid):
message = _("Invalid content type %(content_type)s.")
class BadInputError(Exception):
"""Error resulting from a client sending bad input to a server"""
pass
class MissingArgumentError(Error):
pass

@ -22,7 +22,6 @@
import ConfigParser
import datetime
import exceptions as exception
import inspect
import logging
import os
@ -44,6 +43,18 @@ from quantum.common import exceptions as exception
from quantum.common.exceptions import ProcessExecutionError
def env(*vars, **kwargs):
"""
returns the first environment variable set
if none are non-empty, defaults to '' or keyword arg default
"""
for v in vars:
value = os.environ.get(v)
if value:
return value
return kwargs.get('default', '')
def import_class(import_str):
"""Returns a class from a string including module and class."""
mod_str, _sep, class_str = import_str.rpartition('.')