Adapated plugin infrastructure to allow API to pass options to plugins

Now using in-memory sqlite db for tests on FakePlugin
teardown() now 'resets' the in-memory db
Adding unit tests for APIs
This commit is contained in:
Salvatore Orlando 2011-07-05 17:50:30 +01:00
commit 8af12499a8
21 changed files with 563 additions and 255 deletions

View File

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

View File

@ -19,7 +19,6 @@ import logging
from webob import exc
from quantum import manager
from quantum.common import wsgi
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):
""" Base controller class for Quantum API """
def __init__(self, plugin_conf_file=None):
self._setup_network_manager()
def __init__(self, plugin):
self._plugin = plugin
super(QuantumController, self).__init__()
def _parse_request_params(self, req, params):
@ -65,6 +64,3 @@ class QuantumController(wsgi.Controller):
raise exc.HTTPBadRequest(msg)
results[param_name] = param_value or param.get('default-value')
return results
def _setup_network_manager(self):
self.network_manager = manager.QuantumManager().get_manager()

View File

@ -40,9 +40,9 @@ class Controller(common.QuantumController):
},
}
def __init__(self, plugin_conf_file=None):
def __init__(self, plugin):
self._resource_name = 'network'
super(Controller, self).__init__()
super(Controller, self).__init__(plugin)
def index(self, request, tenant_id):
""" Returns a list of network ids """
@ -51,7 +51,7 @@ class Controller(common.QuantumController):
def _items(self, request, tenant_id, is_detail):
""" 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(request)
result = [builder.build(network, is_detail)['network']
for network in networks]
@ -60,7 +60,7 @@ class Controller(common.QuantumController):
def show(self, request, tenant_id, id):
""" Returns network details for the given network id """
try:
network = self.network_manager.get_network_details(
network = self._plugin.get_network_details(
tenant_id, id)
builder = networks_view.get_view_builder(request)
#build response with details
@ -78,7 +78,7 @@ class Controller(common.QuantumController):
self._network_ops_param_list)
except exc.HTTPError as e:
return faults.Fault(e)
network = self.network_manager.\
network = self._plugin.\
create_network(tenant_id,
request_params['net-name'])
builder = networks_view.get_view_builder(request)
@ -94,7 +94,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
network = self.network_manager.rename_network(tenant_id,
network = self._plugin.rename_network(tenant_id,
id, request_params['network-name'])
builder = networks_view.get_view_builder(request)
@ -106,7 +106,7 @@ class Controller(common.QuantumController):
def delete(self, request, tenant_id, id):
""" Destroys the network with the given id """
try:
self.network_manager.delete_network(tenant_id, id)
self._plugin.delete_network(tenant_id, id)
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:
return faults.Fault(faults.NetworkNotFound(e))

View File

@ -42,9 +42,9 @@ class Controller(common.QuantumController):
"attributes": {
"port": ["id", "state"], }, }, }
def __init__(self, plugin_conf_file=None):
def __init__(self, plugin):
self._resource_name = 'port'
super(Controller, self).__init__()
super(Controller, self).__init__(plugin)
def index(self, request, tenant_id, network_id):
""" 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):
""" Returns a list of networks. """
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)
result = [builder.build(port, is_detail)['port']
for port in ports]
@ -64,7 +64,7 @@ class Controller(common.QuantumController):
def show(self, request, tenant_id, network_id, id):
""" Returns port details for given port and network """
try:
port = self.network_manager.get_port_details(
port = self._plugin.get_port_details(
tenant_id, network_id, id)
builder = ports_view.get_view_builder(request)
#build response with details
@ -84,7 +84,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
port = self.network_manager.create_port(tenant_id,
port = self._plugin.create_port(tenant_id,
network_id,
request_params['port-state'])
builder = ports_view.get_view_builder(request)
@ -104,7 +104,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
port = self.network_manager.update_port(tenant_id, network_id, id,
port = self._plugin.update_port(tenant_id, network_id, id,
request_params['port-state'])
builder = ports_view.get_view_builder(request)
result = builder.build(port, True)
@ -120,7 +120,7 @@ class Controller(common.QuantumController):
""" Destroys the port with the given id """
#look for port state in request
try:
self.network_manager.delete_port(tenant_id, network_id, id)
self._plugin.delete_port(tenant_id, network_id, id)
return exc.HTTPAccepted()
#TODO(salvatore-orlando): Handle portInUse error
except exception.NetworkNotFound as e:
@ -141,9 +141,7 @@ class Controller(common.QuantumController):
except exception.PortNotFound as e:
return faults.Fault(faults.PortNotFound(e))
#TODO - Complete implementation of these APIs
def attach_resource(self, request, tenant_id, network_id, id):
content_type = request.best_match_content_type()
try:
request_params = \
self._parse_request_params(request,
@ -151,7 +149,7 @@ class Controller(common.QuantumController):
except exc.HTTPError as e:
return faults.Fault(e)
try:
self.network_manager.plug_interface(tenant_id,
self._plugin.plug_interface(tenant_id,
network_id, id,
request_params['attachment-id'])
return exc.HTTPAccepted()
@ -167,7 +165,7 @@ class Controller(common.QuantumController):
#TODO - Complete implementation of these APIs
def detach_resource(self, request, tenant_id, network_id, id):
try:
self.network_manager.unplug_interface(tenant_id,
self._plugin.unplug_interface(tenant_id,
network_id, id)
return exc.HTTPAccepted()
except exception.NetworkNotFound as e:

View File

@ -240,6 +240,7 @@ def api_create_port(client, *args):
def delete_port(manager, *args):
tid, nid, pid = args
manager.delete_port(tid, nid, pid)
LOG.info("Deleted Virtual Port:%s " \
"on Virtual Network:%s" % (pid, nid))
@ -319,8 +320,8 @@ def api_unplug_iface(client, *args):
output = res.read()
LOG.debug(output)
if res.status != 202:
LOG.error("Failed to unplug iface from port \"%s\": %s" % (pid,
output))
LOG.error("Failed to unplug iface from port \"%s\": %s" % \
(pid, output))
return
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)
def getPluginFromConfig(file="config.ini"):
def get_plugin_from_config(file="config.ini"):
Config = ConfigParser.ConfigParser()
Config.read(file)
return Config.get("PLUGIN", "provider")

View File

@ -42,6 +42,13 @@ def configure_db(options):
register_models()
def unconfigure_db():
unregister_models()
# Unset the engine
global _ENGINE
_ENGINE = None
def get_session(autocommit=True, expire_on_commit=False):
"""Helper method to grab session"""
global _MAKER, _ENGINE
@ -72,7 +79,7 @@ def network_create(tenant_id, name):
net = None
try:
net = session.query(models.Network).\
filter_by(name=name, tenant_id=tenant_id).\
filter_by(tenant_id=tenant_id, name=name).\
one()
raise Exception("Network with name %(name)s already " \
"exists for tenant %(tenant_id)s" % locals())
@ -105,7 +112,7 @@ def network_rename(net_id, tenant_id, new_name):
session = get_session()
try:
res = session.query(models.Network).\
filter_by(name=new_name).\
filter_by(tenant_id=tenant_id, name=new_name).\
one()
except exc.NoResultFound:
net = network_get(net_id)
@ -167,13 +174,14 @@ def port_set_state(port_id, new_state):
def port_set_attachment(port_id, new_interface_id):
session = get_session()
ports = None
try:
ports = session.query(models.Port).\
filter_by(interface_id=new_interface_id).\
all()
except exc.NoResultFound:
pass
ports = []
if new_interface_id != "":
try:
ports = session.query(models.Port).\
filter_by(interface_id=new_interface_id).\
all()
except exc.NoResultFound:
pass
if len(ports) == 0:
port = port_get(port_id)
port.interface_id = new_interface_id

View File

@ -27,6 +27,7 @@ The caller should make sure that QuantumManager is a singleton.
import gettext
import logging
import os
gettext.install('quantum', unicode=1)
from common import utils
@ -44,22 +45,27 @@ def find_config(basepath):
class QuantumManager(object):
def __init__(self, config=None):
if config == None:
def __init__(self, options=None, config_file=None):
if config_file == None:
self.configuration_file = find_config(
os.path.abspath(os.path.dirname(__file__)))
else:
self.configuration_file = config
plugin_location = utils.getPluginFromConfig(self.configuration_file)
plugin_klass = utils.import_class(plugin_location)
LOG.debug("Plugin location:%s", plugin_location)
self.configuration_file = config_file
# If no options have been provided, create an empty dict
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):
raise Exception("Configured Quantum plug-in " \
"didn't pass compatibility test")
else:
LOG.debug("Successfully imported Quantum plug-in." \
"All compatibility tests passed")
self.plugin = plugin_klass()
self.plugin = plugin_klass(options)
def get_manager(self):
def get_plugin(self):
return self.plugin

View File

@ -233,9 +233,13 @@ class FakePlugin(object):
client/cli/api development
"""
def __init__(self):
db_options = {"sql_connection": "sqlite:///fake_plugin.sqllite"}
db.configure_db(db_options)
def __init__(self, options):
# use supplied options for configuring db
if not options:
options = {"sql_connection": "sqlite:///fake_plugin.sqllite"}
elif not 'sql_connection' in options:
options['sql_connection'] = "sqlite:///fake_plugin.sqllite"
db.configure_db(options)
FakePlugin._net_counter = 0
def _get_network(self, tenant_id, network_id):
@ -246,7 +250,7 @@ class FakePlugin(object):
def _get_port(self, tenant_id, network_id, port_id):
net = self._get_network(tenant_id, network_id)
try:
try:
port = db.port_get(port_id)
except:
raise exc.PortNotFound(net_id=network_id, port_id=port_id)

View File

@ -62,20 +62,25 @@ mysql> FLUSH PRIVILEGES;
distribution tarball (see below) and the agent will use the credentials here
to access the database.
# -- Agent configuration
# -- XenServer Agent configuration
- Create the agent distribution tarball
$ make agent-dist
- Copy the resulting tarball to your xenserver(s) (copy to dom0, not the nova
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
of the integration bridge that you'll need for your nova configuration.
Make sure to specify this in your nova flagfile as --flat_network_bridge.
- Run the agent [on your hypervisor (dom0)]:
$ /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini
# -- 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
- Start quantum [on the quantum service host]:

View File

@ -130,40 +130,40 @@ class OVSBridge:
def get_port_stats(self, port_name):
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
def get_vif_ports(self):
edge_ports = []
port_names = self.get_port_name_list()
for name in port_names:
external_ids = self.db_get_map("Interface", name, "external_ids")
if "iface-id" in external_ids and "attached-mac" in external_ids:
ofport = self.db_get_val("Interface", name, "ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
edge_ports.append(p)
else:
# iface-id might not be set. See if we can figure it out and
# set it here.
external_ids = self.db_get_map("Interface", name,
"external_ids")
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)
if "xs-vm-uuid" in external_ids:
p = xapi_get_port(name)
if p is not None:
edge_ports.append(p)
elif "iface-id" in external_ids and "attached-mac" in external_ids:
ofport = self.db_get_val("Interface", name, "ofport")
p = VifPort(name, ofport, external_ids["iface-id"],
external_ids["attached-mac"], self)
@ -171,13 +171,15 @@ class OVSBridge:
return edge_ports
class OVSNaaSPlugin:
class OVSQuantumAgent:
def __init__(self, integ_br):
self.setup_integration_br(integ_br)
def port_bound(self, port, vlan_id):
self.int_br.set_db_attribute("Port", port.port_name, "tag",
str(vlan_id))
str(vlan_id))
self.int_br.delete_flows(match="in_port=%s" % port.ofport)
def port_unbound(self, port, still_exists):
if still_exists:
@ -186,13 +188,8 @@ class OVSNaaSPlugin:
def setup_integration_br(self, integ_br):
self.int_br = OVSBridge(integ_br)
self.int_br.remove_all_flows()
# drop all traffic on the 'dead vlan'
self.int_br.add_flow(priority=2, match="dl_vlan=4095", actions="drop")
# switch all other traffic using L2 learning
# switch all traffic using L2 learning
self.int_br.add_flow(priority=1, actions="normal")
# FIXME send broadcast everywhere, regardless of tenant
#int_br.add_flow(priority=3, match="dl_dst=ff:ff:ff:ff:ff:ff",
# actions="normal")
def daemon_loop(self, conn):
self.local_vlan_map = {}
@ -201,7 +198,7 @@ class OVSNaaSPlugin:
while True:
cursor = conn.cursor()
cursor.execute("SELECT * FROM network_bindings")
cursor.execute("SELECT * FROM ports")
rows = cursor.fetchall()
cursor.close()
all_bindings = {}
@ -226,22 +223,26 @@ class OVSNaaSPlugin:
else:
# no binding, put him on the 'dead vlan'
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)
new_b = new_local_bindings.get(p.vif_id, None)
if old_b != new_b:
if old_b is not None:
LOG.info("Removing binding to net-id = %s for %s"
% (old_b, str(p)))
self.port_unbound(p, True)
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
# the dead vlan
vlan_id = vlan_bindings.get(all_bindings[p.vif_id],
"4095")
self.port_bound(p, vlan_id)
LOG.info("Adding binding to net-id = %s " \
"for %s on vlan %s" % (new_b, str(p), vlan_id))
for vif_id in old_vif_ports.keys():
if vif_id not in new_vif_ports:
LOG.info("Port Disappeared: %s" % vif_id)
@ -251,8 +252,6 @@ class OVSNaaSPlugin:
old_vif_ports = new_vif_ports
old_local_bindings = new_local_bindings
self.int_br.run_cmd(["bash",
"/etc/xapi.d/plugins/set_external_ids.sh"])
time.sleep(2)
if __name__ == "__main__":
@ -291,7 +290,7 @@ if __name__ == "__main__":
LOG.info("Connecting to database \"%s\" on %s" % (db_name, db_host))
conn = MySQLdb.connect(host=db_host, user=db_user,
passwd=db_pass, db=db_name)
plugin = OVSNaaSPlugin(integ_br)
plugin = OVSQuantumAgent(integ_br)
plugin.daemon_loop(conn)
finally:
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:
pass
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
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):
"""Represents a binding of network_id, vlan_id"""
__tablename__ = 'vlan_bindings'

View File

@ -1,9 +1,9 @@
[DATABASE]
name = ovs_naas
name = ovs_quantum
user = root
pass = foobar
pass = nova
host = 127.0.0.1
port = 3306
[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):
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):
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):
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.
"""
import inspect
from abc import ABCMeta, abstractmethod
@ -29,6 +30,14 @@ class QuantumPluginBase(object):
__metaclass__ = ABCMeta
@abstractmethod
def __init__(self, options):
"""
Initializes the Quantum plugin using provided options.
"""
pass
@abstractmethod
def get_all_networks(self, tenant_id):
"""
@ -242,7 +251,17 @@ class QuantumPluginBase(object):
"""
if cls is QuantumPluginBase:
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
return NotImplemented
return True

View File

@ -23,28 +23,9 @@ import unittest
import tests.unit.testlib 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 APIPortsTest(unittest.TestCase):
def setUp(self):
self.api = server.APIRouterV01()
self.tenant_id = "test_tenant"
self.network_name = "test_network"
def tearDown(self):
"""Clear the test environment"""
# Remove all the networks.
network_req = testlib.create_network_list_request(self.tenant_id)
network_res = network_req.get_response(self.api)
network_data = Serializer().deserialize(network_res.body,
"application/xml")
for network in network_data["networks"].values():
network_delete_req = testlib. \
create_network_delete_request(self.tenant_id, network['id'])
network_delete_req.get_response(self.api)
# Fault names copied here for reference
#
@ -60,37 +41,140 @@ class APIPortsTest(unittest.TestCase):
# 470: "serviceUnavailable",
# 471: "pluginFault"}
def _test_delete_port(self, format):
LOG.debug("_test_delete_port - format:%s - START", format)
LOG = logging.getLogger('quantum.tests.test_api')
class APITest(unittest.TestCase):
def _create_network(self, format):
LOG.debug("Creating network")
content_type = "application/" + format
port_state = "ACTIVE"
LOG.debug("Creating network and port")
network_req = testlib.create_new_network_request(self.tenant_id,
self.network_name,
format)
network_req = testlib.new_network_request(self.tenant_id,
self.network_name,
format)
network_res = network_req.get_response(self.api)
self.assertEqual(network_res.status_int, 200)
network_data = Serializer().deserialize(network_res.body,
content_type)
network_id = network_data['networks']['network']['id']
port_req = testlib.create_new_port_request(self.tenant_id,
network_id, port_state,
format)
return network_data['networks']['network']['id']
def _create_port(self, network_id, port_state, format):
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)
port_res = port_req.get_response(self.api)
self.assertEqual(port_res.status_int, 200)
port_data = Serializer().deserialize(port_res.body, content_type)
port_id = port_data['ports']['port']['id']
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_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)
print show_network_res.body
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_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_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_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.create_port_delete_request(self.tenant_id,
network_id,
port_id,
format)
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.create_port_list_request(self.tenant_id,
network_id,
format)
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)
@ -98,55 +182,102 @@ class APIPortsTest(unittest.TestCase):
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)
pass
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 setUp(self):
self.db_file = ':memory'
options = {}
options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin'
options['sql_connection'] = 'sqlite:///%s' % self.db_file
self.api = server.APIRouterV01(options)
self.tenant_id = "test_tenant"
self.network_name = "test_network"
def tearDown(self):
"""Clear the test environment"""
# Unconfigure database engine
db.unconfigure_db()
def test_create_network_json(self):
self._test_create_network('json')
def test_create_network_xml(self):
self._test_create_network('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_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_create_port_json(self):
self._test_create_port('json')
def test_create_port_xml(self):
self._test_create_port('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(self):
# Test for portinuse
#rv = self.port.create(req, tenant, network_id)
#port_id = rv["ports"]["port"]["id"]
#req = testlib.create_attachment_request(tenant, network_id,
# port_id, "fudd")
#rv = self.port.attach_resource(req, tenant, network_id, port_id)
#self.assertEqual(rv.status_int, 202)
#rv = self.port.delete("", tenant, network_id, port_id)
#self.assertEqual(rv.wrapped_exc.status_int, 432)
pass
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_with_bad_id(self,format):
LOG.debug("_test_delete_port - format:%s - START", format)
content_type = "application/" + format
port_state = "ACTIVE"
LOG.debug("Creating network and port")
network_req = testlib.create_new_network_request(self.tenant_id,
self.network_name,
format)
network_res = network_req.get_response(self.api)
self.assertEqual(network_res.status_int, 200)
network_data = Serializer().deserialize(network_res.body,
content_type)
network_id = network_data['networks']['network']['id']
port_req = testlib.create_new_port_request(self.tenant_id,
network_id, port_state,
format)
port_res = port_req.get_response(self.api)
self.assertEqual(port_res.status_int, 200)
# Test for portnotfound
delete_port_req = testlib.create_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)
def test_delete_port_with_bad_id_xml(self):
self._test_delete_port_wth_bad_id('xml')
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')

View File

@ -12,60 +12,78 @@ def create_request(path, body, content_type, method='GET'):
return req
def create_network_list_request(tenant_id, format='xml'):
def network_list_request(tenant_id, format='xml'):
method = 'GET'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
content_type = "application/" + format
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def create_new_network_request(tenant_id, network_name, format='xml'):
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, format='xml'):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals()
data = {'network': {'net-name': '%s' % network_name}}
content_type = "application/" + format
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def create_network_delete_request(tenant_id, network_id, format='xml'):
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/" + format
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def create_port_list_request(tenant_id, network_id, format='xml'):
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/" + format
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def create_new_port_request(tenant_id, network_id, port_state, format='xml'):
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'):
method = 'POST'
path = "/tenants/%(tenant_id)s/networks/" \
"%(network_id)s/ports.%(format)s" % locals()
data = {'port': {'port-state': '%s' % port_state}}
content_type = "application/" + format
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body, content_type, method)
def create_port_delete_request(tenant_id, network_id, port_id, format='xml'):
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/" + format
content_type = "application/%s" % format
return create_request(path, None, content_type, method)
def create_attachment_request(tid, nid, pid, attachment_id):
path = "/v0.1/tenants/%s/networks/%s/ports/%s/attachment.json" % (tid,
nid, pid)
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/json"
content_type = "application/%s" % format
body = Serializer().serialize(data, content_type)
return create_request(path, body)
return create_request(path, body, 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)