merge and pep8 cleanup
This commit is contained in:
commit
5072a9563e
58
README
58
README
@ -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.
|
||||
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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}',
|
||||
|
@ -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."""
|
||||
|
@ -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)
|
||||
|
@ -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))
|
||||
|
@ -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:
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
||||
|
@ -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")
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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:"
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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
293
run_tests.py
Normal 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
83
run_tests.sh
Executable 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
|
@ -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
|
@ -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()
|
@ -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)
|
||||
|
@ -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
32
tests/unit/__init__.py
Normal 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
|
@ -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
137
tools/install_venv.py
Normal 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
10
tools/pip-requires
Normal 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
21
tools/with_venv.sh
Executable 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 && $@
|
Loading…
Reference in New Issue
Block a user