From: Salvatore Orlando Date: Thu, 25 Aug 2011 17:50:05 +0000 (+0100) Subject: Finishing cli work X-Git-Url: https://review.fuel-infra.org/gitweb?a=commitdiff_plain;h=f4405229e63bdffdf73057a9bad6b3cb085bb6ab;p=openstack-build%2Fneutron-build.git Finishing cli work Fixing bug with XML deserialization --- f4405229e63bdffdf73057a9bad6b3cb085bb6ab diff --cc bin/cli index 000000000,000000000..20ef7b2ae new file mode 100755 --- /dev/null +++ b/bin/cli @@@ -1,0 -1,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"]}, ++ "detail_net": { ++ "func": cli_lib.detail_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"]}, ++ "detail_port": { ++ "func": cli_lib.detail_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] [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) diff --cc quantum/api/attachments.py index 000000000,14f4d9405..e40aabba4 mode 000000,100644..100644 --- a/quantum/api/attachments.py +++ b/quantum/api/attachments.py @@@ -1,0 -1,86 +1,87 @@@ + # Copyright 2011 Citrix Systems. + # 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. + + import logging + + from webob import exc + + from quantum.api import api_common as common + from quantum.api import faults + from quantum.api.views import attachments as attachments_view + from quantum.common import exceptions as exception + + LOG = logging.getLogger('quantum.api.ports') + + + class Controller(common.QuantumController): + """ Port API controller for Quantum API """ + + _attachment_ops_param_list = [{ + 'param-name': 'id', + 'required': True}, ] + + _serialization_metadata = { + "application/xml": { + "attributes": { + "attachment": ["id"], } + }, + } + + def __init__(self, plugin): + self._resource_name = 'attachment' + super(Controller, self).__init__(plugin) + + def get_resource(self, request, tenant_id, network_id, id): + try: + att_data = self._plugin.get_port_details( + tenant_id, network_id, id) + builder = attachments_view.get_view_builder(request) + result = builder.build(att_data)['attachment'] + return dict(attachment=result) + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + + def attach_resource(self, request, tenant_id, network_id, id): + try: + request_params = \ + self._parse_request_params(request, + self._attachment_ops_param_list) + 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() + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) + except exception.PortInUse as e: + return faults.Fault(faults.PortInUse(e)) + except exception.AlreadyAttached as e: + return faults.Fault(faults.AlreadyAttached(e)) + + def detach_resource(self, request, tenant_id, network_id, id): + try: + self._plugin.unplug_interface(tenant_id, + network_id, id) + return exc.HTTPNoContent() + except exception.NetworkNotFound as e: + return faults.Fault(faults.NetworkNotFound(e)) + except exception.PortNotFound as e: + return faults.Fault(faults.PortNotFound(e)) diff --cc quantum/cli_lib.py index 5ebdde268,000000000..cea8d3ae7 mode 100644,000000..100755 --- a/quantum/cli_lib.py +++ b/quantum/cli_lib.py @@@ -1,346 -1,0 +1,231 @@@ ++#!/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 + - from client import Client - from optparse import OptionParser - +FORMAT = "json" - CLI_TEMPLATE = "../quantum/cli_output.template" - LOG = logging.getLogger('cli') ++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 '') - msg_2 = "Error message:%s" % (message or '') - LOG.exception(msg_1 + "-" + msg_2) - print msg_1 - print msg_2 ++ msg_1 = "Command failed with error code: %s" \ ++ % (status_code or '') ++ msg_2 = "Error message:%s" % (message or '') ++ 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_file = open(CLI_TEMPLATE).read() ++ 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': {'net-name': name}} ++ data = {'network': {'name': name}} + new_net_id = None + try: + res = client.create_network(data) - new_net_id = res["networks"]["network"]["id"] ++ 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 detail_net(client, *args): + tenant_id, network_id = args + try: - res = client.list_network_details(network_id)["networks"]["network"] - LOG.debug("Operation 'list_network_details' executed.") ++ #NOTE(salvatore-orlando): Implementing non-efficient version ++ #for now, at least until plugins will not provide correct behaviour ++ #for the show_network_details operation ++ res = client.show_network_details(network_id)["network"] ++ LOG.debug("Operation 'show_network_details' executed.") + ports = client.list_ports(network_id) + LOG.debug("Operation 'list_ports' executed.") - res['ports'] = ports - for port in ports["ports"]: - att_data = client.list_port_attachments(network_id, port['id']) - LOG.debug("Operation 'list_attachments' executed.") - port['attachment'] = att_data["attachment"] - ++ res.update(ports) ++ for port in ports['ports']: ++ att_data = client.show_port_attachment(network_id, port['id']) ++ LOG.debug("Operation 'show_port_attachment' executed.") ++ port['attachment'] = att_data['attachment'].get('id', None) + output = prepare_output("detail_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': {'net-name': '%s' % name}} ++ 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["ports"]["port"]["id"] ++ 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 detail_port(client, *args): + tenant_id, network_id, port_id = args + try: - port = client.list_port_details(network_id, port_id)["port"] ++ 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 + port['attachment'] = '' + output = prepare_output("detail_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': {'port-state': '%s' % new_state}} ++ 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 = {'port': {'attachment': '%s' % attachment}} ++ data = {'attachment': {'id': '%s' % attachment}} + client.attach_resource(network_id, port_id, data) + LOG.debug("Operation 'attach_resource' executed.") + output = prepare_output("plug_interface", 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_interface", tenant_id, + dict(network_id=network_id, + port_id=port_id)) + print output + except Exception as ex: + _handle_exception(ex) - - - commands = { - "list_nets": { - "func": list_nets, - "args": ["tenant-id"]}, - "create_net": { - "func": create_net, - "args": ["tenant-id", "net-name"]}, - "delete_net": { - "func": delete_net, - "args": ["tenant-id", "net-id"]}, - "detail_net": { - "func": detail_net, - "args": ["tenant-id", "net-id"]}, - "rename_net": { - "func": rename_net, - "args": ["tenant-id", "net-id", "new-name"]}, - "list_ports": { - "func": list_ports, - "args": ["tenant-id", "net-id"]}, - "create_port": { - "func": create_port, - "args": ["tenant-id", "net-id"]}, - "delete_port": { - "func": delete_port, - "args": ["tenant-id", "net-id", "port-id"]}, - "set_port_state": { - "func": set_port_state, - "args": ["tenant-id", "net-id", "port-id", "new_state"]}, - "detail_port": { - "func": detail_port, - "args": ["tenant-id", "net-id", "port-id"]}, - "plug_iface": { - "func": plug_iface, - "args": ["tenant-id", "net-id", "port-id", "iface-id"]}, - "unplug_iface": { - "func": 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] [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("-lf", "--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) diff --cc quantum/cli_output.template index e2857bdd2,000000000..12f394e19 mode 100644,000000..100644 --- a/quantum/cli_output.template +++ b/quantum/cli_output.template @@@ -1,53 -1,0 +1,53 @@@ +## Cheetah template for cli output +#if $cmd == 'list_nets' +Virtual Networks on Tenant $tenant_id +#for $network in $networks - \tNetwork ID: $network.id ++ 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 == 'detail_net' +Network: $network.name ($network.id) +Remote Interfaces on Virtual Network - #for $port in $network.port - \tLogical Port $port.id: $port.attachment ++#for $port in $network.ports ++ Logical Port $port.id: $port.attachment +#end for +#elif $cmd == 'rename_net' +Renamed Virtual Network with ID: $network.id for Tenant $tenant_id, - New name is: $network['net-name'] ++New name is: $network.name +#elif $cmd == 'list_ports' +Ports on Virtual Network: $network_id +#for $port in $ports - \tLogical Port: $port.id ++ 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['port-state'] ++New state is: $port.state +on Virtual Network: $network_id +for tenant: $tenant_id +#elif $cmd == 'detail_port' +Logical Port ID: $port.id +On Virtual Network: $network_id +Administrative State: $port.state +Remote Interface: $port.attachment +#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 + diff --cc quantum/client.py index 5898d92b7,c75b5a243..f55244c6e --- a/quantum/client.py +++ b/quantum/client.py @@@ -20,16 -19,24 +20,26 @@@ import loggin import httplib import socket import urllib -from quantum.common.wsgi import Serializer + + from quantum.common import exceptions +from quantum.common.wsgi import Serializer - LOG = logging.getLogger('client') ++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.PortInUse, + 440: exceptions.AlreadyAttached} - class api_call(object): + class ApiCall(object): """A Decorator to add support for format and tenant overriding""" - def __init__(self, f): - self.f = f + def __init__(self, function): + self.function = function def __get__(self, instance, owner): def with_params(*args, **kwargs): @@@ -51,9 -60,7 +63,18 @@@ class Client(object) """A base client class - derived from Glance.BaseClient""" - action_prefix = '/v0.1/tenants/{tenant_id}' - - """Action query strings""" ++ #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" ports_path = "/networks/%s/ports" @@@ -95,12 -105,6 +119,17 @@@ 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): """ @@@ -127,6 -131,10 +156,9 @@@ if type(params) is dict: action += '?' + urllib.urlencode(params) - + if body: + body = self.serialize(body) + try: connection_type = self.get_connection_type() headers = headers or {"Content-Type": @@@ -136,28 -145,38 +168,38 @@@ certs = dict((x, certs[x]) for x in certs if certs[x] != None) if self.use_ssl and len(certs): - c = connection_type(self.host, self.port, **certs) + conn = connection_type(self.host, self.port, **certs) else: - c = connection_type(self.host, self.port) - res = self._send_request(c, method, action, body, headers) + 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() + + if self.logger: + self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \ + % (str(status_code), data)) + if status_code in (httplib.OK, httplib.CREATED, httplib.ACCEPTED, httplib.NO_CONTENT): - return self.deserialize(res) + return self.deserialize(data, status_code) else: - # Create exception with HTTP status code and message + 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()) - ++ # 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): """ @@@ -170,15 -189,31 +212,33 @@@ return response.status def serialize(self, data): - if type(data) is dict: + """ + Serializes a dictionary with a single key (which can contain any + structure) into either xml or json + """ + if data is None: + return None + elif type(data) is dict: return Serializer().serialize(data, self.content_type()) + else: + raise Exception("unable to deserialize object of type = '%s'" \ + % type(data)) - def deserialize(self, data): - if self.get_status_code(data) in (202, 204): - return data.read() - return Serializer().deserialize(data.read(), self.content_type()) + def deserialize(self, data, status_code): + """ + 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()) ++ #server.networks.Controller._serialization_metadata ++ return Serializer(self._serialization_metadata).\ ++ deserialize(data, self.content_type()) def content_type(self, format=None): + """ + Returns the mime-type for either 'xml' or 'json'. Defaults to the + currently set format + """ if not format: format = self.format return "application/%s" % (format) diff --cc tests/unit/test_cli.py index 2b814eada,000000000..6c71e2cba mode 100644,000000..100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@@ -1,320 -1,0 +1,408 @@@ +# 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 ++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.APIRouterV01(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, - 'net-name': nw_list[0].name} ++ '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_detail_network(self): ++ # Verification - get raw result from db ++ nw = db.network_list(self.tenant_id)[0] ++ network = dict(id=nw.uuid, name=nw.name) ++ ports = db.port_list(nw['uuid']) ++ for port in ports: ++ port = db.port_get(port['uuid'], nw['uuid']) ++ network['ports'] = [dict(id=port['uuid'], ++ state=port['state'], ++ attachment=port['interface_id']) ++ for port in ports] ++ # Fill CLI template ++ output = cli.prepare_output('detail_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, 'port-state': port.state} ++ 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_detail_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':''} ++ 'attachment': ''} + # Fill CLI template + output = cli.prepare_output('detail_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_interface", 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_interface", 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_detail_network(self): - # Load some data into the datbase - net = db.network_create(self.tenant_id, self.network_name_1) - db.port_create(net['uuid']) - port = db.port_create(net['uuid']) - cli.detail_net(self.client, self.tenant_id, net['uuid']) - db.port_set_attachment(port['uuid'], net['uuid'], "test_iface_id") ++ try: ++ # Load some data into the datbase ++ net = db.network_create(self.tenant_id, self.network_name_1) ++ db.port_create(net['uuid']) ++ # Create a 2nd port and plug attachment in it ++ port = db.port_create(net['uuid']) ++ db.port_set_attachment(port['uuid'], net['uuid'], "test_iface_id") ++ cli.detail_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_detail_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) - ++ 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_detail_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.detail_port(self.client, self.tenant_id, network_id, port_id) + except: + LOG.exception("Exception caught: %s", sys.exc_info()) + self.fail("test_detail_port failed due to an exception") + + LOG.debug("Operation completed. Verifying result") + LOG.debug(self.fake_stdout.content) + self._verify_detail_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)