Compare commits

..

4 Commits

Author SHA1 Message Date
Jenkins
8d1219d6d5 Merge "bug 963155: add some missing test files to the sdist tarball." into milestone-proposed 2012-04-03 05:38:14 +00:00
Dan Wendlandt
2836995c74 bug 963155: add some missing test files to the sdist tarball.
Change-Id: I78cb561776254e4224eb46e1c06e80e0f2c65d54
2012-04-02 22:19:21 -07:00
Dan Wendlandt
1a1bd5c21a Fix quantum client exception when server returns 500 error.
bug 963494

Also, make sure that exceptions within the client are accompanied with
a full stack trace, otherwise it is hard to track down where it happened.

Change-Id: Ic134ed27523b2c321dcb8e35e6ab0eb144a699f3
2012-04-02 22:19:00 -07:00
Thierry Carrez
c927d1c3f5 Final versioning
Change-Id: I4bcba110e23b50e4d1855bf7fad707925bebe8e7
2012-03-19 10:27:08 +01:00
27 changed files with 1719 additions and 765 deletions

1
.gitignore vendored
View File

@@ -8,5 +8,6 @@ python_quantumclient.egg-info/*
quantum/vcsversion.py
run_tests.err.log
run_tests.log
tests/
.venv/
.tox/

View File

@@ -1,187 +0,0 @@
QuantumClient Style Commandments
================================
- Step 1: Read http://www.python.org/dev/peps/pep-0008/
- Step 2: Read http://www.python.org/dev/peps/pep-0008/ again
- Step 3: Read on
General
-------
- Put two newlines between top-level code (funcs, classes, etc)
- Put one newline between methods in classes and anywhere else
- Do not write "except:", use "except Exception:" at the very least
- Include your name with TODOs as in "#TODO(termie)"
- Do not shadow a built-in or reserved word. Example::
def list():
return [1, 2, 3]
mylist = list() # BAD, shadows `list` built-in
class Foo(object):
def list(self):
return [1, 2, 3]
mylist = Foo().list() # OKAY, does not shadow built-in
Imports
-------
- Do not make relative imports
- Order your imports by the full module path
- Organize your imports according to the following template
Example::
# vim: tabstop=4 shiftwidth=4 softtabstop=4
{{stdlib imports in human alphabetical order}}
\n
{{third-party lib imports in human alphabetical order}}
\n
{{quantum imports in human alphabetical order}}
\n
\n
{{begin your code}}
Human Alphabetical Order Examples
---------------------------------
Example::
import httplib
import logging
import random
import StringIO
import time
import unittest
import eventlet
import webob.exc
import quantum.api.networks
from quantum.api import ports
from quantum.db import models
from quantum.extensions import multiport
import quantum.manager
from quantum import service
Docstrings
----------
Example::
"""A one line docstring looks like this and ends in a period."""
"""A multiline docstring has a one-line summary, less than 80 characters.
Then a new paragraph after a newline that explains in more detail any
general information about the function, class or method. Example usages
are also great to have here if it is a complex class for function.
When writing the docstring for a class, an extra line should be placed
after the closing quotations. For more in-depth explanations for these
decisions see http://www.python.org/dev/peps/pep-0257/
If you are going to describe parameters and return values, use Sphinx, the
appropriate syntax is as follows.
:param foo: the foo parameter
:param bar: the bar parameter
:returns: return_type -- description of the return value
:returns: description of the return value
:raises: AttributeError, KeyError
"""
Dictionaries/Lists
------------------
If a dictionary (dict) or list object is longer than 80 characters, its items
should be split with newlines. Embedded iterables should have their items
indented. Additionally, the last item in the dictionary should have a trailing
comma. This increases readability and simplifies future diffs.
Example::
my_dictionary = {
"image": {
"name": "Just a Snapshot",
"size": 2749573,
"properties": {
"user_id": 12,
"arch": "x86_64",
},
"things": [
"thing_one",
"thing_two",
],
"status": "ACTIVE",
},
}
Calling Methods
---------------
Calls to methods 80 characters or longer should format each argument with
newlines. This is not a requirement, but a guideline::
unnecessarily_long_function_name('string one',
'string two',
kwarg1=constants.ACTIVE,
kwarg2=['a', 'b', 'c'])
Rather than constructing parameters inline, it is better to break things up::
list_of_strings = [
'what_a_long_string',
'not as long',
]
dict_of_numbers = {
'one': 1,
'two': 2,
'twenty four': 24,
}
object_one.call_a_method('string three',
'string four',
kwarg1=list_of_strings,
kwarg2=dict_of_numbers)
Internationalization (i18n) Strings
-----------------------------------
In order to support multiple languages, we have a mechanism to support
automatic translations of exception and log strings.
Example::
msg = _("An error occurred")
raise HTTPBadRequest(explanation=msg)
If you have a variable to place within the string, first internationalize the
template string then do the replacement.
Example::
msg = _("Missing parameter: %s") % ("flavor",)
LOG.error(msg)
If you have multiple variables to place in the string, use keyword parameters.
This helps our translators reorder parameters when needed.
Example::
msg = _("The server with id %(s_id)s has no key %(m_key)s")
LOG.error(msg % {"s_id": "1234", "m_key": "imageId"})
Creating Unit Tests
-------------------
For every new feature, unit tests should be created that both test and
(implicitly) document the usage of said feature. If submitting a patch for a
bug that had no unit test, a new passing unit test should be added. If a
submitted bug fix does have a unit test, be sure to add a new one that fails
without the patch and passes with the patch.

View File

@@ -1,6 +1,6 @@
include tox.ini
include LICENSE README HACKING.rst
include LICENSE README
include version.py
include tools/*
include quantumclient/tests/*
include quantumclient/tests/unit/*
include quantum/client/tests/*
include quantum/client/tests/unit/*

19
quantum/__init__.py Normal file
View File

@@ -0,0 +1,19 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC
# 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.
from pkgutil import extend_path
__path__ = extend_path(__path__, __name__)

View File

@@ -16,25 +16,16 @@
# under the License.
# @author: Tyler Smith, Cisco Systems
import gettext
import httplib
import logging
import httplib
import socket
import time
import urllib
from quantum.common import exceptions
from quantum.common.serializer import Serializer
# gettext must be initialized before any quantumclient imports
gettext.install('quantumclient', unicode=1)
from quantumclient.common import exceptions
from quantumclient.common.serializer import Serializer
LOG = logging.getLogger('quantumclient')
LOG = logging.getLogger('quantum.client')
AUTH_TOKEN_HEADER = "X-Auth-Token"
@@ -55,7 +46,7 @@ def exception_handler_v10(status_code, error_content):
430: 'portNotFound',
431: 'requestedStateInvalid',
432: 'portInUse',
440: 'alreadyAttached',
440: 'alreadyAttached'
}
quantum_errors = {
@@ -68,7 +59,7 @@ def exception_handler_v10(status_code, error_content):
431: exceptions.StateInvalidClient,
432: exceptions.PortInUseClient,
440: exceptions.AlreadyAttachedClient,
501: NotImplementedError,
501: NotImplementedError
}
# Find real error type
@@ -77,7 +68,8 @@ def exception_handler_v10(status_code, error_content):
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']
error_message = error_dict['message'] + "\n" +\
error_dict['detail']
else:
error_message = error_content
# raise the appropriate error!
@@ -129,7 +121,7 @@ def exception_handler_v11(status_code, error_content):
EXCEPTION_HANDLERS = {
'1.0': exception_handler_v10,
'1.1': exception_handler_v11,
'1.1': exception_handler_v11
}
@@ -167,12 +159,9 @@ class Client(object):
"network": ["id", "name"],
"port": ["id", "state"],
"attachment": ["id"]},
"plurals": {
"networks": "network",
"ports": "port",
},
},
}
"plurals": {"networks": "network",
"ports": "port"}},
}
# Action query strings
networks_path = "/networks"
@@ -216,8 +205,6 @@ class Client(object):
self.key_file = key_file
self.cert_file = cert_file
self.logger = logger
if not self.logger:
self.logger = LOG
self.auth_token = auth_token
self.version = version
self.action_prefix = "/v%s%s" % (version, uri_prefix)
@@ -255,8 +242,8 @@ class Client(object):
# 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")
self.logger.debug("Quantum Client Request:\n" \
+ method + " " + action + "\n")
if body:
self.logger.debug(body)
conn.request(method, action, body, headers)
@@ -292,7 +279,7 @@ class Client(object):
try:
connection_type = self.get_connection_type()
headers = headers or {"Content-Type":
"application/%s" % self.format}
"application/%s" % self.format}
# if available, add authentication token
if self.auth_token:
headers[AUTH_TOKEN_HEADER] = self.auth_token
@@ -303,17 +290,12 @@ class Client(object):
conn = connection_type(self.host, self.port, **certs)
else:
conn = connection_type(self.host, self.port)
# besides HTTP(s)Connection, we still have testConnection
if (LOG.isEnabledFor(logging.DEBUG) and
isinstance(conn, httplib.HTTPConnection)):
conn.set_debuglevel(1)
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))
self.logger.debug("Quantum Client Reply (code = %s) :\n %s" \
% (str(status_code), data))
if status_code in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
@@ -346,8 +328,8 @@ class Client(object):
elif type(data) is dict:
return Serializer().serialize(data, self.content_type())
else:
raise Exception("unable to serialize object of type = '%s'" %
type(data))
raise Exception("unable to serialize object of type = '%s'" \
% type(data))
def deserialize(self, data, status_code):
"""
@@ -355,8 +337,8 @@ class Client(object):
"""
if status_code == 204:
return data
return Serializer(self._serialization_metadata).deserialize(
data, self.content_type())
return Serializer(self._serialization_metadata).\
deserialize(data, self.content_type())
def content_type(self, format=None):
"""

121
quantum/client/batch_config.py Executable file
View File

@@ -0,0 +1,121 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
#
# 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: Dan Wendlandt, Nicira Networks, Inc.
import logging as LOG
from optparse import OptionParser
import os
import sys
possible_topdir = os.path.normpath(os.path.join(os.path.abspath(sys.argv[0]),
os.pardir,
os.pardir,
os.pardir))
if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')):
sys.path.insert(0, possible_topdir)
from quantum.client import Client
FORMAT = "json"
CONTENT_TYPE = "application/" + FORMAT
def delete_all_nets(client):
res = client.list_networks()
for n in res["networks"]:
nid = n["id"]
pres = client.list_ports(nid)
for port in pres["ports"]:
pid = port['id']
client.detach_resource(nid, pid)
client.delete_port(nid, pid)
print "Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)
client.delete_network(nid)
print "Deleted Virtual Network with ID:%s" % nid
def create_net_with_attachments(client, net_name, iface_ids):
data = {'network': {'name': '%s' % net_name}}
res = client.create_network(data)
nid = res["network"]["id"]
print "Created a new Virtual Network %s with ID:%s" % (net_name, nid)
for iface_id in iface_ids:
res = client.create_port(nid, {'port': {'state': 'ACTIVE'}})
new_port_id = res["port"]["id"]
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port_id, nid)
data = {'attachment': {'id': iface_id}}
client.attach_resource(nid, new_port_id, data)
print "Plugged interface \"%s\" to port:%s on network:%s" % \
(iface_id, new_port_id, nid)
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <tenant-id> <config-string> [args]\n" \
"Example config-string: net1=instance-1,instance-2"\
":net2=instance-3,instance-4\n" \
"This string would create two networks: \n" \
"'net1' would have two ports, with iface-ids "\
"instance-1 and instance-2 attached\n" \
"'net2' would have two ports, with iface-ids"\
" instance-3 and instance-4 attached\n"
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("-d", "--delete", dest="delete",
action="store_true", default=False, \
help="delete existing tenants networks")
options, args = parser.parse_args()
if options.verbose:
LOG.basicConfig(level=LOG.DEBUG)
else:
LOG.basicConfig(level=LOG.WARN)
if len(args) < 1:
parser.print_help()
sys.exit(1)
nets = {}
tenant_id = args[0]
if len(args) > 1:
config_str = args[1]
for net_str in config_str.split(":"):
arr = net_str.split("=")
net_name = arr[0]
nets[net_name] = arr[1].split(",")
print "nets: %s" % str(nets)
client = Client(options.host, options.port, options.ssl,
format='json', tenant=tenant_id)
if options.delete:
delete_all_nets(client)
for net_name, iface_ids in nets.items():
create_net_with_attachments(client, net_name, iface_ids)
sys.exit(0)

View File

@@ -18,131 +18,116 @@
# @author: Brad Hall, Nicira Networks, Inc.
# @author: Salvatore Orlando, Citrix
import gettext
import logging
import logging.handlers
from optparse import OptionParser
import os
import sys
from quantumclient import cli_lib
from quantumclient import Client
from quantumclient import ClientV11
from quantumclient.common import exceptions
from quantumclient.common import utils
from optparse import OptionParser
from quantum.common import exceptions
# 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
logging.basicConfig()
LOG = logging.getLogger('quantumclient')
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.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_v10 = {
"list_nets": {
"func": cli_lib.list_nets,
"args": ["tenant-id"],
},
"list_nets_detail": {
"func": cli_lib.list_nets_detail,
"args": ["tenant-id"],
},
"create_net": {
"func": cli_lib.create_net,
"args": ["tenant-id", "net-name"],
},
"delete_net": {
"func": cli_lib.delete_net,
"args": ["tenant-id", "net-id"],
},
"show_net": {
"func": cli_lib.show_net,
"args": ["tenant-id", "net-id"],
},
"show_net_detail": {
"func": cli_lib.show_net_detail,
"args": ["tenant-id", "net-id"],
},
"update_net": {
"func": cli_lib.update_net,
"args": ["tenant-id", "net-id", "new-name"],
},
"list_ports": {
"func": cli_lib.list_ports,
"args": ["tenant-id", "net-id"],
},
"list_ports_detail": {
"func": cli_lib.list_ports_detail,
"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"],
},
"update_port": {
"func": cli_lib.update_port,
"args": ["tenant-id", "net-id", "port-id", "params"],
},
"show_port": {
"func": cli_lib.show_port,
"args": ["tenant-id", "net-id", "port-id"],
},
"show_port_detail": {
"func": cli_lib.show_port_detail,
"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"],
},
"show_iface": {
"func": cli_lib.show_iface,
"args": ["tenant-id", "net-id", "port-id"],
},
}
"list_nets": {
"func": cli_lib.list_nets,
"args": ["tenant-id"]},
"list_nets_detail": {
"func": cli_lib.list_nets_detail,
"args": ["tenant-id"]},
"create_net": {
"func": cli_lib.create_net,
"args": ["tenant-id", "net-name"]},
"delete_net": {
"func": cli_lib.delete_net,
"args": ["tenant-id", "net-id"]},
"show_net": {
"func": cli_lib.show_net,
"args": ["tenant-id", "net-id"]},
"show_net_detail": {
"func": cli_lib.show_net_detail,
"args": ["tenant-id", "net-id"]},
"update_net": {
"func": cli_lib.update_net,
"args": ["tenant-id", "net-id", "new-name"]},
"list_ports": {
"func": cli_lib.list_ports,
"args": ["tenant-id", "net-id"]},
"list_ports_detail": {
"func": cli_lib.list_ports_detail,
"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"]},
"update_port": {
"func": cli_lib.update_port,
"args": ["tenant-id", "net-id", "port-id", "params"]},
"show_port": {
"func": cli_lib.show_port,
"args": ["tenant-id", "net-id", "port-id"]},
"show_port_detail": {
"func": cli_lib.show_port_detail,
"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"]},
"show_iface": {
"func": cli_lib.show_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_nets_detail": {
"func": cli_lib.list_nets_detail_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"],
},
"list_ports_detail": {
"func": cli_lib.list_ports_detail_v11,
"args": ["tenant-id", "net-id"],
"filters": ["name", "op-status", "has-attachment", "attachment"],
},
})
"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_nets_detail": {
"func": cli_lib.list_nets_detail_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"]},
"list_ports_detail": {
"func": cli_lib.list_ports_detail_v11,
"args": ["tenant-id", "net-id"],
"filters": ["name", "op-status", "has-attachment", "attachment"]},
})
commands = {
'1.0': commands_v10,
'1.1': commands_v11,
'1.1': commands_v11
}
clients = {
'1.0': Client,
'1.1': ClientV11,
'1.1': ClientV11
}
@@ -150,24 +135,23 @@ def help(version):
print "\nCommands:"
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 "")
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"]]))
print "Usage:\n %s %s" % (cmd,
" ".join(["<%s>" % y for y in cmds[cmd]["args"]]))
def build_args(cmd, cmdargs, arglist):
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))
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
@@ -218,13 +202,12 @@ def build_cmd(cmd, cmd_args, cmd_filters, arglist, 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))
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"]]))
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)
@@ -246,26 +229,23 @@ def 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")
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")
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("--debug", dest="debug",
action="store_true", default=False,
help="print debugging output")
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")
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',
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.debug:
if options.verbose:
LOG.setLevel(logging.DEBUG)
else:
LOG.setLevel(logging.WARN)

View File

@@ -21,13 +21,12 @@
""" Functions providing implementation for CLI commands. """
import logging
import os
import sys
import traceback
LOG = logging.getLogger('quantumclient.cli_lib')
FORMAT = "json"
LOG = logging.getLogger('quantum.client.cli_lib')
class OutputTemplate(object):
@@ -98,146 +97,90 @@ class CmdOutputTemplate(OutputTemplate):
Extends OutputTemplate loading a different template for each command.
"""
_templates_v10 = dict(
list_nets="""
Virtual Networks for Tenant %(tenant_id)s
%(networks|\tNetwork ID: %(id)s)s
""".strip(),
list_nets_detail="""
Virtual Networks for Tenant %(tenant_id)s
%(networks|\tNetwork %(name)s with ID: %(id)s)s
""".strip(),
show_net="""
Network ID: %(network.id)s
Network Name: %(network.name)s
""".strip(),
show_net_detail="""
Network ID: %(network.id)s
Network Name: %(network.name)s
Ports: %(network.ports|\tID: %(id)s
\t\tadministrative state: %(state)s
\t\tinterface: %(attachment.id)s)s
""".strip(),
create_net="""
Created a new Virtual Network with ID: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
update_net="""
Updated Virtual Network with ID: %(network.id)s
for Tenant: %(tenant_id)s
""".strip(),
delete_net="""
Deleted Virtual Network with ID: %(network_id)s
for Tenant %(tenant_id)s
""".strip(),
list_ports="""
Ports on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
%(ports|\tLogical Port: %(id)s)s
""".strip(),
list_ports_detail="""
Ports on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
%(ports|\tLogical Port: %(id)s
\t\tadministrative State: %(state)s)s
""".strip(),
create_port="""
Created new Logical Port with ID: %(port_id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
show_port="""
Logical Port ID: %(port.id)s
administrative State: %(port.state)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
show_port_detail="""
Logical Port ID: %(port.id)s
administrative State: %(port.state)s
interface: %(port.attachment.id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
update_port="""
Updated Logical Port with ID: %(port.id)s
on Virtual Network: %(network_id)s
for tenant: %(tenant_id)s
""".strip(),
delete_port="""
Deleted Logical Port with ID: %(port_id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
plug_iface="""
Plugged interface %(attachment)s
into Logical Port: %(port_id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
unplug_iface="""
Unplugged interface from Logical Port: %(port_id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
show_iface="""
interface: %(iface.id)s
plugged in Logical Port ID: %(port_id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
)
_templates_v10 = {
"list_nets": "Virtual Networks for Tenant %(tenant_id)s\n" +
"%(networks|\tNetwork ID: %(id)s)s",
"list_nets_detail": "Virtual Networks for Tenant %(tenant_id)s\n" +
"%(networks|\tNetwork %(name)s with ID: %(id)s)s",
"show_net": "Network ID: %(network.id)s\n" +
"Network Name: %(network.name)s",
"show_net_detail": "Network ID: %(network.id)s\n" +
"Network Name: %(network.name)s\n" +
"Ports: %(network.ports|\tID: %(id)s\n" +
"\t\tadministrative state: %(state)s\n" +
"\t\tinterface: %(attachment.id)s)s",
"create_net": "Created a new Virtual Network with ID: " +
"%(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"update_net": "Updated Virtual Network with ID: " +
"%(network.id)s\n" +
"for Tenant: %(tenant_id)s\n",
"delete_net": "Deleted Virtual Network with ID: " +
"%(network_id)s\n" +
"for Tenant %(tenant_id)s",
"list_ports": "Ports on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s\n" +
"%(ports|\tLogical Port: %(id)s)s",
"list_ports_detail": "Ports on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s\n" +
"%(ports|\tLogical Port: %(id)s\n" +
"\t\tadministrative State: %(state)s)s",
"create_port": "Created new Logical Port with ID: " +
"%(port_id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"show_port": "Logical Port ID: %(port.id)s\n" +
"administrative State: %(port.state)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"show_port_detail": "Logical Port ID: %(port.id)s\n" +
"administrative State: %(port.state)s\n" +
"interface: %(port.attachment.id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"update_port": "Updated Logical Port " +
"with ID: %(port.id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for tenant: %(tenant_id)s",
"delete_port": "Deleted Logical Port with ID: %(port_id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"plug_iface": "Plugged interface %(attachment)s\n" +
"into Logical Port: %(port_id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"unplug_iface": "Unplugged interface from Logical Port:" +
"%(port_id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"show_iface": "interface: %(iface.id)s\n" +
"plugged in Logical Port ID: %(port_id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s"}
_templates_v11 = _templates_v10.copy()
_templates_v11.update(dict(
show_net="""
Network ID: %(network.id)s
network Name: %(network.name)s
operational Status: %(network.op-status)s
""".strip(),
show_net_detail="""
Network ID: %(network.id)s
Network Name: %(network.name)s
operational Status: %(network.op-status)s
Ports: %(network.ports|\tID: %(id)s
\t\tadministrative state: %(state)s
\t\tinterface: %(attachment.id)s)s
""".strip(),
show_port="""
Logical Port ID: %(port.id)s
administrative state: %(port.state)s
operational status: %(port.op-status)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
show_port_detail="""
Logical Port ID: %(port.id)s
administrative State: %(port.state)s
operational status: %(port.op-status)s
interface: %(port.attachment.id)s
on Virtual Network: %(network_id)s
for Tenant: %(tenant_id)s
""".strip(),
))
_templates_v11.update({
"show_net": "Network ID: %(network.id)s\n" +
"network Name: %(network.name)s\n" +
"operational Status: %(network.op-status)s",
"show_net_detail": "Network ID: %(network.id)s\n" +
"Network Name: %(network.name)s\n" +
"operational Status: %(network.op-status)s\n" +
"Ports: %(network.ports|\tID: %(id)s\n" +
"\t\tadministrative state: %(state)s\n" +
"\t\tinterface: %(attachment.id)s)s",
"show_port": "Logical Port ID: %(port.id)s\n" +
"administrative state: %(port.state)s\n" +
"operational status: %(port.op-status)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
"show_port_detail": "Logical Port ID: %(port.id)s\n" +
"administrative State: %(port.state)s\n" +
"operational status: %(port.op-status)s\n" +
"interface: %(port.attachment.id)s\n" +
"on Virtual Network: %(network_id)s\n" +
"for Tenant: %(tenant_id)s",
})
_templates = {
'1.0': _templates_v10,
@@ -256,8 +199,8 @@ def _handle_exception(ex):
if ex.args and isinstance(ex.args[-1][0], dict):
status_code = ex.args[-1][0].get('status_code', None)
message = ex.args[-1][0].get('message', None)
msg_1 = ("Command failed with error code: %s" %
(status_code or '<missing>'))
msg_1 = "Command failed with error code: %s" \
% (status_code or '<missing>')
msg_2 = "Error message:%s" % (message or '<missing>')
print msg_1
print msg_2
@@ -266,8 +209,8 @@ def _handle_exception(ex):
def prepare_output(cmd, tenant_id, response, version):
LOG.debug("Preparing output for response:%s, version:%s" %
(response, version))
LOG.debug("Preparing output for response:%s, version:%s"
% (response, version))
response['tenant_id'] = tenant_id
output = str(CmdOutputTemplate(cmd, response, version))
LOG.debug("Finished preparing output for command:%s", cmd)

View File

@@ -21,18 +21,18 @@
"""
import logging
import sys
import unittest
from quantum import api as server
from quantum.client import cli_lib as cli
from quantum.client import Client
from quantum.db import api as db
from quantumclient import cli_lib as cli
from quantumclient import Client
from quantumclient.tests.unit import stubs as client_stubs
from quantum.tests.unit.client_tools import stubs as client_stubs
LOG = logging.getLogger('quantumclient.tests.test_cli')
LOG = logging.getLogger('quantum.tests.test_cli')
API_VERSION = "1.1"
FORMAT = 'json'
@@ -42,8 +42,8 @@ class CLITest(unittest.TestCase):
def setUp(self):
"""Prepare the test environment"""
options = {}
options['plugin_provider'] = (
'quantum.plugins.sample.SamplePlugin.FakePlugin')
options['plugin_provider'] = \
'quantum.plugins.sample.SamplePlugin.FakePlugin'
#TODO: make the version of the API router configurable
self.api = server.APIRouterV11(options)
@@ -159,25 +159,17 @@ class CLITest(unittest.TestCase):
'op-status': nw.op_status}
port_list = db.port_list(nw.uuid)
if not port_list:
network['ports'] = [
{
'id': '<none>',
'state': '<none>',
'attachment': {
'id': '<none>',
},
},
]
network['ports'] = [{'id': '<none>',
'state': '<none>',
'attachment': {'id': '<none>'}}]
else:
network['ports'] = []
for port in port_list:
network['ports'].append({
'id': port.uuid,
'state': port.state,
'attachment': {
'id': port.interface_id or '<none>',
},
})
network['ports'].append({'id': port.uuid,
'state': port.state,
'attachment': {'id':
port.interface_id
or '<none>'}})
# Fill CLI template
output = cli.prepare_output('show_net_detail',

View File

@@ -17,16 +17,14 @@
# @author: Tyler Smith, Cisco Systems
import logging
import re
import unittest
import re
from quantumclient.common import exceptions
from quantumclient.common.serializer import Serializer
from quantumclient import Client
LOG = logging.getLogger('quantumclient.tests.test_api')
from quantum.common import exceptions
from quantum.common.serializer import Serializer
from quantum.client import Client
LOG = logging.getLogger('quantum.tests.test_api')
# Set a couple tenants to use for testing
TENANT_1 = 'totore'
@@ -121,7 +119,7 @@ class APITest(unittest.TestCase):
return data
def _test_list_networks(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_list_networks - tenant:%s "
LOG.debug("_test_list_networks - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.list_networks,
@@ -131,13 +129,13 @@ class APITest(unittest.TestCase):
data=[],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_list_networks - tenant:%s - format:%s - END",
format, tenant)
LOG.debug("_test_list_networks - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_list_networks_details(self,
tenant=TENANT_1, format='json',
status=200):
LOG.debug("_test_list_networks_details - tenant:%s "
LOG.debug("_test_list_networks_details - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.list_networks_details,
@@ -147,12 +145,12 @@ class APITest(unittest.TestCase):
data=[],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_list_networks_details - tenant:%s "
LOG.debug("_test_list_networks_details - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_show_network(self,
tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_show_network - tenant:%s "
LOG.debug("_test_show_network - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.show_network,
@@ -162,12 +160,12 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_show_network - tenant:%s "
LOG.debug("_test_show_network - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_show_network_details(self,
tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_show_network_details - tenant:%s "
LOG.debug("_test_show_network_details - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.show_network_details,
@@ -177,11 +175,11 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_show_network_details - tenant:%s "
LOG.debug("_test_show_network_details - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_create_network(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_create_network - tenant:%s "
LOG.debug("_test_create_network - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.create_network,
@@ -191,11 +189,11 @@ class APITest(unittest.TestCase):
data=[{'network': {'net-name': 'testNetwork'}}],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_create_network - tenant:%s "
LOG.debug("_test_create_network - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_update_network(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_update_network - tenant:%s "
LOG.debug("_test_update_network - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.update_network,
@@ -206,11 +204,11 @@ class APITest(unittest.TestCase):
{'network': {'net-name': 'newName'}}],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_update_network - tenant:%s "
LOG.debug("_test_update_network - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_delete_network(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_delete_network - tenant:%s "
LOG.debug("_test_delete_network - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.delete_network,
@@ -220,11 +218,11 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_delete_network - tenant:%s "
LOG.debug("_test_delete_network - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_list_ports(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_list_ports - tenant:%s "
LOG.debug("_test_list_ports - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.list_ports,
@@ -234,12 +232,12 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_list_ports - tenant:%s "
LOG.debug("_test_list_ports - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_list_ports_details(self,
tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_list_ports_details - tenant:%s "
LOG.debug("_test_list_ports_details - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.list_ports_details,
@@ -249,11 +247,11 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_list_ports_details - tenant:%s "
LOG.debug("_test_list_ports_details - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_show_port(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_show_port - tenant:%s "
LOG.debug("_test_show_port - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.show_port,
@@ -263,12 +261,12 @@ class APITest(unittest.TestCase):
data=["001", "001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_show_port - tenant:%s "
LOG.debug("_test_show_port - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_show_port_details(self,
tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_show_port_details - tenant:%s "
LOG.debug("_test_show_port_details - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.show_port_details,
@@ -278,11 +276,11 @@ class APITest(unittest.TestCase):
data=["001", "001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_show_port_details - tenant:%s "
LOG.debug("_test_show_port_details - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_create_port(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_create_port - tenant:%s "
LOG.debug("_test_create_port - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.create_port,
@@ -292,11 +290,11 @@ class APITest(unittest.TestCase):
data=["001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_create_port - tenant:%s "
LOG.debug("_test_create_port - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_delete_port(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_delete_port - tenant:%s "
LOG.debug("_test_delete_port - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.delete_port,
@@ -306,11 +304,11 @@ class APITest(unittest.TestCase):
data=["001", "001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_delete_port - tenant:%s "
LOG.debug("_test_delete_port - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_update_port(self, tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_update_port - tenant:%s "
LOG.debug("_test_update_port - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.update_port,
@@ -321,12 +319,12 @@ class APITest(unittest.TestCase):
{'port': {'state': 'ACTIVE'}}],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_update_port - tenant:%s "
LOG.debug("_test_update_port - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_show_port_attachment(self,
tenant=TENANT_1, format='json', status=200):
LOG.debug("_test_show_port_attachment - tenant:%s "
LOG.debug("_test_show_port_attachment - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.show_port_attachment,
@@ -336,12 +334,12 @@ class APITest(unittest.TestCase):
data=["001", "001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_show_port_attachment - tenant:%s "
LOG.debug("_test_show_port_attachment - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_attach_resource(self, tenant=TENANT_1,
format='json', status=200):
LOG.debug("_test_attach_resource - tenant:%s "
LOG.debug("_test_attach_resource - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.attach_resource,
@@ -352,12 +350,12 @@ class APITest(unittest.TestCase):
{'resource': {'id': '1234'}}],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_attach_resource - tenant:%s "
LOG.debug("_test_attach_resource - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_detach_resource(self, tenant=TENANT_1,
format='json', status=200):
LOG.debug("_test_detach_resource - tenant:%s "
LOG.debug("_test_detach_resource - tenant:%s "\
"- format:%s - START", format, tenant)
self._assert_sanity(self.client.detach_resource,
@@ -367,12 +365,12 @@ class APITest(unittest.TestCase):
data=["001", "001"],
params={'tenant': tenant, 'format': format})
LOG.debug("_test_detach_resource - tenant:%s "
LOG.debug("_test_detach_resource - tenant:%s "\
"- format:%s - END", format, tenant)
def _test_ssl_certificates(self, tenant=TENANT_1,
format='json', status=200):
LOG.debug("_test_ssl_certificates - tenant:%s "
LOG.debug("_test_ssl_certificates - tenant:%s "\
"- format:%s - START", format, tenant)
# Set SSL, and our cert file
@@ -381,16 +379,16 @@ class APITest(unittest.TestCase):
self.client.key_file = self.client.cert_file = cert_file
data = self._assert_sanity(self.client.list_networks,
status,
"GET",
"networks",
data=[],
params={'tenant': tenant, 'format': format})
status,
"GET",
"networks",
data=[],
params={'tenant': tenant, 'format': format})
self.assertEquals(data["key_file"], cert_file)
self.assertEquals(data["cert_file"], cert_file)
LOG.debug("_test_ssl_certificates - tenant:%s "
LOG.debug("_test_ssl_certificates - tenant:%s "\
"- format:%s - END", format, tenant)
def test_list_networks_json(self):

345
quantum/common/config.py Normal file
View File

@@ -0,0 +1,345 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
# 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.
"""
Routines for configuring Quantum
"""
import ConfigParser
import logging
import logging.config
import logging.handlers
import optparse
import os
import re
import sys
import socket
from paste import deploy
from quantum.common import flags
from quantum.common import exceptions as exception
DEFAULT_LOG_FORMAT = "%(asctime)s %(levelname)8s [%(name)s] %(message)s"
DEFAULT_LOG_DATE_FORMAT = "%Y-%m-%d %H:%M:%S"
FLAGS = flags.FLAGS
LOG = logging.getLogger('quantum.wsgi')
def parse_options(parser, cli_args=None):
"""
Returns the parsed CLI options, command to run and its arguments, merged
with any same-named options found in a configuration file.
The function returns a tuple of (options, args), where options is a
mapping of option key/str(value) pairs, and args is the set of arguments
(not options) supplied on the command-line.
The reason that the option values are returned as strings only is that
ConfigParser and paste.deploy only accept string values...
:param parser: The option parser
:param cli_args: (Optional) Set of arguments to process. If not present,
sys.argv[1:] is used.
:retval tuple of (options, args)
"""
(options, args) = parser.parse_args(cli_args)
return (vars(options), args)
def add_common_options(parser):
"""
Given a supplied optparse.OptionParser, adds an OptionGroup that
represents all common configuration options.
:param parser: optparse.OptionParser
"""
help_text = "The following configuration options are common to "\
"all quantum programs."
group = optparse.OptionGroup(parser, "Common Options", help_text)
group.add_option('-v', '--verbose', default=False, dest="verbose",
action="store_true",
help="Print more verbose output")
group.add_option('-d', '--debug', default=False, dest="debug",
action="store_true",
help="Print debugging output")
group.add_option('--config-file', default=None, metavar="PATH",
help="Path to the config file to use. When not specified "
"(the default), we generally look at the first "
"argument specified to be a config file, and if "
"that is also missing, we search standard "
"directories for a config file.")
parser.add_option_group(group)
def add_log_options(parser):
"""
Given a supplied optparse.OptionParser, adds an OptionGroup that
represents all the configuration options around logging.
:param parser: optparse.OptionParser
"""
help_text = "The following configuration options are specific to logging "\
"functionality for this program."
group = optparse.OptionGroup(parser, "Logging Options", help_text)
group.add_option('--log-config', default=None, metavar="PATH",
help="If this option is specified, the logging "
"configuration file specified is used and overrides "
"any other logging options specified. Please see "
"the Python logging module documentation for "
"details on logging configuration files.")
group.add_option('--log-date-format', metavar="FORMAT",
default=DEFAULT_LOG_DATE_FORMAT,
help="Format string for %(asctime)s in log records. "
"Default: %default")
group.add_option('--use-syslog', default=False,
action="store_true",
help="Output logs to syslog.")
group.add_option('--log-file', default=None, metavar="PATH",
help="(Optional) Name of log file to output to. "
"If not set, logging will go to stdout.")
group.add_option("--log-dir", default=None,
help="(Optional) The directory to keep log files in "
"(will be prepended to --logfile)")
parser.add_option_group(group)
def setup_logging(options, conf):
"""
Sets up the logging options for a log with supplied name
:param options: Mapping of typed option key/values
:param conf: Mapping of untyped key/values from config file
"""
if options.get('log_config', None):
# Use a logging configuration file for all settings...
if os.path.exists(options['log_config']):
logging.config.fileConfig(options['log_config'])
return
else:
raise RuntimeError("Unable to locate specified logging "
"config file: %s" % options['log_config'])
# If either the CLI option or the conf value
# is True, we set to True
debug = options.get('debug') or \
get_option(conf, 'debug', type='bool', default=False)
verbose = options.get('verbose') or \
get_option(conf, 'verbose', type='bool', default=False)
root_logger = logging.root
if debug:
root_logger.setLevel(logging.DEBUG)
elif verbose:
root_logger.setLevel(logging.INFO)
else:
root_logger.setLevel(logging.WARNING)
# Set log configuration from options...
# Note that we use a hard-coded log format in the options
# because of Paste.Deploy bug #379
# http://trac.pythonpaste.org/pythonpaste/ticket/379
log_format = options.get('log_format', DEFAULT_LOG_FORMAT)
log_date_format = options.get('log_date_format', DEFAULT_LOG_DATE_FORMAT)
formatter = logging.Formatter(log_format, log_date_format)
syslog = options.get('use_syslog')
if not syslog:
syslog = conf.get('use_syslog')
if syslog:
SysLogHandler = logging.handlers.SysLogHandler
try:
handler = SysLogHandler(address='/dev/log',
facility=SysLogHandler.LOG_SYSLOG)
except socket.error:
handler = SysLogHandler(address='/var/run/syslog',
facility=SysLogHandler.LOG_SYSLOG)
handler.setFormatter(formatter)
root_logger.addHandler(handler)
logfile = options.get('log_file')
if not logfile:
logfile = conf.get('log_file')
if logfile:
logdir = options.get('log_dir')
if not logdir:
logdir = conf.get('log_dir')
if logdir:
logfile = os.path.join(logdir, logfile)
logfile = logging.FileHandler(logfile)
logfile.setFormatter(formatter)
logfile.setFormatter(formatter)
root_logger.addHandler(logfile)
else:
handler = logging.StreamHandler(sys.stdout)
handler.setFormatter(formatter)
root_logger.addHandler(handler)
def find_config_file(options, args, config_file='quantum.conf'):
"""
Return the first config file found.
We search for the paste config file in the following order:
* If --config-file option is used, use that
* If args[0] is a file, use that
* Search for the configuration file in standard directories:
* .
* ~.quantum/
* ~
* $FLAGS.state_path/etc/quantum
* $FLAGS.state_path/etc
:retval Full path to config file, or None if no config file found
"""
fix_path = lambda p: os.path.abspath(os.path.expanduser(p))
if options.get('config_file'):
if os.path.exists(options['config_file']):
return fix_path(options['config_file'])
elif args:
if os.path.exists(args[0]):
return fix_path(args[0])
dir_to_common = os.path.dirname(os.path.abspath(__file__))
root = os.path.join(dir_to_common, '..', '..', '..', '..')
# Handle standard directory search for the config file
config_file_dirs = [fix_path(os.path.join(os.getcwd(), 'etc')),
fix_path(os.path.join('~', '.quantum-venv', 'etc',
'quantum')),
fix_path('~'),
os.path.join(FLAGS.state_path, 'etc'),
os.path.join(FLAGS.state_path, 'etc', 'quantum'),
fix_path(os.path.join('~', '.local',
'etc', 'quantum')),
'/usr/etc/quantum',
'/usr/local/etc/quantum',
'/etc/quantum/',
'/etc']
if 'plugin' in options:
config_file_dirs = [os.path.join(x, 'quantum', 'plugins',
options['plugin'])
for x in config_file_dirs]
if os.path.exists(os.path.join(root, 'plugins')):
plugins = [fix_path(os.path.join(root, 'plugins', p, 'etc'))
for p in os.listdir(os.path.join(root, 'plugins'))]
plugins = [p for p in plugins if os.path.isdir(p)]
config_file_dirs.extend(plugins)
for cfg_dir in config_file_dirs:
cfg_file = os.path.join(cfg_dir, config_file)
if os.path.exists(cfg_file):
return cfg_file
def load_paste_config(app_name, options, args):
"""
Looks for a config file to use for an app and returns the
config file path and a configuration mapping from a paste config file.
We search for the paste config file in the following order:
* If --config-file option is used, use that
* If args[0] is a file, use that
* Search for quantum.conf in standard directories:
* .
* ~.quantum/
* ~
* /etc/quantum
* /etc
:param app_name: Name of the application to load config for, or None.
None signifies to only load the [DEFAULT] section of
the config file.
:param options: Set of typed options returned from parse_options()
:param args: Command line arguments from argv[1:]
:retval Tuple of (conf_file, conf)
:raises RuntimeError when config file cannot be located or there was a
problem loading the configuration file.
"""
conf_file = find_config_file(options, args)
if not conf_file:
raise RuntimeError("Unable to locate any configuration file. "
"Cannot load application %s" % app_name)
try:
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
return conf_file, conf
except Exception, e:
raise RuntimeError("Error trying to load config %s: %s"
% (conf_file, e))
def load_paste_app(app_name, options, args):
"""
Builds and returns a WSGI app from a paste config file.
We search for the paste config file in the following order:
* If --config-file option is used, use that
* If args[0] is a file, use that
* Search for quantum.conf in standard directories:
* .
* ~.quantum/
* ~
* /etc/quantum
* /etc
:param app_name: Name of the application to load
:param options: Set of typed options returned from parse_options()
:param args: Command line arguments from argv[1:]
:raises RuntimeError when config file cannot be located or application
cannot be loaded from config file
"""
conf_file, conf = load_paste_config(app_name, options, args)
try:
app = deploy.loadapp("config:%s" % conf_file, name=app_name)
except (LookupError, ImportError), e:
raise RuntimeError("Unable to load %(app_name)s from "
"configuration file %(conf_file)s."
"\nGot: %(e)r" % locals())
return conf, app
def get_option(options, option, **kwargs):
if option in options:
value = options[option]
type_ = kwargs.get('type', 'str')
if type_ == 'bool':
if hasattr(value, 'lower'):
return value.lower() == 'true'
else:
return value
elif type_ == 'int':
return int(value)
elif type_ == 'float':
return float(value)
else:
return value
elif 'default' in kwargs:
return kwargs['default']
else:
raise KeyError("option '%s' not found" % option)

View File

@@ -16,9 +16,15 @@
# under the License.
"""
Quantum base exception handling.
Quantum base exception handling, including decorator for re-raising
Quantum-type exceptions. SHOULD include dedicated exception logging.
"""
import logging
import gettext
gettext.install('quantum', unicode=1)
class QuantumException(Exception):
"""Base Quantum Exception
@@ -47,6 +53,40 @@ class NotFound(QuantumException):
pass
class ClassNotFound(NotFound):
message = _("Class %(class_name)s could not be found")
class NetworkNotFound(NotFound):
message = _("Network %(net_id)s could not be found")
class PortNotFound(NotFound):
message = _("Port %(port_id)s could not be found " \
"on network %(net_id)s")
class StateInvalid(QuantumException):
message = _("Unsupported port state: %(port_state)s")
class NetworkInUse(QuantumException):
message = _("Unable to complete operation on network %(net_id)s. " \
"There is one or more attachments plugged into its ports.")
class PortInUse(QuantumException):
message = _("Unable to complete operation on port %(port_id)s " \
"for network %(net_id)s. The attachment '%(att_id)s" \
"is plugged into the logical port.")
class AlreadyAttached(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 port %(att_port_id)s")
class QuantumClientException(QuantumException):
def __init__(self, **kwargs):
@@ -105,18 +145,68 @@ class BadInputError(Exception):
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
class NotEmpty(Error):
pass
class Invalid(Error):
pass
class InvalidContentType(Invalid):
message = _("Invalid content type %(content_type)s.")
class MissingArgumentError(Error):
pass
class NotImplementedError(Error):
pass
def wrap_exception(f):
def _wrap(*args, **kw):
try:
return f(*args, **kw)
except Exception, e:
if not isinstance(e, Error):
#exc_type, exc_value, exc_traceback = sys.exc_info()
logging.exception('Uncaught exception')
#logging.error(traceback.extract_stack(exc_traceback))
raise Error(str(e))
raise
_wrap.func_name = f.func_name
return _wrap

249
quantum/common/flags.py Normal file
View File

@@ -0,0 +1,249 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems, Inc.
# 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.
"""Command-line flag library.
Wraps gflags.
Global flags should be defined here, the rest are defined where they're used.
"""
import getopt
import gflags
import os
import string
import sys
class FlagValues(gflags.FlagValues):
"""Extension of gflags.FlagValues that allows undefined and runtime flags.
Unknown flags will be ignored when parsing the command line, but the
command line will be kept so that it can be replayed if new flags are
defined after the initial parsing.
"""
def __init__(self, extra_context=None):
gflags.FlagValues.__init__(self)
self.__dict__['__dirty'] = []
self.__dict__['__was_already_parsed'] = False
self.__dict__['__stored_argv'] = []
self.__dict__['__extra_context'] = extra_context
def __call__(self, argv):
# We're doing some hacky stuff here so that we don't have to copy
# out all the code of the original verbatim and then tweak a few lines.
# We're hijacking the output of getopt so we can still return the
# leftover args at the end
sneaky_unparsed_args = {"value": None}
original_argv = list(argv)
if self.IsGnuGetOpt():
orig_getopt = getattr(getopt, 'gnu_getopt')
orig_name = 'gnu_getopt'
else:
orig_getopt = getattr(getopt, 'getopt')
orig_name = 'getopt'
def _sneaky(*args, **kw):
optlist, unparsed_args = orig_getopt(*args, **kw)
sneaky_unparsed_args['value'] = unparsed_args
return optlist, unparsed_args
try:
setattr(getopt, orig_name, _sneaky)
args = gflags.FlagValues.__call__(self, argv)
except gflags.UnrecognizedFlagError:
# Undefined args were found, for now we don't care so just
# act like everything went well
# (these three lines are copied pretty much verbatim from the end
# of the __call__ function we are wrapping)
unparsed_args = sneaky_unparsed_args['value']
if unparsed_args:
if self.IsGnuGetOpt():
args = argv[:1] + unparsed_args
else:
args = argv[:1] + original_argv[-len(unparsed_args):]
else:
args = argv[:1]
finally:
setattr(getopt, orig_name, orig_getopt)
# Store the arguments for later, we'll need them for new flags
# added at runtime
self.__dict__['__stored_argv'] = original_argv
self.__dict__['__was_already_parsed'] = True
self.ClearDirty()
return args
def Reset(self):
gflags.FlagValues.Reset(self)
self.__dict__['__dirty'] = []
self.__dict__['__was_already_parsed'] = False
self.__dict__['__stored_argv'] = []
def SetDirty(self, name):
"""Mark a flag as dirty so that accessing it will case a reparse."""
self.__dict__['__dirty'].append(name)
def IsDirty(self, name):
return name in self.__dict__['__dirty']
def ClearDirty(self):
self.__dict__['__is_dirty'] = []
def WasAlreadyParsed(self):
return self.__dict__['__was_already_parsed']
def ParseNewFlags(self):
if '__stored_argv' not in self.__dict__:
return
new_flags = FlagValues(self)
for k in self.__dict__['__dirty']:
new_flags[k] = gflags.FlagValues.__getitem__(self, k)
new_flags(self.__dict__['__stored_argv'])
for k in self.__dict__['__dirty']:
setattr(self, k, getattr(new_flags, k))
self.ClearDirty()
def __setitem__(self, name, flag):
gflags.FlagValues.__setitem__(self, name, flag)
if self.WasAlreadyParsed():
self.SetDirty(name)
def __getitem__(self, name):
if self.IsDirty(name):
self.ParseNewFlags()
return gflags.FlagValues.__getitem__(self, name)
def __getattr__(self, name):
if self.IsDirty(name):
self.ParseNewFlags()
val = gflags.FlagValues.__getattr__(self, name)
if type(val) is str:
tmpl = string.Template(val)
context = [self, self.__dict__['__extra_context']]
return tmpl.substitute(StrWrapper(context))
return val
class StrWrapper(object):
"""Wrapper around FlagValues objects.
Wraps FlagValues objects for string.Template so that we're
sure to return strings.
"""
def __init__(self, context_objs):
self.context_objs = context_objs
def __getitem__(self, name):
for context in self.context_objs:
val = getattr(context, name, False)
if val:
return str(val)
raise KeyError(name)
# Copied from gflags with small mods to get the naming correct.
# Originally gflags checks for the first module that is not gflags that is
# in the call chain, we want to check for the first module that is not gflags
# and not this module.
def _GetCallingModule():
"""Returns the name of the module that's calling into this module.
We generally use this function to get the name of the module calling a
DEFINE_foo... function.
"""
# Walk down the stack to find the first globals dict that's not ours.
for depth in range(1, sys.getrecursionlimit()):
if not sys._getframe(depth).f_globals is globals():
module_name = __GetModuleName(sys._getframe(depth).f_globals)
if module_name == 'gflags':
continue
if module_name is not None:
return module_name
raise AssertionError("No module was found")
# Copied from gflags because it is a private function
def __GetModuleName(globals_dict):
"""Given a globals dict, returns the name of the module that defines it.
Args:
globals_dict: A dictionary that should correspond to an environment
providing the values of the globals.
Returns:
A string (the name of the module) or None (if the module could not
be identified.
"""
for name, module in sys.modules.iteritems():
if getattr(module, '__dict__', None) is globals_dict:
if name == '__main__':
return sys.argv[0]
return name
return None
def _wrapper(func):
def _wrapped(*args, **kw):
kw.setdefault('flag_values', FLAGS)
func(*args, **kw)
_wrapped.func_name = func.func_name
return _wrapped
FLAGS = FlagValues()
gflags.FLAGS = FLAGS
gflags._GetCallingModule = _GetCallingModule
DEFINE = _wrapper(gflags.DEFINE)
DEFINE_string = _wrapper(gflags.DEFINE_string)
DEFINE_integer = _wrapper(gflags.DEFINE_integer)
DEFINE_bool = _wrapper(gflags.DEFINE_bool)
DEFINE_boolean = _wrapper(gflags.DEFINE_boolean)
DEFINE_float = _wrapper(gflags.DEFINE_float)
DEFINE_enum = _wrapper(gflags.DEFINE_enum)
DEFINE_list = _wrapper(gflags.DEFINE_list)
DEFINE_spaceseplist = _wrapper(gflags.DEFINE_spaceseplist)
DEFINE_multistring = _wrapper(gflags.DEFINE_multistring)
DEFINE_multi_int = _wrapper(gflags.DEFINE_multi_int)
DEFINE_flag = _wrapper(gflags.DEFINE_flag)
HelpFlag = gflags.HelpFlag
HelpshortFlag = gflags.HelpshortFlag
HelpXMLFlag = gflags.HelpXMLFlag
def DECLARE(name, module_string, flag_values=FLAGS):
if module_string not in sys.modules:
__import__(module_string, globals(), locals())
if name not in flag_values:
raise gflags.UnrecognizedFlag(
"%s not defined by %s" % (name, module_string))
# __GLOBAL FLAGS ONLY__
# Define any app-specific flags in their own files, docs at:
# http://code.google.com/p/python-gflags/source/browse/trunk/gflags.py#a9
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../../'),
"Top-level directory for maintaining quantum's state")

View File

@@ -1,10 +1,9 @@
from xml.dom import minidom
from quantumclient.common import exceptions as exception
from quantumclient.common import utils
from quantum.common import exceptions as exception
from quantum.common import utils
# NOTE(maru): this class is duplicated from quantum.wsgi
class Serializer(object):
"""Serializes and deserializes dictionaries to certain MIME types."""
@@ -43,7 +42,7 @@ class Serializer(object):
return self.get_deserialize_handler(content_type)(datastring)
except Exception:
raise exception.MalformedResponseBody(
reason="Unable to deserialize response body")
reason="Unable to deserialize response body")
def get_deserialize_handler(self, content_type):
handlers = {

291
quantum/common/test_lib.py Normal file
View File

@@ -0,0 +1,291 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack, LLC
# 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.
# Colorizer Code is borrowed from Twisted:
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
import gettext
import os
import unittest
import sys
import logging
from nose import result
from nose import core
from nose import config
class _AnsiColorizer(object):
"""
A colorizer is an object that loosely wraps around a stream, allowing
callers to write text to the stream in a particular color.
Colorizer classes must implement C{supported()} and C{write(text, color)}.
"""
_colors = dict(black=30, red=31, green=32, yellow=33,
blue=34, magenta=35, cyan=36, white=37)
def __init__(self, stream):
self.stream = stream
def supported(cls, stream=sys.stdout):
"""
A class method that returns True if the current platform supports
coloring terminal output using this method. Returns False otherwise.
"""
if not stream.isatty():
return False # auto color only on TTYs
try:
import curses
except ImportError:
return False
else:
try:
try:
return curses.tigetnum("colors") > 2
except curses.error:
curses.setupterm()
return curses.tigetnum("colors") > 2
except:
raise
# guess false in case of error
return False
supported = classmethod(supported)
def write(self, text, color):
"""
Write the given text to the stream in the given color.
@param text: Text to be written to the stream.
@param color: A string label for a color. e.g. 'red', 'white'.
"""
color = self._colors[color]
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
class _Win32Colorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
from win32console import GetStdHandle, STD_OUT_HANDLE, \
FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
FOREGROUND_INTENSITY
red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
FOREGROUND_BLUE, FOREGROUND_INTENSITY)
self.stream = stream
self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
self._colors = {
'normal': red | green | blue,
'red': red | bold,
'green': green | bold,
'blue': blue | bold,
'yellow': red | green | bold,
'magenta': red | blue | bold,
'cyan': green | blue | bold,
'white': red | green | blue | bold}
def supported(cls, stream=sys.stdout):
try:
import win32console
screenBuffer = win32console.GetStdHandle(
win32console.STD_OUT_HANDLE)
except ImportError:
return False
import pywintypes
try:
screenBuffer.SetConsoleTextAttribute(
win32console.FOREGROUND_RED |
win32console.FOREGROUND_GREEN |
win32console.FOREGROUND_BLUE)
except pywintypes.error:
return False
else:
return True
supported = classmethod(supported)
def write(self, text, color):
color = self._colors[color]
self.screenBuffer.SetConsoleTextAttribute(color)
self.stream.write(text)
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
class _NullColorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
self.stream = stream
def supported(cls, stream=sys.stdout):
return True
supported = classmethod(supported)
def write(self, text, color):
self.stream.write(text)
class QuantumTestResult(result.TextTestResult):
def __init__(self, *args, **kw):
result.TextTestResult.__init__(self, *args, **kw)
self._last_case = None
self.colorizer = None
# NOTE(vish, tfukushima): reset stdout for the terminal check
stdout = sys.__stdout__
sys.stdout = sys.__stdout__
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
if colorizer.supported():
self.colorizer = colorizer(self.stream)
break
sys.stdout = stdout
def getDescription(self, test):
return str(test)
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addSuccess(self, test):
unittest.TestResult.addSuccess(self, test)
if self.showAll:
self.colorizer.write("OK", 'green')
self.stream.writeln()
elif self.dots:
self.stream.write('.')
self.stream.flush()
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addFailure(self, test, err):
unittest.TestResult.addFailure(self, test, err)
if self.showAll:
self.colorizer.write("FAIL", 'red')
self.stream.writeln()
elif self.dots:
self.stream.write('F')
self.stream.flush()
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addError(self, test, err):
"""Overrides normal addError to add support for errorClasses.
If the exception is a registered class, the error will be added
to the list for that class, not errors.
"""
stream = getattr(self, 'stream', None)
ec, ev, tb = err
try:
exc_info = self._exc_info_to_string(err, test)
except TypeError:
# This is for compatibility with Python 2.3.
exc_info = self._exc_info_to_string(err)
for cls, (storage, label, isfail) in self.errorClasses.items():
if result.isclass(ec) and issubclass(ec, cls):
if isfail:
test.passwd = False
storage.append((test, exc_info))
# Might get patched into a streamless result
if stream is not None:
if self.showAll:
message = [label]
detail = result._exception_details(err[1])
if detail:
message.append(detail)
stream.writeln(": ".join(message))
elif self.dots:
stream.write(label[:1])
return
self.errors.append((test, exc_info))
test.passed = False
if stream is not None:
if self.showAll:
self.colorizer.write("ERROR", 'red')
self.stream.writeln()
elif self.dots:
stream.write('E')
def startTest(self, test):
unittest.TestResult.startTest(self, test)
current_case = test.test.__class__.__name__
if self.showAll:
if current_case != self._last_case:
self.stream.writeln(current_case)
self._last_case = current_case
#NOTE(salvatore-orlando):
#slightly changed in order to print test case class
#together with unit test name
self.stream.write(
' %s' % str(test.test).ljust(60))
self.stream.flush()
class QuantumTestRunner(core.TextTestRunner):
def _makeResult(self):
return QuantumTestResult(self.stream,
self.descriptions,
self.verbosity,
self.config)
def run_tests(c=None):
logger = logging.getLogger()
hdlr = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
# NOTE(bgh): I'm not entirely sure why but nose gets confused here when
# calling run_tests from a plugin directory run_tests.py (instead of the
# main run_tests.py). It will call run_tests with no arguments and the
# testing of run_tests will fail (though the plugin tests will pass). For
# now we just return True to let the run_tests test pass.
if not c:
return True
runner = QuantumTestRunner(stream=c.stream,
verbosity=c.verbosity,
config=c)
return not core.run(config=c, testRunner=runner)
# describes parameters used by different unit/functional tests
# a plugin-specific testing mechanism should import this dictionary
# and override the values in it if needed (e.g., run_tests.py in
# quantum/plugins/openvswitch/ )
test_config = {
"plugin_name": "quantum.plugins.sample.SamplePlugin.FakePlugin",
"default_net_op_status": "UP",
"default_port_op_status": "UP",
}

279
quantum/common/utils.py Normal file
View File

@@ -0,0 +1,279 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011, Nicira Networks, Inc.
#
# 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.
#
# Borrowed from nova code base, more utilities will be added/borrowed as and
# when needed.
# @author: Somik Behera, Nicira Networks, Inc.
"""Utilities and helper functions."""
import ConfigParser
import datetime
import inspect
import logging
import os
import random
import subprocess
import socket
import sys
import base64
import functools
import json
import re
import string
import struct
import time
import types
from quantum.common import flags
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('.')
try:
__import__(mod_str)
return getattr(sys.modules[mod_str], class_str)
except (ImportError, ValueError, AttributeError), exc:
print(('Inner Exception: %s'), exc)
raise exception.ClassNotFound(class_name=class_str)
def import_object(import_str):
"""Returns an object including a module or module and class."""
try:
__import__(import_str)
return sys.modules[import_str]
except ImportError:
cls = import_class(import_str)
return cls()
def to_primitive(value):
if type(value) is type([]) or type(value) is type((None,)):
o = []
for v in value:
o.append(to_primitive(v))
return o
elif type(value) is type({}):
o = {}
for k, v in value.iteritems():
o[k] = to_primitive(v)
return o
elif isinstance(value, datetime.datetime):
return str(value)
elif hasattr(value, 'iteritems'):
return to_primitive(dict(value.iteritems()))
elif hasattr(value, '__iter__'):
return to_primitive(list(value))
else:
return value
def dumps(value):
try:
return json.dumps(value)
except TypeError:
pass
return json.dumps(to_primitive(value))
def loads(s):
return json.loads(s)
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
FLAGS = flags.FLAGS
def int_from_bool_as_string(subject):
"""
Interpret a string as a boolean and return either 1 or 0.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
return bool_from_string(subject) and 1 or 0
def bool_from_string(subject):
"""
Interpret a string as a boolean.
Any string value in:
('True', 'true', 'On', 'on', '1')
is interpreted as a boolean True.
Useful for JSON-decoded stuff and config file parsing
"""
if type(subject) == type(bool):
return subject
if hasattr(subject, 'startswith'): # str or unicode...
if subject.strip().lower() in ('true', 'on', '1'):
return True
return False
def fetchfile(url, target):
logging.debug("Fetching %s" % url)
# c = pycurl.Curl()
# fp = open(target, "wb")
# c.setopt(c.URL, url)
# c.setopt(c.WRITEDATA, fp)
# c.perform()
# c.close()
# fp.close()
execute("curl --fail %s -o %s" % (url, target))
def execute(cmd, process_input=None, addl_env=None, check_exit_code=True):
logging.debug("Running cmd: %s", cmd)
env = os.environ.copy()
if addl_env:
env.update(addl_env)
obj = subprocess.Popen(cmd, shell=True, stdin=subprocess.PIPE,
stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=env)
result = None
if process_input != None:
result = obj.communicate(process_input)
else:
result = obj.communicate()
obj.stdin.close()
if obj.returncode:
logging.debug("Result was %s" % (obj.returncode))
if check_exit_code and obj.returncode != 0:
(stdout, stderr) = result
raise ProcessExecutionError(exit_code=obj.returncode,
stdout=stdout,
stderr=stderr,
cmd=cmd)
return result
def abspath(s):
return os.path.join(os.path.dirname(__file__), s)
# TODO(sirp): when/if utils is extracted to common library, we should remove
# the argument's default.
#def default_flagfile(filename='nova.conf'):
def default_flagfile(filename='quantum.conf'):
for arg in sys.argv:
if arg.find('flagfile') != -1:
break
else:
if not os.path.isabs(filename):
# turn relative filename into an absolute path
script_dir = os.path.dirname(inspect.stack()[-1][1])
filename = os.path.abspath(os.path.join(script_dir, filename))
if os.path.exists(filename):
sys.argv = \
sys.argv[:1] + ['--flagfile=%s' % filename] + sys.argv[1:]
def debug(arg):
logging.debug('debug in callback: %s', arg)
return arg
def runthis(prompt, cmd, check_exit_code=True):
logging.debug("Running %s" % (cmd))
exit_code = subprocess.call(cmd.split(" "))
logging.debug(prompt % (exit_code))
if check_exit_code and exit_code != 0:
raise ProcessExecutionError(exit_code=exit_code,
stdout=None,
stderr=None,
cmd=cmd)
def generate_uid(topic, size=8):
return '%s-%s' % (topic, ''.join(
[random.choice('01234567890abcdefghijklmnopqrstuvwxyz')
for x in xrange(size)]))
def generate_mac():
mac = [0x02, 0x16, 0x3e, random.randint(0x00, 0x7f),
random.randint(0x00, 0xff), random.randint(0x00, 0xff)]
return ':'.join(map(lambda x: "%02x" % x, mac))
def last_octet(address):
return int(address.split(".")[-1])
def isotime(at=None):
if not at:
at = datetime.datetime.utcnow()
return at.strftime(TIME_FORMAT)
def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT)
def get_plugin_from_config(file="config.ini"):
Config = ConfigParser.ConfigParser()
Config.read(file)
return Config.get("PLUGIN", "provider")
class LazyPluggable(object):
"""A pluggable backend loaded lazily based on some value."""
def __init__(self, pivot, **backends):
self.__backends = backends
self.__pivot = pivot
self.__backend = None
def __get_backend(self):
if not self.__backend:
backend_name = self.__pivot.value
if backend_name not in self.__backends:
raise exception.Error('Invalid backend: %s' % backend_name)
backend = self.__backends[backend_name]
if type(backend) == type(tuple()):
name = backend[0]
fromlist = backend[1]
else:
name = backend
fromlist = backend
self.__backend = __import__(name, None, None, fromlist)
logging.info('backend %s', self.__backend)
return self.__backend
def __getattr__(self, key):
backend = self.__get_backend()
return getattr(backend, key)

View File

@@ -1,70 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011, Nicira Networks, Inc.
#
# 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.
#
# Borrowed from nova code base, more utilities will be added/borrowed as and
# when needed.
# @author: Somik Behera, Nicira Networks, Inc.
"""Utilities and helper functions."""
import datetime
import json
import os
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 to_primitive(value):
if type(value) is type([]) or type(value) is type((None,)):
o = []
for v in value:
o.append(to_primitive(v))
return o
elif type(value) is type({}):
o = {}
for k, v in value.iteritems():
o[k] = to_primitive(v)
return o
elif isinstance(value, datetime.datetime):
return str(value)
elif hasattr(value, 'iteritems'):
return to_primitive(dict(value.iteritems()))
elif hasattr(value, '__iter__'):
return to_primitive(list(value))
else:
return value
def dumps(value):
try:
return json.dumps(value)
except TypeError:
pass
return json.dumps(to_primitive(value))
def loads(s):
return json.loads(s)

View File

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

View File

@@ -5,3 +5,8 @@
# openstack-nose https://github.com/jkoelker/openstack-nose
verbosity=2
detailed-errors=1
with-openstack=1
openstack-red=0.05
openstack-yellow=0.025
openstack-show-elapsed=1

View File

@@ -36,6 +36,9 @@ ShortDescription = Summary
Description = Summary
requires = [
'Paste',
'PasteDeploy',
'python-gflags',
]
EagerResources = [
@@ -60,12 +63,12 @@ setup(
scripts=ProjectScripts,
install_requires=requires,
include_package_data=False,
packages=["quantumclient", "quantumclient.common"],
packages=["quantum", "quantum.client", "quantum.common"],
package_data=PackageData,
eager_resources=EagerResources,
entry_points={
'console_scripts': [
'quantum = quantumclient.cli:main'
'quantum = quantum.client.cli:main'
]
},
)

16
tools/pip-requires Normal file
View File

@@ -0,0 +1,16 @@
# NOTE(danwent): these paste dependencies are only here because of the weird
# split of "common" quantum code between quantum-server and quantum-client.
# Should be removed from here and setup.py once we fix that.
Paste
PasteDeploy==1.5.0
python-gflags==1.3
distribute>=0.6.24
coverage
nose
nosexcover
pep8==0.6.1
-e git+https://review.openstack.org/p/openstack-dev/openstack-nose.git#egg=openstack.nose_plugin
-e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev

View File

@@ -1,11 +0,0 @@
distribute>=0.6.24
mox
nose
nose-exclude
nosexcover
openstack.nose_plugin
pep8==0.6.1
sphinx>=1.1.2
-e git+https://review.openstack.org/p/openstack/quantum#egg=quantum-dev

38
tox.ini
View File

@@ -2,56 +2,30 @@
envlist = py26,py27,pep8
[testenv]
setenv =
VIRTUAL_ENV={envdir}
NOSE_WITH_OPENSTACK=1
NOSE_OPENSTACK_COLOR=1
NOSE_OPENSTACK_RED=0.05
NOSE_OPENSTACK_YELLOW=0.025
NOSE_OPENSTACK_SHOW_ELAPSED=1
deps =
-r{toxinidir}/tools/test-requires
deps = -r{toxinidir}/tools/pip-requires
commands = nosetests
[testenv:pep8]
commands =
pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \
setup.py version.py
[testenv:venv]
commands = {posargs}
commands = pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantum setup.py version.py
[testenv:cover]
commands =
nosetests --with-coverage --cover-html --cover-erase \
--cover-package=quantumclient
commands = nosetests --with-coverage --cover-html --cover-erase --cover-package=quantum
[tox:jenkins]
[testenv:hudson]
downloadcache = ~/cache/pip
[testenv:jenkins26]
basepython = python2.6
setenv = NOSE_WITH_XUNIT=1
deps = file://{toxinidir}/.cache.bundle
[testenv:jenkins27]
basepython = python2.7
setenv = NOSE_WITH_XUNIT=1
deps = file://{toxinidir}/.cache.bundle
[testenv:jenkinspep8]
deps = file://{toxinidir}/.cache.bundle
commands =
pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantumclient \
setup.py version.py
commands = pep8 --exclude=vcsversion.py,*.pyc --repeat --show-source quantum setup.py version.py
[testenv:jenkinscover]
deps = file://{toxinidir}/.cache.bundle
setenv = NOSE_WITH_XUNIT=1
commands =
nosetests --cover-erase --cover-package=quantumclient --with-xcoverage
[testenv:jenkinsvenv]
deps = file://{toxinidir}/.cache.bundle
setenv = NOSE_WITH_XUNIT=1
commands = {posargs}
commands = nosetests --cover-erase --cover-package=quantum --with-xcoverage

View File

@@ -15,16 +15,16 @@
# under the License.
try:
from quantumclient.vcsversion import version_info
from quantum.vcsversion import version_info
except ImportError:
version_info = {'branch_nick': u'LOCALBRANCH',
'revision_id': 'LOCALREVISION',
'revno': 0}
QUANTUM_VERSION = ['2012', '2', None]
QUANTUM_VERSION = ['2012', '1', None]
YEAR, COUNT, REVSISION = QUANTUM_VERSION
FINAL = False # This becomes true at Release Candidate time
FINAL = True # This becomes true at Release Candidate time
def canonical_version_string():