From 645251dba4b6d920caed9d4f58f39431d85161d4 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 21 Jun 2011 00:14:14 -0700 Subject: [PATCH 01/25] Bug fixes and clean-up, including supporting libvirt --- quantum/cli.py | 1 + quantum/db/api.py | 4 +- quantum/plugins/openvswitch/README | 9 +- .../openvswitch/agent/ovs_quantum_agent.py | 77 +++++----- .../openvswitch/agent/set_external_ids.sh | 15 -- .../{install.sh => xenserver_install.sh} | 0 quantum/plugins/openvswitch/ovs_db.py | 16 -- quantum/plugins/openvswitch/ovs_models.py | 16 -- .../openvswitch/ovs_quantum_plugin.ini | 6 +- .../plugins/openvswitch/ovs_quantum_plugin.py | 2 - tools/batch_config.py | 137 ++++++++++++++++++ 11 files changed, 191 insertions(+), 92 deletions(-) delete mode 100755 quantum/plugins/openvswitch/agent/set_external_ids.sh rename quantum/plugins/openvswitch/agent/{install.sh => xenserver_install.sh} (100%) create mode 100644 tools/batch_config.py diff --git a/quantum/cli.py b/quantum/cli.py index 4c0ba4eee5..a0121b0341 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -220,6 +220,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)) diff --git a/quantum/db/api.py b/quantum/db/api.py index 8a6ba305cc..1809af0576 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -67,7 +67,7 @@ def network_create(tenant_id, name): net = None try: net = session.query(models.Network).\ - filter_by(name=name).\ + filter_by(tenant_id=tenant_id,name=name).\ one() raise Exception("Network with name \"%s\" already exists" % name) except exc.NoResultFound: @@ -96,7 +96,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) diff --git a/quantum/plugins/openvswitch/README b/quantum/plugins/openvswitch/README index 689624f1b9..a6351ddf68 100644 --- a/quantum/plugins/openvswitch/README +++ b/quantum/plugins/openvswitch/README @@ -60,19 +60,24 @@ 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. - 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]: diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index 34f28fbdd5..cb21d7a6b4 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -76,7 +76,7 @@ class OVSBridge: def remove_all_flows(self): self.run_ofctl("del-flows", []) - + def get_port_ofport(self, port_name): return self.db_get_val("Interface", port_name, "ofport") @@ -126,6 +126,29 @@ 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): @@ -133,42 +156,25 @@ class OVSBridge: 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) edge_ports.append(p) 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)) + self.int_br.delete_flows(match="in_port=%s" % port.ofport) def port_unbound(self, port, still_exists): if still_exists: @@ -177,12 +183,9 @@ 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 = {} @@ -191,7 +194,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 = {} @@ -215,8 +218,12 @@ class OVSNaaSPlugin: new_local_bindings[p.vif_id] = all_bindings[p.vif_id] else: # no binding, put him on the 'dead vlan' + LOG.info("No binding for %s, setting to dead vlan" \ + % p.vif_id) self.int_br.set_db_attribute("Port", p.port_name, "tag", "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: @@ -225,13 +232,13 @@ class OVSNaaSPlugin: % (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) @@ -241,8 +248,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__": @@ -281,7 +286,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: diff --git a/quantum/plugins/openvswitch/agent/set_external_ids.sh b/quantum/plugins/openvswitch/agent/set_external_ids.sh deleted file mode 100755 index 2fae05f0a1..0000000000 --- a/quantum/plugins/openvswitch/agent/set_external_ids.sh +++ /dev/null @@ -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 - diff --git a/quantum/plugins/openvswitch/agent/install.sh b/quantum/plugins/openvswitch/agent/xenserver_install.sh similarity index 100% rename from quantum/plugins/openvswitch/agent/install.sh rename to quantum/plugins/openvswitch/agent/xenserver_install.sh diff --git a/quantum/plugins/openvswitch/ovs_db.py b/quantum/plugins/openvswitch/ovs_db.py index a2a72ec8ce..a5785c7df1 100644 --- a/quantum/plugins/openvswitch/ovs_db.py +++ b/quantum/plugins/openvswitch/ovs_db.py @@ -54,19 +54,3 @@ def remove_vlan_binding(netid): 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() diff --git a/quantum/plugins/openvswitch/ovs_models.py b/quantum/plugins/openvswitch/ovs_models.py index 610902a7ca..9ce83611df 100644 --- a/quantum/plugins/openvswitch/ovs_models.py +++ b/quantum/plugins/openvswitch/ovs_models.py @@ -26,22 +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 "" % \ - (self.network_id, self.vif_id) - class VlanBinding(BASE): """Represents a binding of network_id, vlan_id""" __tablename__ = 'vlan_bindings' diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini index 0c75b3fc48..66095d85d1 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -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 diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py index 75619cae62..375a4bd87a 100644 --- a/quantum/plugins/openvswitch/ovs_quantum_plugin.py +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -189,11 +189,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) diff --git a/tools/batch_config.py b/tools/batch_config.py new file mode 100644 index 0000000000..e6be088ed7 --- /dev/null +++ b/tools/batch_config.py @@ -0,0 +1,137 @@ +# 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 + + +if __name__ == "__main__": + usagestr = "Usage: %prog [OPTIONS] [args]" + 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") + + 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) + + 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"] + 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 + LOG.info("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: + print "Failed to delete network: %s" % nid + output = res.read() + print output + else: + print "Deleted Virtual Network with ID:%s" % nid + + for net_name, iface_ids in nets.items(): + data = {'network': {'network-name': '%s' % net_name}} + body = Serializer().serialize(data, CONTENT_TYPE) + res = client.do_request(tenant_id, 'POST', + "/networks." + FORMAT, body=body) + rd = json.loads(res.read()) + LOG.debug(rd) + nid = rd["networks"]["network"]["id"] + print "Created a new Virtual Network %s with ID:%s\n" % (net_name,nid) + for iface_id in iface_ids: + res = client.do_request(tenant_id, 'POST', + "/networks/%s/ports.%s" % (nid, FORMAT)) + 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) + + sys.exit(0) From 04062526c1031c40f6f2cde9a2d45bc499651efe Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 21 Jun 2011 10:13:07 -0700 Subject: [PATCH 02/25] add example to usage string for batch_config.py --- tools/batch_config.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tools/batch_config.py b/tools/batch_config.py index e6be088ed7..cf70a8fb9e 100644 --- a/tools/batch_config.py +++ b/tools/batch_config.py @@ -32,7 +32,11 @@ CONTENT_TYPE = "application/" + FORMAT if __name__ == "__main__": - usagestr = "Usage: %prog [OPTIONS] [args]" + usagestr = "Usage: %prog [OPTIONS] [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") From 55338cfd4f841491f9e0b35ba03512b5948d2e64 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Fri, 24 Jun 2011 11:20:03 -0700 Subject: [PATCH 03/25] Fix cut and paste error in api_unplug_iface This fixes lp issue: https://bugs.launchpad.net/quantum/+bug/801598 --- quantum/cli.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/quantum/cli.py b/quantum/cli.py index 8663d548b1..6c653097d0 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -318,8 +318,7 @@ 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" % (vid, - 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) From dad5dbb764d04cb30a22e5e1bbc62d1f2f5c1e2d Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sat, 25 Jun 2011 02:04:55 -0700 Subject: [PATCH 04/25] refactor batch_config, allow multiple attaches with the empty string --- quantum/db/api.py | 15 +- .../openvswitch/agent/ovs_quantum_agent.py | 2 - tools/batch_config.py | 155 ++++++++++-------- 3 files changed, 96 insertions(+), 76 deletions(-) diff --git a/quantum/db/api.py b/quantum/db/api.py index 989f0d6c38..59459cadb4 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -156,13 +156,14 @@ def port_get(port_id): 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 diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py index 60c864543b..5d2a66ca37 100755 --- a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -222,8 +222,6 @@ class OVSQuantumAgent: new_local_bindings[p.vif_id] = all_bindings[p.vif_id] else: # no binding, put him on the 'dead vlan' - LOG.info("No binding for %s, setting to dead vlan" \ - % p.vif_id) self.int_br.set_db_attribute("Port", p.port_name, "tag", "4095") self.int_br.add_flow(priority=2, diff --git a/tools/batch_config.py b/tools/batch_config.py index 21b35684d0..9a7efdda50 100644 --- a/tools/batch_config.py +++ b/tools/batch_config.py @@ -30,6 +30,89 @@ 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] [args]\n" \ @@ -49,6 +132,8 @@ if __name__ == "__main__": 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() @@ -75,74 +160,10 @@ if __name__ == "__main__": client = MiniClient(options.host, options.port, options.ssl) - 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"] - 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 - LOG.info("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: - print "Failed to delete network: %s" % nid - output = res.read() - print output - else: - print "Deleted Virtual Network with ID:%s" % nid + if options.delete: + delete_all_nets(client, tenant_id) for net_name, iface_ids in nets.items(): - data = {'network': {'network-name': '%s' % net_name}} - body = Serializer().serialize(data, CONTENT_TYPE) - res = client.do_request(tenant_id, 'POST', - "/networks." + FORMAT, body=body) - rd = json.loads(res.read()) - LOG.debug(rd) - nid = rd["networks"]["network"]["id"] - print "Created a new Virtual Network %s with ID:%s\n" % (net_name, nid) - - for iface_id in iface_ids: - res = client.do_request(tenant_id, 'POST', - "/networks/%s/ports.%s" % (nid, FORMAT)) - 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) + create_net_with_attachments(net_name, iface_ids) sys.exit(0) From 7534f9dea7ef1899bf881753e568d91672bd36fd Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 27 Jun 2011 09:27:18 -0700 Subject: [PATCH 05/25] more pep8 goodness --- tools/batch_config.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/tools/batch_config.py b/tools/batch_config.py index 9a7efdda50..63f4a5223d 100644 --- a/tools/batch_config.py +++ b/tools/batch_config.py @@ -30,6 +30,7 @@ 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()) @@ -51,14 +52,16 @@ def delete_all_nets(client, tenant_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) + "/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)) + 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)) @@ -79,6 +82,7 @@ def delete_all_nets(client, tenant_id): 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) @@ -133,7 +137,8 @@ if __name__ == "__main__": 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") + action="store_true", default=False, \ + help="delete existing tenants networks") options, args = parser.parse_args() From 41835dc9ede48d8dfad75a3f520dd82c0418aee3 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Mon, 27 Jun 2011 16:07:03 -0700 Subject: [PATCH 06/25] Added some more plugin agnostic tests (attachment and negative tests) and some pep8 fixes. --- quantum/cli.py | 6 +- quantum/plugins/SamplePlugin.py | 13 +++- tests/functional/test_service.py | 116 +++++++++++++++++++++++++++---- 3 files changed, 117 insertions(+), 18 deletions(-) diff --git a/quantum/cli.py b/quantum/cli.py index 6c653097d0..5d3d427cb7 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -299,7 +299,8 @@ def api_plug_iface(client, *args): LOG.error("Failed to plug iface \"%s\" to port \"%s\": %s" % (vid, pid, output)) 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): @@ -318,7 +319,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) diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 376456a6c8..dcac672f29 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -374,7 +374,10 @@ class FakePlugin(object): # TODO(salvatore-orlando): Validate port state in API? self._validate_port_state(port_state) ports = net['net-ports'] - new_port_id = max(ports.keys()) + 1 + if len(ports.keys()) == 0: + new_port_id = 1 + else: + new_port_id = max(ports.keys()) + 1 new_port_dict = {'port-id': new_port_id, 'port-state': port_state, 'attachment': None} @@ -434,3 +437,11 @@ class FakePlugin(object): # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? port['attachment'] = None + + def get_interface_details(self, tenant_id, net_id, port_id): + """ + Get Attachment details + """ + print("get_interface_details() called\n") + port = self._get_port(tenant_id, net_id, port_id) + return port["attachment"] diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py index 6ed728394a..12c648615b 100644 --- a/tests/functional/test_service.py +++ b/tests/functional/test_service.py @@ -17,7 +17,7 @@ # under the License. import gettext -import simplejson +import json import sys import unittest @@ -50,10 +50,10 @@ class QuantumTest(unittest.TestCase): def setUp(self): self.client = MiniClient(HOST, PORT, USE_SSL) - def create_network(self, data): + def create_network(self, data, tenant_id=TENANT_ID): content_type = "application/" + FORMAT body = Serializer().serialize(data, content_type) - res = self.client.do_request(TENANT_ID, 'POST', "/networks." + FORMAT, + res = self.client.do_request(tenant_id, 'POST', "/networks." + FORMAT, body=body) self.assertEqual(res.status, 200, "bad response: %s" % res.read()) @@ -63,23 +63,46 @@ class QuantumTest(unittest.TestCase): res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) self.assertEqual(res.status, 200, "bad response: %s" % res.read()) + def test_getNonexistentNetwork(self): + # TODO(bgh): parse exception and make sure it is NetworkNotFound + try: + res = self.client.do_request(TENANT_ID, 'GET', + "/networks/%s.%s" % ("8675309", "xml")) + self.assertEqual(res.status, 400) + except Exception, e: + print "Caught exception: %s" % (str(e)) + + def test_deleteNonexistentNetwork(self): + # TODO(bgh): parse exception and make sure it is NetworkNotFound + try: + res = self.client.do_request(TENANT_ID, 'DELETE', + "/networks/%s.%s" % ("8675309", "xml")) + self.assertEqual(res.status, 400) + except Exception, e: + print "Caught exception: %s" % (str(e)) + 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()) + resdict = json.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) + self.assertEqual(res.status, 200, "Bad response: %s" % output) + if len(output) > 0: + resdict = json.loads(output) + self.assertTrue(len(resdict["ports"]) == 0, + "Found unexpected ports: %s" % output) + else: + self.assertTrue(len(output) == 0, + "Found unexpected ports: %s" % output) # Step 2 - Create Port for network res = self.client.do_request(TENANT_ID, 'POST', @@ -91,17 +114,59 @@ class QuantumTest(unittest.TestCase): "/networks/%s/ports.%s" % (net_id, FORMAT)) output = res.read() self.assertEqual(res.status, 200, "Bad response: %s" % output) - resdict = simplejson.loads(output) + resdict = json.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_getAttachment(self): + self.create_network(test_network1_data) + res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) + resdict = json.loads(res.read()) + for n in resdict["networks"]: + net_id = n["id"] + + # Step 1 - Create Port for network and attempt to get the + # attachment (even though there isn't one) + res = self.client.do_request(TENANT_ID, 'POST', + "/networks/%s/ports.%s" % (net_id, FORMAT)) + output = res.read() + self.assertEqual(res.status, 200, "Bad response: %s" % output) + resdict = json.loads(output) + port_id = resdict["ports"]["port"]["id"] + + res = self.client.do_request(TENANT_ID, 'GET', + "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + FORMAT)) + output = res.read() + self.assertEqual(res.status, 200, "Bad response: %s" % output) + + # Step 2 - Add an attachment + data = {'port': {'attachment-id': 'fudd'}} + content_type = "application/" + FORMAT + body = Serializer().serialize(data, content_type) + res = self.client.do_request(TENANT_ID, 'PUT', + "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + FORMAT), body=body) + output = res.read() + self.assertEqual(res.status, 202, "Bad response: %s" % output) + + # Step 3 - Fetch the attachment + res = self.client.do_request(TENANT_ID, 'GET', + "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + FORMAT)) + output = res.read() + self.assertEqual(res.status, 200, "Bad response: %s" % output) + resdict = json.loads(output) + attachment = resdict["attachment"] + self.assertEqual(attachment, "fudd", "Attachment: %s" % attachment) + 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()) + resdict = json.loads(res.read()) net_id = resdict["networks"][0]["id"] data = test_network1_data.copy() @@ -110,20 +175,41 @@ class QuantumTest(unittest.TestCase): 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()) + resdict = json.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()) + def test_createNetworkOnMultipleTenants(self): + # Create the same network on multiple tenants + self.create_network(test_network1_data, "tenant1") + self.create_network(test_network1_data, "tenant2") + + def delete_networks(self, tenant_id=TENANT_ID): + # Remove all the networks created on the tenant (including ports and + # attachments) + res = self.client.do_request(tenant_id, 'GET', + "/networks." + FORMAT) + resdict = json.loads(res.read()) for n in resdict["networks"]: net_id = n["id"] - res = self.client.do_request(TENANT_ID, 'DELETE', + # Delete all the ports + 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 = json.loads(output) + ids = [] + for p in resdict["ports"]: + res = self.client.do_request(tenant_id, 'DELETE', + "/networks/%s/ports/%s/attachment.%s" % (net_id, p["id"], + FORMAT)) + res = self.client.do_request(tenant_id, 'DELETE', + "/networks/%s/ports/%s.%s" % (net_id, p["id"], FORMAT)) + # Now, remove the network + res = self.client.do_request(tenant_id, 'DELETE', "/networks/" + net_id + "." + FORMAT) self.assertEqual(res.status, 202) From c0260982ace1338ae2732d9873e0ec3362fa5076 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Mon, 27 Jun 2011 16:08:58 -0700 Subject: [PATCH 07/25] A small start on unit tests: mostly a proof of concept that contains a test for api/ports.py --- tests/unit/test_api.py | 64 ++++++++++++++++++++++++++++++++++++++++++ tests/unit/testlib.py | 42 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+) create mode 100644 tests/unit/test_api.py create mode 100644 tests/unit/testlib.py diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py new file mode 100644 index 0000000000..ee189e5c3f --- /dev/null +++ b/tests/unit/test_api.py @@ -0,0 +1,64 @@ +import quantum.api.ports as ports +import quantum.api.networks as networks +import tests.unit.testlib as testlib +import unittest + + +class APIPortsTest(unittest.TestCase): + def setUp(self): + self.port = ports.Controller() + self.network = networks.Controller() + +# Fault names copied here for reference +# +# _fault_names = { +# 400: "malformedRequest", +# 401: "unauthorized", +# 420: "networkNotFound", +# 421: "networkInUse", +# 430: "portNotFound", +# 431: "requestedStateInvalid", +# 432: "portInUse", +# 440: "alreadyAttached", +# 470: "serviceUnavailable", +# 471: "pluginFault"} + + def test_deletePort(self): + tenant = "tenant1" + network = "test1" + req = testlib.create_network_request(tenant, network) + network_obj = self.network.create(req, tenant) + network_id = network_obj["networks"]["network"]["id"] + req = testlib.create_empty_request() + rv = self.port.create(req, tenant, network_id) + port_id = rv["ports"]["port"]["id"] + self.assertTrue(port_id > 0) + rv = self.port.delete("", tenant, network_id, port_id) + self.assertEqual(rv.status_int, 202) + + def test_deletePortNegative(self): + tenant = "tenant1" + network = "test1" + + # Check for network not found + rv = self.port.delete("", tenant, network, 2) + self.assertEqual(rv.wrapped_exc.status_int, 420) + + # Create a network to put the port on + req = testlib.create_network_request(tenant, network) + network_obj = self.network.create(req, tenant) + network_id = network_obj["networks"]["network"]["id"] + + # Test for portnotfound + rv = self.port.delete("", tenant, network_id, 2) + self.assertEqual(rv.wrapped_exc.status_int, 430) + + # 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) diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py new file mode 100644 index 0000000000..a9ba11af1a --- /dev/null +++ b/tests/unit/testlib.py @@ -0,0 +1,42 @@ +import webob + +from quantum.common.wsgi import Serializer + + +class Request(webob.Request): + + def best_match_content_type(self): + return "application/json" + + def get_content_type(self): + return "application/json" + + +def create_request(path, body): + req = Request.blank(path) + req.method = "POST" + req.headers = {} + req.headers['Accept'] = "application/json" + req.body = body + return req + + +def create_empty_request(): + return create_request("/v0.1/tenant.json", "") + + +def create_network_request(tenant_id, network_name): + path = "/v0.1/tenants/%s/networks.json" % tenant_id + data = {'network': {'network-name': '%s' % network_name}} + content_type = "application/json" + body = Serializer().serialize(data, content_type) + return create_request(path, body) + + +def create_attachment_request(tid, nid, pid, attachment_id): + path = "/v0.1/tenants/%s/networks/%s/ports/%s/attachment.json" % (tid, + nid, pid) + data = {'port': {'attachment-id': attachment_id}} + content_type = "application/json" + body = Serializer().serialize(data, content_type) + return create_request(path, body) From a0692d9cc6c027e4ec8fe6deea9f10fc69748b75 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 27 Jun 2011 16:11:09 -0700 Subject: [PATCH 08/25] fix pep8 introduced by trunk merge --- quantum/cli.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/quantum/cli.py b/quantum/cli.py index 496055be1d..bbbc9fbf9f 100644 --- a/quantum/cli.py +++ b/quantum/cli.py @@ -319,7 +319,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) From d0cb0eea78a85a244a32158ceb93aa78120aa293 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 28 Jun 2011 18:04:27 +0100 Subject: [PATCH 09/25] Work in progress - just starting --- quantum/api/ports.py | 1 - quantum/api/views/networks.py | 1 - quantum/api/views/ports.py | 1 - quantum/manager.py | 11 +- quantum/plugins/SamplePlugin.py | 37 ++--- run_tests.py | 10 +- run_tests.sh | 2 +- tests/functional/test_service.py | 264 +++++++++++++++---------------- tests/unit/test_api.py | 25 ++- 9 files changed, 184 insertions(+), 168 deletions(-) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index c2de0d75fb..e93541dacb 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -143,7 +143,6 @@ class Controller(common.QuantumController): #TODO - Complete implementation of these APIs def attach_resource(self, request, tenant_id, network_id, id): content_type = request.best_match_content_type() - print "Content type:%s" % content_type try: request_params = \ self._parse_request_params(request, diff --git a/quantum/api/views/networks.py b/quantum/api/views/networks.py index 6630c6c39a..98c69730e5 100644 --- a/quantum/api/views/networks.py +++ b/quantum/api/views/networks.py @@ -33,7 +33,6 @@ class ViewBuilder(object): def build(self, network_data, is_detail=False): """Generic method used to generate a network entity.""" - print "NETWORK-DATA:%s" % network_data if is_detail: network = self._build_detail(network_data) else: diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index dabc7cf0f3..87758453b9 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -31,7 +31,6 @@ class ViewBuilder(object): def build(self, port_data, is_detail=False): """Generic method used to generate a port entity.""" - print "PORT-DATA:%s" % port_data if is_detail: port = self._build_detail(port_data) else: diff --git a/quantum/manager.py b/quantum/manager.py index a9662d8eb6..71a13fb9c7 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -25,16 +25,15 @@ class. The caller should make sure that QuantumManager is a singleton. """ import gettext +import logging import os gettext.install('quantum', unicode=1) -import os - from common import utils from quantum_plugin_base import QuantumPluginBase CONFIG_FILE = "plugins.ini" - +LOG = logging.getLogger('quantum.manager') def find_config(basepath): for root, dirs, files in os.walk(basepath): @@ -51,14 +50,14 @@ class QuantumManager(object): else: self.configuration_file = config plugin_location = utils.getPluginFromConfig(self.configuration_file) - print "PLUGIN LOCATION:%s" % plugin_location plugin_klass = utils.import_class(plugin_location) + LOG.debug("Plugin location:%s", plugin_location) if not issubclass(plugin_klass, QuantumPluginBase): raise Exception("Configured Quantum plug-in " \ "didn't pass compatibility test") else: - print("Successfully imported Quantum plug-in." \ - "All compatibility tests passed\n") + LOG.debug("Successfully imported Quantum plug-in." \ + "All compatibility tests passed") self.plugin = plugin_klass() def get_manager(self): diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index dcac672f29..956ad4ca35 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -14,9 +14,13 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Somik Behera, Nicira Networks, Inc. +# @author: Salvatore Orlando, Citrix + +import logging from quantum.common import exceptions as exc +LOG = logging.getLogger('quantum.plugins.SamplePlugin') class QuantumEchoPlugin(object): @@ -290,7 +294,7 @@ class FakePlugin(object): for the specified tenant. """ - print("get_all_networks() called\n") + LOG.debug("FakePlugin.get_all_networks() called") return FakePlugin._networks.values() def get_network_details(self, tenant_id, net_id): @@ -298,7 +302,7 @@ class FakePlugin(object): retrieved a list of all the remote vifs that are attached to the network """ - print("get_network_details() called\n") + LOG.debug("get_network_details() called") return self._get_network(tenant_id, net_id) def create_network(self, tenant_id, net_name): @@ -306,11 +310,10 @@ class FakePlugin(object): Creates a new Virtual Network, and assigns it a symbolic name. """ - print("create_network() called\n") + LOG.debug("FakePlugin.create_network() called") FakePlugin._net_counter += 1 new_net_id = ("0" * (3 - len(str(FakePlugin._net_counter)))) + \ str(FakePlugin._net_counter) - print new_net_id new_net_dict = {'net-id': new_net_id, 'net-name': net_name, 'net-ports': {}} @@ -323,7 +326,7 @@ class FakePlugin(object): Deletes the network with the specified network identifier belonging to the specified tenant. """ - print("delete_network() called\n") + LOG.debug("FakePlugin.delete_network() called") net = FakePlugin._networks.get(net_id) # Verify that no attachments are plugged into the network if net: @@ -341,7 +344,7 @@ class FakePlugin(object): Updates the symbolic name belonging to a particular Virtual Network. """ - print("rename_network() called\n") + LOG.debug("FakePlugin.rename_network() called") net = self._get_network(tenant_id, net_id) net['net-name'] = new_name return net @@ -351,7 +354,7 @@ class FakePlugin(object): Retrieves all port identifiers belonging to the specified Virtual Network. """ - print("get_all_ports() called\n") + LOG.debug("FakePlugin.get_all_ports() called") network = self._get_network(tenant_id, net_id) ports_on_net = network['net-ports'].values() return ports_on_net @@ -361,14 +364,14 @@ class FakePlugin(object): This method allows the user to retrieve a remote interface 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) def create_port(self, tenant_id, net_id, port_state=None): """ 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) # check port state # TODO(salvatore-orlando): Validate port state in API? @@ -388,7 +391,7 @@ class FakePlugin(object): """ 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) self._validate_port_state(port_state) port['port-state'] = port_state @@ -401,7 +404,7 @@ class FakePlugin(object): the remote interface is first un-plugged and then the port is deleted. """ - print("delete_port() called\n") + LOG.debug("FakePlugin.delete_port() called") net = self._get_network(tenant_id, net_id) port = self._get_port(tenant_id, net_id, port_id) if port['attachment']: @@ -417,7 +420,7 @@ class FakePlugin(object): Attaches a remote interface to the specified port on the specified Virtual Network. """ - print("plug_interface() called\n") + LOG.debug("FakePlugin.plug_interface() called") # Validate attachment self._validate_attachment(tenant_id, net_id, port_id, remote_interface_id) @@ -432,16 +435,8 @@ class FakePlugin(object): Detaches a remote interface from the specified port on the specified Virtual Network. """ - print("unplug_interface() called\n") + LOG.debug("FakePlugin.unplug_interface() called") port = self._get_port(tenant_id, net_id, port_id) # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? port['attachment'] = None - - def get_interface_details(self, tenant_id, net_id, port_id): - """ - Get Attachment details - """ - print("get_interface_details() called\n") - port = self._get_port(tenant_id, net_id, port_id) - return port["attachment"] diff --git a/run_tests.py b/run_tests.py index acc8260b81..633a760408 100644 --- a/run_tests.py +++ b/run_tests.py @@ -63,6 +63,7 @@ To run a single functional test module:: """ import gettext +import logging import os import unittest import sys @@ -281,12 +282,19 @@ class QuantumTestRunner(core.TextTestRunner): 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") c = config.Config(stream=sys.stdout, env=os.environ, verbosity=3, workingDir=working_dir) - runner = QuantumTestRunner(stream=c.stream, verbosity=c.verbosity, config=c) diff --git a/run_tests.sh b/run_tests.sh index aa72cbe22d..9c603c9825 100755 --- a/run_tests.sh +++ b/run_tests.sh @@ -2,7 +2,7 @@ function usage { echo "Usage: $0 [OPTION]..." - echo "Run Melange's test suite(s)" + echo "Run Quantum's test suite(s)" echo "" echo " -V, --virtual-env Always use virtualenv. Install automatically if not present" echo " -N, --no-virtual-env Don't use virtualenv. Run tests in local environment" diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py index 12c648615b..a3f8985c60 100644 --- a/tests/functional/test_service.py +++ b/tests/functional/test_service.py @@ -50,168 +50,168 @@ class QuantumTest(unittest.TestCase): def setUp(self): self.client = MiniClient(HOST, PORT, USE_SSL) - def create_network(self, data, tenant_id=TENANT_ID): - 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 create_network(self, data, tenant_id=TENANT_ID): + # 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_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_getNonexistentNetwork(self): + #def test_getNonexistentNetwork(self): # TODO(bgh): parse exception and make sure it is NetworkNotFound - try: - res = self.client.do_request(TENANT_ID, 'GET', - "/networks/%s.%s" % ("8675309", "xml")) - self.assertEqual(res.status, 400) - except Exception, e: - print "Caught exception: %s" % (str(e)) + #try: + # res = self.client.do_request(TENANT_ID, 'GET', + # "/networks/%s.%s" % ("8675309", "xml")) + # self.assertEqual(res.status, 400) + #except Exception, e: + # print "Caught exception: %s" % (str(e)) - def test_deleteNonexistentNetwork(self): + #def test_deleteNonexistentNetwork(self): # TODO(bgh): parse exception and make sure it is NetworkNotFound - try: - res = self.client.do_request(TENANT_ID, 'DELETE', - "/networks/%s.%s" % ("8675309", "xml")) - self.assertEqual(res.status, 400) - except Exception, e: - print "Caught exception: %s" % (str(e)) + #try: + # res = self.client.do_request(TENANT_ID, 'DELETE', + # "/networks/%s.%s" % ("8675309", "xml")) + # self.assertEqual(res.status, 400) + #except Exception, e: + # print "Caught exception: %s" % (str(e)) - def test_createNetwork(self): - self.create_network(test_network1_data) + #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 = json.loads(res.read()) - for n in resdict["networks"]: - net_id = n["id"] + #def test_createPort(self): + #self.create_network(test_network1_data) + #res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) + #resdict = json.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)) - output = res.read() - self.assertEqual(res.status, 200, "Bad response: %s" % output) - if len(output) > 0: - resdict = json.loads(output) - self.assertTrue(len(resdict["ports"]) == 0, - "Found unexpected ports: %s" % output) - else: - self.assertTrue(len(output) == 0, - "Found unexpected ports: %s" % output) + #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) + #if len(output) > 0: + # resdict = json.loads(output) + # self.assertTrue(len(resdict["ports"]) == 0, + # "Found unexpected ports: %s" % output) + #else: + # 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) + #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 = json.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) + #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 = json.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_getAttachment(self): - self.create_network(test_network1_data) - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - resdict = json.loads(res.read()) - for n in resdict["networks"]: - net_id = n["id"] + #def test_getAttachment(self): + #self.create_network(test_network1_data) + #res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) + #resdict = json.loads(res.read()) + #for n in resdict["networks"]: + # net_id = n["id"] # Step 1 - Create Port for network and attempt to get the # attachment (even though there isn't one) - res = self.client.do_request(TENANT_ID, 'POST', - "/networks/%s/ports.%s" % (net_id, FORMAT)) - output = res.read() - self.assertEqual(res.status, 200, "Bad response: %s" % output) - resdict = json.loads(output) - port_id = resdict["ports"]["port"]["id"] + #res = self.client.do_request(TENANT_ID, 'POST', + # "/networks/%s/ports.%s" % (net_id, FORMAT)) + #output = res.read() + #self.assertEqual(res.status, 200, "Bad response: %s" % output) + #resdict = json.loads(output) + #port_id = resdict["ports"]["port"]["id"] - res = self.client.do_request(TENANT_ID, 'GET', - "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - FORMAT)) - output = res.read() - self.assertEqual(res.status, 200, "Bad response: %s" % output) + #res = self.client.do_request(TENANT_ID, 'GET', + # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + # FORMAT)) + #output = res.read() + #self.assertEqual(res.status, 200, "Bad response: %s" % output) # Step 2 - Add an attachment - data = {'port': {'attachment-id': 'fudd'}} - content_type = "application/" + FORMAT - body = Serializer().serialize(data, content_type) - res = self.client.do_request(TENANT_ID, 'PUT', - "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - FORMAT), body=body) - output = res.read() - self.assertEqual(res.status, 202, "Bad response: %s" % output) + #data = {'port': {'attachment-id': 'fudd'}} + #content_type = "application/" + FORMAT + #body = Serializer().serialize(data, content_type) + #res = self.client.do_request(TENANT_ID, 'PUT', + # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + # FORMAT), body=body) + #output = res.read() + #self.assertEqual(res.status, 202, "Bad response: %s" % output) # Step 3 - Fetch the attachment - res = self.client.do_request(TENANT_ID, 'GET', - "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - FORMAT)) - output = res.read() - self.assertEqual(res.status, 200, "Bad response: %s" % output) - resdict = json.loads(output) - attachment = resdict["attachment"] - self.assertEqual(attachment, "fudd", "Attachment: %s" % attachment) + #res = self.client.do_request(TENANT_ID, 'GET', + # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, + # FORMAT)) + #output = res.read() + #self.assertEqual(res.status, 200, "Bad response: %s" % output) + #resdict = json.loads(output) + #attachment = resdict["attachment"] + #self.assertEqual(attachment, "fudd", "Attachment: %s" % attachment) - def test_renameNetwork(self): - self.create_network(test_network1_data) - res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - resdict = json.loads(res.read()) - net_id = resdict["networks"][0]["id"] + #def test_renameNetwork(self): + #self.create_network(test_network1_data) + #res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) + #resdict = json.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 = json.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") + #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 = json.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 test_createNetworkOnMultipleTenants(self): + #def test_createNetworkOnMultipleTenants(self): # Create the same network on multiple tenants - self.create_network(test_network1_data, "tenant1") - self.create_network(test_network1_data, "tenant2") + #self.create_network(test_network1_data, "tenant1") + #self.create_network(test_network1_data, "tenant2") - def delete_networks(self, tenant_id=TENANT_ID): + #def delete_networks(self, tenant_id=TENANT_ID): # Remove all the networks created on the tenant (including ports and # attachments) - res = self.client.do_request(tenant_id, 'GET', - "/networks." + FORMAT) - resdict = json.loads(res.read()) - for n in resdict["networks"]: - net_id = n["id"] - # Delete all the ports - 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 = json.loads(output) - ids = [] - for p in resdict["ports"]: - res = self.client.do_request(tenant_id, 'DELETE', - "/networks/%s/ports/%s/attachment.%s" % (net_id, p["id"], - FORMAT)) - res = self.client.do_request(tenant_id, 'DELETE', - "/networks/%s/ports/%s.%s" % (net_id, p["id"], FORMAT)) + #res = self.client.do_request(tenant_id, 'GET', + # "/networks." + FORMAT) + #resdict = json.loads(res.read()) + #for n in resdict["networks"]: + # net_id = n["id"] + # # Delete all the ports + # 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 = json.loads(output) + # ids = [] + # for p in resdict["ports"]: + # res = self.client.do_request(tenant_id, 'DELETE', + # "/networks/%s/ports/%s/attachment.%s" % (net_id, p["id"], + # FORMAT)) + # res = self.client.do_request(tenant_id, 'DELETE', + # "/networks/%s/ports/%s.%s" % (net_id, p["id"], FORMAT)) # Now, remove the network - res = self.client.do_request(tenant_id, 'DELETE', - "/networks/" + net_id + "." + FORMAT) - self.assertEqual(res.status, 202) + # res = self.client.do_request(tenant_id, 'DELETE', + # "/networks/" + net_id + "." + FORMAT) + # self.assertEqual(res.status, 202) def tearDown(self): self.delete_networks() diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index ee189e5c3f..0f955d6aed 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -1,13 +1,30 @@ -import quantum.api.ports as ports -import quantum.api.networks as networks +# 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 tests.unit.testlib as testlib import unittest +from quantum import api as server class APIPortsTest(unittest.TestCase): def setUp(self): - self.port = ports.Controller() - self.network = networks.Controller() + self.api = server.APIRouterv01() # Fault names copied here for reference # From a99caf43c10752534da6288f596901c4067dd5e7 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 29 Jun 2011 16:40:15 +0100 Subject: [PATCH 10/25] - Unit tests will use FakePlugin - FakePlugin adapted to db API with sqlite - db Models updated to inherit from generic Quantum Base model (provides utility functions and capabilities for treating db objects as dicts - see nova.db.models.NovaBase) - functional tests commented out temporarily. Will un-comment when code for starting actual service is in place --- quantum/api/networks.py | 2 +- quantum/db/api.py | 15 ++++- quantum/db/models.py | 70 ++++++++++++++++++++-- quantum/plugins/SamplePlugin.py | 102 +++++++++++++++----------------- 4 files changed, 127 insertions(+), 62 deletions(-) diff --git a/quantum/api/networks.py b/quantum/api/networks.py index a24cf09ab5..a3c75bb7ca 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -29,7 +29,7 @@ class Controller(common.QuantumController): """ Network API controller for Quantum API """ _network_ops_param_list = [{ - 'param-name': 'network-name', + 'param-name': 'net-name', 'required': True}, ] _serialization_metadata = { diff --git a/quantum/db/api.py b/quantum/db/api.py index 2a296f2f07..8981661107 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -72,9 +72,10 @@ def network_create(tenant_id, name): net = None try: net = session.query(models.Network).\ - filter_by(name=name).\ + filter_by(name=name, tenant_id=tenant_id).\ 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: with session.begin(): net = models.Network(tenant_id, name) @@ -154,6 +155,16 @@ def port_get(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): session = get_session() ports = None diff --git a/quantum/db/models.py b/quantum/db/models.py index 115ab282da..af9189df05 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -26,7 +26,61 @@ from sqlalchemy.orm import relation BASE = declarative_base() -class Port(BASE): +class QuantumBase(object): + """Base class for Quantum Models.""" + + def save(self, session=None): + """Save this object.""" + if not session: + session = get_session() + session.add(self) + try: + session.flush() + except IntegrityError, e: + if str(e).endswith('is not unique'): + raise exception.Duplicate(str(e)) + else: + raise + + def delete(self, session=None): + """Delete this object.""" + self.deleted = True + self.deleted_at = utils.utcnow() + self.save(session=session) + + 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""" __tablename__ = 'ports' @@ -34,17 +88,21 @@ class Port(BASE): network_id = Column(String(255), ForeignKey("networks.uuid"), nullable=False) interface_id = Column(String(255)) + # Port state - Hardcoding string value at the moment + state = Column(String(8)) def __init__(self, network_id): - self.uuid = uuid.uuid4() + self.uuid = str(uuid.uuid4()) self.network_id = network_id + self.state = "DOWN" + def __repr__(self): - return "" % (self.uuid, self.network_id, - self.interface_id) + return "" % (self.uuid, self.network_id, + self.state,self.interface_id) -class Network(BASE): +class Network(BASE, QuantumBase): """Represents a quantum network""" __tablename__ = 'networks' @@ -54,7 +112,7 @@ class Network(BASE): ports = relation(Port, order_by=Port.uuid, backref="network") def __init__(self, tenant_id, name): - self.uuid = uuid.uuid4() + self.uuid = str(uuid.uuid4()) self.tenant_id = tenant_id self.name = name diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 956ad4ca35..2a5120d700 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -19,6 +19,7 @@ import logging from quantum.common import exceptions as exc +from quantum.db import api as db LOG = logging.getLogger('quantum.plugins.SamplePlugin') @@ -258,35 +259,37 @@ class FakePlugin(object): 'net-ports': _port_dict_2}} def __init__(self): - FakePlugin._net_counter = len(FakePlugin._networks) + db_options = {"sql_connection": "sqlite:///fake_plugin.sqllite"} + db.configure_db(db_options) + FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): - network = FakePlugin._networks.get(network_id) + network = db.network_get(network_id) if not network: raise exc.NetworkNotFound(net_id=network_id) return network def _get_port(self, tenant_id, network_id, port_id): net = self._get_network(tenant_id, network_id) - port = net['net-ports'].get(int(port_id)) - if not port: + port = db.port_get(port_id) + # Port must exist and belong to the appropriate network. + if not port or port['network_id']!=net['uuid']: raise exc.PortNotFound(net_id=network_id, port_id=port_id) return port 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) return True def _validate_attachment(self, tenant_id, network_id, port_id, remote_interface_id): - network = self._get_network(tenant_id, network_id) - for port in network['net-ports'].values(): - if port['attachment'] == remote_interface_id: + for port in db.port_list(network_id): + if port['interface_id'] == remote_interface_id: raise exc.AlreadyAttached(net_id=network_id, port_id=port_id, - att_id=port['attachment'], - att_port_id=port['port-id']) + att_id=port['interface_id'], + att_port_id=port['uuid']) def get_all_networks(self, tenant_id): """ @@ -295,14 +298,19 @@ class FakePlugin(object): the specified tenant. """ 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): """ retrieved a list of all the remote vifs that are attached to the network """ - LOG.debug("get_network_details() called") + LOG.debug("FakePlugin.get_network_details() called") return self._get_network(tenant_id, net_id) def create_network(self, tenant_id, net_name): @@ -311,15 +319,9 @@ class FakePlugin(object): a symbolic name. """ LOG.debug("FakePlugin.create_network() called") - FakePlugin._net_counter += 1 - new_net_id = ("0" * (3 - len(str(FakePlugin._net_counter)))) + \ - str(FakePlugin._net_counter) - 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 + new_net = db.network_create(tenant_id, net_name) + # Return uuid for newly created network as net-id. + return {'net-id': new_net['uuid']} def delete_network(self, tenant_id, net_id): """ @@ -327,14 +329,14 @@ class FakePlugin(object): belonging to the specified tenant. """ 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 if net: if net['net-ports']: - for port in net['net-ports'].values(): - if port['attachment']: + for port in db.port_list(net_id): + if port['interface-id']: raise exc.NetworkInUse(net_id=net_id) - FakePlugin._networks.pop(net_id) + db.network_destroy(net_id) return net # Network not found raise exc.NetworkNotFound(net_id=net_id) @@ -345,8 +347,8 @@ class FakePlugin(object): Virtual Network. """ LOG.debug("FakePlugin.rename_network() called") + db.network_rename(net_id, tenant_id, new_name) net = self._get_network(tenant_id, net_id) - net['net-name'] = new_name return net def get_all_ports(self, tenant_id, net_id): @@ -355,9 +357,12 @@ class FakePlugin(object): specified Virtual Network. """ LOG.debug("FakePlugin.get_all_ports() called") - network = self._get_network(tenant_id, net_id) - ports_on_net = network['net-ports'].values() - return ports_on_net + port_ids = [] + ports = db.port_list(net_id) + 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): """ @@ -372,30 +377,19 @@ class FakePlugin(object): Creates a port on the specified Virtual Network. """ LOG.debug("FakePlugin.create_port() called") - net = self._get_network(tenant_id, net_id) - # check port state - # TODO(salvatore-orlando): Validate port state in API? - self._validate_port_state(port_state) - ports = net['net-ports'] - if len(ports.keys()) == 0: - new_port_id = 1 - else: - 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 + port = db.port_create(net_id) + port_item = {'port-id':str(port.uuid)} + return port_item - 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. """ + port=self._get_port(tenant_id, net_id, port_id) LOG.debug("FakePlugin.update_port() called") - port = self._get_port(tenant_id, net_id, port_id) self._validate_port_state(port_state) - port['port-state'] = port_state - return port + db.port_set_state(new_state) + return def delete_port(self, tenant_id, net_id, port_id): """ @@ -411,9 +405,12 @@ class FakePlugin(object): raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port['attachment']) try: - net['net-ports'].pop(int(port_id)) - except KeyError: - raise exc.PortNotFound(net_id=net_id, port_id=port_id) + port = db.port_destroy(port_id) + except Exception, e: + 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): """ @@ -428,7 +425,7 @@ class FakePlugin(object): if port['attachment']: raise exc.PortInUse(net_id=net_id, port_id=port_id, att_id=port['attachment']) - port['attachment'] = remote_interface_id + db.port_set_attachment(port_id, remote_interface_id) def unplug_interface(self, tenant_id, net_id, port_id): """ @@ -436,7 +433,6 @@ class FakePlugin(object): specified Virtual Network. """ LOG.debug("FakePlugin.unplug_interface() called") - port = self._get_port(tenant_id, net_id, port_id) # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? - port['attachment'] = None + db.port_set_attachment(port_id, None) From 2a8dc1cc881c40be2676bace03ee7e74838cb608 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 29 Jun 2011 16:48:55 +0100 Subject: [PATCH 11/25] Removing static data for FakePlugin --- quantum/plugins/SamplePlugin.py | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 2a5120d700..603c4d7946 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -232,32 +232,6 @@ class FakePlugin(object): 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': 'UP', - 'attachment': None}} - _port_dict_2 = { - 1: {'port-id': 1, - 'port-state': 'UP', - '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): db_options = {"sql_connection": "sqlite:///fake_plugin.sqllite"} db.configure_db(db_options) From fcc0f44230582dcf6951e90bf04bf3a780c303c2 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 30 Jun 2011 18:13:01 +0100 Subject: [PATCH 12/25] Starting implementation of unit tests Fixing minor bugs with FakePlugin --- quantum/api/networks.py | 2 +- quantum/api/ports.py | 4 +- quantum/api/views/ports.py | 1 - quantum/common/wsgi.py | 5 -- quantum/plugins/SamplePlugin.py | 30 +++++---- tests/unit/test_api.py | 108 ++++++++++++++++++++++---------- tests/unit/testlib.py | 61 +++++++++++------- 7 files changed, 136 insertions(+), 75 deletions(-) diff --git a/quantum/api/networks.py b/quantum/api/networks.py index a3c75bb7ca..e629d40d52 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -80,7 +80,7 @@ class Controller(common.QuantumController): return faults.Fault(e) network = self.network_manager.\ create_network(tenant_id, - request_params['network-name']) + request_params['net-name']) builder = networks_view.get_view_builder(request) result = builder.build(network) return dict(networks=result) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index e93541dacb..be74cc9e7b 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -132,8 +132,8 @@ class Controller(common.QuantumController): def get_resource(self, request, tenant_id, network_id, id): try: - result = self.network_manager.get_interface_details( - tenant_id, network_id, id) + result = self.network_manager.get_port_details( + tenant_id, network_id, id).get('attachment-id', None) return dict(attachment=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/api/views/ports.py b/quantum/api/views/ports.py index 87758453b9..71dac25268 100644 --- a/quantum/api/views/ports.py +++ b/quantum/api/views/ports.py @@ -44,5 +44,4 @@ class ViewBuilder(object): def _build_detail(self, port_data): """Return a simple model of a server.""" return dict(port=dict(id=port_data['port-id'], - attachment=port_data['attachment'], state=port_data['port-state'])) diff --git a/quantum/common/wsgi.py b/quantum/common/wsgi.py index 23e7c1c3d0..d883746acb 100644 --- a/quantum/common/wsgi.py +++ b/quantum/common/wsgi.py @@ -299,7 +299,6 @@ class Router(object): Route the incoming request to a controller based on self.map. If no match, return a 404. """ - LOG.debug("HERE - wsgi.Router.__call__") return self._router @staticmethod @@ -337,10 +336,6 @@ class Controller(object): arg_dict = req.environ['wsgiorg.routing_args'][1] action = arg_dict['action'] method = getattr(self, action) - LOG.debug("ARG_DICT:%s", arg_dict) - LOG.debug("Action:%s", action) - LOG.debug("Method:%s", method) - LOG.debug("%s %s" % (req.method, req.url)) del arg_dict['controller'] del arg_dict['action'] if 'format' in arg_dict: diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 603c4d7946..aba0c391d2 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -285,7 +285,9 @@ class FakePlugin(object): are attached to the network """ 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): """ @@ -306,10 +308,9 @@ class FakePlugin(object): net = self._get_network(tenant_id, net_id) # Verify that no attachments are plugged into the network if net: - if net['net-ports']: - for port in db.port_list(net_id): - if port['interface-id']: - raise exc.NetworkInUse(net_id=net_id) + for port in db.port_list(net_id): + if port['interface-id']: + raise exc.NetworkInUse(net_id=net_id) db.network_destroy(net_id) return net # Network not found @@ -344,7 +345,11 @@ class FakePlugin(object): that is attached to this particular port. """ 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): """ @@ -359,10 +364,9 @@ class FakePlugin(object): """ Updates the state of a port on the specified Virtual Network. """ - port=self._get_port(tenant_id, net_id, port_id) LOG.debug("FakePlugin.update_port() called") - self._validate_port_state(port_state) - db.port_set_state(new_state) + self._validate_port_state(new_state) + db.port_set_state(port_id,new_state) return def delete_port(self, tenant_id, net_id, port_id): @@ -375,9 +379,9 @@ class FakePlugin(object): LOG.debug("FakePlugin.delete_port() called") net = self._get_network(tenant_id, net_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, - att_id=port['attachment']) + att_id=port['interface_id']) try: port = db.port_destroy(port_id) except Exception, e: @@ -396,9 +400,9 @@ class FakePlugin(object): self._validate_attachment(tenant_id, net_id, port_id, remote_interface_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, - att_id=port['attachment']) + att_id=port['interface_id']) db.port_set_attachment(port_id, remote_interface_id) def unplug_interface(self, tenant_id, net_id, port_id): diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 0f955d6aed..8f349674e6 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -17,15 +17,36 @@ # @author: Brad Hall, Nicira Networks # @author: Salvatore Orlando, Citrix Systems -import tests.unit.testlib as testlib +import logging import unittest +import tests.unit.testlib as testlib + from quantum import api as server +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.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_list_networks_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 # # _fault_names = { @@ -40,42 +61,65 @@ class APIPortsTest(unittest.TestCase): # 470: "serviceUnavailable", # 471: "pluginFault"} - def test_deletePort(self): - tenant = "tenant1" - network = "test1" - req = testlib.create_network_request(tenant, network) - network_obj = self.network.create(req, tenant) - network_id = network_obj["networks"]["network"]["id"] - req = testlib.create_empty_request() - rv = self.port.create(req, tenant, network_id) - port_id = rv["ports"]["port"]["id"] - self.assertTrue(port_id > 0) - rv = self.port.delete("", tenant, network_id, port_id) - self.assertEqual(rv.status_int, 202) + + def _test_delete_port(self, format): + content_type = "application/" + format + port_state = "ACTIVE" + 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) + port_data = Serializer().deserialize(port_res.body, content_type) + port_id = port_data["ports"]["port"]["id"] + 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_res = delete_port_req.get_response(self.api) + self.assertEqual(delete_port_res.status_int, 202) - def test_deletePortNegative(self): - tenant = "tenant1" - network = "test1" + def test_deletePort_xml(self): + self._test_delete_port('xml') + + def test_deletePort_json(self): + self._test_delete_port('json') + + + #def test_deletePortNegative(self): + # tenant = "tenant1" + # network = "test1" # Check for network not found - rv = self.port.delete("", tenant, network, 2) - self.assertEqual(rv.wrapped_exc.status_int, 420) + #rv = self.port.delete("", tenant, network, 2) + #self.assertEqual(rv.wrapped_exc.status_int, 420) # Create a network to put the port on - req = testlib.create_network_request(tenant, network) - network_obj = self.network.create(req, tenant) - network_id = network_obj["networks"]["network"]["id"] + #req = testlib.create_network_request(tenant, network) + #network_obj = self.network.create(req, tenant) + #network_id = network_obj["networks"]["network"]["id"] # Test for portnotfound - rv = self.port.delete("", tenant, network_id, 2) - self.assertEqual(rv.wrapped_exc.status_int, 430) + #rv = self.port.delete("", tenant, network_id, 2) + #self.assertEqual(rv.wrapped_exc.status_int, 430) # 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) + #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) +# \ No newline at end of file diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py index a9ba11af1a..15bb008118 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib.py @@ -3,34 +3,53 @@ import webob from quantum.common.wsgi import Serializer -class Request(webob.Request): - - def best_match_content_type(self): - return "application/json" - - def get_content_type(self): - return "application/json" - - -def create_request(path, body): - req = Request.blank(path) - req.method = "POST" +def create_request(path, body, content_type, method= 'GET'): + req = webob.Request.blank(path) + req.method = method req.headers = {} - req.headers['Accept'] = "application/json" + req.headers['Accept'] = content_type req.body = body return req - -def create_empty_request(): - return create_request("/v0.1/tenant.json", "") +def create_list_networks_request(tenant_id, format='xml'): + method = 'GET' + path = "/tenants/%(tenant_id)s/networks.%(format)s" % locals() + content_type = "application/" + format + return create_request(path, None, content_type, method) -def create_network_request(tenant_id, network_name): - path = "/v0.1/tenants/%s/networks.json" % tenant_id - data = {'network': {'network-name': '%s' % network_name}} - content_type = "application/json" +def create_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 body = Serializer().serialize(data, content_type) - return create_request(path, body) + return create_request(path, body, content_type, method) + + +def create_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 + return create_request(path, None, content_type, method) + + +def create_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 + 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'): + method = 'DELETE' + path = "/tenants/%(tenant_id)s/networks/" \ + "%(network_id)s/ports/%(port_id)s.%(format)s" % locals() + content_type = "application/" + format + return create_request(path, None, content_type, method) def create_attachment_request(tid, nid, pid, attachment_id): From a93c1b1acbffc264ba21f3c0859af5f33afc9a95 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Fri, 1 Jul 2011 12:20:31 +0100 Subject: [PATCH 13/25] Adding unit test Applying pep8 fixes Note: teardown does not yet work properly. Thinking about replacing it with something that simply deletes the temporary sql lite db. --- quantum/api/ports.py | 3 +- quantum/db/models.py | 3 +- quantum/manager.py | 1 + quantum/plugins/SamplePlugin.py | 37 ++++++------ run_tests.py | 4 +- tests/functional/test_service.py | 5 +- tests/unit/test_api.py | 99 ++++++++++++++++++++------------ tests/unit/testlib.py | 18 ++++-- 8 files changed, 105 insertions(+), 65 deletions(-) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index be74cc9e7b..2a38fbe394 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -133,7 +133,8 @@ class Controller(common.QuantumController): def get_resource(self, request, tenant_id, network_id, id): try: result = self.network_manager.get_port_details( - tenant_id, network_id, id).get('attachment-id', None) + tenant_id, network_id, id).get('attachment-id', + None) return dict(attachment=result) except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/db/models.py b/quantum/db/models.py index af9189df05..2a0845386a 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -96,10 +96,9 @@ class Port(BASE, QuantumBase): self.network_id = network_id self.state = "DOWN" - def __repr__(self): return "" % (self.uuid, self.network_id, - self.state,self.interface_id) + self.state, self.interface_id) class Network(BASE, QuantumBase): diff --git a/quantum/manager.py b/quantum/manager.py index 71a13fb9c7..a42f15c68c 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -35,6 +35,7 @@ from quantum_plugin_base import QuantumPluginBase CONFIG_FILE = "plugins.ini" LOG = logging.getLogger('quantum.manager') + def find_config(basepath): for root, dirs, files in os.walk(basepath): if CONFIG_FILE in files: diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index aba0c391d2..623042a122 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -23,6 +23,7 @@ from quantum.db import api as db LOG = logging.getLogger('quantum.plugins.SamplePlugin') + class QuantumEchoPlugin(object): """ @@ -234,7 +235,7 @@ class FakePlugin(object): def __init__(self): db_options = {"sql_connection": "sqlite:///fake_plugin.sqllite"} - db.configure_db(db_options) + db.configure_db(db_options) FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): @@ -245,9 +246,12 @@ class FakePlugin(object): def _get_port(self, tenant_id, network_id, port_id): net = self._get_network(tenant_id, network_id) - port = db.port_get(port_id) + try: + 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 not port or port['network_id']!=net['uuid']: + if port['network_id'] != net['uuid']: raise exc.PortNotFound(net_id=network_id, port_id=port_id) return port @@ -274,10 +278,10 @@ class FakePlugin(object): LOG.debug("FakePlugin.get_all_networks() called") nets = [] for net in db.network_list(tenant_id): - net_item = {'net-id':str(net.uuid), - 'net-name':net.name} + net_item = {'net-id': str(net.uuid), + 'net-name': net.name} nets.append(net_item) - return nets + return nets def get_network_details(self, tenant_id, net_id): """ @@ -286,8 +290,8 @@ class FakePlugin(object): """ LOG.debug("FakePlugin.get_network_details() called") net = self._get_network(tenant_id, net_id) - return {'net-id':str(net.uuid), - 'net-name':net.name} + return {'net-id': str(net.uuid), + 'net-name': net.name} def create_network(self, tenant_id, net_name): """ @@ -309,7 +313,7 @@ class FakePlugin(object): # Verify that no attachments are plugged into the network if net: for port in db.port_list(net_id): - if port['interface-id']: + if port['interface_id']: raise exc.NetworkInUse(net_id=net_id) db.network_destroy(net_id) return net @@ -335,7 +339,7 @@ class FakePlugin(object): port_ids = [] ports = db.port_list(net_id) for x in ports: - d = {'port-id':str(x.uuid)} + d = {'port-id': str(x.uuid)} port_ids.append(d) return port_ids @@ -346,10 +350,9 @@ class FakePlugin(object): """ LOG.debug("FakePlugin.get_port_details() called") 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} - + 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): """ @@ -357,7 +360,7 @@ class FakePlugin(object): """ LOG.debug("FakePlugin.create_port() called") port = db.port_create(net_id) - port_item = {'port-id':str(port.uuid)} + port_item = {'port-id': str(port.uuid)} return port_item def update_port(self, tenant_id, net_id, port_id, new_state): @@ -366,8 +369,8 @@ class FakePlugin(object): """ LOG.debug("FakePlugin.update_port() called") self._validate_port_state(new_state) - db.port_set_state(port_id,new_state) - return + db.port_set_state(port_id, new_state) + return def delete_port(self, tenant_id, net_id, port_id): """ diff --git a/run_tests.py b/run_tests.py index 633a760408..d63cc34a42 100644 --- a/run_tests.py +++ b/run_tests.py @@ -63,7 +63,7 @@ To run a single functional test module:: """ import gettext -import logging +import logging import os import unittest import sys @@ -289,7 +289,7 @@ if __name__ == '__main__': hdlr.setFormatter(formatter) logger.addHandler(hdlr) logger.setLevel(logging.DEBUG) - + working_dir = os.path.abspath("tests") c = config.Config(stream=sys.stdout, env=os.environ, diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py index a3f8985c60..e897843e09 100644 --- a/tests/functional/test_service.py +++ b/tests/functional/test_service.py @@ -17,8 +17,6 @@ # under the License. import gettext -import json -import sys import unittest gettext.install('quantum', unicode=1) @@ -161,7 +159,8 @@ class QuantumTest(unittest.TestCase): #self.assertEqual(res.status, 200, "Bad response: %s" % output) #resdict = json.loads(output) #attachment = resdict["attachment"] - #self.assertEqual(attachment, "fudd", "Attachment: %s" % attachment) + #self.assertEqual(attachment, "fudd", "Attachment: %s" + #% attachment) #def test_renameNetwork(self): #self.create_network(test_network1_data) diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 8f349674e6..6e4fe1960d 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -15,14 +15,14 @@ # License for the specific language governing permissions and limitations # under the License. # @author: Brad Hall, Nicira Networks -# @author: Salvatore Orlando, Citrix Systems +# @author: Salvatore Orlando, Citrix Systems import logging import unittest import tests.unit.testlib as testlib -from quantum import api as server +from quantum import api as server from quantum.common.wsgi import Serializer LOG = logging.getLogger('quantum.tests.test_api') @@ -37,16 +37,15 @@ class APIPortsTest(unittest.TestCase): def tearDown(self): """Clear the test environment""" # Remove all the networks. - network_req = testlib.create_list_networks_request(self.tenant_id) + 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") + 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) - - - + create_network_delete_request(self.tenant_id, network['id']) + network_delete_req.get_response(self.api) + # Fault names copied here for reference # # _fault_names = { @@ -61,58 +60,51 @@ class APIPortsTest(unittest.TestCase): # 470: "serviceUnavailable", # 471: "pluginFault"} - def _test_delete_port(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, + 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 = 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) port_data = Serializer().deserialize(port_res.body, content_type) - port_id = port_data["ports"]["port"]["id"] + port_id = port_data['ports']['port']['id'] 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, + 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_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_deletePort_xml(self): + def test_delete_port_xml(self): self._test_delete_port('xml') - def test_deletePort_json(self): + def test_delete_port_json(self): self._test_delete_port('json') - - #def test_deletePortNegative(self): - # tenant = "tenant1" - # network = "test1" - - # Check for network not found - #rv = self.port.delete("", tenant, network, 2) - #self.assertEqual(rv.wrapped_exc.status_int, 420) - - # Create a network to put the port on - #req = testlib.create_network_request(tenant, network) - #network_obj = self.network.create(req, tenant) - #network_id = network_obj["networks"]["network"]["id"] - - # Test for portnotfound - #rv = self.port.delete("", tenant, network_id, 2) - #self.assertEqual(rv.wrapped_exc.status_int, 430) - + def _test_delete_port_in_use(self): # Test for portinuse #rv = self.port.create(req, tenant, network_id) #port_id = rv["ports"]["port"]["id"] @@ -122,4 +114,39 @@ class APIPortsTest(unittest.TestCase): #self.assertEqual(rv.status_int, 202) #rv = self.port.delete("", tenant, network_id, port_id) #self.assertEqual(rv.wrapped_exc.status_int, 432) -# \ No newline at end of file + pass + + + 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') + + def test_delete_port_with_bad_id_json(self): + self._test_delete_port_with_bad_id('json') + diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py index 15bb008118..024b8202ad 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib.py @@ -3,7 +3,7 @@ import webob from quantum.common.wsgi import Serializer -def create_request(path, body, content_type, method= 'GET'): +def create_request(path, body, content_type, method='GET'): req = webob.Request.blank(path) req.method = method req.headers = {} @@ -11,10 +11,11 @@ def create_request(path, body, content_type, method= 'GET'): req.body = body return req -def create_list_networks_request(tenant_id, format='xml'): + +def create_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/" + format return create_request(path, None, content_type, method) @@ -22,7 +23,7 @@ def create_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/" + format body = Serializer().serialize(data, content_type) return create_request(path, body, content_type, method) @@ -35,6 +36,14 @@ def create_network_delete_request(tenant_id, network_id, format='xml'): return create_request(path, None, content_type, method) +def create_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 + return create_request(path, None, content_type, method) + + def create_new_port_request(tenant_id, network_id, port_state, format='xml'): method = 'POST' path = "/tenants/%(tenant_id)s/networks/" \ @@ -44,6 +53,7 @@ def create_new_port_request(tenant_id, network_id, port_state, format='xml'): 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'): method = 'DELETE' path = "/tenants/%(tenant_id)s/networks/" \ From dbd0fa51932745753e65e8949ac869c63324fad7 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 4 Jul 2011 11:43:16 +0100 Subject: [PATCH 14/25] Now loading plugin before setting up routes. Passing same plugin instance to API controllers. Also renamed 'network_manager' to 'plugin' in API controllers. --- quantum/api/__init__.py | 15 ++++++++------- quantum/api/api_common.py | 8 ++------ quantum/api/networks.py | 14 +++++++------- quantum/api/ports.py | 20 ++++++++++---------- quantum/manager.py | 2 +- 5 files changed, 28 insertions(+), 31 deletions(-) diff --git a/quantum/api/__init__.py b/quantum/api/__init__.py index 3bf7f113ad..fc34767333 100644 --- a/quantum/api/__init__.py +++ b/quantum/api/__init__.py @@ -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 @@ -46,32 +47,32 @@ class APIRouterV01(wsgi.Router): super(APIRouterV01, self).__init__(mapper) def _setup_routes(self, mapper): - + # Loads the quantum plugin + plugin = manager.QuantumManager().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'])) diff --git a/quantum/api/api_common.py b/quantum/api/api_common.py index df8608df3b..19e189e90e 100644 --- a/quantum/api/api_common.py +++ b/quantum/api/api_common.py @@ -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() diff --git a/quantum/api/networks.py b/quantum/api/networks.py index a24cf09ab5..e8d3db51bc 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -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['network-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)) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index c2de0d75fb..b2d4016e53 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -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: @@ -132,7 +132,7 @@ class Controller(common.QuantumController): def get_resource(self, request, tenant_id, network_id, id): try: - result = self.network_manager.get_interface_details( + result = self._plugin.get_interface_details( tenant_id, network_id, id) return dict(attachment=result) except exception.NetworkNotFound as e: @@ -151,7 +151,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 +167,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: diff --git a/quantum/manager.py b/quantum/manager.py index a9662d8eb6..5f8fe6531a 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -61,5 +61,5 @@ class QuantumManager(object): "All compatibility tests passed\n") self.plugin = plugin_klass() - def get_manager(self): + def get_plugin(self): return self.plugin From c8be977cdcf6bc816a36e0522ac64d8b812cd249 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 4 Jul 2011 18:50:45 +0100 Subject: [PATCH 15/25] Temporary commit --- quantum/db/models.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/quantum/db/models.py b/quantum/db/models.py index 2a0845386a..547ea106ce 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -20,6 +20,7 @@ import uuid from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.exc import IntegrityError from sqlalchemy.ext.declarative import declarative_base from sqlalchemy.orm import relation @@ -44,8 +45,7 @@ class QuantumBase(object): def delete(self, session=None): """Delete this object.""" - self.deleted = True - self.deleted_at = utils.utcnow() + # TODO: this method does not do anything at the moment self.save(session=session) def __setitem__(self, key, value): From 704ed9a4e1c96fb23f8b964b08664fea3f2be2ff Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 5 Jul 2011 01:08:18 +0100 Subject: [PATCH 16/25] Fixing syntax errors in db/models.py --- quantum/db/models.py | 23 ++--------------------- 1 file changed, 2 insertions(+), 21 deletions(-) diff --git a/quantum/db/models.py b/quantum/db/models.py index 547ea106ce..1a18e7e518 100644 --- a/quantum/db/models.py +++ b/quantum/db/models.py @@ -19,10 +19,9 @@ import uuid -from sqlalchemy import Column, Integer, String, ForeignKey -from sqlalchemy.exc import IntegrityError +from sqlalchemy import Column, String, ForeignKey from sqlalchemy.ext.declarative import declarative_base -from sqlalchemy.orm import relation +from sqlalchemy.orm import relation, object_mapper BASE = declarative_base() @@ -30,24 +29,6 @@ BASE = declarative_base() class QuantumBase(object): """Base class for Quantum Models.""" - def save(self, session=None): - """Save this object.""" - if not session: - session = get_session() - session.add(self) - try: - session.flush() - except IntegrityError, e: - if str(e).endswith('is not unique'): - raise exception.Duplicate(str(e)) - else: - raise - - def delete(self, session=None): - """Delete this object.""" - # TODO: this method does not do anything at the moment - self.save(session=session) - def __setitem__(self, key, value): setattr(self, key, value) From a975ec64514affa5bb95e61957409954e3d9f7fa Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 5 Jul 2011 10:24:13 +0100 Subject: [PATCH 17/25] Removing excess debug line --- quantum/manager.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/quantum/manager.py b/quantum/manager.py index 5f8fe6531a..6ab46d5fef 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -26,9 +26,8 @@ The caller should make sure that QuantumManager is a singleton. """ import gettext import os -gettext.install('quantum', unicode=1) -import os +gettext.install('quantum', unicode=1) from common import utils from quantum_plugin_base import QuantumPluginBase @@ -37,7 +36,7 @@ CONFIG_FILE = "plugins.ini" def find_config(basepath): - for root, dirs, files in os.walk(basepath): + for root, files in os.walk(basepath): if CONFIG_FILE in files: return os.path.join(root, CONFIG_FILE) return None @@ -51,7 +50,6 @@ class QuantumManager(object): else: self.configuration_file = config plugin_location = utils.getPluginFromConfig(self.configuration_file) - print "PLUGIN LOCATION:%s" % plugin_location plugin_klass = utils.import_class(plugin_location) if not issubclass(plugin_klass, QuantumPluginBase): raise Exception("Configured Quantum plug-in " \ From 7e8f06d6ccc4f61a356ff1743e8ee43f8419f277 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 5 Jul 2011 18:27:26 +0100 Subject: [PATCH 18/25] Now REALLY using in-memory db --- quantum/db/api.py | 11 ++++++----- tests/unit/test_api.py | 12 ++++++------ 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/quantum/db/api.py b/quantum/db/api.py index 212308e8fc..43dc1c5f1e 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -19,7 +19,8 @@ from sqlalchemy import create_engine from sqlalchemy.orm import sessionmaker, exc -import models + +from quantum.db import models _ENGINE = None _MAKER = None @@ -42,11 +43,11 @@ def configure_db(options): register_models() -def unconfigure_db(): - unregister_models() - # Unset the engine +def clear_db(): global _ENGINE - _ENGINE = None + assert _ENGINE + for table in reversed(BASE.metadata.sorted_tables): + _ENGINE.execute(table.delete()) def get_session(autocommit=True, expire_on_commit=False): diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index f6997a2cf9..af64bd686c 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -20,6 +20,7 @@ import logging import unittest + import tests.unit.testlib as testlib from quantum import api as server @@ -93,7 +94,6 @@ class APITest(unittest.TestCase): 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}, @@ -221,7 +221,7 @@ class APITest(unittest.TestCase): LOG.debug("_test_delete_port_with_bad_id - format:%s - END", format) def setUp(self): - self.db_file = ':memory' + self.db_file = ':memory:' options = {} options['plugin_provider'] = 'quantum.plugins.SamplePlugin.FakePlugin' options['sql_connection'] = 'sqlite:///%s' % self.db_file @@ -231,14 +231,14 @@ class APITest(unittest.TestCase): def tearDown(self): """Clear the test environment""" - # Unconfigure database engine - db.unconfigure_db() + # Remove database contents + db.clear_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_create_network_xml(self): + # self._test_create_network('xml') def test_show_network_json(self): self._test_show_network('json') From e92064d74522f5db91425286f97ec1ed32cf933f Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 6 Jul 2011 12:23:18 +0100 Subject: [PATCH 19/25] Added more unit tests for API Starting work on functional tests, importing code from Glance --- quantum/api/networks.py | 9 +- quantum/plugins/SamplePlugin.py | 10 +- tests/functional/__init__.py | 296 +++++++++++++++++++++++++++++++ tests/functional/test_service.py | 2 +- tests/unit/test_api.py | 65 ++++++- tests/unit/testlib.py | 12 +- 6 files changed, 380 insertions(+), 14 deletions(-) diff --git a/quantum/api/networks.py b/quantum/api/networks.py index b36e2edd9d..9bad90ab29 100644 --- a/quantum/api/networks.py +++ b/quantum/api/networks.py @@ -94,12 +94,9 @@ class Controller(common.QuantumController): except exc.HTTPError as e: return faults.Fault(e) try: - network = self._plugin.rename_network(tenant_id, - id, request_params['network-name']) - - builder = networks_view.get_view_builder(request) - result = builder.build(network, True) - return dict(networks=result) + self._plugin.rename_network(tenant_id, id, + request_params['net-name']) + return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 1f2b1ce61d..086f558759 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -243,8 +243,9 @@ class FakePlugin(object): FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): - network = db.network_get(network_id) - if not network: + try: + network = db.network_get(network_id) + except: raise exc.NetworkNotFound(net_id=network_id) return network @@ -330,7 +331,10 @@ class FakePlugin(object): Virtual Network. """ LOG.debug("FakePlugin.rename_network() called") - db.network_rename(net_id, tenant_id, new_name) + 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) return net diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py index e69de29bb2..bd79dcdff2 100644 --- a/tests/functional/__init__.py +++ b/tests/functional/__init__.py @@ -0,0 +1,296 @@ +# vim: tabstop=4 shiftwidth=4 softtabstop=4 + +# Copyright 2011 Somebody PLC +# 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. + +""" +Base test class for running non-stubbed tests (functional tests) + +The FunctionalTest class contains helper methods for starting the API +and Registry server, grabbing the logs of each, cleaning up pidfiles, +and spinning down the servers. +""" + +import datetime +import functools +import os +import random +import shutil +import signal +import socket +import tempfile +import time +import unittest +import urlparse + +from tests.utils import execute, get_unused_port + +from sqlalchemy import create_engine + + +class Server(object): + """ + Class used to easily manage starting and stopping + a server during functional test runs. + """ + def __init__(self, test_dir, port): + """ + Creates a new Server object. + + :param test_dir: The directory where all test stuff is kept. This is + passed from the FunctionalTestCase. + :param port: The port to start a server up on. + """ + self.verbose = True + self.debug = True + self.test_dir = test_dir + self.bind_port = port + self.conf_file = None + self.conf_base = None + + def start(self, **kwargs): + """ + Starts the server. + + Any kwargs passed to this method will override the configuration + value in the conf file used in starting the servers. + """ + if self.conf_file: + raise RuntimeError("Server configuration file already exists!") + if not self.conf_base: + raise RuntimeError("Subclass did not populate config_base!") + + conf_override = self.__dict__.copy() + if kwargs: + conf_override.update(**kwargs) + + # Create temporary configuration file for Quantum Unit tests. + + conf_file = tempfile.NamedTemporaryFile() + conf_file.write(self.conf_base % conf_override) + conf_file.flush() + self.conf_file = conf_file + self.conf_file_name = conf_file.name + + cmd = ("./bin/quantum %(conf_file_name)s" % self.__dict__) + return execute(cmd) + + def stop(self): + """ + Spin down the server. + """ + # The only way we can do that at the moment is by killing quantum + # TODO - find quantum PID and do a sudo kill + + +class ApiServer(Server): + + """ + Server object that starts/stops/manages the API server + """ + + def __init__(self, test_dir, port, registry_port): + super(ApiServer, self).__init__(test_dir, port) + self.server_name = 'api' + self.default_store = 'file' + self.image_dir = os.path.join(self.test_dir, + "images") + self.pid_file = os.path.join(self.test_dir, + "api.pid") + self.log_file = os.path.join(self.test_dir, "api.log") + self.registry_port = registry_port + self.conf_base = """[DEFAULT] +verbose = %(verbose)s +debug = %(debug)s +filesystem_store_datadir=%(image_dir)s +default_store = %(default_store)s +bind_host = 0.0.0.0 +bind_port = %(bind_port)s +registry_host = 0.0.0.0 +registry_port = %(registry_port)s +log_file = %(log_file)s + +[pipeline:glance-api] +pipeline = versionnegotiation apiv1app + +[pipeline:versions] +pipeline = versionsapp + +[app:versionsapp] +paste.app_factory = glance.api.versions:app_factory + +[app:apiv1app] +paste.app_factory = glance.api.v1:app_factory + +[filter:versionnegotiation] +paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory +""" + + +class QuantumAPIServer(Server): + + """ + Server object that starts/stops/manages the Quantum API Server + """ + + def __init__(self, test_dir, port): + super(QuantumAPIServer, self).__init__(test_dir, port) + + self.db_file = os.path.join(self.test_dir, ':memory:') + self.sql_connection = 'sqlite:///%s' % self.db_file + self.conf_base = """[DEFAULT] +# Show more verbose log output (sets INFO log level output) +verbose = %(verbose)s +# Show debugging output in logs (sets DEBUG log level output) +debug = %(debug)s +# Address to bind the API server +bind_host = 0.0.0.0 +# Port for test API server +bind_port = %(bind_port)s + +[composite:quantum] +use = egg:Paste#urlmap +/: quantumversions +/v0.1: quantumapi + +[app:quantumversions] +paste.app_factory = quantum.api.versions:Versions.factory + +[app:quantumapi] +paste.app_factory = quantum.api:APIRouterV01.factory +""" + + +class FunctionalTest(unittest.TestCase): + + """ + Base test class for any test that wants to test the actual + servers and clients and not just the stubbed out interfaces + """ + + def setUp(self): + + self.test_id = random.randint(0, 100000) + self.test_port = get_unused_port() + + self.quantum_server = QuantumAPIServer(self.test_dir, + self.test_port) + + + def tearDown(self): + self.cleanup() + # We destroy the test data store between each test case, + # and recreate it, which ensures that we have no side-effects + # from the tests + self._reset_database() + + def _reset_database(self): + conn_string = self.registry_server.sql_connection + conn_pieces = urlparse.urlparse(conn_string) + if conn_string.startswith('sqlite'): + # We can just delete the SQLite database, which is + # the easiest and cleanest solution + db_path = conn_pieces.path.strip('/') + if db_path and os.path.exists(db_path): + os.unlink(db_path) + # No need to recreate the SQLite DB. SQLite will + # create it for us if it's not there... + elif conn_string.startswith('mysql'): + # We can execute the MySQL client to destroy and re-create + # the MYSQL database, which is easier and less error-prone + # than using SQLAlchemy to do this via MetaData...trust me. + database = conn_pieces.path.strip('/') + loc_pieces = conn_pieces.netloc.split('@') + host = loc_pieces[1] + auth_pieces = loc_pieces[0].split(':') + user = auth_pieces[0] + password = "" + if len(auth_pieces) > 1: + if auth_pieces[1].strip(): + password = "-p%s" % auth_pieces[1] + sql = ("drop database if exists %(database)s; " + "create database %(database)s;") % locals() + cmd = ("mysql -u%(user)s %(password)s -h%(host)s " + "-e\"%(sql)s\"") % locals() + exitcode, out, err = execute(cmd) + self.assertEqual(0, exitcode) + + + def start_servers(self, **kwargs): + """ + Starts the Quantum API server on an unused port. + + Any kwargs passed to this method will override the configuration + value in the conf file used in starting the server. + """ + + exitcode, out, err = self.quantum_server.start(**kwargs) + + self.assertEqual(0, exitcode, + "Failed to spin up the Quantum server. " + "Got: %s" % err) + #self.assertTrue("Starting quantum with" in out) + #TODO: replace with appropriate assert + + self.wait_for_servers() + + def ping_server(self, port): + """ + Simple ping on the port. If responsive, return True, else + return False. + + :note We use raw sockets, not ping here, since ping uses ICMP and + has no concept of ports... + """ + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + s.connect(("127.0.0.1", port)) + s.close() + return True + except socket.error, e: + return False + + def wait_for_servers(self, timeout=3): + """ + Tight loop, waiting for both API and registry server to be + available on the ports. Returns when both are pingable. There + is a timeout on waiting for the servers to come up. + + :param timeout: Optional, defaults to 3 seconds + """ + now = datetime.datetime.now() + timeout_time = now + datetime.timedelta(seconds=timeout) + while (timeout_time > now): + if self.ping_server(self.api_port) and\ + self.ping_server(self.registry_port): + return + now = datetime.datetime.now() + time.sleep(0.05) + self.assertFalse(True, "Failed to start servers.") + + def stop_servers(self): + """ + Called to stop the started servers in a normal fashion. Note + that cleanup() will stop the servers using a fairly draconian + method of sending a SIGTERM signal to the servers. Here, we use + the glance-control stop method to gracefully shut the server down. + This method also asserts that the shutdown was clean, and so it + is meant to be called during a normal test case sequence. + """ + + exitcode, out, err = self.quantum_server.stop() + self.assertEqual(0, exitcode, + "Failed to spin down the Quantum server. " + "Got: %s" % err) diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py index e897843e09..bdd926f578 100644 --- a/tests/functional/test_service.py +++ b/tests/functional/test_service.py @@ -49,7 +49,7 @@ class QuantumTest(unittest.TestCase): self.client = MiniClient(HOST, PORT, USE_SSL) #def create_network(self, data, tenant_id=TENANT_ID): - # content_type = "application/" + FORMAT + # content_type = "application/%s" % FORMAT # body = Serializer().serialize(data, content_type) # res = self.client.do_request(tenant_id, 'POST', "/networks." + FORMAT, # body=body) diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index af64bd686c..d27d508c5b 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -100,6 +100,49 @@ class APITest(unittest.TestCase): 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_not_found(self, format): + LOG.debug("_test_rename_network_not_found - format:%s - START", format) + content_type = "application/%s" % 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 @@ -237,8 +280,14 @@ class APITest(unittest.TestCase): 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_xml(self): + self._test_create_network('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') @@ -252,6 +301,18 @@ class APITest(unittest.TestCase): 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_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') diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py index c78c700cba..357abd83ab 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib.py @@ -21,8 +21,8 @@ def network_list_request(tenant_id, 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() + 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) @@ -35,6 +35,14 @@ def new_network_request(tenant_id, network_name, format='xml'): 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'): + method = 'PUT' + path = "/tenants/%(tenant_id)s/networks" \ + "/%(network_id)s.%(format)s" % locals() + data = {'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' From 67ee9c1fd4034d2375e0f746d4fd78c7829825ba Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Wed, 6 Jul 2011 12:41:13 +0100 Subject: [PATCH 20/25] removing pep8 errors --- quantum/plugins/SamplePlugin.py | 6 +++--- tests/functional/__init__.py | 6 ++---- tests/unit/testlib.py | 2 ++ 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 086f558759..d78b845126 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -243,9 +243,9 @@ class FakePlugin(object): FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): - try: + try: network = db.network_get(network_id) - except: + except: raise exc.NetworkNotFound(net_id=network_id) return network @@ -331,7 +331,7 @@ class FakePlugin(object): Virtual Network. """ LOG.debug("FakePlugin.rename_network() called") - try: + try: db.network_rename(net_id, tenant_id, new_name) except: raise exc.NetworkNotFound(net_id=net_id) diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py index bd79dcdff2..92e9598243 100644 --- a/tests/functional/__init__.py +++ b/tests/functional/__init__.py @@ -76,7 +76,7 @@ class Server(object): if kwargs: conf_override.update(**kwargs) - # Create temporary configuration file for Quantum Unit tests. + # Create temporary configuration file for Quantum Unit tests. conf_file = tempfile.NamedTemporaryFile() conf_file.write(self.conf_base % conf_override) @@ -188,7 +188,6 @@ class FunctionalTest(unittest.TestCase): self.quantum_server = QuantumAPIServer(self.test_dir, self.test_port) - def tearDown(self): self.cleanup() # We destroy the test data store between each test case, @@ -227,7 +226,6 @@ class FunctionalTest(unittest.TestCase): exitcode, out, err = execute(cmd) self.assertEqual(0, exitcode) - def start_servers(self, **kwargs): """ Starts the Quantum API server on an unused port. @@ -242,7 +240,7 @@ class FunctionalTest(unittest.TestCase): "Failed to spin up the Quantum server. " "Got: %s" % err) #self.assertTrue("Starting quantum with" in out) - #TODO: replace with appropriate assert + #TODO: replace with appropriate assert self.wait_for_servers() diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py index 357abd83ab..724cd0a92f 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib.py @@ -35,6 +35,7 @@ def new_network_request(tenant_id, network_name, format='xml'): 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'): method = 'PUT' path = "/tenants/%(tenant_id)s/networks" \ @@ -44,6 +45,7 @@ def update_network_request(tenant_id, network_id, network_name, format='xml'): 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/" \ From 71ceb8fb841770b85eea937f0e22d5d2f7b442c7 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 14 Jul 2011 12:47:49 +0100 Subject: [PATCH 21/25] Removing code related to functional tests --- tests/functional/__init__.py | 294 ------------------------------- tests/functional/miniclient.py | 99 ----------- tests/functional/test_service.py | 221 ----------------------- 3 files changed, 614 deletions(-) delete mode 100644 tests/functional/__init__.py delete mode 100644 tests/functional/miniclient.py delete mode 100644 tests/functional/test_service.py diff --git a/tests/functional/__init__.py b/tests/functional/__init__.py deleted file mode 100644 index 92e9598243..0000000000 --- a/tests/functional/__init__.py +++ /dev/null @@ -1,294 +0,0 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright 2011 Somebody PLC -# 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. - -""" -Base test class for running non-stubbed tests (functional tests) - -The FunctionalTest class contains helper methods for starting the API -and Registry server, grabbing the logs of each, cleaning up pidfiles, -and spinning down the servers. -""" - -import datetime -import functools -import os -import random -import shutil -import signal -import socket -import tempfile -import time -import unittest -import urlparse - -from tests.utils import execute, get_unused_port - -from sqlalchemy import create_engine - - -class Server(object): - """ - Class used to easily manage starting and stopping - a server during functional test runs. - """ - def __init__(self, test_dir, port): - """ - Creates a new Server object. - - :param test_dir: The directory where all test stuff is kept. This is - passed from the FunctionalTestCase. - :param port: The port to start a server up on. - """ - self.verbose = True - self.debug = True - self.test_dir = test_dir - self.bind_port = port - self.conf_file = None - self.conf_base = None - - def start(self, **kwargs): - """ - Starts the server. - - Any kwargs passed to this method will override the configuration - value in the conf file used in starting the servers. - """ - if self.conf_file: - raise RuntimeError("Server configuration file already exists!") - if not self.conf_base: - raise RuntimeError("Subclass did not populate config_base!") - - conf_override = self.__dict__.copy() - if kwargs: - conf_override.update(**kwargs) - - # Create temporary configuration file for Quantum Unit tests. - - conf_file = tempfile.NamedTemporaryFile() - conf_file.write(self.conf_base % conf_override) - conf_file.flush() - self.conf_file = conf_file - self.conf_file_name = conf_file.name - - cmd = ("./bin/quantum %(conf_file_name)s" % self.__dict__) - return execute(cmd) - - def stop(self): - """ - Spin down the server. - """ - # The only way we can do that at the moment is by killing quantum - # TODO - find quantum PID and do a sudo kill - - -class ApiServer(Server): - - """ - Server object that starts/stops/manages the API server - """ - - def __init__(self, test_dir, port, registry_port): - super(ApiServer, self).__init__(test_dir, port) - self.server_name = 'api' - self.default_store = 'file' - self.image_dir = os.path.join(self.test_dir, - "images") - self.pid_file = os.path.join(self.test_dir, - "api.pid") - self.log_file = os.path.join(self.test_dir, "api.log") - self.registry_port = registry_port - self.conf_base = """[DEFAULT] -verbose = %(verbose)s -debug = %(debug)s -filesystem_store_datadir=%(image_dir)s -default_store = %(default_store)s -bind_host = 0.0.0.0 -bind_port = %(bind_port)s -registry_host = 0.0.0.0 -registry_port = %(registry_port)s -log_file = %(log_file)s - -[pipeline:glance-api] -pipeline = versionnegotiation apiv1app - -[pipeline:versions] -pipeline = versionsapp - -[app:versionsapp] -paste.app_factory = glance.api.versions:app_factory - -[app:apiv1app] -paste.app_factory = glance.api.v1:app_factory - -[filter:versionnegotiation] -paste.filter_factory = glance.api.middleware.version_negotiation:filter_factory -""" - - -class QuantumAPIServer(Server): - - """ - Server object that starts/stops/manages the Quantum API Server - """ - - def __init__(self, test_dir, port): - super(QuantumAPIServer, self).__init__(test_dir, port) - - self.db_file = os.path.join(self.test_dir, ':memory:') - self.sql_connection = 'sqlite:///%s' % self.db_file - self.conf_base = """[DEFAULT] -# Show more verbose log output (sets INFO log level output) -verbose = %(verbose)s -# Show debugging output in logs (sets DEBUG log level output) -debug = %(debug)s -# Address to bind the API server -bind_host = 0.0.0.0 -# Port for test API server -bind_port = %(bind_port)s - -[composite:quantum] -use = egg:Paste#urlmap -/: quantumversions -/v0.1: quantumapi - -[app:quantumversions] -paste.app_factory = quantum.api.versions:Versions.factory - -[app:quantumapi] -paste.app_factory = quantum.api:APIRouterV01.factory -""" - - -class FunctionalTest(unittest.TestCase): - - """ - Base test class for any test that wants to test the actual - servers and clients and not just the stubbed out interfaces - """ - - def setUp(self): - - self.test_id = random.randint(0, 100000) - self.test_port = get_unused_port() - - self.quantum_server = QuantumAPIServer(self.test_dir, - self.test_port) - - def tearDown(self): - self.cleanup() - # We destroy the test data store between each test case, - # and recreate it, which ensures that we have no side-effects - # from the tests - self._reset_database() - - def _reset_database(self): - conn_string = self.registry_server.sql_connection - conn_pieces = urlparse.urlparse(conn_string) - if conn_string.startswith('sqlite'): - # We can just delete the SQLite database, which is - # the easiest and cleanest solution - db_path = conn_pieces.path.strip('/') - if db_path and os.path.exists(db_path): - os.unlink(db_path) - # No need to recreate the SQLite DB. SQLite will - # create it for us if it's not there... - elif conn_string.startswith('mysql'): - # We can execute the MySQL client to destroy and re-create - # the MYSQL database, which is easier and less error-prone - # than using SQLAlchemy to do this via MetaData...trust me. - database = conn_pieces.path.strip('/') - loc_pieces = conn_pieces.netloc.split('@') - host = loc_pieces[1] - auth_pieces = loc_pieces[0].split(':') - user = auth_pieces[0] - password = "" - if len(auth_pieces) > 1: - if auth_pieces[1].strip(): - password = "-p%s" % auth_pieces[1] - sql = ("drop database if exists %(database)s; " - "create database %(database)s;") % locals() - cmd = ("mysql -u%(user)s %(password)s -h%(host)s " - "-e\"%(sql)s\"") % locals() - exitcode, out, err = execute(cmd) - self.assertEqual(0, exitcode) - - def start_servers(self, **kwargs): - """ - Starts the Quantum API server on an unused port. - - Any kwargs passed to this method will override the configuration - value in the conf file used in starting the server. - """ - - exitcode, out, err = self.quantum_server.start(**kwargs) - - self.assertEqual(0, exitcode, - "Failed to spin up the Quantum server. " - "Got: %s" % err) - #self.assertTrue("Starting quantum with" in out) - #TODO: replace with appropriate assert - - self.wait_for_servers() - - def ping_server(self, port): - """ - Simple ping on the port. If responsive, return True, else - return False. - - :note We use raw sockets, not ping here, since ping uses ICMP and - has no concept of ports... - """ - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - s.connect(("127.0.0.1", port)) - s.close() - return True - except socket.error, e: - return False - - def wait_for_servers(self, timeout=3): - """ - Tight loop, waiting for both API and registry server to be - available on the ports. Returns when both are pingable. There - is a timeout on waiting for the servers to come up. - - :param timeout: Optional, defaults to 3 seconds - """ - now = datetime.datetime.now() - timeout_time = now + datetime.timedelta(seconds=timeout) - while (timeout_time > now): - if self.ping_server(self.api_port) and\ - self.ping_server(self.registry_port): - return - now = datetime.datetime.now() - time.sleep(0.05) - self.assertFalse(True, "Failed to start servers.") - - def stop_servers(self): - """ - Called to stop the started servers in a normal fashion. Note - that cleanup() will stop the servers using a fairly draconian - method of sending a SIGTERM signal to the servers. Here, we use - the glance-control stop method to gracefully shut the server down. - This method also asserts that the shutdown was clean, and so it - is meant to be called during a normal test case sequence. - """ - - exitcode, out, err = self.quantum_server.stop() - self.assertEqual(0, exitcode, - "Failed to spin down the Quantum server. " - "Got: %s" % err) diff --git a/tests/functional/miniclient.py b/tests/functional/miniclient.py deleted file mode 100644 index be49867355..0000000000 --- a/tests/functional/miniclient.py +++ /dev/null @@ -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 diff --git a/tests/functional/test_service.py b/tests/functional/test_service.py deleted file mode 100644 index bdd926f578..0000000000 --- a/tests/functional/test_service.py +++ /dev/null @@ -1,221 +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 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, tenant_id=TENANT_ID): - # content_type = "application/%s" % 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_getNonexistentNetwork(self): - # TODO(bgh): parse exception and make sure it is NetworkNotFound - #try: - # res = self.client.do_request(TENANT_ID, 'GET', - # "/networks/%s.%s" % ("8675309", "xml")) - # self.assertEqual(res.status, 400) - #except Exception, e: - # print "Caught exception: %s" % (str(e)) - - #def test_deleteNonexistentNetwork(self): - # TODO(bgh): parse exception and make sure it is NetworkNotFound - #try: - # res = self.client.do_request(TENANT_ID, 'DELETE', - # "/networks/%s.%s" % ("8675309", "xml")) - # self.assertEqual(res.status, 400) - #except Exception, e: - # print "Caught exception: %s" % (str(e)) - - #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 = json.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)) - #output = res.read() - #self.assertEqual(res.status, 200, "Bad response: %s" % output) - #if len(output) > 0: - # resdict = json.loads(output) - # self.assertTrue(len(resdict["ports"]) == 0, - # "Found unexpected ports: %s" % output) - #else: - # 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 = json.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_getAttachment(self): - #self.create_network(test_network1_data) - #res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - #resdict = json.loads(res.read()) - #for n in resdict["networks"]: - # net_id = n["id"] - - # Step 1 - Create Port for network and attempt to get the - # attachment (even though there isn't one) - #res = self.client.do_request(TENANT_ID, 'POST', - # "/networks/%s/ports.%s" % (net_id, FORMAT)) - #output = res.read() - #self.assertEqual(res.status, 200, "Bad response: %s" % output) - #resdict = json.loads(output) - #port_id = resdict["ports"]["port"]["id"] - - #res = self.client.do_request(TENANT_ID, 'GET', - # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - # FORMAT)) - #output = res.read() - #self.assertEqual(res.status, 200, "Bad response: %s" % output) - - # Step 2 - Add an attachment - #data = {'port': {'attachment-id': 'fudd'}} - #content_type = "application/" + FORMAT - #body = Serializer().serialize(data, content_type) - #res = self.client.do_request(TENANT_ID, 'PUT', - # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - # FORMAT), body=body) - #output = res.read() - #self.assertEqual(res.status, 202, "Bad response: %s" % output) - - # Step 3 - Fetch the attachment - #res = self.client.do_request(TENANT_ID, 'GET', - # "/networks/%s/ports/%s/attachment.%s" % (net_id, port_id, - # FORMAT)) - #output = res.read() - #self.assertEqual(res.status, 200, "Bad response: %s" % output) - #resdict = json.loads(output) - #attachment = resdict["attachment"] - #self.assertEqual(attachment, "fudd", "Attachment: %s" - #% attachment) - - #def test_renameNetwork(self): - #self.create_network(test_network1_data) - #res = self.client.do_request(TENANT_ID, 'GET', "/networks." + FORMAT) - #resdict = json.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 = json.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 test_createNetworkOnMultipleTenants(self): - # Create the same network on multiple tenants - #self.create_network(test_network1_data, "tenant1") - #self.create_network(test_network1_data, "tenant2") - - #def delete_networks(self, tenant_id=TENANT_ID): - # Remove all the networks created on the tenant (including ports and - # attachments) - #res = self.client.do_request(tenant_id, 'GET', - # "/networks." + FORMAT) - #resdict = json.loads(res.read()) - #for n in resdict["networks"]: - # net_id = n["id"] - # # Delete all the ports - # 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 = json.loads(output) - # ids = [] - # for p in resdict["ports"]: - # res = self.client.do_request(tenant_id, 'DELETE', - # "/networks/%s/ports/%s/attachment.%s" % (net_id, p["id"], - # FORMAT)) - # res = self.client.do_request(tenant_id, 'DELETE', - # "/networks/%s/ports/%s.%s" % (net_id, p["id"], FORMAT)) - # Now, remove the network - # res = self.client.do_request(tenant_id, 'DELETE', - # "/networks/" + net_id + "." + FORMAT) - # self.assertEqual(res.status, 202) - - def tearDown(self): - self.delete_networks() - -# Standard boilerplate to call the main() function. -if __name__ == '__main__': - suite = unittest.TestLoader().loadTestsFromTestCase(QuantumTest) - unittest.TextTestRunner(verbosity=2).run(suite) From 3ecf1c6ab65ffa759a85b4a2b9b56f01fb905dd8 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 18 Jul 2011 17:56:42 +0100 Subject: [PATCH 22/25] Adding more unit tests --- quantum/api/ports.py | 2 +- quantum/db/api.py | 3 +- quantum/plugins/SamplePlugin.py | 12 +- tests/unit/test_api.py | 372 ++++++++++++++++++++++++++++++-- tests/unit/testlib.py | 34 ++- 5 files changed, 401 insertions(+), 22 deletions(-) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index f9f39c400c..93472bd884 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -132,7 +132,7 @@ class Controller(common.QuantumController): def get_resource(self, request, tenant_id, network_id, id): try: - result = self.network_manager.get_port_details( + result = self._plugin.get_port_details( tenant_id, network_id, id).get('attachment-id', None) return dict(attachment=result) diff --git a/quantum/db/api.py b/quantum/db/api.py index 43dc1c5f1e..88d34030e1 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -137,10 +137,11 @@ def network_destroy(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() with session.begin(): port = models.Port(net_id) + port['state'] = state or 'DOWN' session.add(port) session.flush() return port diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index d78b845126..bc11669be5 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -367,7 +367,9 @@ class FakePlugin(object): Creates a port on the specified Virtual Network. """ LOG.debug("FakePlugin.create_port() called") - port = db.port_create(net_id) + # verify net_id + self._get_network(tenant_id, net_id) + port = db.port_create(net_id, port_state) port_item = {'port-id': str(port.uuid)} return port_item @@ -376,9 +378,15 @@ class FakePlugin(object): Updates the state of a port on the specified Virtual Network. """ LOG.debug("FakePlugin.update_port() called") + #validate port and network ids + self._get_network(tenant_id, net_id) + self._get_port(tenant_id, net_id, port_id) self._validate_port_state(new_state) db.port_set_state(port_id, new_state) - return + port_item = {'port-id': port_id, + 'port-state': new_state} + return port_item + def delete_port(self, tenant_id, net_id, port_id): """ diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index d27d508c5b..dbf10cd5e1 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -48,27 +48,37 @@ LOG = logging.getLogger('quantum.tests.test_api') class APITest(unittest.TestCase): - def _create_network(self, format): + + 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, - self.network_name, - format) + net_name,format, + custom_req_body) network_res = network_req.get_response(self.api) - self.assertEqual(network_res.status_int, 200) - network_data = Serializer().deserialize(network_res.body, + 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'] + return network_data['networks']['network']['id'] - def _create_port(self, network_id, port_state, format): + 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) + port_state, format, + custom_req_body) port_res = port_req.get_response(self.api) - self.assertEqual(port_res.status_int, 200) - port_data = Serializer().deserialize(port_res.body, content_type) - return port_data['ports']['port']['id'] + 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) @@ -85,6 +95,28 @@ class APITest(unittest.TestCase): 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 @@ -131,9 +163,20 @@ class APITest(unittest.TestCase): 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) - content_type = "application/%s" % format new_name = 'new_network_name' update_network_req = testlib.update_network_request(self.tenant_id, "A BAD ID", @@ -189,6 +232,62 @@ class APITest(unittest.TestCase): 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 @@ -203,6 +302,21 @@ class APITest(unittest.TestCase): 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 @@ -263,6 +377,138 @@ class APITest(unittest.TestCase): 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_put_attachment(self, format): + LOG.debug("_test_put_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_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 setUp(self): self.db_file = ':memory:' options = {} @@ -277,12 +523,24 @@ class APITest(unittest.TestCase): # 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') @@ -307,6 +565,12 @@ class APITest(unittest.TestCase): 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') @@ -319,12 +583,48 @@ class APITest(unittest.TestCase): 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') @@ -337,8 +637,56 @@ class APITest(unittest.TestCase): 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_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') \ No newline at end of file diff --git a/tests/unit/testlib.py b/tests/unit/testlib.py index 724cd0a92f..638febf534 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib.py @@ -27,20 +27,22 @@ def show_network_request(tenant_id, network_id, format='xml'): return create_request(path, None, content_type, method) -def new_network_request(tenant_id, network_name, format='xml'): +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 = {'network': {'net-name': '%s' % network_name}} + 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'): +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 = {'network': {'net-name': '%s' % network_name}} + 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) @@ -70,11 +72,12 @@ def show_port_request(tenant_id, network_id, port_id, format='xml'): return create_request(path, None, content_type, method) -def new_port_request(tenant_id, network_id, port_state, format='xml'): +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 = {'port': {'port-state': '%s' % port_state}} + 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) @@ -88,6 +91,25 @@ def port_delete_request(tenant_id, network_id, port_id, format='xml'): 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' From 74727d29d9048b187528cebf0ecc83c60cd44a87 Mon Sep 17 00:00:00 2001 From: Brad Hall Date: Mon, 18 Jul 2011 13:44:48 -0700 Subject: [PATCH 23/25] Add TESTING document: description and polices for quantum tests --- TESTING | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 TESTING diff --git a/TESTING b/TESTING new file mode 100644 index 0000000000..2b433d7eb2 --- /dev/null +++ b/TESTING @@ -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. + From 6feacc0c6d71785783fcee52a4a183230adf1bae Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 18 Jul 2011 23:51:22 +0100 Subject: [PATCH 24/25] Unit tests for API completed fixed pep8 errors --- quantum/api/ports.py | 3 +- quantum/db/api.py | 8 + quantum/plugins/SamplePlugin.py | 6 +- tests/unit/test_api.py | 325 ++++++++++++++++------ tests/unit/{testlib.py => testlib_api.py} | 17 +- 5 files changed, 258 insertions(+), 101 deletions(-) rename tests/unit/{testlib.py => testlib_api.py} (89%) diff --git a/quantum/api/ports.py b/quantum/api/ports.py index 93472bd884..8a7ee81c10 100644 --- a/quantum/api/ports.py +++ b/quantum/api/ports.py @@ -162,11 +162,10 @@ class Controller(common.QuantumController): except exception.AlreadyAttached as e: return faults.Fault(faults.AlreadyAttached(e)) - #TODO - Complete implementation of these APIs def detach_resource(self, request, tenant_id, network_id, id): try: self._plugin.unplug_interface(tenant_id, - network_id, id) + network_id, id) return exc.HTTPAccepted() except exception.NetworkNotFound as e: return faults.Fault(faults.NetworkNotFound(e)) diff --git a/quantum/db/api.py b/quantum/db/api.py index 88d34030e1..6813d2096c 100644 --- a/quantum/db/api.py +++ b/quantum/db/api.py @@ -195,6 +195,14 @@ def port_set_attachment(port_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): session = get_session() try: diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index bc11669be5..412d5f18b1 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -383,11 +383,10 @@ class FakePlugin(object): self._get_port(tenant_id, net_id, port_id) self._validate_port_state(new_state) db.port_set_state(port_id, new_state) - port_item = {'port-id': port_id, + port_item = {'port-id': port_id, 'port-state': new_state} return port_item - def delete_port(self, tenant_id, net_id, port_id): """ Deletes a port on a specified Virtual Network, @@ -430,6 +429,7 @@ class FakePlugin(object): specified Virtual Network. """ LOG.debug("FakePlugin.unplug_interface() called") + self._get_port(tenant_id, net_id, port_id) # TODO(salvatore-orlando): # Should unplug on port without attachment raise an Error? - db.port_set_attachment(port_id, None) + db.port_unset_attachment(port_id) diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index dbf10cd5e1..84339f7159 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -20,54 +20,37 @@ import logging import unittest - -import tests.unit.testlib as testlib +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 -# Fault names copied here for reference -# -# _fault_names = { -# 400: "malformedRequest", -# 401: "unauthorized", -# 420: "networkNotFound", -# 421: "networkInUse", -# 430: "portNotFound", -# 431: "requestedStateInvalid", -# 432: "portInUse", -# 440: "alreadyAttached", -# 470: "serviceUnavailable", -# 471: "pluginFault"} - - 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: + if name: net_name = name else: net_name = self.network_name network_req = testlib.new_network_request(self.tenant_id, - net_name,format, + 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) + content_type) return network_data['networks']['network']['id'] - def _create_port(self, network_id, port_state, format, + 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 @@ -96,11 +79,13 @@ class APITest(unittest.TestCase): 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) + 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) + 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) @@ -114,9 +99,9 @@ class APITest(unittest.TestCase): network_data = Serializer().deserialize(list_network_res.body, content_type) # Check network count: should return 2 - self.assertEqual(len(network_data['networks']),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 @@ -147,9 +132,9 @@ class APITest(unittest.TestCase): 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) + 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, @@ -164,27 +149,31 @@ class APITest(unittest.TestCase): 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) + 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_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) + 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) + 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) + "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) + 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) @@ -197,7 +186,8 @@ class APITest(unittest.TestCase): 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_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) @@ -246,9 +236,9 @@ class APITest(unittest.TestCase): port_data = Serializer().deserialize(list_port_res.body, content_type) # Check port count: should return 2 - self.assertEqual(len(port_data['ports']),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 @@ -256,7 +246,7 @@ class APITest(unittest.TestCase): 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, + network_id, port_id, format) show_port_res = show_port_req.get_response(self.api) self.assertEqual(show_port_res.status_int, 200) @@ -267,35 +257,38 @@ class APITest(unittest.TestCase): 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) + 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, + "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) + 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", + 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_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) @@ -303,10 +296,13 @@ class APITest(unittest.TestCase): 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) + 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) + 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) @@ -316,7 +312,7 @@ class APITest(unittest.TestCase): 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 @@ -362,10 +358,10 @@ class APITest(unittest.TestCase): 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) + 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) @@ -378,7 +374,8 @@ class APITest(unittest.TestCase): 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) + 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) @@ -387,7 +384,8 @@ class APITest(unittest.TestCase): 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) + 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) @@ -397,13 +395,13 @@ class APITest(unittest.TestCase): 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, + 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, + network_id, port_id, format) show_port_res = show_port_req.get_response(self.api) self.assertEqual(show_port_res.status_int, 200) @@ -412,51 +410,58 @@ class APITest(unittest.TestCase): 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) + 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, + "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) + 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) + 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", + 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) + 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) + 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, + 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_put_attachment(self, format): - LOG.debug("_test_put_attachment - format:%s - START", format) + 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) @@ -477,10 +482,56 @@ class APITest(unittest.TestCase): 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) + LOG.debug("_test_put_attachment_networknotfound - format:%s - START", + format) port_state = 'DOWN' interface_id = "test_interface" network_id = self._create_network(format) @@ -492,10 +543,12 @@ class APITest(unittest.TestCase): 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) + 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) + LOG.debug("_test_put_attachment_portnotfound - format:%s - START", + format) port_state = 'DOWN' interface_id = "test_interface" network_id = self._create_network(format) @@ -507,8 +560,60 @@ class APITest(unittest.TestCase): 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) - + 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): self.db_file = ':memory:' options = {} @@ -528,7 +633,7 @@ class APITest(unittest.TestCase): def test_list_networks_xml(self): self._test_list_networks('xml') - + def test_create_network_json(self): self._test_create_network('json') @@ -594,19 +699,19 @@ class APITest(unittest.TestCase): 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') @@ -648,34 +753,52 @@ class APITest(unittest.TestCase): 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') @@ -684,9 +807,27 @@ class APITest(unittest.TestCase): 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') \ No newline at end of file + 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') diff --git a/tests/unit/testlib.py b/tests/unit/testlib_api.py similarity index 89% rename from tests/unit/testlib.py rename to tests/unit/testlib_api.py index 638febf534..1a731be154 100644 --- a/tests/unit/testlib.py +++ b/tests/unit/testlib_api.py @@ -27,7 +27,7 @@ def show_network_request(tenant_id, network_id, format='xml'): return create_request(path, None, content_type, method) -def new_network_request(tenant_id, network_name='new_name', +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() @@ -37,7 +37,7 @@ def new_network_request(tenant_id, network_name='new_name', return create_request(path, body, content_type, method) -def update_network_request(tenant_id, network_id, network_name, format='xml', +def update_network_request(tenant_id, network_id, network_name, format='xml', custom_req_body=None): method = 'PUT' path = "/tenants/%(tenant_id)s/networks" \ @@ -91,8 +91,8 @@ def port_delete_request(tenant_id, network_id, port_id, format='xml'): 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): +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() @@ -119,3 +119,12 @@ def put_attachment_request(tenant_id, network_id, port_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) From 7e8ae4a4166bf87c5d3f6bc5efa4dd59a9e3efbc Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Thu, 21 Jul 2011 16:34:58 +0100 Subject: [PATCH 25/25] Restoring quantum_plugin_base to previous state. Will discuss in the future whether allow API layer to pass options to plugins upon initialization. --- quantum/manager.py | 2 +- quantum/plugins.ini | 3 ++- quantum/plugins/SamplePlugin.py | 9 ++------- quantum/quantum_plugin_base.py | 8 -------- tests/unit/test_api.py | 2 -- 5 files changed, 5 insertions(+), 19 deletions(-) diff --git a/quantum/manager.py b/quantum/manager.py index 14b7f979ca..2a2383b3e2 100644 --- a/quantum/manager.py +++ b/quantum/manager.py @@ -65,7 +65,7 @@ class QuantumManager(object): else: LOG.debug("Successfully imported Quantum plug-in." \ "All compatibility tests passed") - self.plugin = plugin_klass(options) + self.plugin = plugin_klass() def get_plugin(self): return self.plugin diff --git a/quantum/plugins.ini b/quantum/plugins.ini index 307d2b48d2..448cab59ea 100644 --- a/quantum/plugins.ini +++ b/quantum/plugins.ini @@ -1,3 +1,4 @@ [PLUGIN] # Quantum plugin provider module -provider = quantum.plugins.SamplePlugin.FakePlugin +#provider = quantum.plugins.SamplePlugin.FakePlugin +provider = quantum.plugins.openvswitch.ovs_quantum_plugin.OVSQuantumPlugin diff --git a/quantum/plugins/SamplePlugin.py b/quantum/plugins/SamplePlugin.py index 412d5f18b1..8f46e43731 100644 --- a/quantum/plugins/SamplePlugin.py +++ b/quantum/plugins/SamplePlugin.py @@ -233,13 +233,8 @@ class FakePlugin(object): client/cli/api development """ - 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) + def __init__(self): + db.configure_db({'sql_connection':'sqlite:///:memory:'}) FakePlugin._net_counter = 0 def _get_network(self, tenant_id, network_id): diff --git a/quantum/quantum_plugin_base.py b/quantum/quantum_plugin_base.py index e82c552531..3228f868c7 100644 --- a/quantum/quantum_plugin_base.py +++ b/quantum/quantum_plugin_base.py @@ -30,14 +30,6 @@ 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): """ diff --git a/tests/unit/test_api.py b/tests/unit/test_api.py index 84339f7159..c09af76990 100644 --- a/tests/unit/test_api.py +++ b/tests/unit/test_api.py @@ -615,10 +615,8 @@ class APITest(unittest.TestCase): "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"