Merge with quantum-unit-tests

This commit is contained in:
Salvatore Orlando 2011-07-21 17:24:24 +01:00
commit 70d793c816
29 changed files with 1504 additions and 521 deletions

47
TESTING Normal file
View File

@ -0,0 +1,47 @@
Testing Quantum
=============================================================
Overview
There are two types of tests in quantum: functional and unit. Their
respective directories are located in the tests/ directory.
The functional tests are intended to be used when the service is running.
Their goal is to make sure the service is working end to end and also to
test any plugin for conformance and functionality. Also note that the
functional tests expect quantum to be running on the local machine. If it
isn't you will have to change the tests to point to your quuntum instance.
The unit tests can be run without the service running. They are designed
to test the various pieces of the quantum tree to make sure any new
changes don't break existing functionality.
Running tests
All tests can be run via the run_tests.sh script, which will allow you to
run them in the standard environment or create a virtual environment to
run them in. All of the functional tests will fail if the service isn't
running. One current TODO item is to be able to specify whether you want
to run the functional or unit tests via run_tests.sh.
After running all of the tests, run_test.sh will report any pep8 errors
found in the tree.
Adding more tests
Quantum is a pretty new code base at this point and there is plenty of
areas that need tests. The current blueprint and branch for adding tests
is located at:
https://code.launchpad.net/~netstack/quantum/quantum-unit-tests
Also, there is a wiki page tracking the status of the test effort:
http://wiki.openstack.org/QuantumUnitTestStatus
Development process
It is expected that any new changes that are proposed for merge come with
unit tests for that feature or code area. Ideally any bugs fixes that are
submitted also have unit tests to prove that they stay fixed! :) In
addition, before proposing for merge, all of the current unit tests should
be passing.

View File

@ -25,6 +25,7 @@ import routes
import webob.dec import webob.dec
import webob.exc import webob.exc
from quantum import manager
from quantum.api import faults from quantum.api import faults
from quantum.api import networks from quantum.api import networks
from quantum.api import ports from quantum.api import ports
@ -41,41 +42,41 @@ class APIRouterV01(wsgi.Router):
Routes requests on the Quantum API to the appropriate controller Routes requests on the Quantum API to the appropriate controller
""" """
def __init__(self, ext_mgr=None): def __init__(self, options=None):
mapper = routes.Mapper() mapper = routes.Mapper()
self._setup_routes(mapper) self._setup_routes(mapper, options)
super(APIRouterV01, self).__init__(mapper) super(APIRouterV01, self).__init__(mapper)
def _setup_routes(self, mapper): def _setup_routes(self, mapper, options):
# Loads the quantum plugin
plugin = manager.QuantumManager(options).get_plugin()
uri_prefix = '/tenants/{tenant_id}/' uri_prefix = '/tenants/{tenant_id}/'
mapper.resource('network', 'networks', mapper.resource('network', 'networks',
controller=networks.Controller(), controller=networks.Controller(plugin),
collection={'detail': 'GET'}, collection={'detail': 'GET'},
member={'detail': 'GET'}, member={'detail': 'GET'},
path_prefix=uri_prefix) path_prefix=uri_prefix)
mapper.resource('port', 'ports', mapper.resource('port', 'ports',
controller=ports.Controller(), controller=ports.Controller(plugin),
parent_resource=dict(member_name='network', parent_resource=dict(member_name='network',
collection_name=uri_prefix +\ collection_name=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}',
controller=ports.Controller(), controller=ports.Controller(plugin),
action="get_resource", action="get_resource",
conditions=dict(method=['GET'])) conditions=dict(method=['GET']))
mapper.connect("attach_resource", mapper.connect("attach_resource",
uri_prefix + 'networks/{network_id}/' \ uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}', 'ports/{id}/attachment{.format}',
controller=ports.Controller(), controller=ports.Controller(plugin),
action="attach_resource", action="attach_resource",
conditions=dict(method=['PUT'])) conditions=dict(method=['PUT']))
mapper.connect("detach_resource", mapper.connect("detach_resource",
uri_prefix + 'networks/{network_id}/' \ uri_prefix + 'networks/{network_id}/' \
'ports/{id}/attachment{.format}', 'ports/{id}/attachment{.format}',
controller=ports.Controller(), controller=ports.Controller(plugin),
action="detach_resource", action="detach_resource",
conditions=dict(method=['DELETE'])) conditions=dict(method=['DELETE']))
print "MAPPED ROUTES" print "MAPPED ROUTES"

View File

@ -19,7 +19,6 @@ import logging
from webob import exc from webob import exc
from quantum import manager
from quantum.common import wsgi from quantum.common import wsgi
XML_NS_V01 = 'http://netstack.org/quantum/api/v0.1' XML_NS_V01 = 'http://netstack.org/quantum/api/v0.1'
@ -30,8 +29,8 @@ LOG = logging.getLogger('quantum.api.api_common')
class QuantumController(wsgi.Controller): class QuantumController(wsgi.Controller):
""" Base controller class for Quantum API """ """ Base controller class for Quantum API """
def __init__(self, plugin_conf_file=None): def __init__(self, plugin):
self._setup_network_manager() self._plugin = plugin
super(QuantumController, self).__init__() super(QuantumController, self).__init__()
def _parse_request_params(self, req, params): def _parse_request_params(self, req, params):
@ -64,6 +63,3 @@ class QuantumController(wsgi.Controller):
raise exc.HTTPBadRequest(msg) raise exc.HTTPBadRequest(msg)
results[param_name] = param_value or param.get('default-value') results[param_name] = param_value or param.get('default-value')
return results return results
def _setup_network_manager(self):
self.network_manager = manager.QuantumManager().get_manager()

View File

@ -29,7 +29,7 @@ class Controller(common.QuantumController):
""" Network API controller for Quantum API """ """ Network API controller for Quantum API """
_network_ops_param_list = [{ _network_ops_param_list = [{
'param-name': 'network-name', 'param-name': 'net-name',
'required': True}, ] 'required': True}, ]
_serialization_metadata = { _serialization_metadata = {
@ -41,9 +41,9 @@ class Controller(common.QuantumController):
}, },
} }
def __init__(self, plugin_conf_file=None): def __init__(self, plugin):
self._resource_name = 'network' self._resource_name = 'network'
super(Controller, self).__init__() super(Controller, self).__init__(plugin)
def index(self, request, tenant_id): def index(self, request, tenant_id):
""" Returns a list of network ids """ """ Returns a list of network ids """
@ -60,7 +60,7 @@ class Controller(common.QuantumController):
def _items(self, req, tenant_id, net_details=False, port_details=False): def _items(self, req, tenant_id, net_details=False, port_details=False):
""" Returns a list of networks. """ """ Returns a list of networks. """
networks = self.network_manager.get_all_networks(tenant_id) networks = self._plugin.get_all_networks(tenant_id)
builder = networks_view.get_view_builder(req) builder = networks_view.get_view_builder(req)
result = [builder.build(network, net_details, port_details)['network'] result = [builder.build(network, net_details, port_details)['network']
for network in networks] for network in networks]
@ -85,6 +85,12 @@ class Controller(common.QuantumController):
#do like show but with detaik #do like show but with detaik
return self._items(request, tenant_id, return self._items(request, tenant_id,
net_details=True, port_details=False) net_details=True, port_details=False)
network = self._plugin.get_network_details(
tenant_id, id)
builder = networks_view.get_view_builder(request)
#build response with details
result = builder.build(network, True)
return dict(networks=result)
except exception.NetworkNotFound as e: except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e)) return faults.Fault(faults.NetworkNotFound(e))
@ -97,9 +103,9 @@ class Controller(common.QuantumController):
self._network_ops_param_list) 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._plugin.\
create_network(tenant_id, create_network(tenant_id,
request_params['network-name']) request_params['net-name'])
builder = networks_view.get_view_builder(request) builder = networks_view.get_view_builder(request)
result = builder.build(network) result = builder.build(network)
return dict(networks=result) return dict(networks=result)
@ -113,19 +119,16 @@ class Controller(common.QuantumController):
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, self._plugin.rename_network(tenant_id, id,
id, request_params['network-name']) request_params['net-name'])
return exc.HTTPAccepted()
builder = networks_view.get_view_builder(request)
result = builder.build(network, True)
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, request, 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._plugin.delete_network(tenant_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

@ -42,9 +42,9 @@ class Controller(common.QuantumController):
"attributes": { "attributes": {
"port": ["id", "state"], }, }, } "port": ["id", "state"], }, }, }
def __init__(self, plugin_conf_file=None): def __init__(self, plugin):
self._resource_name = 'port' self._resource_name = 'port'
super(Controller, self).__init__() super(Controller, self).__init__(plugin)
def index(self, request, 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 """
@ -53,7 +53,7 @@ class Controller(common.QuantumController):
def _items(self, request, 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._plugin.get_all_ports(tenant_id, network_id)
builder = ports_view.get_view_builder(request) 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]
@ -64,7 +64,7 @@ class Controller(common.QuantumController):
def show(self, request, 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._plugin.get_port_details(
tenant_id, network_id, id) tenant_id, network_id, id)
builder = ports_view.get_view_builder(request) builder = ports_view.get_view_builder(request)
#build response with details #build response with details
@ -84,7 +84,7 @@ class Controller(common.QuantumController):
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._plugin.create_port(tenant_id,
network_id, network_id,
request_params['port-state']) request_params['port-state'])
builder = ports_view.get_view_builder(request) builder = ports_view.get_view_builder(request)
@ -104,8 +104,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e: except exc.HTTPError as e:
return faults.Fault(e) return faults.Fault(e)
try: try:
port = self.network_manager.\ port = self._plugin.update_port(tenant_id, network_id, id,
update_port(tenant_id, network_id, id,
request_params['port-state']) request_params['port-state'])
builder = ports_view.get_view_builder(request) builder = ports_view.get_view_builder(request)
result = builder.build(port, True) result = builder.build(port, True)
@ -121,7 +120,7 @@ class Controller(common.QuantumController):
""" 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:
self.network_manager.delete_port(tenant_id, network_id, id) self._plugin.delete_port(tenant_id, network_id, id)
return exc.HTTPAccepted() return exc.HTTPAccepted()
# TODO(salvatore-orlando): Handle portInUse error # TODO(salvatore-orlando): Handle portInUse error
except exception.NetworkNotFound as e: except exception.NetworkNotFound as e:
@ -133,8 +132,9 @@ class Controller(common.QuantumController):
def get_resource(self, request, tenant_id, network_id, id): def get_resource(self, request, tenant_id, network_id, id):
try: try:
result = self.network_manager.get_port_details( result = self._plugin.get_port_details(
tenant_id, network_id, id).get('attachment', None) tenant_id, network_id, id).get('attachment-id',
None)
return dict(attachment=result) return dict(attachment=result)
except exception.NetworkNotFound as e: except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e)) return faults.Fault(faults.NetworkNotFound(e))
@ -142,8 +142,6 @@ class Controller(common.QuantumController):
return faults.Fault(faults.PortNotFound(e)) return faults.Fault(faults.PortNotFound(e))
def attach_resource(self, request, tenant_id, network_id, id): def attach_resource(self, request, tenant_id, network_id, id):
content_type = request.best_match_content_type()
print "Content type:%s" % content_type
try: try:
request_params = \ request_params = \
self._parse_request_params(request, self._parse_request_params(request,
@ -151,7 +149,7 @@ class Controller(common.QuantumController):
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._plugin.plug_interface(tenant_id,
network_id, id, network_id, id,
request_params['attachment-id']) request_params['attachment-id'])
return exc.HTTPAccepted() return exc.HTTPAccepted()
@ -166,7 +164,7 @@ class Controller(common.QuantumController):
def detach_resource(self, request, 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._plugin.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:

View File

@ -240,6 +240,7 @@ def api_create_port(client, *args):
def delete_port(manager, *args): def delete_port(manager, *args):
tid, nid, pid = args tid, nid, pid = args
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))
@ -299,7 +300,8 @@ def api_plug_iface(client, *args):
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid, LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid,
pid, output)) pid, output))
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):
@ -318,8 +320,8 @@ def api_unplug_iface(client, *args):
output = res.read() output = res.read()
LOG.debug(output) LOG.debug(output)
if res.status != 202: if res.status != 202:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid, LOG.error("Failed to unplug iface from port \"%s\": %s" % \
pid, output)) (pid, output))
return return
print "Unplugged interface from port:%s on network:%s" % (pid, nid) print "Unplugged interface from port:%s on network:%s" % (pid, nid)

View File

@ -190,7 +190,7 @@ def parse_isotime(timestr):
return datetime.datetime.strptime(timestr, TIME_FORMAT) return datetime.datetime.strptime(timestr, TIME_FORMAT)
def getPluginFromConfig(file="config.ini"): def get_plugin_from_config(file="config.ini"):
Config = ConfigParser.ConfigParser() Config = ConfigParser.ConfigParser()
Config.read(file) Config.read(file)
return Config.get("PLUGIN", "provider") return Config.get("PLUGIN", "provider")

View File

@ -19,7 +19,8 @@
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker, exc from sqlalchemy.orm import sessionmaker, exc
import models
from quantum.db import models
_ENGINE = None _ENGINE = None
_MAKER = None _MAKER = None
@ -42,6 +43,13 @@ def configure_db(options):
register_models() register_models()
def clear_db():
global _ENGINE
assert _ENGINE
for table in reversed(BASE.metadata.sorted_tables):
_ENGINE.execute(table.delete())
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
@ -72,9 +80,10 @@ def network_create(tenant_id, name):
net = None net = None
try: try:
net = session.query(models.Network).\ net = session.query(models.Network).\
filter_by(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 %(name)s already " \
"exists for tenant %(tenant_id)s" % locals())
except exc.NoResultFound: except exc.NoResultFound:
with session.begin(): with session.begin():
net = models.Network(tenant_id, name) net = models.Network(tenant_id, name)
@ -104,7 +113,7 @@ 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(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)
@ -128,10 +137,11 @@ def network_destroy(net_id):
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, state=None):
session = get_session() session = get_session()
with session.begin(): with session.begin():
port = models.Port(net_id) port = models.Port(net_id)
port['state'] = state or 'DOWN'
session.add(port) session.add(port)
session.flush() session.flush()
return port return port
@ -154,9 +164,20 @@ def port_get(port_id):
raise Exception("No port found with id = %s " % port_id) raise Exception("No port found with id = %s " % port_id)
def port_set_state(port_id, new_state):
port = port_get(port_id)
if port:
session = get_session()
port.state = new_state
session.merge(port)
session.flush()
return port
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 = []
if new_interface_id != "":
try: try:
ports = session.query(models.Port).\ ports = session.query(models.Port).\
filter_by(interface_id=new_interface_id).\ filter_by(interface_id=new_interface_id).\
@ -174,6 +195,14 @@ def port_set_attachment(port_id, new_interface_id):
% (new_interface_id)) % (new_interface_id))
def port_unset_attachment(port_id):
session = get_session()
port = port_get(port_id)
port.interface_id = None
session.merge(port)
session.flush
def port_destroy(port_id): def port_destroy(port_id):
session = get_session() session = get_session()
try: try:

View File

@ -19,14 +19,49 @@
import uuid import uuid
from sqlalchemy import Column, Integer, String, ForeignKey from sqlalchemy import Column, 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, object_mapper
BASE = declarative_base() BASE = declarative_base()
class Port(BASE): class QuantumBase(object):
"""Base class for Quantum Models."""
def __setitem__(self, key, value):
setattr(self, key, value)
def __getitem__(self, key):
return getattr(self, key)
def get(self, key, default=None):
return getattr(self, key, default)
def __iter__(self):
self._i = iter(object_mapper(self).columns)
return self
def next(self):
n = self._i.next().name
return n, getattr(self, n)
def update(self, values):
"""Make the model object behave like a dict"""
for k, v in values.iteritems():
setattr(self, k, v)
def iteritems(self):
"""Make the model object behave like a dict.
Includes attributes from joins."""
local = dict(self)
joined = dict([(k, v) for k, v in self.__dict__.iteritems()
if not k[0] == '_'])
local.update(joined)
return local.iteritems()
class Port(BASE, QuantumBase):
"""Represents a port on a quantum network""" """Represents a port on a quantum network"""
__tablename__ = 'ports' __tablename__ = 'ports'
@ -34,17 +69,20 @@ class Port(BASE):
network_id = Column(String(255), ForeignKey("networks.uuid"), network_id = Column(String(255), ForeignKey("networks.uuid"),
nullable=False) nullable=False)
interface_id = Column(String(255)) interface_id = Column(String(255))
# Port state - Hardcoding string value at the moment
state = Column(String(8))
def __init__(self, network_id): def __init__(self, network_id):
self.uuid = uuid.uuid4() self.uuid = str(uuid.uuid4())
self.network_id = network_id self.network_id = network_id
self.state = "DOWN"
def __repr__(self): def __repr__(self):
return "<Port(%s,%s,%s)>" % (self.uuid, self.network_id, return "<Port(%s,%s,%s,%s)>" % (self.uuid, self.network_id,
self.interface_id) self.state, self.interface_id)
class Network(BASE): class Network(BASE, QuantumBase):
"""Represents a quantum network""" """Represents a quantum network"""
__tablename__ = 'networks' __tablename__ = 'networks'
@ -54,7 +92,7 @@ class Network(BASE):
ports = relation(Port, order_by=Port.uuid, backref="network") ports = relation(Port, order_by=Port.uuid, backref="network")
def __init__(self, tenant_id, name): def __init__(self, tenant_id, name):
self.uuid = uuid.uuid4() self.uuid = str(uuid.uuid4())
self.tenant_id = tenant_id self.tenant_id = tenant_id
self.name = name self.name = name

View File

@ -24,15 +24,16 @@ 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 logging
import os import os
gettext.install('quantum', unicode=1)
import os gettext.install('quantum', unicode=1)
from common import utils from common import utils
from quantum_plugin_base import QuantumPluginBase from quantum_plugin_base import QuantumPluginBase
CONFIG_FILE = "plugins.ini" CONFIG_FILE = "plugins.ini"
LOG = logging.getLogger('quantum.manager')
def find_config(basepath): def find_config(basepath):
@ -43,22 +44,27 @@ def find_config(basepath):
class QuantumManager(object): class QuantumManager(object):
def __init__(self, options=None, config_file=None):
def __init__(self, config=None): if config_file == None:
if config == None:
self.configuration_file = find_config( self.configuration_file = find_config(
os.path.abspath(os.path.dirname(__file__))) os.path.abspath(os.path.dirname(__file__)))
else: else:
self.configuration_file = config self.configuration_file = config_file
plugin_location = utils.getPluginFromConfig(self.configuration_file) # If no options have been provided, create an empty dict
plugin_klass = utils.import_class(plugin_location) if not options:
options = {}
if not 'plugin_provider' in options:
options['plugin_provider'] = \
utils.get_plugin_from_config(self.configuration_file)
LOG.debug("Plugin location:%s", options['plugin_provider'])
plugin_klass = utils.import_class(options['plugin_provider'])
if not issubclass(plugin_klass, QuantumPluginBase): if not issubclass(plugin_klass, QuantumPluginBase):
raise Exception("Configured Quantum plug-in " \ raise Exception("Configured Quantum plug-in " \
"didn't pass compatibility test") "didn't pass compatibility test")
else: else:
print("Successfully imported Quantum plug-in." \ LOG.debug("Successfully imported Quantum plug-in." \
"All compatibility tests passed\n") "All compatibility tests passed")
self.plugin = plugin_klass() self.plugin = plugin_klass()
def get_manager(self): def get_plugin(self):
return self.plugin return self.plugin

View File

@ -1,3 +1,4 @@
[PLUGIN] [PLUGIN]
# Quantum plugin provider module # Quantum plugin provider module
provider = quantum.plugins.SamplePlugin.FakePlugin #provider = quantum.plugins.SamplePlugin.FakePlugin
provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin

View File

@ -14,8 +14,14 @@
# 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.
# @author: Salvatore Orlando, Citrix
import logging
from quantum.common import exceptions as exc from quantum.common import exceptions as exc
from quantum.db import api as db
LOG = logging.getLogger('quantum.plugins.SamplePlugin')
class QuantumEchoPlugin(object): class QuantumEchoPlugin(object):
@ -220,63 +226,41 @@ class FakePlugin(object):
client/cli/api development client/cli/api development
""" """
#static data for networks and ports
_port_dict_1 = {
1: {'port-id': 1,
'port-state': 'DOWN',
'attachment': None},
2: {'port-id': 2,
'port-state': 'ACTIVE',
'attachment': None}}
_port_dict_2 = {
1: {'port-id': 1,
'port-state': 'ACTIVE',
'attachment': 'SomeFormOfVIFID'},
2: {'port-id': 2,
'port-state': 'DOWN',
'attachment': None}}
_networks = {'001':
{
'net-id': '001',
'net-name': 'pippotest',
'net-ports': _port_dict_1
},
'002':
{
'net-id': '002',
'net-name': 'cicciotest',
'net-ports': _port_dict_2}}
def __init__(self): def __init__(self):
FakePlugin._net_counter = len(FakePlugin._networks) db.configure_db({'sql_connection': 'sqlite:///:memory:'})
FakePlugin._net_counter = 0
def _get_network(self, tenant_id, network_id): def _get_network(self, tenant_id, network_id):
network = FakePlugin._networks.get(network_id) try:
if not network: network = db.network_get(network_id)
except:
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)) try:
if not port: port = db.port_get(port_id)
except:
raise exc.PortNotFound(net_id=network_id, port_id=port_id)
# Port must exist and belong to the appropriate network.
if port['network_id'] != net['uuid']:
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 ('ACTIVE', '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) for port in db.port_list(network_id):
for port in network['net-ports'].values(): if port['interface_id'] == 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['interface_id'],
att_port_id=port['port-id']) att_port_id=port['uuid'])
def get_all_networks(self, tenant_id): def get_all_networks(self, tenant_id):
""" """
@ -284,49 +268,47 @@ class FakePlugin(object):
<network_uuid, network_name> for <network_uuid, network_name> for
the specified tenant. the specified tenant.
""" """
print("get_all_networks() called\n") LOG.debug("FakePlugin.get_all_networks() called")
return FakePlugin._networks.values() nets = []
for net in db.network_list(tenant_id):
net_item = {'net-id': str(net.uuid),
'net-name': net.name}
nets.append(net_item)
return nets
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
are attached to the network are attached to the network
""" """
print("get_network_details() called\n") LOG.debug("FakePlugin.get_network_details() called")
return self._get_network(tenant_id, net_id) net = self._get_network(tenant_id, net_id)
return {'net-id': str(net.uuid),
'net-name': net.name}
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") LOG.debug("FakePlugin.create_network() called")
FakePlugin._net_counter += 1 new_net = db.network_create(tenant_id, net_name)
new_net_id = ("0" * (3 - len(str(FakePlugin._net_counter)))) + \ # Return uuid for newly created network as net-id.
str(FakePlugin._net_counter) return {'net-id': new_net['uuid']}
print new_net_id
new_net_dict = {'net-id': new_net_id,
'net-name': net_name,
'net-ports': {}}
FakePlugin._networks[new_net_id] = new_net_dict
# return network_id of the created network
return new_net_dict
def delete_network(self, tenant_id, net_id): def delete_network(self, tenant_id, net_id):
""" """
Deletes the network with the specified network identifier Deletes the network with the specified network identifier
belonging to the specified tenant. belonging to the specified tenant.
""" """
print("delete_network() called\n") LOG.debug("FakePlugin.delete_network() called")
net = FakePlugin._networks.get(net_id) net = self._get_network(tenant_id, net_id)
# Verify that no attachments are plugged into the network # Verify that no attachments are plugged into the network
if net: if net:
if net['net-ports']: for port in db.port_list(net_id):
for port in net['net-ports'].values(): if port['interface_id']:
if port['attachment']:
raise exc.NetworkInUse(net_id=net_id) raise exc.NetworkInUse(net_id=net_id)
FakePlugin._networks.pop(net_id) db.network_destroy(net_id)
return net return net
# Network not found # Network not found
raise exc.NetworkNotFound(net_id=net_id) raise exc.NetworkNotFound(net_id=net_id)
@ -336,9 +318,12 @@ class FakePlugin(object):
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") LOG.debug("FakePlugin.rename_network() called")
try:
db.network_rename(net_id, tenant_id, new_name)
except:
raise exc.NetworkNotFound(net_id=net_id)
net = self._get_network(tenant_id, net_id) net = self._get_network(tenant_id, net_id)
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):
@ -346,45 +331,49 @@ class FakePlugin(object):
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") LOG.debug("FakePlugin.get_all_ports() called")
network = self._get_network(tenant_id, net_id) port_ids = []
ports_on_net = network['net-ports'].values() ports = db.port_list(net_id)
return ports_on_net for x in ports:
d = {'port-id': str(x.uuid)}
port_ids.append(d)
return port_ids
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") LOG.debug("FakePlugin.get_port_details() called")
return self._get_port(tenant_id, net_id, port_id) port = self._get_port(tenant_id, net_id, port_id)
return {'port-id': str(port.uuid),
'attachment-id': port.interface_id,
'port-state': port.state}
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.
""" """
print("create_port() called\n") LOG.debug("FakePlugin.create_port() called")
net = self._get_network(tenant_id, net_id) # verify net_id
# check port state self._get_network(tenant_id, net_id)
# TODO(salvatore-orlando): Validate port state in API? port = db.port_create(net_id, port_state)
self._validate_port_state(port_state) port_item = {'port-id': str(port.uuid)}
ports = net['net-ports'] return port_item
new_port_id = max(ports.keys()) + 1
new_port_dict = {'port-id': new_port_id,
'port-state': port_state,
'attachment': None}
ports[new_port_id] = new_port_dict
return new_port_dict
def update_port(self, tenant_id, net_id, port_id, port_state): def update_port(self, tenant_id, net_id, port_id, new_state):
""" """
Updates the state of a port on the specified Virtual Network. Updates the state of a port on the specified Virtual Network.
""" """
print("create_port() called\n") LOG.debug("FakePlugin.update_port() called")
port = self._get_port(tenant_id, net_id, port_id) #validate port and network ids
self._validate_port_state(port_state) self._get_network(tenant_id, net_id)
port['port-state'] = port_state self._get_port(tenant_id, net_id, port_id)
return port self._validate_port_state(new_state)
db.port_set_state(port_id, new_state)
port_item = {'port-id': port_id,
'port-state': new_state}
return port_item
def delete_port(self, tenant_id, net_id, port_id): def delete_port(self, tenant_id, net_id, port_id):
""" """
@ -393,39 +382,42 @@ class FakePlugin(object):
the remote interface is first un-plugged and then the port the remote interface is first un-plugged and then the port
is deleted. is deleted.
""" """
print("delete_port() called\n") LOG.debug("FakePlugin.delete_port() called")
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['interface_id']:
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['interface_id'])
try: try:
net['net-ports'].pop(int(port_id)) port = db.port_destroy(port_id)
except KeyError: except Exception, e:
raise exc.PortNotFound(net_id=net_id, port_id=port_id) raise Exception("Failed to delete port: %s" % str(e))
d = {}
d["port-id"] = str(port.uuid)
return d
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") LOG.debug("FakePlugin.plug_interface() called")
# Validate attachment # Validate attachment
self._validate_attachment(tenant_id, net_id, port_id, self._validate_attachment(tenant_id, net_id, port_id,
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['interface_id']:
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['interface_id'])
port['attachment'] = remote_interface_id db.port_set_attachment(port_id, 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
specified Virtual Network. specified Virtual Network.
""" """
print("unplug_interface() called\n") LOG.debug("FakePlugin.unplug_interface() called")
port = self._get_port(tenant_id, net_id, port_id) self._get_port(tenant_id, net_id, port_id)
# 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 db.port_unset_attachment(port_id)

View File

@ -62,20 +62,25 @@ mysql> FLUSH PRIVILEGES;
distribution tarball (see below) and the agent will use the credentials here distribution tarball (see below) and the agent will use the credentials here
to access the database. to access the database.
# -- Agent configuration # -- XenServer Agent configuration
- Create the agent distribution tarball - Create the agent distribution tarball
$ make agent-dist $ make agent-dist
- Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova - Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova
compute node) compute node)
- Unpack the tarball and run 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. 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
# -- KVM Agent configuration
- Copy ovs_quantum_agent.py and ovs_quantum_plugin.ini to the Linux host and run:
$ python ovs_quantum_agent.py ovs_quantum_plugin.ini
# -- Getting quantum up and running # -- Getting quantum up and running
- Start quantum [on the quantum service host]: - Start quantum [on the quantum service host]:

View File

@ -130,40 +130,40 @@ class OVSBridge:
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
# to quantum. We have this here for now as it lets us work with
# unmodified nova
def xapi_get_port(self, name):
external_ids = self.db_get_map("Interface", name, "external_ids")
if "attached-mac" not in external_ids:
return None
vm_uuid = external_ids.get("xs-vm-uuid", "")
if len(vm_uuid) == 0:
return None
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" \
% vm_uuid).readline().strip()
if len(res) == 0:
return None
external_ids["iface-id"] = res
LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res))
self.set_db_attribute("Interface", name,
"external-ids:iface-id", res)
ofport = self.db_get_val("Interface", name, "ofport")
return VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
# returns a VIF object for each VIF port # returns a VIF object for each VIF port
def get_vif_ports(self): def get_vif_ports(self):
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 "iface-id" in external_ids and "attached-mac" in external_ids: if "xs-vm-uuid" in external_ids:
ofport = self.db_get_val("Interface", name, "ofport") p = xapi_get_port(name)
p = VifPort(name, ofport, external_ids["iface-id"], if p is not None:
external_ids["attached-mac"], self)
edge_ports.append(p) edge_ports.append(p)
else: elif "iface-id" in external_ids and "attached-mac" in external_ids:
# iface-id might not be set. See if we can figure it out and
# set it here.
external_ids = self.db_get_map("Interface", name,
"external_ids")
if "attached-mac" not in external_ids:
continue
vif_uuid = external_ids.get("xs-vif-uuid", "")
if len(vif_uuid) == 0:
continue
LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid)
res = os.popen("xe vif-param-get param-name=other-config "
"uuid=%s | grep nicira-iface-id | "
"awk '{print $2}'"
% vif_uuid).readline()
res = res.strip()
if len(res) == 0:
continue
external_ids["iface-id"] = res
LOG.info("Setting interface \"%s\" iface-id to \"%s\""
% (name, res))
self.set_db_attribute("Interface", name,
"external-ids:iface-id", res)
ofport = self.db_get_val("Interface", name, "ofport") 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)
@ -171,13 +171,15 @@ class OVSBridge:
return edge_ports return edge_ports
class OVSNaaSPlugin: 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)
def port_unbound(self, port, still_exists): def port_unbound(self, port, still_exists):
if still_exists: if still_exists:
@ -186,13 +188,8 @@ class OVSNaaSPlugin:
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)
self.int_br.remove_all_flows() self.int_br.remove_all_flows()
# drop all traffic on the 'dead vlan' # switch all traffic using L2 learning
self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop")
# switch all other traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal") self.int_br.add_flow(priority=1, actions="normal")
# FIXME send broadcast everywhere, regardless of tenant
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff",
# actions="normal")
def daemon_loop(self, conn): def daemon_loop(self, conn):
self.local_vlan_map = {} self.local_vlan_map = {}
@ -201,7 +198,7 @@ class OVSNaaSPlugin:
while True: while True:
cursor = conn.cursor() cursor = conn.cursor()
cursor.execute("SELECT * FROM network_bindings") cursor.execute("SELECT * FROM ports")
rows = cursor.fetchall() rows = cursor.fetchall()
cursor.close() cursor.close()
all_bindings = {} all_bindings = {}
@ -227,21 +224,25 @@ class OVSNaaSPlugin:
# no binding, put him on the 'dead vlan' # no binding, put him on the 'dead vlan'
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,
match="in_port=%s" % p.ofport, actions="drop")
old_b = old_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) 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"
% (old_b, str(p))) % (old_b, str(p)))
self.port_unbound(p, True) self.port_unbound(p, True)
if new_b is not None: if new_b is not None:
LOG.info("Adding binding to net-id = %s for %s" \
% (new_b, str(p)))
# If we don't have a binding we have to stick it on # If we don't have a binding we have to stick it on
# the dead vlan # the dead vlan
vlan_id = vlan_bindings.get(all_bindings[p.vif_id], vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
"4095") "4095")
self.port_bound(p, vlan_id) self.port_bound(p, vlan_id)
LOG.info("Adding binding to net-id = %s " \
"for %s on vlan %s" % (new_b, str(p), vlan_id))
for 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)
@ -251,8 +252,6 @@ class OVSNaaSPlugin:
old_vif_ports = new_vif_ports old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings old_local_bindings = new_local_bindings
self.int_br.run_cmd(["bash",
"/etc/xapi.d/plugins/set_external_ids.sh"])
time.sleep(2) time.sleep(2)
if __name__ == "__main__": if __name__ == "__main__":
@ -291,7 +290,7 @@ if __name__ == "__main__":
LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host)) LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
conn = MySQLdb.connect(host=db_host, user=db_user, conn = MySQLdb.connect(host=db_host, user=db_user,
passwd=db_pass, db=db_name) passwd=db_pass, db=db_name)
plugin = OVSNaaSPlugin(integ_br) plugin = OVSQuantumAgent(integ_br)
plugin.daemon_loop(conn) plugin.daemon_loop(conn)
finally: finally:
if conn: if conn:

View File

@ -1,15 +0,0 @@
#!/bin/sh
VIFLIST=`xe vif-list params=uuid --minimal | sed s/,/" "/g`
for VIF_UUID in $VIFLIST; do
DEVICE_NUM=`xe vif-list params=device uuid=$VIF_UUID --minimal`
VM_NAME=`xe vif-list params=vm-name-label uuid=$VIF_UUID --minimal`
NAME="$VM_NAME-eth$DEVICE_NUM"
echo "Vif: $VIF_UUID is '$NAME'"
xe vif-param-set uuid=$VIF_UUID other-config:nicira-iface-id="$NAME"
done
ps auxw | grep -v grep | grep ovs-xapi-sync > /dev/null 2>&1
if [ $? -eq 0 ]; then
killall -HUP ovs-xapi-sync
fi

View File

@ -56,21 +56,3 @@ def remove_vlan_binding(netid):
except exc.NoResultFound: except exc.NoResultFound:
pass pass
session.flush() session.flush()
def update_network_binding(netid, ifaceid):
session = db.get_session()
# Add to or delete from the bindings table
if ifaceid == None:
try:
binding = session.query(ovs_models.NetworkBinding).\
filter_by(network_id=netid).\
one()
session.delete(binding)
except exc.NoResultFound:
raise Exception("No binding found with network_id = %s" % netid)
else:
binding = ovs_models.NetworkBinding(netid, ifaceid)
session.add(binding)
session.flush()

View File

@ -26,23 +26,6 @@ from sqlalchemy.orm import relation
from quantum.db.models import BASE from quantum.db.models import BASE
class NetworkBinding(BASE):
"""Represents a binding of network_id, vif_id"""
__tablename__ = 'network_bindings'
id = Column(Integer, primary_key=True, autoincrement=True)
network_id = Column(String(255))
vif_id = Column(String(255))
def __init__(self, network_id, vif_id):
self.network_id = network_id
self.vif_id = vif_id
def __repr__(self):
return "<NetworkBinding(%s,%s)>" % \
(self.network_id, self.vif_id)
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

@ -1,9 +1,9 @@
[DATABASE] [DATABASE]
name = ovs_naas name = ovs_quantum
user = root user = root
pass = foobar pass = nova
host = 127.0.0.1 host = 127.0.0.1
port = 3306 port = 3306
[OVS] [OVS]
integration-bridge = xapi1 integration-bridge = br100

View File

@ -200,11 +200,9 @@ class OVSQuantumPlugin(QuantumPluginBase):
def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id): def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
db.port_set_attachment(port_id, remote_iface_id) db.port_set_attachment(port_id, remote_iface_id)
ovs_db.update_network_binding(net_id, remote_iface_id)
def unplug_interface(self, tenant_id, net_id, port_id): def unplug_interface(self, tenant_id, net_id, port_id):
db.port_set_attachment(port_id, "") db.port_set_attachment(port_id, "")
ovs_db.update_network_binding(net_id, None)
def get_interface_details(self, tenant_id, net_id, port_id): def get_interface_details(self, tenant_id, net_id, port_id):
res = db.port_get(port_id) res = db.port_get(port_id)

View File

@ -22,6 +22,7 @@ QuantumPluginBase provides the definition of minimum set of
methods that needs to be implemented by a Quantum Plug-in. methods that needs to be implemented by a Quantum Plug-in.
""" """
import inspect
from abc import ABCMeta, abstractmethod from abc import ABCMeta, abstractmethod
@ -242,7 +243,17 @@ class QuantumPluginBase(object):
""" """
if cls is QuantumPluginBase: if cls is QuantumPluginBase:
for method in cls.__abstractmethods__: for method in cls.__abstractmethods__:
if any(method in base.__dict__ for base in klass.__mro__): method_ok = False
for base in klass.__mro__:
if method in base.__dict__:
fn_obj = base.__dict__[method]
if inspect.isfunction(fn_obj):
abstract_fn_obj = cls.__dict__[method]
arg_count = fn_obj.func_code.co_argcount
expected_arg_count = \
abstract_fn_obj.func_code.co_argcount
method_ok = arg_count == expected_arg_count
if method_ok:
continue continue
return NotImplemented return NotImplemented
return True return True

View File

@ -63,6 +63,7 @@ To run a single functional test module::
""" """
import gettext import gettext
import logging
import os import os
import unittest import unittest
import sys import sys
@ -281,12 +282,19 @@ class QuantumTestRunner(core.TextTestRunner):
if __name__ == '__main__': if __name__ == '__main__':
# Set up test logger.
logger = logging.getLogger()
hdlr = logging.StreamHandler()
formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
hdlr.setFormatter(formatter)
logger.addHandler(hdlr)
logger.setLevel(logging.DEBUG)
working_dir = os.path.abspath("tests") working_dir = os.path.abspath("tests")
c = config.Config(stream=sys.stdout, c = config.Config(stream=sys.stdout,
env=os.environ, env=os.environ,
verbosity=3, verbosity=3,
workingDir=working_dir) workingDir=working_dir)
runner = QuantumTestRunner(stream=c.stream, runner = QuantumTestRunner(stream=c.stream,
verbosity=c.verbosity, verbosity=c.verbosity,
config=c) config=c)

View File

@ -2,7 +2,7 @@
function usage { function usage {
echo "Usage: $0 [OPTION]..." echo "Usage: $0 [OPTION]..."
echo "Run Melange's test suite(s)" echo "Run Quantum's test suite(s)"
echo "" echo ""
echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" 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 " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment"

View File

@ -1,99 +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,137 +0,0 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Citrix Systems
# Copyright 2011 Nicira Networks
# 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
import simplejson
import sys
import unittest
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'
FORMAT = "json"
test_network1_data = \
{'network': {'network-name': 'test1'}}
test_network2_data = \
{'network': {'network-name': 'test2'}}
def print_response(res):
content = res.read()
print "Status: %s" % res.status
print "Content: %s" % content
return content
class QuantumTest(unittest.TestCase):
def setUp(self):
self.client = MiniClient(HOST, PORT, USE_SSL)
def create_network(self, data):
content_type = "application/" + FORMAT
body = Serializer().serialize(data, content_type)
res = self.client.do_request(TENANT_ID, 'POST', "/networks." + FORMAT,
body=body)
self.assertEqual(res.status, 200, "bad response: %s" % res.read())
def test_listNetworks(self):
self.create_network(test_network1_data)
self.create_network(test_network2_data)
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
self.assertEqual(res.status, 200, "bad response: %s" % res.read())
def test_createNetwork(self):
self.create_network(test_network1_data)
def test_createPort(self):
self.create_network(test_network1_data)
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
resdict = simplejson.loads(res.read())
for n in resdict["networks"]:
net_id = n["id"]
# Step 1 - List Ports for network (should not find any)
res = self.client.do_request(TENANT_ID, 'GET',
"/networks/%s/ports.%s" % (net_id, FORMAT))
self.assertEqual(res.status, 200, "Bad response: %s" % res.read())
output = res.read()
self.assertTrue(len(output) == 0,
"Found unexpected ports: %s" % output)
# Step 2 - Create Port for network
res = self.client.do_request(TENANT_ID, 'POST',
"/networks/%s/ports.%s" % (net_id, FORMAT))
self.assertEqual(res.status, 200, "Bad response: %s" % output)
# Step 3 - List Ports for network (again); should find one
res = self.client.do_request(TENANT_ID, 'GET',
"/networks/%s/ports.%s" % (net_id, FORMAT))
output = res.read()
self.assertEqual(res.status, 200, "Bad response: %s" % output)
resdict = simplejson.loads(output)
ids = []
for p in resdict["ports"]:
ids.append(p["id"])
self.assertTrue(len(ids) == 1,
"Didn't find expected # of ports (1): %s" % ids)
def test_renameNetwork(self):
self.create_network(test_network1_data)
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
resdict = simplejson.loads(res.read())
net_id = resdict["networks"][0]["id"]
data = test_network1_data.copy()
data['network']['network-name'] = 'test_renamed'
content_type = "application/" + FORMAT
body = Serializer().serialize(data, content_type)
res = self.client.do_request(TENANT_ID, 'PUT',
"/networks/%s.%s" % (net_id, FORMAT), body=body)
resdict = simplejson.loads(res.read())
self.assertTrue(resdict["networks"]["network"]["id"] == net_id,
"Network_rename: renamed network has a different uuid")
self.assertTrue(
resdict["networks"]["network"]["name"] == "test_renamed",
"Network rename didn't take effect")
def delete_networks(self):
# Remove all the networks created on the tenant
res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT)
resdict = simplejson.loads(res.read())
for n in resdict["networks"]:
net_id = n["id"]
res = self.client.do_request(TENANT_ID, 'DELETE',
"/networks/" + net_id + "." + FORMAT)
self.assertEqual(res.status, 202)
def tearDown(self):
pass
#self.delete_networks()
# Standard boilerplate to call the main() function.
if __name__ == '__main__':
suite = unittest.TestLoader().loadTestsFromTestCase(QuantumTest)
unittest.TextTestRunner(verbosity=2).run(suite)

831
tests/unit/test_api.py Normal file
View File

@ -0,0 +1,831 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2010-2011 ????
# 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.
# @author: Brad Hall, Nicira Networks
# @author: Salvatore Orlando, Citrix Systems
import logging
import unittest
import tests.unit.testlib_api as testlib
from quantum import api as server
from quantum.db import api as db
from quantum.common.wsgi import Serializer
LOG = logging.getLogger('quantum.tests.test_api')
class APITest(unittest.TestCase):
def _create_network(self, format, name=None, custom_req_body=None,
expected_res_status=200):
LOG.debug("Creating network")
content_type = "application/" + format
if name:
net_name = name
else:
net_name = self.network_name
network_req = testlib.new_network_request(self.tenant_id,
net_name, format,
custom_req_body)
network_res = network_req.get_response(self.api)
self.assertEqual(network_res.status_int, expected_res_status)
if expected_res_status == 200:
network_data = Serializer().deserialize(network_res.body,
content_type)
return network_data['networks']['network']['id']
def _create_port(self, network_id, port_state, format,
custom_req_body=None, expected_res_status=200):
LOG.debug("Creating port for network %s", network_id)
content_type = "application/%s" % format
port_req = testlib.new_port_request(self.tenant_id, network_id,
port_state, format,
custom_req_body)
port_res = port_req.get_response(self.api)
self.assertEqual(port_res.status_int, expected_res_status)
if expected_res_status == 200:
port_data = Serializer().deserialize(port_res.body, content_type)
return port_data['ports']['port']['id']
def _test_create_network(self, format):
LOG.debug("_test_create_network - format:%s - START", format)
content_type = "application/%s" % format
network_id = self._create_network(format)
show_network_req = testlib.show_network_request(self.tenant_id,
network_id,
format)
show_network_res = show_network_req.get_response(self.api)
self.assertEqual(show_network_res.status_int, 200)
network_data = Serializer().deserialize(show_network_res.body,
content_type)
self.assertEqual(network_id,
network_data['networks']['network']['id'])
LOG.debug("_test_create_network - format:%s - END", format)
def _test_create_network_badrequest(self, format):
LOG.debug("_test_create_network_badrequest - format:%s - START",
format)
bad_body = {'network': {'bad-attribute': 'very-bad'}}
self._create_network(format, custom_req_body=bad_body,
expected_res_status=400)
LOG.debug("_test_create_network_badrequest - format:%s - END",
format)
def _test_list_networks(self, format):
LOG.debug("_test_list_networks - format:%s - START", format)
content_type = "application/%s" % format
self._create_network(format, "net_1")
self._create_network(format, "net_2")
list_network_req = testlib.network_list_request(self.tenant_id,
format)
list_network_res = list_network_req.get_response(self.api)
self.assertEqual(list_network_res.status_int, 200)
network_data = Serializer().deserialize(list_network_res.body,
content_type)
# Check network count: should return 2
self.assertEqual(len(network_data['networks']), 2)
LOG.debug("_test_list_networks - format:%s - END", format)
def _test_show_network(self, format):
LOG.debug("_test_show_network - format:%s - START", format)
content_type = "application/%s" % format
network_id = self._create_network(format)
show_network_req = testlib.show_network_request(self.tenant_id,
network_id,
format)
show_network_res = show_network_req.get_response(self.api)
self.assertEqual(show_network_res.status_int, 200)
network_data = Serializer().deserialize(show_network_res.body,
content_type)
self.assertEqual({'id': network_id, 'name': self.network_name},
network_data['networks']['network'])
LOG.debug("_test_show_network - format:%s - END", format)
def _test_show_network_not_found(self, format):
LOG.debug("_test_show_network_not_found - format:%s - START", format)
show_network_req = testlib.show_network_request(self.tenant_id,
"A_BAD_ID",
format)
show_network_res = show_network_req.get_response(self.api)
self.assertEqual(show_network_res.status_int, 420)
LOG.debug("_test_show_network_not_found - format:%s - END", format)
def _test_rename_network(self, format):
LOG.debug("_test_rename_network - format:%s - START", format)
content_type = "application/%s" % format
new_name = 'new_network_name'
network_id = self._create_network(format)
update_network_req = testlib.update_network_request(self.tenant_id,
network_id,
new_name,
format)
update_network_res = update_network_req.get_response(self.api)
self.assertEqual(update_network_res.status_int, 202)
show_network_req = testlib.show_network_request(self.tenant_id,
network_id,
format)
show_network_res = show_network_req.get_response(self.api)
self.assertEqual(show_network_res.status_int, 200)
network_data = Serializer().deserialize(show_network_res.body,
content_type)
self.assertEqual({'id': network_id, 'name': new_name},
network_data['networks']['network'])
LOG.debug("_test_rename_network - format:%s - END", format)
def _test_rename_network_badrequest(self, format):
LOG.debug("_test_rename_network_badrequest - format:%s - START",
format)
network_id = self._create_network(format)
bad_body = {'network': {'bad-attribute': 'very-bad'}}
update_network_req = testlib.\
update_network_request(self.tenant_id,
network_id, format,
custom_req_body=bad_body)
update_network_res = update_network_req.get_response(self.api)
self.assertEqual(update_network_res.status_int, 400)
LOG.debug("_test_rename_network_badrequest - format:%s - END",
format)
def _test_rename_network_not_found(self, format):
LOG.debug("_test_rename_network_not_found - format:%s - START",
format)
new_name = 'new_network_name'
update_network_req = testlib.update_network_request(self.tenant_id,
"A BAD ID",
new_name,
format)
update_network_res = update_network_req.get_response(self.api)
self.assertEqual(update_network_res.status_int, 420)
LOG.debug("_test_rename_network_not_found - format:%s - END",
format)
def _test_delete_network(self, format):
LOG.debug("_test_delete_network - format:%s - START", format)
content_type = "application/%s" % format
network_id = self._create_network(format)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_network_req = testlib.network_delete_request(self.tenant_id,
network_id,
format)
delete_network_res = delete_network_req.get_response(self.api)
self.assertEqual(delete_network_res.status_int, 202)
list_network_req = testlib.network_list_request(self.tenant_id,
format)
list_network_res = list_network_req.get_response(self.api)
network_list_data = Serializer().deserialize(list_network_res.body,
content_type)
network_count = len(network_list_data['networks'])
self.assertEqual(network_count, 0)
LOG.debug("_test_delete_network - format:%s - END", format)
def _test_delete_network_in_use(self, format):
LOG.debug("_test_delete_network_in_use - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
attachment_id = "test_attachment"
network_id = self._create_network(format)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
port_id = self._create_port(network_id, port_state, format)
#plug an attachment into the port
LOG.debug("Putting attachment into port %s", port_id)
attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
attachment_id)
attachment_res = attachment_req.get_response(self.api)
self.assertEquals(attachment_res.status_int, 202)
LOG.debug("Deleting network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_network_req = testlib.network_delete_request(self.tenant_id,
network_id,
format)
delete_network_res = delete_network_req.get_response(self.api)
self.assertEqual(delete_network_res.status_int, 421)
LOG.debug("_test_delete_network_in_use - format:%s - END", format)
def _test_list_ports(self, format):
LOG.debug("_test_list_ports - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
self._create_port(network_id, port_state, format)
list_port_req = testlib.port_list_request(self.tenant_id,
network_id, format)
list_port_res = list_port_req.get_response(self.api)
self.assertEqual(list_port_res.status_int, 200)
port_data = Serializer().deserialize(list_port_res.body,
content_type)
# Check port count: should return 2
self.assertEqual(len(port_data['ports']), 2)
LOG.debug("_test_list_ports - format:%s - END", format)
def _test_show_port(self, format):
LOG.debug("_test_show_port - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id, port_id,
format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
port_data = Serializer().deserialize(show_port_res.body,
content_type)
self.assertEqual({'id': port_id, 'state': port_state},
port_data['ports']['port'])
LOG.debug("_test_show_port - format:%s - END", format)
def _test_show_port_networknotfound(self, format):
LOG.debug("_test_show_port_networknotfound - format:%s - START",
format)
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
show_port_req = testlib.show_port_request(self.tenant_id,
"A_BAD_ID", port_id,
format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 420)
LOG.debug("_test_show_port_networknotfound - format:%s - END",
format)
def _test_show_port_portnotfound(self, format):
LOG.debug("_test_show_port_portnotfound - format:%s - START", format)
network_id = self._create_network(format)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id,
"A_BAD_ID",
format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 430)
LOG.debug("_test_show_port_portnotfound - format:%s - END", format)
def _test_create_port(self, format):
LOG.debug("_test_create_port - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id, port_id, format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
port_data = Serializer().deserialize(show_port_res.body, content_type)
self.assertEqual(port_id, port_data['ports']['port']['id'])
LOG.debug("_test_create_port - format:%s - END", format)
def _test_create_port_networknotfound(self, format):
LOG.debug("_test_create_port_networknotfound - format:%s - START",
format)
port_state = "ACTIVE"
self._create_port("A_BAD_ID", port_state, format,
expected_res_status=420)
LOG.debug("_test_create_port_networknotfound - format:%s - END",
format)
def _test_create_port_badrequest(self, format):
LOG.debug("_test_create_port_badrequest - format:%s - START", format)
bad_body = {'bad-resource': {'bad-attribute': 'bad-value'}}
network_id = self._create_network(format)
port_state = "ACTIVE"
self._create_port(network_id, port_state, format,
custom_req_body=bad_body, expected_res_status=400)
LOG.debug("_test_create_port_badrequest - format:%s - END", format)
def _test_delete_port(self, format):
LOG.debug("_test_delete_port - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_port_req = testlib.port_delete_request(self.tenant_id,
network_id, port_id,
format)
delete_port_res = delete_port_req.get_response(self.api)
self.assertEqual(delete_port_res.status_int, 202)
list_port_req = testlib.port_list_request(self.tenant_id, network_id,
format)
list_port_res = list_port_req.get_response(self.api)
port_list_data = Serializer().deserialize(list_port_res.body,
content_type)
port_count = len(port_list_data['ports'])
self.assertEqual(port_count, 0)
LOG.debug("_test_delete_port - format:%s - END", format)
def _test_delete_port_in_use(self, format):
LOG.debug("_test_delete_port_in_use - format:%s - START", format)
content_type = "application/" + format
port_state = "ACTIVE"
attachment_id = "test_attachment"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
#plug an attachment into the port
LOG.debug("Putting attachment into port %s", port_id)
attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
attachment_id)
attachment_res = attachment_req.get_response(self.api)
self.assertEquals(attachment_res.status_int, 202)
LOG.debug("Deleting port %(port_id)s for network %(network_id)s"\
" of tenant %(tenant_id)s", locals())
delete_port_req = testlib.port_delete_request(self.tenant_id,
network_id, port_id,
format)
delete_port_res = delete_port_req.get_response(self.api)
self.assertEqual(delete_port_res.status_int, 432)
LOG.debug("_test_delete_port_in_use - format:%s - END", format)
def _test_delete_port_with_bad_id(self, format):
LOG.debug("_test_delete_port_with_bad_id - format:%s - START",
format)
port_state = "ACTIVE"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
# Test for portnotfound
delete_port_req = testlib.port_delete_request(self.tenant_id,
network_id, "A_BAD_ID",
format)
delete_port_res = delete_port_req.get_response(self.api)
self.assertEqual(delete_port_res.status_int, 430)
LOG.debug("_test_delete_port_with_bad_id - format:%s - END", format)
def _test_delete_port_networknotfound(self, format):
LOG.debug("_test_delete_port_networknotfound - format:%s - START",
format)
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
delete_port_req = testlib.port_delete_request(self.tenant_id,
"A_BAD_ID", port_id,
format)
delete_port_res = delete_port_req.get_response(self.api)
self.assertEqual(delete_port_res.status_int, 420)
LOG.debug("_test_delete_port_networknotfound - format:%s - END",
format)
def _test_set_port_state(self, format):
LOG.debug("_test_set_port_state - format:%s - START", format)
content_type = "application/%s" % format
port_state = 'DOWN'
new_port_state = 'ACTIVE'
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
update_port_req = testlib.update_port_request(self.tenant_id,
network_id, port_id,
new_port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 200)
show_port_req = testlib.show_port_request(self.tenant_id,
network_id, port_id,
format)
show_port_res = show_port_req.get_response(self.api)
self.assertEqual(show_port_res.status_int, 200)
network_data = Serializer().deserialize(show_port_res.body,
content_type)
self.assertEqual({'id': port_id, 'state': new_port_state},
network_data['ports']['port'])
LOG.debug("_test_set_port_state - format:%s - END", format)
def _test_set_port_state_networknotfound(self, format):
LOG.debug("_test_set_port_state_networknotfound - format:%s - START",
format)
port_state = 'DOWN'
new_port_state = 'ACTIVE'
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
update_port_req = testlib.update_port_request(self.tenant_id,
"A_BAD_ID", port_id,
new_port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 420)
LOG.debug("_test_set_port_state_networknotfound - format:%s - END",
format)
def _test_set_port_state_portnotfound(self, format):
LOG.debug("_test_set_port_state_portnotfound - format:%s - START",
format)
port_state = 'DOWN'
new_port_state = 'ACTIVE'
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
update_port_req = testlib.update_port_request(self.tenant_id,
network_id,
"A_BAD_ID",
new_port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 430)
LOG.debug("_test_set_port_state_portnotfound - format:%s - END",
format)
def _test_set_port_state_stateinvalid(self, format):
LOG.debug("_test_set_port_state_stateinvalid - format:%s - START",
format)
port_state = 'DOWN'
new_port_state = 'A_BAD_STATE'
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
update_port_req = testlib.update_port_request(self.tenant_id,
network_id, port_id,
new_port_state,
format)
update_port_res = update_port_req.get_response(self.api)
self.assertEqual(update_port_res.status_int, 431)
LOG.debug("_test_set_port_state_stateinvalid - format:%s - END",
format)
def _test_show_attachment(self, format):
LOG.debug("_test_show_attachment - format:%s - START", format)
content_type = "application/%s" % format
port_state = "ACTIVE"
network_id = self._create_network(format)
interface_id = "test_interface"
port_id = self._create_port(network_id, port_state, format)
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
get_attachment_req = testlib.get_attachment_request(self.tenant_id,
network_id,
port_id,
format)
get_attachment_res = get_attachment_req.get_response(self.api)
attachment_data = Serializer().deserialize(get_attachment_res.body,
content_type)
self.assertEqual(attachment_data['attachment'], interface_id)
LOG.debug("_test_show_attachment - format:%s - END", format)
def _test_show_attachment_networknotfound(self, format):
LOG.debug("_test_show_attachment_networknotfound - format:%s - START",
format)
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
get_attachment_req = testlib.get_attachment_request(self.tenant_id,
"A_BAD_ID",
port_id,
format)
get_attachment_res = get_attachment_req.get_response(self.api)
self.assertEqual(get_attachment_res.status_int, 420)
LOG.debug("_test_show_attachment_networknotfound - format:%s - END",
format)
def _test_show_attachment_portnotfound(self, format):
LOG.debug("_test_show_attachment_portnotfound - format:%s - START",
format)
port_state = "ACTIVE"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
get_attachment_req = testlib.get_attachment_request(self.tenant_id,
network_id,
"A_BAD_ID",
format)
get_attachment_res = get_attachment_req.get_response(self.api)
self.assertEqual(get_attachment_res.status_int, 430)
LOG.debug("_test_show_attachment_portnotfound - format:%s - END",
format)
def _test_put_attachment(self, format):
LOG.debug("_test_put_attachment - format:%s - START", format)
port_state = "ACTIVE"
network_id = self._create_network(format)
interface_id = "test_interface"
port_id = self._create_port(network_id, port_state, format)
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
LOG.debug("_test_put_attachment - format:%s - END", format)
def _test_put_attachment_networknotfound(self, format):
LOG.debug("_test_put_attachment_networknotfound - format:%s - START",
format)
port_state = 'DOWN'
interface_id = "test_interface"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
"A_BAD_ID",
port_id,
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 420)
LOG.debug("_test_put_attachment_networknotfound - format:%s - END",
format)
def _test_put_attachment_portnotfound(self, format):
LOG.debug("_test_put_attachment_portnotfound - format:%s - START",
format)
port_state = 'DOWN'
interface_id = "test_interface"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
"A_BAD_ID",
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 430)
LOG.debug("_test_put_attachment_portnotfound - format:%s - END",
format)
def _test_delete_attachment(self, format):
LOG.debug("_test_delete_attachment - format:%s - START", format)
port_state = "ACTIVE"
network_id = self._create_network(format)
interface_id = "test_interface"
port_id = self._create_port(network_id, port_state, format)
put_attachment_req = testlib.put_attachment_request(self.tenant_id,
network_id,
port_id,
interface_id,
format)
put_attachment_res = put_attachment_req.get_response(self.api)
self.assertEqual(put_attachment_res.status_int, 202)
del_attachment_req = testlib.delete_attachment_request(self.tenant_id,
network_id,
port_id,
format)
del_attachment_res = del_attachment_req.get_response(self.api)
self.assertEqual(del_attachment_res.status_int, 202)
LOG.debug("_test_delete_attachment - format:%s - END", format)
def _test_delete_attachment_networknotfound(self, format):
LOG.debug("_test_delete_attachment_networknotfound -" \
" format:%s - START", format)
port_state = "ACTIVE"
network_id = self._create_network(format)
port_id = self._create_port(network_id, port_state, format)
del_attachment_req = testlib.delete_attachment_request(self.tenant_id,
"A_BAD_ID",
port_id,
format)
del_attachment_res = del_attachment_req.get_response(self.api)
self.assertEqual(del_attachment_res.status_int, 420)
LOG.debug("_test_delete_attachment_networknotfound -" \
" format:%s - END", format)
def _test_delete_attachment_portnotfound(self, format):
LOG.debug("_test_delete_attachment_portnotfound - " \
" format:%s - START", format)
port_state = "ACTIVE"
network_id = self._create_network(format)
self._create_port(network_id, port_state, format)
del_attachment_req = testlib.delete_attachment_request(self.tenant_id,
network_id,
"A_BAD_ID",
format)
del_attachment_res = del_attachment_req.get_response(self.api)
self.assertEqual(del_attachment_res.status_int, 430)
LOG.debug("_test_delete_attachment_portnotfound - " \
"format:%s - END", format)
def setUp(self):
options = {}
options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin'
self.api = server.APIRouterV01(options)
self.tenant_id = "test_tenant"
self.network_name = "test_network"
def tearDown(self):
"""Clear the test environment"""
# Remove database contents
db.clear_db()
def test_list_networks_json(self):
self._test_list_networks('json')
def test_list_networks_xml(self):
self._test_list_networks('xml')
def test_create_network_json(self):
self._test_create_network('json')
def test_create_network_xml(self):
self._test_create_network('xml')
def test_create_network_badrequest_json(self):
self._test_create_network_badrequest('json')
def test_create_network_badreqyest_xml(self):
self._test_create_network_badrequest('xml')
def test_show_network_not_found_json(self):
self._test_show_network_not_found('json')
def test_show_network_not_found_xml(self):
self._test_show_network_not_found('xml')
def test_show_network_json(self):
self._test_show_network('json')
def test_show_network_xml(self):
self._test_show_network('xml')
def test_delete_network_json(self):
self._test_delete_network('json')
def test_delete_network_xml(self):
self._test_delete_network('xml')
def test_rename_network_json(self):
self._test_rename_network('json')
def test_rename_network_xml(self):
self._test_rename_network('xml')
def test_rename_network_badrequest_json(self):
self._test_rename_network_badrequest('json')
def test_rename_network_badrequest_xml(self):
self._test_rename_network_badrequest('xml')
def test_rename_network_not_found_json(self):
self._test_rename_network_not_found('json')
def test_rename_network_not_found_xml(self):
self._test_rename_network_not_found('xml')
def test_delete_network_in_use_json(self):
self._test_delete_network_in_use('json')
def test_delete_network_in_use_xml(self):
self._test_delete_network_in_use('xml')
def test_list_ports_json(self):
self._test_list_ports('json')
def test_list_ports_xml(self):
self._test_list_ports('xml')
def test_show_port_json(self):
self._test_show_port('json')
def test_show_port_xml(self):
self._test_show_port('xml')
def test_show_port_networknotfound_json(self):
self._test_show_port_networknotfound('json')
def test_show_port_networknotfound_xml(self):
self._test_show_port_networknotfound('xml')
def test_show_port_portnotfound_json(self):
self._test_show_port_portnotfound('json')
def test_show_port_portnotfound_xml(self):
self._test_show_port_portnotfound('xml')
def test_create_port_json(self):
self._test_create_port('json')
def test_create_port_xml(self):
self._test_create_port('xml')
def test_create_port_networknotfound_json(self):
self._test_create_port_networknotfound('json')
def test_create_port_networknotfound_xml(self):
self._test_create_port_networknotfound('xml')
def test_create_port_badrequest_json(self):
self._test_create_port_badrequest('json')
def test_create_port_badrequest_xml(self):
self._test_create_port_badrequest('xml')
def test_delete_port_xml(self):
self._test_delete_port('xml')
def test_delete_port_json(self):
self._test_delete_port('json')
def test_delete_port_in_use_xml(self):
self._test_delete_port_in_use('xml')
def test_delete_port_in_use_json(self):
self._test_delete_port_in_use('json')
def test_delete_port_networknotfound_xml(self):
self._test_delete_port_networknotfound('xml')
def test_delete_port_networknotfound_json(self):
self._test_delete_port_networknotfound('json')
def test_delete_port_with_bad_id_xml(self):
self._test_delete_port_with_bad_id('xml')
def test_delete_port_with_bad_id_json(self):
self._test_delete_port_with_bad_id('json')
def test_set_port_state_xml(self):
self._test_set_port_state('xml')
def test_set_port_state_json(self):
self._test_set_port_state('json')
def test_set_port_state_networknotfound_xml(self):
self._test_set_port_state_networknotfound('xml')
def test_set_port_state_networknotfound_json(self):
self._test_set_port_state_networknotfound('json')
def test_set_port_state_portnotfound_xml(self):
self._test_set_port_state_portnotfound('xml')
def test_set_port_state_portnotfound_json(self):
self._test_set_port_state_portnotfound('json')
def test_set_port_state_stateinvalid_xml(self):
self._test_set_port_state_stateinvalid('xml')
def test_set_port_state_stateinvalid_json(self):
self._test_set_port_state_stateinvalid('json')
def test_show_attachment_xml(self):
self._test_show_attachment('xml')
def test_show_attachment_json(self):
self._test_show_attachment('json')
def test_show_attachment_networknotfound_xml(self):
self._test_show_attachment_networknotfound('xml')
def test_show_attachment_networknotfound_json(self):
self._test_show_attachment_networknotfound('json')
def test_show_attachment_portnotfound_xml(self):
self._test_show_attachment_portnotfound('xml')
def test_show_attachment_portnotfound_json(self):
self._test_show_attachment_portnotfound('json')
def test_put_attachment_xml(self):
self._test_put_attachment('xml')
def test_put_attachment_json(self):
self._test_put_attachment('json')
def test_put_attachment_networknotfound_xml(self):
self._test_put_attachment_networknotfound('xml')
def test_put_attachment_networknotfound_json(self):
self._test_put_attachment_networknotfound('json')
def test_put_attachment_portnotfound_xml(self):
self._test_put_attachment_portnotfound('xml')
def test_put_attachment_portnotfound_json(self):
self._test_put_attachment_portnotfound('json')
def test_delete_attachment_xml(self):
self._test_delete_attachment('xml')
def test_delete_attachment_json(self):
self._test_delete_attachment('json')
def test_delete_attachment_networknotfound_xml(self):
self._test_delete_attachment_networknotfound('xml')
def test_delete_attachment_networknotfound_json(self):
self._test_delete_attachment_networknotfound('json')
def test_delete_attachment_portnotfound_xml(self):
self._test_delete_attachment_portnotfound('xml')
def test_delete_attachment_portnotfound_json(self):
self._test_delete_attachment_portnotfound('json')

130
tests/unit/testlib_api.py Normal file
View File

@ -0,0 +1,130 @@
import webob
from quantum.common.wsgi import Serializer
def create_request(path, body, content_type, method='GET'):
req = webob.Request.blank(path)
req.method = method
req.headers = {}
req.headers['Accept'] = content_type
req.body = body
return req
def network_list_request(tenant_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_network_request(tenant_id, network_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def new_network_request(tenant_id, network_name='new_name',
format='xml', custom_req_body=None):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
data = custom_req_body or {'network': {'net-name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def update_network_request(tenant_id, network_id, network_name, format='xml',
custom_req_body=None):
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s.%(format)s" % locals()
data = custom_req_body or {'network': {'net-name': '%s' % network_name}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def network_delete_request(tenant_id, network_id, format='xml'):
method = 'DELETE'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def port_list_request(tenant_id, network_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def show_port_request(tenant_id, network_id, port_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks/%(network_id)s" \
"/ports/%(port_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def new_port_request(tenant_id, network_id, port_state,
format='xml', custom_req_body=None):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports.%(format)s" % locals()
data = custom_req_body or {'port': {'port-state': '%s' % port_state}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def port_delete_request(tenant_id, network_id, port_id, format='xml'):
method = 'DELETE'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports/%(port_id)s.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def update_port_request(tenant_id, network_id, port_id, port_state,
format='xml', custom_req_body=None):
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks" \
"/%(network_id)s/ports/%(port_id)s.%(format)s" % locals()
data = custom_req_body or {'port': {'port-state': '%s' % port_state}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def get_attachment_request(tenant_id, network_id, port_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def put_attachment_request(tenant_id, network_id, port_id,
attachment_id, format='xml'):
method = 'PUT'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()
data = {'port': {'attachment-id': attachment_id}}
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def delete_attachment_request(tenant_id, network_id, port_id,
attachment_id, format='xml'):
method = 'DELETE'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports/%(port_id)s/attachment.%(format)s" % locals()
content_type = "application/%s" % format
return create_request(path, None, content_type, method)

174
tools/batch_config.py Normal file
View File

@ -0,0 +1,174 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright 2011 Nicira Networks, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# @author: Dan Wendlandt, Nicira Networks, Inc.
import httplib
import logging as LOG
import json
import socket
import sys
import urllib
from quantum.manager import QuantumManager
from optparse import OptionParser
from quantum.common.wsgi import Serializer
from quantum.cli import MiniClient
FORMAT = "json"
CONTENT_TYPE = "application/" + FORMAT
def delete_all_nets(client, tenant_id):
res = client.do_request(tenant_id, 'GET', "/networks." + FORMAT)
resdict = json.loads(res.read())
LOG.debug(resdict)
for n in resdict["networks"]:
nid = n["id"]
res = client.do_request(tenant_id, 'GET',
"/networks/%s/ports.%s" % (nid, FORMAT))
output = res.read()
if res.status != 200:
LOG.error("Failed to list ports: %s" % output)
continue
rd = json.loads(output)
LOG.debug(rd)
for port in rd["ports"]:
pid = port["id"]
data = {'port': {'attachment-id': ''}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'DELETE',
"/networks/%s/ports/%s/attachment.%s" % \
(nid, pid, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (vid,
pid, output))
continue
LOG.info("Unplugged interface from port:%s on network:%s" % (pid,
nid))
res = client.do_request(tenant_id, 'DELETE',
"/networks/%s/ports/%s.%s" % (nid, pid, FORMAT))
output = res.read()
if res.status != 202:
LOG.error("Failed to delete port: %s" % output)
continue
print "Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid)
res = client.do_request(tenant_id, 'DELETE',
"/networks/" + nid + "." + FORMAT)
status = res.status
if status != 202:
Log.error("Failed to delete network: %s" % nid)
output = res.read()
print output
else:
print "Deleted Virtual Network with ID:%s" % nid
def create_net_with_attachments(net_name, iface_ids):
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"]
print "Created a new Virtual Network %s with ID:%s" % (net_name, nid)
for iface_id in iface_ids:
res = client.do_request(tenant_id, 'POST',
"/networks/%s/ports.%s" % (nid, FORMAT))
output = res.read()
if res.status != 200:
LOG.error("Failed to create port: %s" % output)
continue
rd = json.loads(output)
new_port_id = rd["ports"]["port"]["id"]
print "Created Virtual Port:%s " \
"on Virtual Network:%s" % (new_port_id, nid)
data = {'port': {'attachment-id': '%s' % iface_id}}
body = Serializer().serialize(data, CONTENT_TYPE)
res = client.do_request(tenant_id, 'PUT',
"/networks/%s/ports/%s/attachment.%s" %\
(nid, new_port_id, FORMAT), body=body)
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % \
(iface_id, new_port_id, output))
continue
print "Plugged interface \"%s\" to port:%s on network:%s" % \
(iface_id, new_port_id, nid)
if __name__ == "__main__":
usagestr = "Usage: %prog [OPTIONS] <tenant-id> <config-string> [args]\n" \
"Example config-string: net1=instance-1,instance-2"\
":net2=instance-3,instance-4\n" \
"This string would create two networks: \n" \
"'net1' would have two ports, with iface-ids "\
"instance-1 and instance-2 attached\n" \
"'net2' would have two ports, with iface-ids"\
" instance-3 and instance-4 attached\n"
parser = OptionParser(usage=usagestr)
parser.add_option("-H", "--host", dest="host",
type="string", default="127.0.0.1", help="ip address of api host")
parser.add_option("-p", "--port", dest="port",
type="int", default=9696, help="api poort")
parser.add_option("-s", "--ssl", dest="ssl",
action="store_true", default=False, help="use ssl")
parser.add_option("-v", "--verbose", dest="verbose",
action="store_true", default=False, help="turn on verbose logging")
parser.add_option("-d", "--delete", dest="delete",
action="store_true", default=False, \
help="delete existing tenants networks")
options, args = parser.parse_args()
if options.verbose:
LOG.basicConfig(level=LOG.DEBUG)
else:
LOG.basicConfig(level=LOG.WARN)
if len(args) < 1:
parser.print_help()
help()
sys.exit(1)
nets = {}
tenant_id = args[0]
if len(args) > 1:
config_str = args[1]
for net_str in config_str.split(":"):
arr = net_str.split("=")
net_name = arr[0]
nets[net_name] = arr[1].split(",")
print "nets: %s" % str(nets)
client = MiniClient(options.host, options.port, options.ssl)
if options.delete:
delete_all_nets(client, tenant_id)
for net_name, iface_ids in nets.items():
create_net_with_attachments(net_name, iface_ids)
sys.exit(0)