merge and pep8 cleanup

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

58
README
View File

@ -1,25 +1,29 @@
# -- Welcome! # -- Welcome!
You have come across a cloud computing network fabric controller. It has identified You have come across a cloud computing network fabric controller. It has
itself as "Quantum." It aims to tame your (cloud) networking! identified itself as "Quantum." It aims to tame your (cloud) networking!
# -- Basics: # -- Basics:
1) Quantum REST API: Quantum supports a REST-ful programmatic interface to manage your 1) Quantum REST API: Quantum supports a REST-ful programmatic interface to
cloud networking fabric. manage your cloud networking fabric.
2) Quantum Plugins: Quantum sports a plug-able architecture that allows Quantum's REST API 2) Quantum Plugins: Quantum sports a plug-able architecture that allows
to be backed by various entities that can create a cloud-class virtual networking fabric. Quantum's REST API to be backed by various entities that can create a
The advantages of this plug-able architecture is two-folds: 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 b) Allows Quantum users to not be tied down to a single Quantum
enables them to switch out a plug-in by simple editing a config file - plugins.ini implementation and enables them to switch out a plug-in by simple editing a
config file - plugins.ini
# -- Dependencies # -- 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 eventlet>=0.9.12
nose nose
@ -32,7 +36,9 @@
webob webob
webtest 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: 2) Install pip:
$ easy_install pip==dev $ easy_install pip==dev
3) Install packages with pip: 3) Install packages with pip:
@ -40,14 +46,16 @@
# -- Configuring Quantum plug-in # -- 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 3) Read the plugin specific README, this is usually found in the same
"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
directory as your Quantum plug-in, and follow configuration instructions. directory as your Quantum plug-in, and follow configuration instructions.
# -- Launching the Quantum Service # -- 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 # -- CLI tools to program the Quantum-managed Cloud networking fabric
Quantum comes with a programmatic CLI that is driven by the Quantum Web Service Quantum comes with a programmatic CLI that is driven by the Quantum Web
You can use the CLI by issuing the following command: Service. You can use the CLI by issuing the following command:
~/src/quantum$ PYTHONPATH=.:$PYTHONPATH python quantum/cli.py ~/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: There are a few requirements to writing your own plugin:
1) Your plugin should implement all methods defined in 1) Your plugin should implement all methods defined in the
../quantum/quantum/quantum_plugin_base.QuantumPluginBase class 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 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 QuantumPluginBase class and specify the location of your custom plugin
as the "provider" as the "provider"
4) Launch the Quantum Service, and your plug-in is configured and ready to manage 4) Launch the Quantum Service, and your plug-in is configured and ready to
a Cloud Networking Fabric. manage a Cloud Networking Fabric.

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,4 +13,4 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# @author: Somik Behera, Nicira Networks, Inc. # @author: Somik Behera, Nicira Networks, Inc.

View File

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

View File

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

View File

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

View File

@ -209,7 +209,7 @@ def find_config_file(options, args):
fix_path(os.path.join('~', '.quantum')), fix_path(os.path.join('~', '.quantum')),
fix_path('~'), fix_path('~'),
os.path.join(FLAGS.state_path, 'etc'), 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/quantum/',
'/etc'] '/etc']
for cfg_dir in config_file_dirs: for cfg_dir in config_file_dirs:
@ -244,12 +244,10 @@ def load_paste_config(app_name, options, args):
problem loading the configuration file. problem loading the configuration file.
""" """
conf_file = find_config_file(options, args) conf_file = find_config_file(options, args)
print "Conf_file:%s" %conf_file
if not conf_file: if not conf_file:
raise RuntimeError("Unable to locate any configuration file. " raise RuntimeError("Unable to locate any configuration file. "
"Cannot load application %s" % app_name) "Cannot load application %s" % app_name)
try: try:
print "App_name:%s" %app_name
conf = deploy.appconfig("config:%s" % conf_file, name=app_name) conf = deploy.appconfig("config:%s" % conf_file, name=app_name)
return conf_file, conf return conf_file, conf
except Exception, e: except Exception, e:
@ -257,7 +255,7 @@ def load_paste_config(app_name, options, args):
% (conf_file, e)) % (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. 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 :raises RuntimeError when config file cannot be located or application
cannot be loaded from config file 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: try:
conf_file = os.path.abspath(conf_file)
app = deploy.loadapp("config:%s" % conf_file, name=app_name) app = deploy.loadapp("config:%s" % conf_file, name=app_name)
except (LookupError, ImportError), e: except (LookupError, ImportError), e:
raise RuntimeError("Unable to load %(app_name)s from " raise RuntimeError("Unable to load %(app_name)s from "
"configuration file %(conf_file)s." "configuration file %(conf_file)s."
"\nGot: %(e)r" % locals()) "\nGot: %(e)r" % locals())
return app return conf, app
def get_option(options, option, **kwargs): def get_option(options, option, **kwargs):

View File

@ -25,7 +25,7 @@ import logging
class QuantumException(Exception): class QuantumException(Exception):
"""Base Quantum Exception """Base Quantum Exception
Taken from nova.exception.NovaException Taken from nova.exception.NovaException
To correctly use this class, inherit from it and define To correctly use this class, inherit from it and define
a 'message' property. That message will get printf'd a 'message' property. That message will get printf'd
@ -45,6 +45,7 @@ class QuantumException(Exception):
def __str__(self): def __str__(self):
return self._error_string return self._error_string
class ProcessExecutionError(IOError): class ProcessExecutionError(IOError):
def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None, def __init__(self, stdout=None, stderr=None, exit_code=None, cmd=None,
description=None): description=None):
@ -84,11 +85,11 @@ class NetworkNotFound(NotFound):
class PortNotFound(NotFound): class PortNotFound(NotFound):
message = _("Port %(port_id)s could not be found " \ message = _("Port %(port_id)s could not be found " \
"on network %(net_id)s") "on network %(net_id)s")
class StateInvalid(QuantumException): class StateInvalid(QuantumException):
message = _("Unsupported port state: %(port_state)s") message = _("Unsupported port state: %(port_state)s")
class NetworkInUse(QuantumException): class NetworkInUse(QuantumException):
message = _("Unable to complete operation on network %(net_id)s. " \ message = _("Unable to complete operation on network %(net_id)s. " \
@ -100,11 +101,13 @@ class PortInUse(QuantumException):
"for network %(net_id)s. The attachment '%(att_id)s" \ "for network %(net_id)s. The attachment '%(att_id)s" \
"is plugged into the logical port.") "is plugged into the logical port.")
class AlreadyAttached(QuantumException): class AlreadyAttached(QuantumException):
message = _("Unable to plug the attachment %(att_id)s into port " \ message = _("Unable to plug the attachment %(att_id)s into port " \
"%(port_id)s for network %(net_id)s. The attachment is " \ "%(port_id)s for network %(net_id)s. The attachment is " \
"already plugged into port %(att_port_id)s") "already plugged into port %(att_port_id)s")
class Duplicate(Error): class Duplicate(Error):
pass pass

View File

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

View File

@ -37,6 +37,7 @@ from exceptions import ProcessExecutionError
TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ" TIME_FORMAT = "%Y-%m-%dT%H:%M:%SZ"
FLAGS = flags.FLAGS FLAGS = flags.FLAGS
def int_from_bool_as_string(subject): def int_from_bool_as_string(subject):
""" """
Interpret a string as a boolean and return either 1 or 0. Interpret a string as a boolean and return either 1 or 0.
@ -188,6 +189,7 @@ def isotime(at=None):
def parse_isotime(timestr): def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT) return datetime.datetime.strptime(timestr, TIME_FORMAT)
def getPluginFromConfig(file="config.ini"): def getPluginFromConfig(file="config.ini"):
Config = ConfigParser.ConfigParser() Config = ConfigParser.ConfigParser()
Config.read(file) Config.read(file)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,4 +13,4 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
# @author: Somik Behera, Nicira Networks, Inc. # @author: Somik Behera, Nicira Networks, Inc.

View File

@ -43,11 +43,13 @@ To prep mysql, run:
$ mysql -u root -p -e "create database ovs_quantum" $ 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 //log in to mysql service
$ mysql -u root -p $ 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'; mysql> GRANT USAGE ON *.* to root@'yourremotehost' IDENTIFIED BY 'newpassword';
//force update of authorization changes //force update of authorization changes
mysql> FLUSH PRIVILEGES; mysql> FLUSH PRIVILEGES;
@ -70,6 +72,7 @@ $ make agent-dist
- Unpack the tarball and run xenserver_install.sh. This will install all of the - 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 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. 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)]: - Run the agent [on your hypervisor (dom0)]:
$ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini $ /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: An example session looks like this:
$ export TENANT=t1 $ 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 Created a new Virtual Network with ID:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export NETWORK=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 Created Virtual Port:5a1e121b-ccc8-471d-9445-24f15f9f854c on Virtual Network:e754e7c0-a8eb-40e5-861a-b182d30c3441
$ export PORT=5a1e121b-ccc8-471d-9445-24f15f9f854c $ 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 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 # -- Other items
- To get a listing of the vif names that the ovs quantum service will expect - To get a listing of the vif names in the format that the ovs quantum service
them in, issue the following command on the hypervisor (dom0): 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 $ for vif in `xe vif-list params=uuid --minimal | sed s/,/" "/g`; do echo $(xe vif-list params=vm-name-label uuid=${vif} --minimal)-eth$(xe vif-list params=device uuid=${vif} --minimal); done

View File

@ -28,6 +28,7 @@ import time
from optparse import OptionParser from optparse import OptionParser
from subprocess import * from subprocess import *
# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' # A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
# attributes set). # attributes set).
class VifPort: class VifPort:
@ -37,11 +38,13 @@ class VifPort:
self.vif_id = vif_id self.vif_id = vif_id
self.vif_mac = vif_mac self.vif_mac = vif_mac
self.switch = switch self.switch = switch
def __str__(self): def __str__(self):
return "iface-id=" + self.vif_id + ", vif_mac=" + \ return "iface-id=" + self.vif_id + ", vif_mac=" + \
self.vif_mac + ", port_name=" + self.port_name + \ self.vif_mac + ", port_name=" + self.port_name + \
", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name ", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name
class OVSBridge: class OVSBridge:
def __init__(self, br_name): def __init__(self, br_name):
self.br_name = br_name self.br_name = br_name
@ -51,36 +54,36 @@ class OVSBridge:
return Popen(args, stdout=PIPE).communicate()[0] return Popen(args, stdout=PIPE).communicate()[0]
def run_vsctl(self, args): def run_vsctl(self, args):
full_args = ["ovs-vsctl" ] + args full_args = ["ovs-vsctl"] + args
return self.run_cmd(full_args) return self.run_cmd(full_args)
def reset_bridge(self): 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]) self.run_vsctl(["add-br", self.br_name])
def delete_port(self, port_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]) port_name])
def set_db_attribute(self, table_name, record, column, value): 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) self.run_vsctl(args)
def clear_db_attribute(self, table_name,record, column): def clear_db_attribute(self, table_name, record, column):
args = [ "clear", table_name, record, column ] args = ["clear", table_name, record, column]
self.run_vsctl(args) self.run_vsctl(args)
def run_ofctl(self, cmd, 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) return self.run_cmd(full_args)
def remove_all_flows(self): def remove_all_flows(self):
self.run_ofctl("del-flows", []) self.run_ofctl("del-flows", [])
def get_port_ofport(self, port_name): def get_port_ofport(self, port_name):
return self.db_get_val("Interface", port_name, "ofport") return self.db_get_val("Interface", port_name, "ofport")
def add_flow(self,**dict): def add_flow(self, **dict):
if "actions" not in dict: if "actions" not in dict:
raise Exception("must specify one or more actions") raise Exception("must specify one or more actions")
if "priority" not in dict: if "priority" not in dict:
@ -90,9 +93,9 @@ class OVSBridge:
if "match" in dict: if "match" in dict:
flow_str += "," + dict["match"] flow_str += "," + dict["match"]
flow_str += ",actions=%s" % (dict["actions"]) 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 = [] all_args = []
if "priority" in dict: if "priority" in dict:
all_args.append("priority=%s" % dict["priority"]) all_args.append("priority=%s" % dict["priority"])
@ -101,14 +104,14 @@ class OVSBridge:
if "actions" in dict: if "actions" in dict:
all_args.append("actions=%s" % (dict["actions"])) all_args.append("actions=%s" % (dict["actions"]))
flow_str = ",".join(all_args) 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): 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) return self.db_str_to_map(str)
def db_get_val(self, table, record, column): 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): def db_str_to_map(self, full_str):
list = full_str.strip("{}").split(", ") list = full_str.strip("{}").split(", ")
@ -121,33 +124,33 @@ class OVSBridge:
return ret return ret
def get_port_name_list(self): 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] return res.split("\n")[0:-1]
def get_port_stats(self, port_name): def get_port_stats(self, port_name):
return self.db_get_map("Interface", port_name, "statistics") return self.db_get_map("Interface", port_name, "statistics")
# this is a hack that should go away once nova properly reports bindings # this is a hack that should go away once nova properly reports bindings
# to quantum. We have this here for now as it lets us work with # to quantum. We have this here for now as it lets us work with
# unmodified nova # unmodified nova
def xapi_get_port(self, name): 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: if "attached-mac" not in external_ids:
return None return None
vm_uuid = external_ids.get("xs-vm-uuid", "") vm_uuid = external_ids.get("xs-vm-uuid", "")
if len(vm_uuid) == 0: if len(vm_uuid) == 0:
return None return None
LOG.debug("iface-id not set, got xs-vm-uuid: %s" % vm_uuid) LOG.debug("iface-id not set, got xs-vm-uuid: %s" % vm_uuid)
res = os.popen("xe vm-list uuid=%s params=name-label --minimal" \ res = os.popen("xe vm-list uuid=%s params=name-label --minimal" \
% vm_uuid).readline().strip() % vm_uuid).readline().strip()
if len(res) == 0: if len(res) == 0:
return None return None
external_ids["iface-id"] = res external_ids["iface-id"] = res
LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res)) LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res))
self.set_db_attribute("Interface", name, self.set_db_attribute("Interface", name,
"external-ids:iface-id", res) "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"], return VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self) external_ids["attached-mac"], self)
# returns a VIF object for each VIF port # returns a VIF object for each VIF port
@ -155,30 +158,32 @@ class OVSBridge:
edge_ports = [] edge_ports = []
port_names = self.get_port_name_list() port_names = self.get_port_name_list()
for name in port_names: 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: if "xs-vm-uuid" in external_ids:
p = xapi_get_port(name) p = xapi_get_port(name)
if p is not None: if p is not None:
edge_ports.append(p) edge_ports.append(p)
elif "iface-id" in external_ids and "attached-mac" in external_ids: 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"], p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self) external_ids["attached-mac"], self)
edge_ports.append(p) edge_ports.append(p)
return edge_ports return edge_ports
class OVSQuantumAgent: class OVSQuantumAgent:
def __init__(self, integ_br): def __init__(self, integ_br):
self.setup_integration_br(integ_br) self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id): 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)) str(vlan_id))
self.int_br.delete_flows(match="in_port=%s" % port.ofport) self.int_br.delete_flows(match="in_port=%s" % port.ofport)
def port_unbound(self, port, still_exists): def port_unbound(self, port, still_exists):
if 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): def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br) self.int_br = OVSBridge(integ_br)
@ -186,7 +191,6 @@ class OVSQuantumAgent:
# switch all traffic using L2 learning # switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal") self.int_br.add_flow(priority=1, actions="normal")
def daemon_loop(self, conn): def daemon_loop(self, conn):
self.local_vlan_map = {} self.local_vlan_map = {}
old_local_bindings = {} old_local_bindings = {}
@ -219,13 +223,15 @@ class OVSQuantumAgent:
else: else:
# no binding, put him on the 'dead vlan' # no binding, put him on the 'dead vlan'
LOG.info("No binding for %s, setting to dead vlan" \ LOG.info("No binding for %s, setting to dead vlan" \
% p.vif_id) % p.vif_id)
self.int_br.set_db_attribute("Port", p.port_name, "tag", self.int_br.set_db_attribute("Port", p.port_name, "tag",
"4095") "4095")
self.int_br.add_flow(priority=2, self.int_br.add_flow(priority=2,
match="in_port=%s" % p.ofport, actions="drop") 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 != new_b:
if old_b is not None: if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s" LOG.info("Removing binding to net-id = %s for %s"
@ -238,7 +244,7 @@ class OVSQuantumAgent:
"4095") "4095")
self.port_bound(p, vlan_id) self.port_bound(p, vlan_id)
LOG.info("Adding binding to net-id = %s " \ 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(): for vif_id in old_vif_ports.keys():
if vif_id not in new_vif_ports: if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id) LOG.info("Port Disappeared: %s" % vif_id)

View File

@ -8,7 +8,7 @@ if [ ! -d /etc/xapi.d/plugins ]; then
fi fi
# Make sure we have mysql-python # 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 if [ $? -ne 0 ]; then
echo "MySQL-python not found" echo "MySQL-python not found"
echo "Please enable the centos repositories and install mysql-python:" echo "Please enable the centos repositories and install mysql-python:"

View File

@ -24,6 +24,7 @@ import quantum.db.api as db
import quantum.db.models as models import quantum.db.models as models
import ovs_models import ovs_models
def get_vlans(): def get_vlans():
session = db.get_session() session = db.get_session()
try: try:
@ -33,9 +34,10 @@ def get_vlans():
return [] return []
res = [] res = []
for x in bindings: for x in bindings:
res.append((x.vlan_id, x.network_id)) res.append((x.vlan_id, x.network_id))
return res return res
def add_vlan_binding(vlanid, netid): def add_vlan_binding(vlanid, netid):
session = db.get_session() session = db.get_session()
binding = ovs_models.VlanBinding(vlanid, netid) binding = ovs_models.VlanBinding(vlanid, netid)
@ -43,6 +45,7 @@ def add_vlan_binding(vlanid, netid):
session.flush() session.flush()
return binding.vlan_id return binding.vlan_id
def remove_vlan_binding(netid): def remove_vlan_binding(netid):
session = db.get_session() session = db.get_session()
try: try:
@ -53,4 +56,3 @@ def remove_vlan_binding(netid):
except exc.NoResultFound: except exc.NoResultFound:
pass pass
session.flush() session.flush()

View File

@ -23,9 +23,9 @@ import uuid
from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy import Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relation from sqlalchemy.orm import relation
from quantum.db.models import BASE from quantum.db.models import BASE
class VlanBinding(BASE): class VlanBinding(BASE):
"""Represents a binding of network_id, vlan_id""" """Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings' __tablename__ = 'vlan_bindings'

View File

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

View File

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

293
run_tests.py Normal file
View File

@ -0,0 +1,293 @@
#!/usr/bin/env python
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010 OpenStack, LLC
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Colorizer Code is borrowed from Twisted:
# Copyright (c) 2001-2010 Twisted Matrix Laboratories.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""Unittest runner for quantum
To run all test::
python run_tests.py
To run all unit tests::
python run_tests.py unit
To run all functional tests::
python run_tests.py functional
To run a single unit test::
python run_tests.py unit.test_stores:TestSwiftBackend.test_get
To run a single functional test::
python run_tests.py functional.test_service:TestController.test_create
To run a single unit test module::
python run_tests.py unit.test_stores
To run a single functional test module::
python run_tests.py functional.test_stores
"""
import gettext
import os
import unittest
import sys
from nose import config
from nose import result
from nose import core
class _AnsiColorizer(object):
"""
A colorizer is an object that loosely wraps around a stream, allowing
callers to write text to the stream in a particular color.
Colorizer classes must implement C{supported()} and C{write(text, color)}.
"""
_colors = dict(black=30, red=31, green=32, yellow=33,
blue=34, magenta=35, cyan=36, white=37)
def __init__(self, stream):
self.stream = stream
def supported(cls, stream=sys.stdout):
"""
A class method that returns True if the current platform supports
coloring terminal output using this method. Returns False otherwise.
"""
if not stream.isatty():
return False # auto color only on TTYs
try:
import curses
except ImportError:
return False
else:
try:
try:
return curses.tigetnum("colors") > 2
except curses.error:
curses.setupterm()
return curses.tigetnum("colors") > 2
except:
raise
# guess false in case of error
return False
supported = classmethod(supported)
def write(self, text, color):
"""
Write the given text to the stream in the given color.
@param text: Text to be written to the stream.
@param color: A string label for a color. e.g. 'red', 'white'.
"""
color = self._colors[color]
self.stream.write('\x1b[%s;1m%s\x1b[0m' % (color, text))
class _Win32Colorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
from win32console import GetStdHandle, STD_OUT_HANDLE, \
FOREGROUND_RED, FOREGROUND_BLUE, FOREGROUND_GREEN, \
FOREGROUND_INTENSITY
red, green, blue, bold = (FOREGROUND_RED, FOREGROUND_GREEN,
FOREGROUND_BLUE, FOREGROUND_INTENSITY)
self.stream = stream
self.screenBuffer = GetStdHandle(STD_OUT_HANDLE)
self._colors = {
'normal': red | green | blue,
'red': red | bold,
'green': green | bold,
'blue': blue | bold,
'yellow': red | green | bold,
'magenta': red | blue | bold,
'cyan': green | blue | bold,
'white': red | green | blue | bold}
def supported(cls, stream=sys.stdout):
try:
import win32console
screenBuffer = win32console.GetStdHandle(
win32console.STD_OUT_HANDLE)
except ImportError:
return False
import pywintypes
try:
screenBuffer.SetConsoleTextAttribute(
win32console.FOREGROUND_RED |
win32console.FOREGROUND_GREEN |
win32console.FOREGROUND_BLUE)
except pywintypes.error:
return False
else:
return True
supported = classmethod(supported)
def write(self, text, color):
color = self._colors[color]
self.screenBuffer.SetConsoleTextAttribute(color)
self.stream.write(text)
self.screenBuffer.SetConsoleTextAttribute(self._colors['normal'])
class _NullColorizer(object):
"""
See _AnsiColorizer docstring.
"""
def __init__(self, stream):
self.stream = stream
def supported(cls, stream=sys.stdout):
return True
supported = classmethod(supported)
def write(self, text, color):
self.stream.write(text)
class QuantumTestResult(result.TextTestResult):
def __init__(self, *args, **kw):
result.TextTestResult.__init__(self, *args, **kw)
self._last_case = None
self.colorizer = None
# NOTE(vish, tfukushima): reset stdout for the terminal check
stdout = sys.__stdout__
for colorizer in [_Win32Colorizer, _AnsiColorizer, _NullColorizer]:
if colorizer.supported():
self.colorizer = colorizer(self.stream)
break
sys.stdout = stdout
def getDescription(self, test):
return str(test)
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addSuccess(self, test):
unittest.TestResult.addSuccess(self, test)
if self.showAll:
self.colorizer.write("OK", 'green')
self.stream.writeln()
elif self.dots:
self.stream.write('.')
self.stream.flush()
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addFailure(self, test, err):
unittest.TestResult.addFailure(self, test, err)
if self.showAll:
self.colorizer.write("FAIL", 'red')
self.stream.writeln()
elif self.dots:
self.stream.write('F')
self.stream.flush()
# NOTE(vish, tfukushima): copied from unittest with edit to add color
def addError(self, test, err):
"""Overrides normal addError to add support for errorClasses.
If the exception is a registered class, the error will be added
to the list for that class, not errors.
"""
stream = getattr(self, 'stream', None)
ec, ev, tb = err
try:
exc_info = self._exc_info_to_string(err, test)
except TypeError:
# This is for compatibility with Python 2.3.
exc_info = self._exc_info_to_string(err)
for cls, (storage, label, isfail) in self.errorClasses.items():
if result.isclass(ec) and issubclass(ec, cls):
if isfail:
test.passwd = False
storage.append((test, exc_info))
# Might get patched into a streamless result
if stream is not None:
if self.showAll:
message = [label]
detail = result._exception_details(err[1])
if detail:
message.append(detail)
stream.writeln(": ".join(message))
elif self.dots:
stream.write(label[:1])
return
self.errors.append((test, exc_info))
test.passed = False
if stream is not None:
if self.showAll:
self.colorizer.write("ERROR", 'red')
self.stream.writeln()
elif self.dots:
stream.write('E')
def startTest(self, test):
unittest.TestResult.startTest(self, test)
current_case = test.test.__class__.__name__
if self.showAll:
if current_case != self._last_case:
self.stream.writeln(current_case)
self._last_case = current_case
self.stream.write(
' %s' % str(test.test._testMethodName).ljust(60))
self.stream.flush()
class QuantumTestRunner(core.TextTestRunner):
def _makeResult(self):
return QuantumTestResult(self.stream,
self.descriptions,
self.verbosity,
self.config)
if __name__ == '__main__':
working_dir = os.path.abspath("tests")
c = config.Config(stream=sys.stdout,
env=os.environ,
verbosity=3,
workingDir=working_dir)
runner = QuantumTestRunner(stream=c.stream,
verbosity=c.verbosity,
config=c)
sys.exit(not core.run(config=c, testRunner=runner))

83
run_tests.sh Executable file
View File

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

View File

@ -1,98 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import httplib
import socket
import urllib
class MiniClient(object):
"""A base client class - derived from Glance.BaseClient"""
action_prefix = '/v0.1/tenants/{tenant_id}'
def __init__(self, host, port, use_ssl):
"""
Creates a new client to some service.
:param host: The host where service resides
:param port: The port where service resides
:param use_ssl: Should we use HTTPS?
"""
self.host = host
self.port = port
self.use_ssl = use_ssl
self.connection = None
def get_connection_type(self):
"""
Returns the proper connection type
"""
if self.use_ssl:
return httplib.HTTPSConnection
else:
return httplib.HTTPConnection
def do_request(self, tenant, method, action, body=None,
headers=None, params=None):
"""
Connects to the server and issues a request.
Returns the result data, or raises an appropriate exception if
HTTP status code is not 2xx
:param method: HTTP method ("GET", "POST", "PUT", etc...)
:param body: string of data to send, or None (default)
:param headers: mapping of key/value pairs to add as headers
:param params: dictionary of key/value pairs to add to append
to action
"""
action = MiniClient.action_prefix + action
action = action.replace('{tenant_id}',tenant)
if type(params) is dict:
action += '?' + urllib.urlencode(params)
try:
connection_type = self.get_connection_type()
headers = headers or {}
# Open connection and send request
c = connection_type(self.host, self.port)
c.request(method, action, body, headers)
res = c.getresponse()
status_code = self.get_status_code(res)
if status_code in (httplib.OK,
httplib.CREATED,
httplib.ACCEPTED,
httplib.NO_CONTENT):
return res
else:
raise Exception("Server returned error: %s" % res.read())
except (socket.error, IOError), e:
raise Exception("Unable to connect to "
"server. Got error: %s" % e)
def get_status_code(self, response):
"""
Returns the integer status code from the response, which
can be either a Webob.Response (used in testing) or httplib.Response
"""
if hasattr(response, 'status_int'):
return response.status_int
else:
return response.status

View File

@ -1,150 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import gettext
gettext.install('quantum', unicode=1)
from miniclient import MiniClient
from quantum.common.wsgi import Serializer
HOST = '127.0.0.1'
PORT = 9696
USE_SSL = False
TENANT_ID = 'totore'
test_network_data = \
{'network': {'network-name': 'test' }}
def print_response(res):
content = res.read()
print "Status: %s" %res.status
print "Content: %s" %content
return content
def test_list_networks_and_ports(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST LIST NETWORKS AND PORTS -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "--> Step 2 - Details for Network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "--> Step 3 - Ports for Network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "--> Step 4 - Details for Port 1"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports/1." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_create_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST CREATE NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - Create Network"
content_type = "application/" + format
body = Serializer().serialize(test_network_data, content_type)
res = client.do_request(TENANT_ID,'POST', "/networks." + format, body=body)
print_response(res)
print "--> Step 2 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_rename_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
content_type = "application/" + format
print "TEST RENAME NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - Retrieve network"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "--> Step 2 - Rename network to 'test_renamed'"
test_network_data['network']['network-name'] = 'test_renamed'
body = Serializer().serialize(test_network_data, content_type)
res = client.do_request(TENANT_ID,'PUT', "/networks/001." + format, body=body)
print_response(res)
print "--> Step 2 - Retrieve network (again)"
res = client.do_request(TENANT_ID,'GET', "/networks/001." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_delete_network(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
content_type = "application/" + format
print "TEST DELETE NETWORK -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List All Networks"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
content = print_response(res)
network_data = Serializer().deserialize(content, content_type)
print network_data
net_id = network_data['networks'][0]['id']
print "--> Step 2 - Delete network %s" %net_id
res = client.do_request(TENANT_ID,'DELETE',
"/networks/" + net_id + "." + format)
print_response(res)
print "--> Step 3 - List All Networks (Again)"
res = client.do_request(TENANT_ID,'GET', "/networks." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def test_create_port(format = 'xml'):
client = MiniClient(HOST, PORT, USE_SSL)
print "TEST CREATE PORT -- FORMAT:%s" %format
print "----------------------------"
print "--> Step 1 - List Ports for network 001"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "--> Step 2 - Create Port for network 001"
res = client.do_request(TENANT_ID,'POST', "/networks/001/ports." + format)
print_response(res)
print "--> Step 3 - List Ports for network 001 (again)"
res = client.do_request(TENANT_ID,'GET', "/networks/001/ports." + format)
print_response(res)
print "COMPLETED"
print "----------------------------"
def main():
test_list_networks_and_ports('xml')
test_list_networks_and_ports('json')
test_create_network('xml')
test_create_network('json')
test_rename_network('xml')
test_rename_network('json')
# NOTE: XML deserializer does not work properly
# disabling XML test - this is NOT a server-side issue
#test_delete_network('xml')
test_delete_network('json')
test_create_port('xml')
test_create_port('json')
pass
# Standard boilerplate to call the main() function.
if __name__ == '__main__':
main()

View File

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

View File

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

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

@ -0,0 +1,32 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# See http://code.google.com/p/python-nose/issues/detail?id=373
# The code below enables nosetests to work with i18n _() blocks
import __builtin__
import unittest
setattr(__builtin__, '_', lambda x: x)
class BaseTest(unittest.TestCase):
def setUp(self):
pass
def setUp():
pass

View File

@ -33,10 +33,13 @@ CONTENT_TYPE = "application/" + FORMAT
if __name__ == "__main__": if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <tenant-id> <config-string> [args]\n" \ 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"\
"This string would create two networks: \n" \ ":net2=instance-3,instance-4\n" \
"'net1' would have two ports, with iface-ids instance-1 and instance-2 attached\n" \ "This string would create two networks: \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 = OptionParser(usage=usagestr)
parser.add_option("-H", "--host", dest="host", parser.add_option("-H", "--host", dest="host",
type="string", default="127.0.0.1", help="ip address of api host") type="string", default="127.0.0.1", help="ip address of api host")
@ -60,82 +63,86 @@ if __name__ == "__main__":
sys.exit(1) sys.exit(1)
nets = {} nets = {}
tenant_id = args[0] tenant_id = args[0]
if len(args) > 1: if len(args) > 1:
config_str = args[1] config_str = args[1]
for net_str in config_str.split(":"): for net_str in config_str.split(":"):
arr = net_str.split("=") arr = net_str.split("=")
net_name = arr[0] net_name = arr[0]
nets[net_name] = arr[1].split(",") nets[net_name] = arr[1].split(",")
print "nets: %s" % str(nets)
print "nets: %s" % str(nets)
client = MiniClient(options.host, options.port, options.ssl) client = MiniClient(options.host, options.port, options.ssl)
res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT) res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT)
resdict = json.loads(res.read()) resdict = json.loads(res.read())
LOG.debug(resdict) LOG.debug(resdict)
for n in resdict["networks"]: for n in resdict["networks"]:
nid = n["id"] nid = n["id"]
res = client.do_request(tenant_id, 'GET', res = client.do_request(tenant_id, 'GET',
"/networks/%s/ports.%s" % (nid, FORMAT)) "/networks/%s/ports.%s" % (nid, FORMAT))
output = res.read() output = res.read()
if res.status != 200: if res.status != 200:
LOG.error("Failed to list ports: %s" % output) LOG.error("Failed to list ports: %s" % output)
continue continue
rd = json.loads(output) rd = json.loads(output)
LOG.debug(rd) LOG.debug(rd)
for port in rd["ports"]: for port in rd["ports"]:
pid = port["id"] pid = port["id"]
res = client.do_request(tenant_id, 'DELETE', res = client.do_request(tenant_id, 'DELETE',
"/networks/%s/ports/%s.%s" % (nid, pid, FORMAT)) "/networks/%s/ports/%s.%s" % (nid, pid, FORMAT))
output = res.read() output = res.read()
if res.status != 202: if res.status != 202:
LOG.error("Failed to delete port: %s" % output) LOG.error("Failed to delete port: %s" % output)
continue continue
LOG.info("Deleted Virtual Port:%s " \ LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)) "on Virtual Network:%s" % (pid, nid))
res = client.do_request(tenant_id, 'DELETE',
"/networks/" + nid + "." + FORMAT)
status = res.status
if status != 202:
print "Failed to delete network: %s" % nid
output = res.read()
print output
else:
print "Deleted Virtual Network with ID:%s" % nid
res = client.do_request(tenant_id, 'DELETE', "/networks/" + nid + "." + FORMAT) for net_name, iface_ids in nets.items():
status = res.status data = {'network': {'network-name': '%s' % net_name}}
if status != 202: body = Serializer().serialize(data, CONTENT_TYPE)
print "Failed to delete network: %s" % nid res = client.do_request(tenant_id, 'POST',
output = res.read() "/networks." + FORMAT, body=body)
print output rd = json.loads(res.read())
else: LOG.debug(rd)
print "Deleted Virtual Network with ID:%s" % nid
for net_name, iface_ids in nets.items():
data = {'network': {'network-name': '%s' % net_name}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'POST',
"/networks." + FORMAT, body=body)
rd = json.loads(res.read())
LOG.debug(rd)
nid = rd["networks"]["network"]["id"] 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', for iface_id in iface_ids:
"/networks/%s/ports.%s" % (nid, FORMAT)) res = client.do_request(tenant_id, 'POST',
output = res.read() "/networks/%s/ports.%s" % (nid, FORMAT))
if res.status != 200: output = res.read()
LOG.error("Failed to create port: %s" % output) if res.status != 200:
continue LOG.error("Failed to create port: %s" % output)
rd = json.loads(output) continue
new_port_id = rd["ports"]["port"]["id"] rd = json.loads(output)
print "Created Virtual Port:%s " \ new_port_id = rd["ports"]["port"]["id"]
"on Virtual Network:%s" % (new_port_id, nid) print "Created Virtual Port:%s " \
data = {'port': {'attachment-id': '%s' % iface_id}} "on Virtual Network:%s" % (new_port_id, nid)
body = Serializer().serialize(data, CONTENT_TYPE) data = {'port': {'attachment-id': '%s' % iface_id}}
res = client.do_request(tenant_id, 'PUT', body = Serializer().serialize(data, CONTENT_TYPE)
"/networks/%s/ports/%s/attachment.%s" % (nid, new_port_id, FORMAT), body=body) res = client.do_request(tenant_id, 'PUT',
output = res.read() "/networks/%s/ports/%s/attachment.%s" %\
LOG.debug(output) (nid, new_port_id, FORMAT), body=body)
if res.status != 202: output = res.read()
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (iface_id,new_port_id, output)) LOG.debug(output)
continue if res.status != 202:
print "Plugged interface \"%s\" to port:%s on network:%s" % (iface_id, new_port_id, nid) 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)
sys.exit(0) sys.exit(0)

137
tools/install_venv.py Normal file
View File

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

10
tools/pip-requires Normal file
View File

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

21
tools/with_venv.sh Executable file
View File

@ -0,0 +1,21 @@
#!/bin/bash
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 OpenStack LLC.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
TOOLS=`dirname $0`
VENV=$TOOLS/../.quantum-venv
source $VENV/bin/activate && $@