From 645251dba4b6d920caed9d4f58f39431d85161d4 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Tue, 21 Jun 2011 00:14:14 -0700 Subject: [PATCH 1/7] 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 4c0ba4eee..a0121b034 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 8a6ba305c..1809af057 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 689624f1b..a6351ddf6 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 34f28fbdd..cb21d7a6b 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 2fae05f0a..000000000 --- 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 a2a72ec8c..a5785c7df 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 610902a7c..9ce83611d 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 0c75b3fc4..66095d85d 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 75619cae6..375a4bd87 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 000000000..e6be088ed --- /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 2/7] 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 e6be088ed..cf70a8fb9 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 dad5dbb764d04cb30a22e5e1bbc62d1f2f5c1e2d Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Sat, 25 Jun 2011 02:04:55 -0700 Subject: [PATCH 3/7] 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 989f0d6c3..59459cadb 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 60c864543..5d2a66ca3 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 21b35684d..9a7efdda5 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 4/7] 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 9a7efdda5..63f4a5223 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 a0692d9cc6c027e4ec8fe6deea9f10fc69748b75 Mon Sep 17 00:00:00 2001 From: Dan Wendlandt Date: Mon, 27 Jun 2011 16:11:09 -0700 Subject: [PATCH 5/7] 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 496055be1..bbbc9fbf9 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 dbd0fa51932745753e65e8949ac869c63324fad7 Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Mon, 4 Jul 2011 11:43:16 +0100 Subject: [PATCH 6/7] 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 3bf7f113a..fc3476733 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 df8608df3..19e189e90 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 a24cf09ab..e8d3db51b 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 c2de0d75f..b2d4016e5 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 a9662d8eb..5f8fe6531 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 a975ec64514affa5bb95e61957409954e3d9f7fa Mon Sep 17 00:00:00 2001 From: Salvatore Orlando Date: Tue, 5 Jul 2011 10:24:13 +0100 Subject: [PATCH 7/7] 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 5f8fe6531..6ab46d5fe 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 " \