--- /dev/null
--- /dev/null
++#!/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] <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)
--- /dev/null
+ # 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))
--- /dev/null
- from client import Client
- from optparse import OptionParser
-
++#!/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
+
- CLI_TEMPLATE = "../quantum/cli_output.template"
- LOG = logging.getLogger('cli')
+FORMAT = "json"
- 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
++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)
- template_file = open(CLI_TEMPLATE).read()
++ 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
- data = {'network': {'net-name': name}}
++ 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
- new_net_id = res["networks"]["network"]["id"]
++ data = {'network': {'name': name}}
+ new_net_id = None
+ try:
+ res = client.create_network(data)
- res = client.list_network_details(network_id)["networks"]["network"]
- LOG.debug("Operation 'list_network_details' executed.")
++ 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['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"]
-
++ #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.")
- data = {'network': {'net-name': '%s' % name}}
++ 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
- new_port_id = res["ports"]["port"]["id"]
++ 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.")
- port = client.list_port_details(network_id, port_id)["port"]
++ 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:
- data = {'port': {'port-state': '%s' % new_state}}
++ 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'] = '<unavailable>'
+ 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': {'attachment': '%s' % attachment}}
++ 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:
-
-
- 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] <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("-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)
++ 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)
--- /dev/null
- \tNetwork ID: $network.id
+## Cheetah template for cli output
+#if $cmd == 'list_nets'
+Virtual Networks on Tenant $tenant_id
+#for $network in $networks
- #for $port in $network.port
- \tLogical Port $port.id: $port.attachment
++ 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
- New name is: $network['net-name']
++#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,
- \tLogical Port: $port.id
++New name is: $network.name
+#elif $cmd == 'list_ports'
+Ports on Virtual Network: $network_id
+#for $port in $ports
- New state is: $port['port-state']
++ 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 == '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
+
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):
"""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"
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):
"""
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":
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):
"""
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)
--- /dev/null
- from quantum import cli
+# 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
- 'net-name': nw_list[0].name}
++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,
-
++ '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')
- port_data = {'id': port.uuid, 'port-state': port.state}
++
+ def _verify_set_port_state(self, network_id, port_id):
+ # Verification - get raw result from db
+ port = db.port_get(port_id, network_id)
- 'attachment':'<unavailable>'}
++ 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,
- # 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")
++ 'attachment': '<unavailable>'}
+ # 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):
-
++ 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")
- self._verify_list_ports(network_id)
-
++
+ 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_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)