merge and pep8 cleanup

This commit is contained in:
Dan Wendlandt 2011-06-21 17:40:05 -07:00
commit 5072a9563e
41 changed files with 1050 additions and 658 deletions

58
README
View File

@ -1,25 +1,29 @@
# -- Welcome!
You have come across a cloud computing network fabric controller. It has identified
itself as "Quantum." It aims to tame your (cloud) networking!
You have come across a cloud computing network fabric controller. It has
identified itself as "Quantum." It aims to tame your (cloud) networking!
# -- Basics:
1) Quantum REST API: Quantum supports a REST-ful programmatic interface to manage your
cloud networking fabric.
1) Quantum REST API: Quantum supports a REST-ful programmatic interface to
manage your cloud networking fabric.
2) Quantum Plugins: Quantum sports a plug-able architecture that allows Quantum's REST API
to be backed by various entities that can create a cloud-class virtual networking fabric.
The advantages of this plug-able architecture is two-folds:
2) Quantum Plugins: Quantum sports a plug-able architecture that allows
Quantum's REST API to be backed by various entities that can create a
cloud-class virtual networking fabric. The advantages of this plug-able
architecture is two-folds:
a) Allows for ANY open-source project or commercial vendor to write a Quantum plug-in.
a) Allows for ANY open-source project or commercial vendor to write a
Quantum plug-in.
b) Allows Quantum users to not be tied down to a single Quantum implementation and
enables them to switch out a plug-in by simple editing a config file - plugins.ini
b) Allows Quantum users to not be tied down to a single Quantum
implementation and enables them to switch out a plug-in by simple editing a
config file - plugins.ini
# -- Dependencies
The following python packages are required to run quantum. These can be installed using pip:
The following python packages are required to run quantum. These can be
installed using pip:
eventlet>=0.9.12
nose
@ -32,7 +36,9 @@
webob
webtest
1) Install easy_install (there is probably a distribution specific package for this)
1) Install easy_install (there is probably a distribution specific package for
this)
2) Install pip:
$ easy_install pip==dev
3) Install packages with pip:
@ -40,14 +46,16 @@
# -- Configuring Quantum plug-in
1) Explore sample and real Quantum plug-ins in the quantum.plugins module.
1) Identify your desired plug-in. Choose a plugin from one of he options in
the quantum/plugins directory.
2) Or copy another Quantum plug-in into the quantum.plugins module.
2) Update plug-in configuration by editing the quantum/plugins.ini file and
modify "provider" property to point to the location of the Quantum plug-in.
It should specify the class path to the plugin and the class name (i.e. for
a plugin class MyPlugin in quantum/plugins/myplugin/myplugin.py the
provider would be: quantum.plugins.myplugin.myplugin.MyPlugin)
3) Update plug-in configuration by editing plugins.ini file and modify
"provider" property to point to the location of the Quantum plug-in.
4) Read the plugin specific README, this is usually found in the same
3) Read the plugin specific README, this is usually found in the same
directory as your Quantum plug-in, and follow configuration instructions.
# -- Launching the Quantum Service
@ -63,8 +71,8 @@ Please refer to sample Web Service client code in:
# -- CLI tools to program the Quantum-managed Cloud networking fabric
Quantum comes with a programmatic CLI that is driven by the Quantum Web Service
You can use the CLI by issuing the following command:
Quantum comes with a programmatic CLI that is driven by the Quantum Web
Service. You can use the CLI by issuing the following command:
~/src/quantum$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py
@ -85,16 +93,16 @@ well as sample plugins available in:
There are a few requirements to writing your own plugin:
1) Your plugin should implement all methods defined in
../quantum/quantum/quantum_plugin_base.QuantumPluginBase class
1) Your plugin should implement all methods defined in the
quantum/quantum_plugin_base.QuantumPluginBase class
2) Copy your Quantum plug-in over to the ../quantum/quantum/plugins/.. directory
2) Copy your Quantum plug-in over to the quantum/quantum/plugins/.. directory
3) The next step is to edit the plugins.ini file in the same directory
as QuantumPluginBase class and specify the location of your custom plugin
as the "provider"
4) Launch the Quantum Service, and your plug-in is configured and ready to manage
a Cloud Networking Fabric.
4) Launch the Quantum Service, and your plug-in is configured and ready to
manage a Cloud Networking Fabric.

View File

@ -36,6 +36,7 @@ gettext.install('quantum', unicode=1)
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

@ -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."""

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)
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

@ -33,7 +33,7 @@ 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:

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,12 +237,14 @@ 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
manager.delete_port(tid, nid,pid)
manager.delete_port(tid, nid, pid)
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',
@ -235,12 +256,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',
@ -257,12 +280,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}}
@ -277,12 +302,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):
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': ''}}
@ -297,63 +324,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:"
@ -361,6 +378,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[:]
@ -382,6 +400,7 @@ def build_args(cmd, cmdargs, arglist):
return None
return args
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <command> [args]"
parser = OptionParser(usage=usagestr)
@ -421,7 +440,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

@ -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):
@ -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

@ -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,24 +52,27 @@ 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
try:
net = session.query(models.Network).\
filter_by(tenant_id=tenant_id,name=name).\
filter_by(tenant_id=tenant_id, name=name).\
one()
raise Exception("Network with name \"%s\" already exists" % name)
except exc.NoResultFound:
@ -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,11 +99,12 @@ 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:
res = session.query(models.Network).\
filter_by(tenant_id=tenant_id,name=new_name).\
filter_by(tenant_id=tenant_id, name=new_name).\
one()
except exc.NoResultFound:
net = network_get(net_id)
@ -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,6 +17,7 @@
from quantum.common import exceptions as exc
class QuantumEchoPlugin(object):
"""
@ -35,7 +36,6 @@ class QuantumEchoPlugin(object):
"""
print("get_all_networks() called\n")
def create_network(self, tenant_id, net_name):
"""
Creates a new Virtual Network, and assigns it
@ -43,7 +43,6 @@ class QuantumEchoPlugin(object):
"""
print("create_network() called\n")
def delete_network(self, tenant_id, net_id):
"""
Deletes the network with the specified network identifier
@ -51,7 +50,6 @@ 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
@ -59,7 +57,6 @@ class QuantumEchoPlugin(object):
"""
print("get_network_details() called\n")
def rename_network(self, tenant_id, net_id, new_name):
"""
Updates the symbolic name belonging to a particular
@ -67,7 +64,6 @@ class QuantumEchoPlugin(object):
"""
print("rename_network() called\n")
def get_all_ports(self, tenant_id, net_id):
"""
Retrieves all port identifiers belonging to the
@ -75,14 +71,12 @@ class QuantumEchoPlugin(object):
"""
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,
@ -98,7 +92,6 @@ class QuantumEchoPlugin(object):
"""
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
@ -106,7 +99,6 @@ class QuantumEchoPlugin(object):
"""
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
@ -114,7 +106,6 @@ class QuantumEchoPlugin(object):
"""
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
@ -137,11 +128,10 @@ class DummyDataPlugin(object):
<network_uuid, network_name> for
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
@ -151,7 +141,6 @@ class DummyDataPlugin(object):
# 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
@ -169,7 +157,6 @@ class DummyDataPlugin(object):
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
@ -177,7 +164,6 @@ class DummyDataPlugin(object):
"""
print("rename_network() called\n")
def get_all_ports(self, tenant_id, net_id):
"""
Retrieves all port identifiers belonging to the
@ -187,7 +173,6 @@ class DummyDataPlugin(object):
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.
@ -202,7 +187,6 @@ class DummyDataPlugin(object):
"""
print("update_port() called\n")
def delete_port(self, tenant_id, net_id, port_id):
"""
Deletes a port on a specified Virtual Network,
@ -212,7 +196,6 @@ class DummyDataPlugin(object):
"""
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
@ -222,7 +205,6 @@ class DummyDataPlugin(object):
#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
@ -230,7 +212,6 @@ class DummyDataPlugin(object):
"""
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
@ -248,37 +229,32 @@ 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)
@ -286,7 +262,6 @@ class FakePlugin(object):
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))
@ -295,7 +270,7 @@ class FakePlugin(object):
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
@ -304,10 +279,10 @@ class FakePlugin(object):
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):
"""
@ -333,13 +308,13 @@ 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
@ -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):
@ -399,8 +374,8 @@ class FakePlugin(object):
# 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
@ -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:
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,7 +420,7 @@ 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
@ -460,5 +434,3 @@ class FakePlugin(object):
# TODO(salvatore-orlando):
# Should unplug on port without attachment raise an Error?
port['attachment'] = None

View File

@ -43,11 +43,13 @@ To prep mysql, run:
$ mysql -u root -p -e "create database ovs_quantum"
Make sure any xenserver running the ovs quantum agent will be able to communicate with the host running the quantum service:
Make sure any xenserver running the ovs quantum agent will be able to
communicate with the host running the quantum service:
//log in to mysql service
$ mysql -u root -p
//grant access to user-remote host combination
// grant access to user-remote host combination. Note: if you're going to use
// a wildcard here it should be a management network with only trusted hosts.
mysql> GRANT USAGE ON *.* to root@'yourremotehost' IDENTIFIED BY 'newpassword';
//force update of authorization changes
mysql> FLUSH PRIVILEGES;
@ -70,6 +72,7 @@ $ make agent-dist
- Unpack the tarball and run xenserver_install.sh. This will install all of the
necessary pieces into /etc/xapi.d/plugins. It will also spit out the name
of the integration bridge that you'll need for your nova configuration.
Make sure to specify this in your nova flagfile as --flat_network_bridge.
- Run the agent [on your hypervisor (dom0)]:
$ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini
@ -91,21 +94,19 @@ This will show help all of the available commands.
An example session looks like this:
$ export TENANT=t1
$ PYTHONPATH=. python quantum/cli.py -v create_net $TENANT network1
$ PYTHONPATH=. python quantum/cli.py create_net $TENANT network1
Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export NETWORK=e754e7c0-a8eb-40e5-861a-b182d30c3441
$ PYTHONPATH=. python quantum/cli.py -v create_port $TENANT $NETWORK
$ PYTHONPATH=. python quantum/cli.py create_port $TENANT $NETWORK
Created Virtual Port:5a1e121b-ccc8-471d-9445-24f15f9f854c on Virtual Network:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export PORT=5a1e121b-ccc8-471d-9445-24f15f9f854c
$ PYTHONPATH=. python quantum/cli.py -v plug_iface $TENANT $NETWORK $PORT ubuntu1-eth1
$ PYTHONPATH=. python quantum/cli.py plug_iface $TENANT $NETWORK $PORT ubuntu1-eth1
Plugged interface "ubuntu1-eth1" to port:5a1e121b-ccc8-471d-9445-24f15f9f854c on network:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ PYTHONPATH=. python quantum/cli.py -v plug_iface $TENANT $NETWORK $PORT ubuntu2-eth1
Plugged interface "ubuntu2-eth1" to port:5a1e121b-ccc8-471d-9445-24f15f9f854c on network:e754e7c0-a8eb-40e5-861a-b182d30c3441
Now you should have connectivity between ubuntu1-eth1 and ubuntu2-eth1..
(.. repeat for more ports and interface combinations..)
# -- Other items
- To get a listing of the vif names that the ovs quantum service will expect
them in, issue the following command on the hypervisor (dom0):
- To get a listing of the vif names in the format that the ovs quantum service
will expect them in, issue the following command on the hypervisor (dom0):
$ for vif in `xe vif-list params=uuid --minimal | sed s/,/" "/g`; do echo $(xe vif-list params=vm-name-label uuid=${vif} --minimal)-eth$(xe vif-list params=device uuid=${vif} --minimal); done

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):
@ -131,7 +134,7 @@ class OVSBridge:
# to quantum. We have this here for now as it lets us work with
# unmodified nova
def xapi_get_port(self, name):
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:
return None
vm_uuid = external_ids.get("xs-vm-uuid", "")
@ -146,7 +149,7 @@ class OVSBridge:
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")
ofport = self.db_get_val("Interface", name, "ofport")
return VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
@ -155,30 +158,32 @@ 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 "xs-vm-uuid" in external_ids:
p = xapi_get_port(name)
if p is not None:
edge_ports.append(p)
elif "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)
return edge_ports
class OVSQuantumAgent:
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))
self.int_br.delete_flows(match="in_port=%s" % port.ofport)
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)
@ -186,7 +191,6 @@ class OVSQuantumAgent:
# switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
def daemon_loop(self, conn):
self.local_vlan_map = {}
old_local_bindings = {}
@ -224,8 +228,10 @@ class OVSQuantumAgent:
"4095")
self.int_br.add_flow(priority=2,
match="in_port=%s" % p.ofport, actions="drop")
old_b = old_local_bindings.get(p.vif_id,None)
new_b = new_local_bindings.get(p.vif_id,None)
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"
@ -238,7 +244,7 @@ class OVSQuantumAgent:
"4095")
self.port_bound(p, vlan_id)
LOG.info("Adding binding to net-id = %s " \
"for %s on vlan %s" % (new_b, str(p),vlan_id))
"for %s on vlan %s" % (new_b, str(p), vlan_id))
for vif_id in old_vif_ports.keys():
if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id)

View File

@ -8,7 +8,7 @@ if [ ! -d /etc/xapi.d/plugins ]; then
fi
# Make sure we have mysql-python
rpm -qa | grep MYyQL-python >/dev/null 2>&1
rpm -qa | grep MySQL-python >/dev/null 2>&1
if [ $? -ne 0 ]; then
echo "MySQL-python not found"
echo "Please enable the centos repositories and install mysql-python:"

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:
@ -36,6 +37,7 @@ def get_vlans():
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:
@ -53,4 +56,3 @@ def remove_vlan_binding(netid):
except exc.NoResultFound:
pass
session.flush()

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 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):
@ -197,21 +208,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"
@ -310,6 +328,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"""
@ -62,7 +63,7 @@ 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)

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,7 +113,8 @@ 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",
self.assertTrue(
resdict["networks"]["network"]["name"] == "test_renamed",
"Network rename didn't take effect")
def delete_networks(self):

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

View File

@ -33,10 +33,13 @@ CONTENT_TYPE = "application/" + FORMAT
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" \
"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"
"'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")
@ -97,8 +100,8 @@ if __name__ == "__main__":
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
res = client.do_request(tenant_id, 'DELETE', "/networks/" + nid + "." + FORMAT)
res = client.do_request(tenant_id, 'DELETE',
"/networks/" + nid + "." + FORMAT)
status = res.status
if status != 202:
print "Failed to delete network: %s" % nid
@ -115,7 +118,8 @@ if __name__ == "__main__":
rd = json.loads(res.read())
LOG.debug(rd)
nid = rd["networks"]["network"]["id"]
print "Created a new Virtual Network %s with ID:%s\n" % (net_name,nid)
print "Created a new Virtual Network %s with ID:%s\n" % (net_name, nid)
for iface_id in iface_ids:
res = client.do_request(tenant_id, 'POST',
"/networks/%s/ports.%s" % (nid, FORMAT))
@ -130,12 +134,15 @@ if __name__ == "__main__":
data = {'port': {'attachment-id': '%s' % iface_id}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'PUT',
"/networks/%s/ports/%s/attachment.%s" % (nid, new_port_id, FORMAT), body=body)
"/networks/%s/ports/%s/attachment.%s" %\
(nid, new_port_id, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (iface_id,new_port_id, output))
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \
(iface_id, new_port_id, output))
continue
print "Plugged interface \"%s\" to port:%s on network:%s" % (iface_id, new_port_id, nid)
print "Plugged interface \"%s\" to port:%s on network:%s" % \
(iface_id, new_port_id, nid)
sys.exit(0)

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 && $@