Merging latest changes from parent repo - lp:network-service , Parent repo had approved merge proposal for merging lp:~santhom/network-service/quantum_testing_framework , which has now been merged into lp:network-service

This commit is contained in:
Somik Behera 2011-06-21 10:37:34 -07:00
commit 8b20e8270d
37 changed files with 904 additions and 521 deletions

View File

@ -33,9 +33,10 @@ if os.path.exists(os.path.join(possible_topdir, 'quantum', '__init__.py')):
gettext.install('quantum', unicode=1)
from quantum import service
from quantum import service
from quantum.common import config
def create_options(parser):
"""
Sets up the CLI and config-file options that may be
@ -58,4 +59,3 @@ if __name__ == '__main__':
service.wait()
except RuntimeError, e:
sys.exit("ERROR: %s" % e)

View File

@ -12,4 +12,4 @@ paste.app_factory = quantum.l2Network.service:app_factory
bind_host = 0.0.0.0
# Port the bind the API server to
bind_port = 9696
bind_port = 9696

View File

@ -54,8 +54,9 @@ class APIRouterV01(wsgi.Router):
mapper.resource('port', 'ports',
controller=ports.Controller(),
parent_resource=dict(member_name='network',
collection_name=\
uri_prefix + 'networks'))
collection_name=uri_prefix +\
'networks'))
mapper.connect("get_resource",
uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}',

View File

@ -36,8 +36,7 @@ class Fault(webob.exc.HTTPException):
432: "portInUse",
440: "alreadyAttached",
470: "serviceUnavailable",
471: "pluginFault"
}
471: "pluginFault"}
def __init__(self, exception):
"""Create a Fault for the given webob.exc.exception."""
@ -52,7 +51,7 @@ class Fault(webob.exc.HTTPException):
fault_data = {
fault_name: {
'code': code,
'message': self.wrapped_exc.explanation,
'message': self.wrapped_exc.explanation,
'detail': self.wrapped_exc.detail}}
# 'code' is an attribute on the fault tag itself
metadata = {'application/xml': {'attributes': {fault_name: 'code'}}}

View File

@ -44,63 +44,66 @@ class Controller(common.QuantumController):
self._resource_name = 'network'
super(Controller, self).__init__()
def index(self, req, tenant_id):
def index(self, request, tenant_id):
""" Returns a list of network ids """
#TODO: this should be for a given tenant!!!
return self._items(req, tenant_id, is_detail=False)
return self._items(request, tenant_id, is_detail=False)
def _items(self, req, tenant_id, is_detail):
def _items(self, request, tenant_id, is_detail):
""" Returns a list of networks. """
networks = self.network_manager.get_all_networks(tenant_id)
builder = networks_view.get_view_builder(req)
builder = networks_view.get_view_builder(request)
result = [builder.build(network, is_detail)['network']
for network in networks]
return dict(networks=result)
def show(self, req, tenant_id, id):
def show(self, request, tenant_id, id):
""" Returns network details for the given network id """
try:
network = self.network_manager.get_network_details(
tenant_id, id)
builder = networks_view.get_view_builder(req)
builder = networks_view.get_view_builder(request)
#build response with details
result = builder.build(network, True)
return dict(networks=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
def create(self, req, tenant_id):
def create(self, request, tenant_id):
""" Creates a new network for a given tenant """
#look for network name in request
try:
req_params = \
self._parse_request_params(req, self._network_ops_param_list)
request_params = \
self._parse_request_params(request,
self._network_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
network = self.network_manager.\
create_network(tenant_id,req_params['network-name'])
builder = networks_view.get_view_builder(req)
create_network(tenant_id,
request_params['network-name'])
builder = networks_view.get_view_builder(request)
result = builder.build(network)
return dict(networks=result)
def update(self, req, tenant_id, id):
def update(self, request, tenant_id, id):
""" Updates the name for the network with the given id """
try:
req_params = \
self._parse_request_params(req, self._network_ops_param_list)
request_params = \
self._parse_request_params(request,
self._network_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
network = self.network_manager.rename_network(tenant_id,
id, req_params['network-name'])
id, request_params['network-name'])
builder = networks_view.get_view_builder(req)
builder = networks_view.get_view_builder(request)
result = builder.build(network, True)
return dict(networks=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
def delete(self, req, tenant_id, id):
def delete(self, request, tenant_id, id):
""" Destroys the network with the given id """
try:
self.network_manager.delete_network(tenant_id, id)

View File

@ -24,51 +24,49 @@ from quantum.common import exceptions as exception
LOG = logging.getLogger('quantum.api.ports')
class Controller(common.QuantumController):
""" Port API controller for Quantum API """
_port_ops_param_list = [{
'param-name': 'port-state',
'default-value': 'DOWN',
'required': False},]
'required': False}, ]
_attachment_ops_param_list = [{
'param-name': 'attachment-id',
'required': True},]
'required': True}, ]
_serialization_metadata = {
"application/xml": {
"attributes": {
"port": ["id","state"],
},
},
}
"port": ["id", "state"], }, }, }
def __init__(self, plugin_conf_file=None):
self._resource_name = 'port'
super(Controller, self).__init__()
def index(self, req, tenant_id, network_id):
def index(self, request, tenant_id, network_id):
""" Returns a list of port ids for a given network """
return self._items(req, tenant_id, network_id, is_detail=False)
return self._items(request, tenant_id, network_id, is_detail=False)
def _items(self, req, tenant_id, network_id, is_detail):
def _items(self, request, tenant_id, network_id, is_detail):
""" Returns a list of networks. """
try :
try:
ports = self.network_manager.get_all_ports(tenant_id, network_id)
builder = ports_view.get_view_builder(req)
builder = ports_view.get_view_builder(request)
result = [builder.build(port, is_detail)['port']
for port in ports]
return dict(ports=result)
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
def show(self, req, tenant_id, network_id, id):
def show(self, request, tenant_id, network_id, id):
""" Returns port details for given port and network """
try:
port = self.network_manager.get_port_details(
tenant_id, network_id, id)
builder = ports_view.get_view_builder(req)
builder = ports_view.get_view_builder(request)
#build response with details
result = builder.build(port, True)
return dict(ports=result)
@ -77,19 +75,19 @@ class Controller(common.QuantumController):
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
def create(self, req, tenant_id, network_id):
def create(self, request, tenant_id, network_id):
""" Creates a new port for a given network """
#look for port state in request
try:
req_params = \
self._parse_request_params(req, self._port_ops_param_list)
request_params = \
self._parse_request_params(request, self._port_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
port = self.network_manager.create_port(tenant_id,
network_id,
req_params['port-state'])
builder = ports_view.get_view_builder(req)
network_id,
request_params['port-state'])
builder = ports_view.get_view_builder(request)
result = builder.build(port)
return dict(ports=result)
except exception.NetworkNotFound as e:
@ -97,18 +95,18 @@ class Controller(common.QuantumController):
except exception.StateInvalid as e:
return faults.Fault(faults.RequestedStateInvalid(e))
def update(self, req, tenant_id, network_id, id):
def update(self, request, tenant_id, network_id, id):
""" Updates the state of a port for a given network """
#look for port state in request
try:
req_params = \
self._parse_request_params(req, self._port_ops_param_list)
request_params = \
self._parse_request_params(request, self._port_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
port = self.network_manager.update_port(tenant_id,network_id, id,
req_params['port-state'])
builder = ports_view.get_view_builder(req)
port = self.network_manager.update_port(tenant_id, network_id, id,
request_params['port-state'])
builder = ports_view.get_view_builder(request)
result = builder.build(port, True)
return dict(ports=result)
except exception.NetworkNotFound as e:
@ -118,7 +116,7 @@ class Controller(common.QuantumController):
except exception.StateInvalid as e:
return faults.Fault(faults.RequestedStateInvalid(e))
def delete(self, req, tenant_id, network_id, id):
def delete(self, request, tenant_id, network_id, id):
""" Destroys the port with the given id """
#look for port state in request
try:
@ -132,7 +130,7 @@ class Controller(common.QuantumController):
except exception.PortInUse as e:
return faults.Fault(faults.PortInUse(e))
def get_resource(self,req,tenant_id, network_id, id):
def get_resource(self, request, tenant_id, network_id, id):
try:
result = self.network_manager.get_interface_details(
tenant_id, network_id, id)
@ -143,19 +141,19 @@ class Controller(common.QuantumController):
return faults.Fault(faults.PortNotFound(e))
#TODO - Complete implementation of these APIs
def attach_resource(self,req,tenant_id, network_id, id):
content_type = req.best_match_content_type()
print "Content type:%s" %content_type
def attach_resource(self, request, tenant_id, network_id, id):
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try:
req_params = \
self._parse_request_params(req,
request_params = \
self._parse_request_params(request,
self._attachment_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
try:
self.network_manager.plug_interface(tenant_id,
network_id,id,
req_params['attachment-id'])
network_id, id,
request_params['attachment-id'])
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))
@ -167,10 +165,10 @@ class Controller(common.QuantumController):
return faults.Fault(faults.AlreadyAttached(e))
#TODO - Complete implementation of these APIs
def detach_resource(self,req,tenant_id, network_id, id):
def detach_resource(self, request, tenant_id, network_id, id):
try:
self.network_manager.unplug_interface(tenant_id,
network_id,id)
network_id, id)
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))

View File

@ -13,4 +13,4 @@
# 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: Somik Behera, Nicira Networks, Inc.

View File

@ -33,17 +33,17 @@ class ViewBuilder(object):
def build(self, network_data, is_detail=False):
"""Generic method used to generate a network entity."""
print "NETWORK-DATA:%s" %network_data
print "NETWORK-DATA:%s" % network_data
if is_detail:
network = self._build_detail(network_data)
else:
network = self._build_simple(network_data)
return network
def _build_simple(self, network_data):
"""Return a simple model of a server."""
return dict(network=dict(id=network_data['net-id']))
def _build_detail(self, network_data):
"""Return a simple model of a server."""
return dict(network=dict(id=network_data['net-id'],

View File

@ -31,7 +31,7 @@ class ViewBuilder(object):
def build(self, port_data, is_detail=False):
"""Generic method used to generate a port entity."""
print "PORT-DATA:%s" %port_data
print "PORT-DATA:%s" % port_data
if is_detail:
port = self._build_detail(port_data)
else:

View File

@ -31,25 +31,29 @@ from quantum.common.wsgi import Serializer
FORMAT = "json"
CONTENT_TYPE = "application/" + FORMAT
### --- Miniclient (taking from the test directory)
### TODO(bgh): move this to a library within quantum
class MiniClient(object):
"""A base client class - derived from Glance.BaseClient"""
action_prefix = '/v0.1/tenants/{tenant_id}'
def __init__(self, host, port, use_ssl):
self.host = host
self.port = port
self.use_ssl = use_ssl
self.connection = None
def get_connection_type(self):
if self.use_ssl:
return httplib.HTTPSConnection
else:
return httplib.HTTPConnection
def do_request(self, tenant, method, action, body=None,
headers=None, params=None):
action = MiniClient.action_prefix + action
action = action.replace('{tenant_id}',tenant)
action = action.replace('{tenant_id}', tenant)
if type(params) is dict:
action += '?' + urllib.urlencode(params)
try:
@ -67,6 +71,7 @@ class MiniClient(object):
raise Exception("Server returned error: %s" % res.read())
except (socket.error, IOError), e:
raise Exception("Unable to connect to server. Got error: %s" % e)
def get_status_code(self, response):
if hasattr(response, 'status_int'):
return response.status_int
@ -76,6 +81,7 @@ class MiniClient(object):
### -- Core CLI functions
def list_nets(manager, *args):
tenant_id = args[0]
networks = manager.get_all_networks(tenant_id)
@ -85,6 +91,7 @@ def list_nets(manager, *args):
name = net["net-name"]
print "\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name)
def api_list_nets(client, *args):
tenant_id = args[0]
res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT)
@ -98,11 +105,13 @@ def api_list_nets(client, *args):
# name = n["net-name"]
# LOG.info("\tNetwork ID:%s \n\tNetwork Name:%s \n" % (id, name))
def create_net(manager, *args):
tid, name = args
new_net_id = manager.create_network(tid, name)
print "Created a new Virtual Network with ID:%s\n" % new_net_id
def api_create_net(client, *args):
tid, name = args
data = {'network': {'network-name': '%s' % name}}
@ -119,11 +128,13 @@ def api_create_net(client, *args):
return
print "Created a new Virtual Network with ID:%s\n" % nid
def delete_net(manager, *args):
tid, nid = args
manager.delete_network(tid, nid)
print "Deleted Virtual Network with ID:%s" % nid
def api_delete_net(client, *args):
tid, nid = args
res = client.do_request(tid, 'DELETE', "/networks/" + nid + "." + FORMAT)
@ -135,6 +146,7 @@ def api_delete_net(client, *args):
else:
print "Deleted Virtual Network with ID:%s" % nid
def detail_net(manager, *args):
tid, nid = args
iface_list = manager.get_network_details(tid, nid)
@ -142,6 +154,7 @@ def detail_net(manager, *args):
for iface in iface_list:
print "\tRemote interface:%s" % iface
def api_detail_net(client, *args):
tid, nid = args
res = client.do_request(tid, 'GET',
@ -163,11 +176,13 @@ def api_detail_net(client, *args):
remote_iface = rd["attachment"]
print "\tRemote interface:%s" % remote_iface
def rename_net(manager, *args):
tid, nid, name = args
manager.rename_network(tid, nid, name)
print "Renamed Virtual Network with ID:%s" % nid
def api_rename_net(client, *args):
tid, nid, name = args
data = {'network': {'network-name': '%s' % name}}
@ -178,6 +193,7 @@ def api_rename_net(client, *args):
LOG.debug(resdict)
print "Renamed Virtual Network with ID:%s" % nid
def list_ports(manager, *args):
tid, nid = args
ports = manager.get_all_ports(tid, nid)
@ -185,6 +201,7 @@ def list_ports(manager, *args):
for port in ports:
print "\tVirtual Port:%s" % port["port-id"]
def api_list_ports(client, *args):
tid, nid = args
res = client.do_request(tid, 'GET',
@ -199,12 +216,14 @@ def api_list_ports(client, *args):
for port in rd["ports"]:
print "\tVirtual Port:%s" % port["id"]
def create_port(manager, *args):
tid, nid = args
new_port = manager.create_port(tid, nid)
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port, nid)
def api_create_port(client, *args):
tid, nid = args
res = client.do_request(tid, 'POST',
@ -218,11 +237,13 @@ def api_create_port(client, *args):
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port, nid)
def delete_port(manager, *args):
tid, nid, pid = args
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
def api_delete_port(client, *args):
tid, nid, pid = args
res = client.do_request(tid, 'DELETE',
@ -234,12 +255,14 @@ def api_delete_port(client, *args):
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
def detail_port(manager, *args):
tid, nid, pid = args
port_detail = manager.get_port_details(tid, nid, pid)
print "Virtual Port:%s on Virtual Network:%s " \
"contains remote interface:%s" % (pid, nid, port_detail)
def api_detail_port(client, *args):
tid, nid, pid = args
res = client.do_request(tid, 'GET',
@ -256,12 +279,14 @@ def api_detail_port(client, *args):
print "Virtual Port:%s on Virtual Network:%s " \
"contains remote interface:%s" % (pid, nid, attachment)
def plug_iface(manager, *args):
tid, nid, pid, vid = args
manager.plug_interface(tid, nid, pid, vid)
print "Plugged remote interface:%s " \
"into Virtual Network:%s" % (vid, nid)
def api_plug_iface(client, *args):
tid, nid, pid, vid = args
data = {'port': {'attachment-id': '%s' % vid}}
@ -276,12 +301,14 @@ def api_plug_iface(client, *args):
return
print "Plugged interface \"%s\" to port:%s on network:%s" % (vid, pid, nid)
def unplug_iface(manager, *args):
def unplug_iface(manager, *args):
tid, nid, pid = args
manager.unplug_interface(tid, nid, pid)
print "UnPlugged remote interface " \
"from Virtual Port:%s Virtual Network:%s" % (pid, nid)
def api_unplug_iface(client, *args):
tid, nid, pid = args
data = {'port': {'attachment-id': ''}}
@ -296,63 +323,53 @@ def api_unplug_iface(client, *args):
return
print "Unplugged interface from port:%s on network:%s" % (pid, nid)
commands = {
"list_nets": {
"func": list_nets,
"api_func": api_list_nets,
"args": ["tenant-id"]
},
"args": ["tenant-id"]},
"create_net": {
"func": create_net,
"api_func": api_create_net,
"args": ["tenant-id", "net-name"]
},
"args": ["tenant-id", "net-name"]},
"delete_net": {
"func": delete_net,
"api_func": api_delete_net,
"args": ["tenant-id", "net-id"]
},
"args": ["tenant-id", "net-id"]},
"detail_net": {
"func": detail_net,
"api_func": api_detail_net,
"args": ["tenant-id", "net-id"]
},
"args": ["tenant-id", "net-id"]},
"rename_net": {
"func": rename_net,
"api_func": api_rename_net,
"args": ["tenant-id", "net-id", "new-name"]
},
"args": ["tenant-id", "net-id", "new-name"]},
"list_ports": {
"func": list_ports,
"api_func": api_list_ports,
"args": ["tenant-id", "net-id"]
},
"args": ["tenant-id", "net-id"]},
"create_port": {
"func": create_port,
"api_func": api_create_port,
"args": ["tenant-id", "net-id"]
},
"args": ["tenant-id", "net-id"]},
"delete_port": {
"func": delete_port,
"api_func": api_delete_port,
"args": ["tenant-id", "net-id", "port-id"]
},
"args": ["tenant-id", "net-id", "port-id"]},
"detail_port": {
"func": detail_port,
"api_func": api_detail_port,
"args": ["tenant-id", "net-id", "port-id"]
},
"args": ["tenant-id", "net-id", "port-id"]},
"plug_iface": {
"func": plug_iface,
"api_func": api_plug_iface,
"args": ["tenant-id", "net-id", "port-id", "iface-id"]
},
"args": ["tenant-id", "net-id", "port-id", "iface-id"]},
"unplug_iface": {
"func": unplug_iface,
"api_func": api_unplug_iface,
"args": ["tenant-id", "net-id", "port-id"]
},
}
"args": ["tenant-id", "net-id", "port-id"]}, }
def help():
print "\nCommands:"
@ -360,6 +377,7 @@ def help():
print " %s %s" % (k,
" ".join(["<%s>" % y for y in commands[k]["args"]]))
def build_args(cmd, cmdargs, arglist):
args = []
orig_arglist = arglist[:]
@ -381,6 +399,7 @@ def build_args(cmd, cmdargs, arglist):
return None
return args
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)
@ -420,7 +439,7 @@ if __name__ == "__main__":
LOG.debug("Executing command \"%s\" with args: %s" % (cmd, args))
if not options.load_plugin:
client = MiniClient(options.host, options.port, options.ssl)
if not commands[cmd].has_key("api_func"):
if "api_func" not in commands[cmd]:
LOG.error("API version of \"%s\" is not yet implemented" % cmd)
sys.exit(1)
commands[cmd]["api_func"](client, *args)

View File

@ -209,7 +209,7 @@ def find_config_file(options, args):
fix_path(os.path.join('~', '.quantum')),
fix_path('~'),
os.path.join(FLAGS.state_path, 'etc'),
os.path.join(FLAGS.state_path, 'etc','quantum'),
os.path.join(FLAGS.state_path, 'etc', 'quantum'),
'/etc/quantum/',
'/etc']
for cfg_dir in config_file_dirs:
@ -244,12 +244,10 @@ def load_paste_config(app_name, options, args):
problem loading the configuration file.
"""
conf_file = find_config_file(options, args)
print "Conf_file:%s" %conf_file
if not conf_file:
raise RuntimeError("Unable to locate any configuration file. "
"Cannot load application %s" % app_name)
try:
print "App_name:%s" %app_name
conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
return conf_file, conf
except Exception, e:
@ -257,7 +255,7 @@ def load_paste_config(app_name, options, args):
% (conf_file, e))
def load_paste_app(conf_file, app_name):
def load_paste_app(app_name, options, args):
"""
Builds and returns a WSGI app from a paste config file.
@ -278,16 +276,15 @@ def load_paste_app(conf_file, app_name):
: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)
conf_file, conf = load_paste_config(app_name, options, args)
try:
conf_file = os.path.abspath(conf_file)
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 app
return conf, app
def get_option(options, option, **kwargs):

View File

@ -25,7 +25,7 @@ import logging
class QuantumException(Exception):
"""Base Quantum Exception
Taken from nova.exception.NovaException
To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd
@ -45,6 +45,7 @@ class QuantumException(Exception):
def __str__(self):
return self._error_string
class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None):
@ -84,11 +85,11 @@ class NetworkNotFound(NotFound):
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. " \
@ -100,11 +101,13 @@ class PortInUse(QuantumException):
"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 Duplicate(Error):
pass

View File

@ -23,7 +23,7 @@ Global flags should be defined here, the rest are defined where they're used.
"""
import getopt
import os
import os
import string
import sys
@ -249,4 +249,3 @@ def DECLARE(name, module_string, flag_values=FLAGS):
DEFINE_string('state_path', os.path.join(os.path.dirname(__file__), '../../'),
"Top-level directory for maintaining quantum's state")

View File

@ -37,6 +37,7 @@ from exceptions import ProcessExecutionError
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.
@ -188,6 +189,7 @@ def isotime(at=None):
def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT)
def getPluginFromConfig(file="config.ini"):
Config = ConfigParser.ConfigParser()
Config.read(file)

View File

@ -40,6 +40,7 @@ from quantum.common import exceptions as exception
LOG = logging.getLogger('quantum.common.wsgi')
class WritableLogger(object):
"""A thin wrapper that responds to `write` and logs."""
@ -126,7 +127,7 @@ class Request(webob.Request):
"""
parts = self.path.rsplit('.', 1)
LOG.debug("Request parts:%s",parts)
LOG.debug("Request parts:%s", parts)
if len(parts) > 1:
format = parts[1]
if format in ['json', 'xml']:
@ -134,7 +135,7 @@ class Request(webob.Request):
ctypes = ['application/json', 'application/xml']
bm = self.accept.best_match(ctypes)
LOG.debug("BM:%s",bm)
LOG.debug("BM:%s", bm)
return bm or 'application/json'
def get_content_type(self):
@ -336,21 +337,21 @@ class Controller(object):
arg_dict = req.environ['wsgiorg.routing_args'][1]
action = arg_dict['action']
method = getattr(self, action)
LOG.debug("ARG_DICT:%s",arg_dict)
LOG.debug("Action:%s",action)
LOG.debug("Method:%s",method)
LOG.debug("ARG_DICT:%s", arg_dict)
LOG.debug("Action:%s", action)
LOG.debug("Method:%s", method)
LOG.debug("%s %s" % (req.method, req.url))
del arg_dict['controller']
del arg_dict['action']
if 'format' in arg_dict:
del arg_dict['format']
arg_dict['req'] = req
arg_dict['request'] = req
result = method(**arg_dict)
if type(result) is dict:
content_type = req.best_match_content_type()
LOG.debug("Content type:%s",content_type)
LOG.debug("Result:%s",result)
LOG.debug("Content type:%s", content_type)
LOG.debug("Result:%s", result)
default_xmlns = self.get_default_xmlns(req)
body = self._serialize(result, content_type, default_xmlns)
@ -497,7 +498,7 @@ class Serializer(object):
xmlns = metadata.get('xmlns', None)
if xmlns:
result.setAttribute('xmlns', xmlns)
LOG.debug("DATA:%s",data)
LOG.debug("DATA:%s", data)
if type(data) is list:
LOG.debug("TYPE IS LIST")
collections = metadata.get('list_collections', {})
@ -538,8 +539,7 @@ class Serializer(object):
result.appendChild(node)
else:
# Type is atom
LOG.debug("TYPE IS ATOM:%s",data)
LOG.debug("TYPE IS ATOM:%s", data)
node = doc.createTextNode(str(data))
result.appendChild(node)
return result

View File

@ -25,6 +25,7 @@ _ENGINE = None
_MAKER = None
BASE = models.BASE
def configure_db(options):
"""
Establish the database, create an engine if needed, and
@ -40,6 +41,7 @@ def configure_db(options):
pool_recycle=3600)
register_models()
def get_session(autocommit=True, expire_on_commit=False):
"""Helper method to grab session"""
global _MAKER, _ENGINE
@ -50,18 +52,21 @@ def get_session(autocommit=True, expire_on_commit=False):
expire_on_commit=expire_on_commit)
return _MAKER()
def register_models():
"""Register Models and create properties"""
global _ENGINE
assert _ENGINE
BASE.metadata.create_all(_ENGINE)
def unregister_models():
"""Unregister Models, useful clearing out data before testing"""
global _ENGINE
assert _ENGINE
BASE.metadata.drop_all(_ENGINE)
def network_create(tenant_id, name):
session = get_session()
net = None
@ -77,12 +82,14 @@ def network_create(tenant_id, name):
session.flush()
return net
def network_list(tenant_id):
session = get_session()
return session.query(models.Network).\
filter_by(tenant_id=tenant_id).\
all()
def network_get(net_id):
session = get_session()
try:
@ -92,6 +99,7 @@ def network_get(net_id):
except exc.NoResultFound:
raise Exception("No net found with id = %s" % net_id)
def network_rename(net_id, tenant_id, new_name):
session = get_session()
try:
@ -106,6 +114,7 @@ def network_rename(net_id, tenant_id, new_name):
return net
raise Exception("A network with name \"%s\" already exists" % new_name)
def network_destroy(net_id):
session = get_session()
try:
@ -118,6 +127,7 @@ def network_destroy(net_id):
except exc.NoResultFound:
raise Exception("No network found with id = %s" % net_id)
def port_create(net_id):
session = get_session()
with session.begin():
@ -126,12 +136,14 @@ def port_create(net_id):
session.flush()
return port
def port_list(net_id):
session = get_session()
return session.query(models.Port).\
filter_by(network_id=net_id).\
all()
def port_get(port_id):
session = get_session()
try:
@ -141,6 +153,7 @@ def port_get(port_id):
except exc.NoResultFound:
raise Exception("No port found with id = %s " % port_id)
def port_set_attachment(port_id, new_interface_id):
session = get_session()
ports = None
@ -157,7 +170,9 @@ def port_set_attachment(port_id, new_interface_id):
session.flush()
return port
else:
raise Exception("Port with attachment \"%s\" already exists" % (new_interface_id))
raise Exception("Port with attachment \"%s\" already exists"
% (new_interface_id))
def port_destroy(port_id):
session = get_session()
@ -170,4 +185,3 @@ def port_destroy(port_id):
return port
except exc.NoResultFound:
raise Exception("No port found with id = %s " % port_id)

View File

@ -25,12 +25,14 @@ from sqlalchemy.orm import relation
BASE = declarative_base()
class Port(BASE):
"""Represents a port on a quantum network"""
__tablename__ = 'ports'
uuid = Column(String(255), primary_key=True)
network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False)
network_id = Column(String(255), ForeignKey("networks.uuid"),
nullable=False)
interface_id = Column(String(255))
def __init__(self, network_id):
@ -38,7 +40,9 @@ class Port(BASE):
self.network_id = network_id
def __repr__(self):
return "<Port(%s,%s,%s)>" % (self.uuid, self.network_id, self.interface_id)
return "<Port(%s,%s,%s)>" % (self.uuid, self.network_id,
self.interface_id)
class Network(BASE):
"""Represents a quantum network"""
@ -56,4 +60,4 @@ class Network(BASE):
def __repr__(self):
return "<Network(%s,%s,%s)>" % \
(self.uuid,self.name,self.tenant_id)
(self.uuid, self.name, self.tenant_id)

View File

@ -18,12 +18,14 @@
"""
Quantum's Manager class is responsible for parsing a config file and instantiating the correct
plugin that concretely implement quantum_plugin_base class
Quantum's Manager class is responsible for parsing a config file and
instantiating the correct plugin that concretely implement quantum_plugin_base
class.
The caller should make sure that QuantumManager is a singleton.
"""
import gettext
import os
gettext.install('quantum', unicode=1)
import os
@ -33,16 +35,19 @@ from quantum_plugin_base import QuantumPluginBase
CONFIG_FILE = "plugins.ini"
def find_config(basepath):
for root, dirs, files in os.walk(basepath):
if CONFIG_FILE in files:
return os.path.join(root, CONFIG_FILE)
return None
class QuantumManager(object):
def __init__(self, config=None):
if config == None:
self.configuration_file = find_config(os.path.abspath(os.path.dirname(__file__)))
self.configuration_file = find_config(
os.path.abspath(os.path.dirname(__file__)))
else:
self.configuration_file = config
plugin_location = utils.getPluginFromConfig(self.configuration_file)
@ -58,4 +63,3 @@ class QuantumManager(object):
def get_manager(self):
return self.plugin

View File

@ -17,33 +17,32 @@
from quantum.common import exceptions as exc
class QuantumEchoPlugin(object):
"""
QuantumEchoPlugin is a demo plugin that doesn't
do anything but demonstrated the concept of a
concrete Quantum Plugin. Any call to this plugin
will result in just a "print" to std. out with
will result in just a "print" to std. out with
the name of the method that was called.
"""
def get_all_networks(self, tenant_id):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
the specified tenant.
"""
print("get_all_networks() called\n")
def create_network(self, tenant_id, net_name):
"""
Creates a new Virtual Network, and assigns it
a symbolic name.
"""
print("create_network() called\n")
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
@ -51,38 +50,33 @@ class QuantumEchoPlugin(object):
"""
print("delete_network() called\n")
def get_network_details(self, tenant_id, net_id):
"""
Deletes the Virtual Network belonging to a the
spec
"""
print("get_network_details() called\n")
def rename_network(self, tenant_id, net_id, new_name):
"""
Updates the symbolic name belonging to a particular
Virtual Network.
"""
print("rename_network() called\n")
def get_all_ports(self, tenant_id, net_id):
"""
Retrieves all port identifiers belonging to the
specified Virtual Network.
"""
print("get_all_ports() called\n")
def create_port(self, tenant_id, net_id):
"""
Creates a port on the specified Virtual Network.
"""
print("create_port() called\n")
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
@ -97,24 +91,21 @@ class QuantumEchoPlugin(object):
Updates the state of a port on the specified Virtual Network.
"""
print("update_port() called\n")
def get_port_details(self, tenant_id, net_id, port_id):
"""
This method allows the user to retrieve a remote interface
that is attached to this particular port.
"""
print("get_port_details() called\n")
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
"""
Attaches a remote interface to the specified port on the
specified Virtual Network.
"""
print("plug_interface() called\n")
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
@ -130,18 +121,17 @@ class DummyDataPlugin(object):
hard-coded data structures to aid in quantum
client/cli development
"""
def get_all_networks(self, tenant_id):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
the specified tenant.
"""
nets = {"001": "lNet1", "002": "lNet2" , "003": "lNet3"}
nets = {"001": "lNet1", "002": "lNet2", "003": "lNet3"}
print("get_all_networks() called\n")
return nets
def create_network(self, tenant_id, net_name):
"""
Creates a new Virtual Network, and assigns it
@ -150,8 +140,7 @@ class DummyDataPlugin(object):
print("create_network() called\n")
# return network_id of the created network
return 101
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
@ -159,7 +148,6 @@ class DummyDataPlugin(object):
"""
print("delete_network() called\n")
def get_network_details(self, tenant_id, net_id):
"""
retrieved a list of all the remote vifs that
@ -168,16 +156,14 @@ class DummyDataPlugin(object):
print("get_network_details() called\n")
vifs_on_net = ["/tenant1/networks/net_id/portid/vif2.0"]
return vifs_on_net
def rename_network(self, tenant_id, net_id, new_name):
"""
Updates the symbolic name belonging to a particular
Virtual Network.
"""
print("rename_network() called\n")
def get_all_ports(self, tenant_id, net_id):
"""
Retrieves all port identifiers belonging to the
@ -186,8 +172,7 @@ class DummyDataPlugin(object):
print("get_all_ports() called\n")
port_ids_on_net = ["2", "3", "4"]
return port_ids_on_net
def create_port(self, tenant_id, net_id):
"""
Creates a port on the specified Virtual Network.
@ -201,8 +186,7 @@ class DummyDataPlugin(object):
Updates the state of a port on the specified Virtual Network.
"""
print("update_port() called\n")
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
@ -211,8 +195,7 @@ class DummyDataPlugin(object):
is deleted.
"""
print("delete_port() called\n")
def get_port_details(self, tenant_id, net_id, port_id):
"""
This method allows the user to retrieve a remote interface
@ -221,24 +204,22 @@ class DummyDataPlugin(object):
print("get_port_details() called\n")
#returns the remote interface UUID
return "/tenant1/networks/net_id/portid/vif2.1"
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
"""
Attaches a remote interface to the specified port on the
specified Virtual Network.
"""
print("plug_interface() called\n")
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
specified Virtual Network.
"""
print("unplug_interface() called\n")
class FakePlugin(object):
"""
FakePlugin is a demo plugin that provides
@ -248,72 +229,66 @@ class FakePlugin(object):
#static data for networks and ports
_port_dict_1 = {
1 : {'port-id': 1,
1: {'port-id': 1,
'port-state': 'DOWN',
'attachment': None},
2 : {'port-id': 2,
'port-state':'UP',
'attachment': None}
}
2: {'port-id': 2,
'port-state': 'UP',
'attachment': None}}
_port_dict_2 = {
1 : {'port-id': 1,
1: {'port-id': 1,
'port-state': 'UP',
'attachment': 'SomeFormOfVIFID'},
2 : {'port-id': 2,
'port-state':'DOWN',
'attachment': None}
}
_networks={'001':
2: {'port-id': 2,
'port-state': 'DOWN',
'attachment': None}}
_networks = {'001':
{
'net-id':'001',
'net-name':'pippotest',
'net-ports': _port_dict_1
},
'net-id': '001',
'net-name': 'pippotest',
'net-ports': _port_dict_1},
'002':
{
'net-id':'002',
'net-name':'cicciotest',
'net-ports': _port_dict_2
}}
'net-id': '002',
'net-name': 'cicciotest',
'net-ports': _port_dict_2}}
def __init__(self):
FakePlugin._net_counter=len(FakePlugin._networks)
FakePlugin._net_counter = len(FakePlugin._networks)
def _get_network(self, tenant_id, network_id):
network = FakePlugin._networks.get(network_id)
if not network:
raise exc.NetworkNotFound(net_id=network_id)
return network
def _get_port(self, tenant_id, network_id, port_id):
net = self._get_network(tenant_id, network_id)
port = net['net-ports'].get(int(port_id))
if not port:
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
return port
def _validate_port_state(self, port_state):
if port_state.upper() not in ('UP','DOWN'):
if port_state.upper() not in ('UP', 'DOWN'):
raise exc.StateInvalid(port_state=port_state)
return True
def _validate_attachment(self, tenant_id, network_id, port_id,
remote_interface_id):
network = self._get_network(tenant_id, network_id)
for port in network['net-ports'].values():
if port['attachment'] == remote_interface_id:
raise exc.AlreadyAttached(net_id = network_id,
port_id = port_id,
att_id = port['attachment'],
att_port_id = port['port-id'])
raise exc.AlreadyAttached(net_id=network_id,
port_id=port_id,
att_id=port['attachment'],
att_port_id=port['port-id'])
def get_all_networks(self, tenant_id):
"""
Returns a dictionary containing all
<network_uuid, network_name> for
the specified tenant.
the specified tenant.
"""
print("get_all_networks() called\n")
return FakePlugin._networks.values()
@ -333,16 +308,16 @@ class FakePlugin(object):
"""
print("create_network() called\n")
FakePlugin._net_counter += 1
new_net_id=("0" * (3 - len(str(FakePlugin._net_counter)))) + \
new_net_id = ("0" * (3 - len(str(FakePlugin._net_counter)))) + \
str(FakePlugin._net_counter)
print new_net_id
new_net_dict={'net-id':new_net_id,
'net-name':net_name,
new_net_dict = {'net-id': new_net_id,
'net-name': net_name,
'net-ports': {}}
FakePlugin._networks[new_net_id]=new_net_dict
FakePlugin._networks[new_net_id] = new_net_dict
# return network_id of the created network
return new_net_dict
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
@ -360,7 +335,7 @@ class FakePlugin(object):
return net
# Network not found
raise exc.NetworkNotFound(net_id=net_id)
def rename_network(self, tenant_id, net_id, new_name):
"""
Updates the symbolic name belonging to a particular
@ -368,7 +343,7 @@ class FakePlugin(object):
"""
print("rename_network() called\n")
net = self._get_network(tenant_id, net_id)
net['net-name']=new_name
net['net-name'] = new_name
return net
def get_all_ports(self, tenant_id, net_id):
@ -388,7 +363,7 @@ class FakePlugin(object):
"""
print("get_port_details() called\n")
return self._get_port(tenant_id, net_id, port_id)
def create_port(self, tenant_id, net_id, port_state=None):
"""
Creates a port on the specified Virtual Network.
@ -396,15 +371,15 @@ class FakePlugin(object):
print("create_port() called\n")
net = self._get_network(tenant_id, net_id)
# check port state
# TODO(salvatore-orlando): Validate port state in API?
# TODO(salvatore-orlando): Validate port state in API?
self._validate_port_state(port_state)
ports = net['net-ports']
new_port_id = max(ports.keys())+1
new_port_dict = {'port-id':new_port_id,
new_port_id = max(ports.keys()) + 1
new_port_dict = {'port-id': new_port_id,
'port-state': port_state,
'attachment': None}
ports[new_port_id] = new_port_dict
return new_port_dict
return new_port_dict
def update_port(self, tenant_id, net_id, port_id, port_state):
"""
@ -414,8 +389,8 @@ class FakePlugin(object):
port = self._get_port(tenant_id, net_id, port_id)
self._validate_port_state(port_state)
port['port-state'] = port_state
return port
return port
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
@ -427,14 +402,13 @@ class FakePlugin(object):
net = self._get_network(tenant_id, net_id)
port = self._get_port(tenant_id, net_id, port_id)
if port['attachment']:
raise exc.PortInUse(net_id=net_id,port_id=port_id,
raise exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['attachment'])
try:
net['net-ports'].pop(int(port_id))
except KeyError:
except KeyError:
raise exc.PortNotFound(net_id=net_id, port_id=port_id)
def plug_interface(self, tenant_id, net_id, port_id, remote_interface_id):
"""
Attaches a remote interface to the specified port on the
@ -446,10 +420,10 @@ class FakePlugin(object):
remote_interface_id)
port = self._get_port(tenant_id, net_id, port_id)
if port['attachment']:
raise exc.PortInUse(net_id=net_id,port_id=port_id,
raise exc.PortInUse(net_id=net_id, port_id=port_id,
att_id=port['attachment'])
port['attachment'] = remote_interface_id
def unplug_interface(self, tenant_id, net_id, port_id):
"""
Detaches a remote interface from the specified port on the
@ -460,5 +434,3 @@ class FakePlugin(object):
# TODO(salvatore-orlando):
# Should unplug on port without attachment raise an Error?
port['attachment'] = None

View File

@ -13,4 +13,4 @@
# 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: Somik Behera, Nicira Networks, Inc.

View File

@ -28,6 +28,7 @@ import time
from optparse import OptionParser
from subprocess import *
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
# attributes set).
class VifPort:
@ -37,11 +38,13 @@ class VifPort:
self.vif_id = vif_id
self.vif_mac = vif_mac
self.switch = switch
def __str__(self):
return "iface-id=" + self.vif_id + ", vif_mac=" + \
self.vif_mac + ", port_name=" + self.port_name + \
", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name
class OVSBridge:
def __init__(self, br_name):
self.br_name = br_name
@ -51,27 +54,27 @@ class OVSBridge:
return Popen(args, stdout=PIPE).communicate()[0]
def run_vsctl(self, args):
full_args = ["ovs-vsctl" ] + args
full_args = ["ovs-vsctl"] + args
return self.run_cmd(full_args)
def reset_bridge(self):
self.run_vsctl([ "--" , "--if-exists", "del-br", self.br_name])
self.run_vsctl(["--", "--if-exists", "del-br", self.br_name])
self.run_vsctl(["add-br", self.br_name])
def delete_port(self, port_name):
self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name,
self.run_vsctl(["--", "--if-exists", "del-port", self.br_name,
port_name])
def set_db_attribute(self, table_name, record, column, value):
args = [ "set", table_name, record, "%s=%s" % (column,value) ]
args = ["set", table_name, record, "%s=%s" % (column, value)]
self.run_vsctl(args)
def clear_db_attribute(self, table_name,record, column):
args = [ "clear", table_name, record, column ]
def clear_db_attribute(self, table_name, record, column):
args = ["clear", table_name, record, column]
self.run_vsctl(args)
def run_ofctl(self, cmd, args):
full_args = ["ovs-ofctl", cmd, self.br_name ] + args
full_args = ["ovs-ofctl", cmd, self.br_name] + args
return self.run_cmd(full_args)
def remove_all_flows(self):
@ -80,7 +83,7 @@ class OVSBridge:
def get_port_ofport(self, port_name):
return self.db_get_val("Interface", port_name, "ofport")
def add_flow(self,**dict):
def add_flow(self, **dict):
if "actions" not in dict:
raise Exception("must specify one or more actions")
if "priority" not in dict:
@ -90,9 +93,9 @@ class OVSBridge:
if "match" in dict:
flow_str += "," + dict["match"]
flow_str += ",actions=%s" % (dict["actions"])
self.run_ofctl("add-flow", [ flow_str ] )
self.run_ofctl("add-flow", [flow_str])
def delete_flows(self,**dict):
def delete_flows(self, **dict):
all_args = []
if "priority" in dict:
all_args.append("priority=%s" % dict["priority"])
@ -101,14 +104,14 @@ class OVSBridge:
if "actions" in dict:
all_args.append("actions=%s" % (dict["actions"]))
flow_str = ",".join(all_args)
self.run_ofctl("del-flows", [ flow_str ] )
self.run_ofctl("del-flows", [flow_str])
def db_get_map(self, table, record, column):
str = self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
str = self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
return self.db_str_to_map(str)
def db_get_val(self, table, record, column):
return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r")
return self.run_vsctl(["get", table, record, column]).rstrip("\n\r")
def db_str_to_map(self, full_str):
list = full_str.strip("{}").split(", ")
@ -121,7 +124,7 @@ class OVSBridge:
return ret
def get_port_name_list(self):
res = self.run_vsctl([ "list-ports", self.br_name])
res = self.run_vsctl(["list-ports", self.br_name])
return res.split("\n")[0:-1]
def get_port_stats(self, port_name):
@ -132,47 +135,53 @@ class OVSBridge:
edge_ports = []
port_names = self.get_port_name_list()
for name in port_names:
external_ids = self.db_get_map("Interface",name,"external_ids")
external_ids = self.db_get_map("Interface", name, "external_ids")
if "iface-id" in external_ids and "attached-mac" in external_ids:
ofport = self.db_get_val("Interface",name,"ofport")
ofport = self.db_get_val("Interface", name, "ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
edge_ports.append(p)
else:
# iface-id might not be set. See if we can figure it out and
# set it here.
external_ids = self.db_get_map("Interface",name,"external_ids")
external_ids = self.db_get_map("Interface", name,
"external_ids")
if "attached-mac" not in external_ids:
continue
vif_uuid = external_ids.get("xs-vif-uuid", "")
if len(vif_uuid) == 0:
continue
LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid)
res = os.popen("xe vif-param-get param-name=other-config uuid=%s | grep nicira-iface-id | awk '{print $2}'" % vif_uuid).readline()
res = os.popen("xe vif-param-get param-name=other-config "
"uuid=%s | grep nicira-iface-id | "
"awk '{print $2}'"
% vif_uuid).readline()
res = res.strip()
if len(res) == 0:
continue
external_ids["iface-id"] = res
LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res))
LOG.info("Setting interface \"%s\" iface-id to \"%s\""
% (name, res))
self.set_db_attribute("Interface", name,
"external-ids:iface-id", res)
ofport = self.db_get_val("Interface",name,"ofport")
"external-ids:iface-id", res)
ofport = self.db_get_val("Interface", name, "ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
external_ids["attached-mac"], self)
edge_ports.append(p)
return edge_ports
class OVSNaaSPlugin:
def __init__(self, integ_br):
self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name,"tag",
self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(vlan_id))
def port_unbound(self, port, still_exists):
if still_exists:
self.int_br.clear_db_attribute("Port", port.port_name,"tag")
self.int_br.clear_db_attribute("Port", port.port_name, "tag")
def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br)
@ -182,7 +191,8 @@ class OVSNaaSPlugin:
# switch all other traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
# FIXME send broadcast everywhere, regardless of tenant
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff", actions="normal")
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff",
# actions="normal")
def daemon_loop(self, conn):
self.local_vlan_map = {}
@ -216,9 +226,9 @@ class OVSNaaSPlugin:
else:
# no binding, put him on the 'dead vlan'
self.int_br.set_db_attribute("Port", p.port_name, "tag",
"4095")
old_b = old_local_bindings.get(p.vif_id,None)
new_b = new_local_bindings.get(p.vif_id,None)
"4095")
old_b = old_local_bindings.get(p.vif_id, None)
new_b = new_local_bindings.get(p.vif_id, None)
if old_b != new_b:
if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s"

View File

@ -24,6 +24,7 @@ import quantum.db.api as db
import quantum.db.models as models
import ovs_models
def get_vlans():
session = db.get_session()
try:
@ -33,9 +34,10 @@ def get_vlans():
return []
res = []
for x in bindings:
res.append((x.vlan_id, x.network_id))
res.append((x.vlan_id, x.network_id))
return res
def add_vlan_binding(vlanid, netid):
session = db.get_session()
binding = ovs_models.VlanBinding(vlanid, netid)
@ -43,6 +45,7 @@ def add_vlan_binding(vlanid, netid):
session.flush()
return binding.vlan_id
def remove_vlan_binding(netid):
session = db.get_session()
try:
@ -54,6 +57,7 @@ def remove_vlan_binding(netid):
pass
session.flush()
def update_network_binding(netid, ifaceid):
session = db.get_session()
# Add to or delete from the bindings table

View File

@ -23,9 +23,9 @@ import uuid
from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation
from quantum.db.models import BASE
class NetworkBinding(BASE):
"""Represents a binding of network_id, vif_id"""
__tablename__ = 'network_bindings'
@ -42,6 +42,7 @@ class NetworkBinding(BASE):
return "<NetworkBinding(%s,%s)>" % \
(self.network_id, self.vif_id)
class VlanBinding(BASE):
"""Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings'

View File

@ -29,24 +29,29 @@ from optparse import OptionParser
import quantum.db.api as db
import ovs_db
CONF_FILE="ovs_quantum_plugin.ini"
CONF_FILE = "ovs_quantum_plugin.ini"
LOG.basicConfig(level=LOG.WARN)
LOG.getLogger("ovs_quantum_plugin")
def find_config(basepath):
for root, dirs, files in os.walk(basepath):
if CONF_FILE in files:
return os.path.join(root, CONF_FILE)
return None
class VlanMap(object):
vlans = {}
def __init__(self):
for x in xrange(2, 4094):
self.vlans[x] = None
def set(self, vlan_id, network_id):
self.vlans[vlan_id] = network_id
def acquire(self, network_id):
for x in xrange(2, 4094):
if self.vlans[x] == None:
@ -54,8 +59,10 @@ class VlanMap(object):
# LOG.debug("VlanMap::acquire %s -> %s" % (x, network_id))
return x
raise Exception("No free vlans..")
def get(self, vlan_id):
return self.vlans[vlan_id]
def release(self, network_id):
for x in self.vlans.keys():
if self.vlans[x] == network_id:
@ -64,14 +71,17 @@ class VlanMap(object):
return
LOG.error("No vlan found with network \"%s\"" % network_id)
class OVSQuantumPlugin(QuantumPluginBase):
def __init__(self, configfile=None):
config = ConfigParser.ConfigParser()
if configfile == None:
if os.path.exists(CONF_FILE):
configfile = CONF_FILE
else:
configfile = find_config(os.path.abspath(os.path.dirname(__file__)))
configfile = find_config(os.path.abspath(
os.path.dirname(__file__)))
if configfile == None:
raise Exception("Configuration file \"%s\" doesn't exist" %
(configfile))
@ -93,7 +103,8 @@ class OVSQuantumPlugin(QuantumPluginBase):
vlans = ovs_db.get_vlans()
for x in vlans:
vlan_id, network_id = x
# LOG.debug("Adding already populated vlan %s -> %s" % (vlan_id, network_id))
# LOG.debug("Adding already populated vlan %s -> %s"
# % (vlan_id, network_id))
self.vmap.set(vlan_id, network_id)
def get_all_networks(self, tenant_id):
@ -109,8 +120,8 @@ class OVSQuantumPlugin(QuantumPluginBase):
def create_network(self, tenant_id, net_name):
d = {}
try:
res = db.network_create(tenant_id, net_name)
LOG.debug("Created newtork: %s" % res)
res = db.network_create(tenant_id, net_name)
LOG.debug("Created newtork: %s" % res)
except Exception, e:
LOG.error("Error: %s" % str(e))
return d
@ -199,21 +210,28 @@ class OVSQuantumPlugin(QuantumPluginBase):
res = db.port_get(port_id)
return res.interface_id
class VlanMapTest(unittest.TestCase):
def setUp(self):
self.vmap = VlanMap()
def tearDown(self):
pass
def testAddVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.assertTrue(vlan_id == 2)
def testReleaseVlan(self):
vlan_id = self.vmap.acquire("foobar")
self.vmap.release("foobar")
self.assertTrue(self.vmap.get(vlan_id) == None)
# TODO(bgh): Make the tests use a sqlite database instead of mysql
class OVSPluginTest(unittest.TestCase):
def setUp(self):
self.quantum = OVSQuantumPlugin()
self.tenant_id = "testtenant"
@ -312,6 +330,7 @@ class OVSPluginTest(unittest.TestCase):
self.quantum.delete_port(self.tenant_id, id, p["port-id"])
self.quantum.delete_network(self.tenant_id, id)
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)

View File

@ -102,7 +102,9 @@ def serve_wsgi(cls, conf=None, options=None, args=None):
def _run_wsgi(app_name, paste_conf, paste_config_file):
LOG.info(_('Using paste.deploy config at: %s'), paste_config_file)
app = config.load_paste_app(paste_config_file, app_name)
conf, app = config.load_paste_app(app_name,
{'config_file': paste_config_file},
None)
if not app:
LOG.error(_('No known API applications configured in %s.'),
paste_config_file)

293
run_tests.py Normal file
View File

@ -0,0 +1,293 @@
#!/usr/bin/env python
# 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.
"""Unittest runner for quantum
To run all test::
python run_tests.py
To run all unit tests::
python run_tests.py unit
To run all functional tests::
python run_tests.py functional
To run a single unit test::
python run_tests.py unit.test_stores:TestSwiftBackend.test_get
To run a single functional test::
python run_tests.py functional.test_service:TestController.test_create
To run a single unit test module::
python run_tests.py unit.test_stores
To run a single functional test module::
python run_tests.py functional.test_stores
"""
import gettext
import os
import unittest
import sys
from nose import config
from nose import result
from nose import core
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__
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
self.stream.write(
' %s' % str(test.test._testMethodName).ljust(60))
self.stream.flush()
class QuantumTestRunner(core.TextTestRunner):
def _makeResult(self):
return QuantumTestResult(self.stream,
self.descriptions,
self.verbosity,
self.config)
if __name__ == '__main__':
working_dir = os.path.abspath("tests")
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
workingDir=working_dir)
runner = QuantumTestRunner(stream=c.stream,
verbosity=c.verbosity,
config=c)
sys.exit(not core.run(config=c, testRunner=runner))

83
run_tests.sh Executable file
View File

@ -0,0 +1,83 @@
#!/bin/bash
function usage {
echo "Usage: $0 [OPTION]..."
echo "Run Melange's test suite(s)"
echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present"
echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"
echo " -f, --force Force a clean re-build of the virtual environment. Useful when dependencies have been added."
echo " -h, --help Print this usage message"
echo ""
echo "Note: with no options specified, the script will try to run the tests in a virtual environment,"
echo " If no virtualenv is found, the script will ask if you would like to create one. If you "
echo " prefer to run tests NOT in a virtual environment, simply pass the -N option."
exit
}
function process_option {
case "$1" in
-h|--help) usage;;
-V|--virtual-env) let always_venv=1; let never_venv=0;;
-N|--no-virtual-env) let always_venv=0; let never_venv=1;;
-f|--force) let force=1;;
*) noseargs="$noseargs $1"
esac
}
venv=.quantum-venv
with_venv=tools/with_venv.sh
always_venv=0
never_venv=0
force=0
noseargs=
wrapper=""
for arg in "$@"; do
process_option $arg
done
function run_tests {
# Just run the test suites in current environment
${wrapper} rm -f tests.sqlite
${wrapper} $NOSETESTS 2> run_tests.err.log
}
NOSETESTS="python run_tests.py $noseargs"
if [ $never_venv -eq 0 ]
then
# Remove the virtual environment if --force used
if [ $force -eq 1 ]; then
echo "Cleaning virtualenv..."
rm -rf ${venv}
fi
if [ -e ${venv} ]; then
wrapper="${with_venv}"
else
if [ $always_venv -eq 1 ]; then
# Automatically install the virtualenv
python tools/install_venv.py
wrapper="${with_venv}"
else
echo -e "No virtual environment found...create one? (Y/n) \c"
read use_ve
if [ "x$use_ve" = "xY" -o "x$use_ve" = "x" -o "x$use_ve" = "xy" ]; then
# Install the virtualenv and run the test suite in it
python tools/install_venv.py
wrapper=${with_venv}
fi
fi
fi
fi
# FIXME(sirp): bzr version-info is not currently pep-8. This was fixed with
# lp701898 [1], however, until that version of bzr becomes standard, I'm just
# excluding the vcsversion.py file
#
# [1] https://bugs.launchpad.net/bzr/+bug/701898
#
PEP8_EXCLUDE=vcsversion.py
PEP8_OPTIONS="--exclude=$PEP8_EXCLUDE --repeat --show-source"
PEP8_INCLUDE="bin/* quantum tests tools run_tests.py"
run_tests && pep8 $PEP8_OPTIONS $PEP8_INCLUDE || exit 1

View File

@ -1,98 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 httplib
import socket
import urllib
class MiniClient(object):
"""A base client class - derived from Glance.BaseClient"""
action_prefix = '/v0.1/tenants/{tenant_id}'
def __init__(self, host, port, use_ssl):
"""
Creates a new client to some service.
:param host: The host where service resides
:param port: The port where service resides
:param use_ssl: Should we use HTTPS?
"""
self.host = host
self.port = port
self.use_ssl = use_ssl
self.connection = None
def get_connection_type(self):
"""
Returns the proper connection type
"""
if self.use_ssl:
return httplib.HTTPSConnection
else:
return httplib.HTTPConnection
def do_request(self, tenant, method, action, body=None,
headers=None, params=None):
"""
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
HTTP status code is not 2xx
:param method: HTTP method ("GET", "POST", "PUT", etc...)
:param body: string of data to send, or None (default)
:param headers: mapping of key/value pairs to add as headers
:param params: dictionary of key/value pairs to add to append
to action
"""
action = MiniClient.action_prefix + action
action = action.replace('{tenant_id}',tenant)
if type(params) is dict:
action += '?' + urllib.urlencode(params)
try:
connection_type = self.get_connection_type()
headers = headers or {}
# Open connection and send request
c = connection_type(self.host, self.port)
c.request(method, action, body, headers)
res = c.getresponse()
status_code = self.get_status_code(res)
if status_code in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
httplib.NO_CONTENT):
return res
else:
raise Exception("Server returned error: %s" % res.read())
except (socket.error, IOError), e:
raise Exception("Unable to connect to "
"server. Got error: %s" % e)
def get_status_code(self, response):
"""
Returns the integer status code from the response, which
can be either a Webob.Response (used in testing) or httplib.Response
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
return response.status

View File

@ -1,150 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# 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 gettext
gettext.install('quantum', unicode=1)
from miniclient import MiniClient
from quantum.common.wsgi import Serializer
HOST = '127.0.0.1'
PORT = 9696
USE_SSL = False
TENANT_ID = 'totore'
test_network_data = \
{'network': {'network-name': 'test' }}
def print_response(res):
content = res.read()
print "Status: %s" %res.status
print "Content: %s" %content
return content
def test_list_networks_and_ports(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST LIST NETWORKS AND PORTS -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "--> Step 2 - Details for Network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "--> Step 3 - Ports for Network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "--> Step 4 - Details for Port 1"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports/1." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_create_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST CREATE NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - Create Network"
content_type = "application/" + format
body = Serializer().serialize(test_network_data, content_type)
res = client.do_request(TENANT_ID,'POST', "/networks." + format, body=body)
print_response(res)
print "--> Step 2 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_rename_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
content_type = "application/" + format
print "TEST RENAME NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - Retrieve network"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "--> Step 2 - Rename network to 'test_renamed'"
test_network_data['network']['network-name'] = 'test_renamed'
body = Serializer().serialize(test_network_data, content_type)
res = client.do_request(TENANT_ID,'PUT', "/networks/001." + format, body=body)
print_response(res)
print "--> Step 2 - Retrieve network (again)"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_delete_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
content_type = "application/" + format
print "TEST DELETE NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
content = print_response(res)
network_data = Serializer().deserialize(content, content_type)
print network_data
net_id = network_data['networks'][0]['id']
print "--> Step 2 - Delete network %s" %net_id
res = client.do_request(TENANT_ID,'DELETE',
"/networks/" + net_id + "." + format)
print_response(res)
print "--> Step 3 - List All Networks (Again)"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_create_port(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST CREATE PORT -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List Ports for network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "--> Step 2 - Create Port for network 001"
res = client.do_request(TENANT_ID,'POST', "/networks/001/ports." + format)
print_response(res)
print "--> Step 3 - List Ports for network 001 (again)"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def main():
test_list_networks_and_ports('xml')
test_list_networks_and_ports('json')
test_create_network('xml')
test_create_network('json')
test_rename_network('xml')
test_rename_network('json')
# NOTE: XML deserializer does not work properly
# disabling XML test - this is NOT a server-side issue
#test_delete_network('xml')
test_delete_network('json')
test_create_port('xml')
test_create_port('json')
pass
# Standard boilerplate to call the main() function.
if __name__ == '__main__':
main()

View File

@ -19,6 +19,7 @@ import httplib
import socket
import urllib
class MiniClient(object):
"""A base client class - derived from Glance.BaseClient"""
@ -50,7 +51,7 @@ class MiniClient(object):
def do_request(self, tenant, method, action, body=None,
headers=None, params=None):
"""
Connects to the server and issues a request.
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
HTTP status code is not 2xx
@ -62,14 +63,14 @@ class MiniClient(object):
"""
action = MiniClient.action_prefix + action
action = action.replace('{tenant_id}',tenant)
action = action.replace('{tenant_id}', tenant)
if type(params) is dict:
action += '?' + urllib.urlencode(params)
try:
connection_type = self.get_connection_type()
headers = headers or {}
# Open connection and send request
c = connection_type(self.host, self.port)
c.request(method, action, body, headers)
@ -95,4 +96,4 @@ class MiniClient(object):
if hasattr(response, 'status_int'):
return response.status_int
else:
return response.status
return response.status

View File

@ -34,16 +34,18 @@ TENANT_ID = 'totore'
FORMAT = "json"
test_network1_data = \
{'network': {'network-name': 'test1' }}
{'network': {'network-name': 'test1'}}
test_network2_data = \
{'network': {'network-name': 'test2' }}
{'network': {'network-name': 'test2'}}
def print_response(res):
content = res.read()
print "Status: %s" %res.status
print "Content: %s" %content
print "Status: %s" % res.status
print "Content: %s" % content
return content
class QuantumTest(unittest.TestCase):
def setUp(self):
self.client = MiniClient(HOST, PORT, USE_SSL)
@ -58,7 +60,7 @@ class QuantumTest(unittest.TestCase):
def test_listNetworks(self):
self.create_network(test_network1_data)
self.create_network(test_network2_data)
res = self.client.do_request(TENANT_ID,'GET', "/networks." + FORMAT)
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
self.assertEqual(res.status, 200, "bad response: %s" % res.read())
def test_createNetwork(self):
@ -111,8 +113,9 @@ class QuantumTest(unittest.TestCase):
resdict = simplejson.loads(res.read())
self.assertTrue(resdict["networks"]["network"]["id"] == net_id,
"Network_rename: renamed network has a different uuid")
self.assertTrue(resdict["networks"]["network"]["name"] == "test_renamed",
"Network rename didn't take effect")
self.assertTrue(
resdict["networks"]["network"]["name"] == "test_renamed",
"Network rename didn't take effect")
def delete_networks(self):
# Remove all the networks created on the tenant

32
tests/unit/__init__.py Normal file
View File

@ -0,0 +1,32 @@
# 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.
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks
import __builtin__
import unittest
setattr(__builtin__, '_', lambda x: x)
class BaseTest(unittest.TestCase):
def setUp(self):
pass
def setUp():
pass

137
tools/install_venv.py Normal file
View File

@ -0,0 +1,137 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 United States Government as represented by the
# Administrator of the National Aeronautics and Space Administration.
# All Rights Reserved.
#
# Copyright 2010 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.
"""
Installation script for Quantum's development virtualenv
"""
import os
import subprocess
import sys
ROOT = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
VENV = os.path.join(ROOT, '.quantum-venv')
PIP_REQUIRES = os.path.join(ROOT, 'tools', 'pip-requires')
def die(message, *args):
print >> sys.stderr, message % args
sys.exit(1)
def run_command(cmd, redirect_output=True, check_exit_code=True):
"""
Runs a command in an out-of-process shell, returning the
output of that command. Working directory is ROOT.
"""
if redirect_output:
stdout = subprocess.PIPE
else:
stdout = None
proc = subprocess.Popen(cmd, cwd=ROOT, stdout=stdout)
output = proc.communicate()[0]
if check_exit_code and proc.returncode != 0:
die('Command "%s" failed.\n%s', ' '.join(cmd), output)
return output
HAS_EASY_INSTALL = bool(run_command(['which', 'easy_install'],
check_exit_code=False).strip())
HAS_VIRTUALENV = bool(run_command(['which', 'virtualenv'],
check_exit_code=False).strip())
def check_dependencies():
"""Make sure virtualenv is in the path."""
if not HAS_VIRTUALENV:
print 'not found.'
# Try installing it via easy_install...
if HAS_EASY_INSTALL:
print 'Installing virtualenv via easy_install...',
if not run_command(['which', 'easy_install']):
die('ERROR: virtualenv not found.\n\n'
'Quantum requires virtualenv, please install'
' it using your favorite package management tool')
print 'done.'
print 'done.'
def create_virtualenv(venv=VENV):
"""Creates the virtual environment and installs PIP only into the
virtual environment
"""
print 'Creating venv...',
run_command(['virtualenv', '-q', '--no-site-packages', VENV])
print 'done.'
print 'Installing pip in virtualenv...',
if not run_command(['tools/with_venv.sh', 'easy_install', 'pip']).strip():
die("Failed to install pip.")
print 'done.'
def install_dependencies(venv=VENV):
print 'Installing dependencies with pip (this can take a while)...'
# Install greenlet by hand - just listing it in the requires file does not
# get it in stalled in the right order
venv_tool = 'tools/with_venv.sh'
run_command([venv_tool, 'pip', 'install', '-E', venv, '-r', PIP_REQUIRES],
redirect_output=False)
# Tell the virtual env how to "import quantum"
pthfile = os.path.join(venv, "lib", "python2.6", "site-packages",
"quantum.pth")
f = open(pthfile, 'w')
f.write("%s\n" % ROOT)
def print_help():
help = """
Quantum development environment setup is complete.
Quantum development uses virtualenv to track and manage Python dependencies
while in development and testing.
To activate the Quantum virtualenv for the extent of your current shell
session you can run:
$ source .quantum-venv/bin/activate
Or, if you prefer, you can run commands in the virtualenv on a case by case
basis by running:
$ tools/with_venv.sh <your command>
Also, make test will automatically use the virtualenv.
"""
print help
def main(argv):
check_dependencies()
create_virtualenv()
install_dependencies()
print_help()
if __name__ == '__main__':
main(sys.argv)

10
tools/pip-requires Normal file
View File

@ -0,0 +1,10 @@
eventlet>=0.9.12
nose
Paste
PasteDeploy
pep8==0.5.0
python-gflags
routes
simplejson
webob
webtest

21
tools/with_venv.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# 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.
TOOLS=`dirname $0`
VENV=$TOOLS/../.quantum-venv
source $VENV/bin/activate && $@