diff --git a/quantum/plugins/openvswitch/Makefile b/quantum/plugins/openvswitch/Makefile new file mode 100644 index 00000000000..7cd5e620ed7 --- /dev/null +++ b/quantum/plugins/openvswitch/Makefile @@ -0,0 +1,30 @@ +QUANTUM_PATH=../../../ + +# TODO(bgh): DIST_DIR and target for plugin + +AGENT_DIST_DIR=ovs_quantum_agent +AGENT_DIST_TARBALL=ovs_quantum_agent.tgz + +agent-dist: distclean + mkdir $(AGENT_DIST_DIR) + cp agent/*.py $(AGENT_DIST_DIR) + cp agent/*.sh $(AGENT_DIST_DIR) + cp README $(AGENT_DIST_DIR) + cp ovs_quantum_plugin.ini $(AGENT_DIST_DIR) + tar -zcvf $(AGENT_DIST_TARBALL) $(AGENT_DIST_DIR)/ + @echo "Agent tarball created: $(AGENT_DIST_TARBALL)" + @echo "See README for installation details" + +all: + +clean: + $(find . -name *.pyc | xargs rm) + +distclean: + -rm -rf $(AGENT_DIST_DIR) + -rm -f $(AGENT_DIST_TARBALL) + +check: + PYTHONPATH=$(QUANTUM_PATH):. python ovs_quantum_plugin.py + +PHONY: agent-dist check clean distclean diff --git a/quantum/plugins/openvswitch/README b/quantum/plugins/openvswitch/README new file mode 100644 index 00000000000..ee2248a1736 --- /dev/null +++ b/quantum/plugins/openvswitch/README @@ -0,0 +1,37 @@ +To Run: + +1) On the "Openstack Controller" host: + +MySQL should be installed on the host, and all plugins and clients must be +configured with access to the database. + +To prep mysql, run: + +mysql -u root -p -e "create database ovs_naas" + +2) Edit the configuration file (src/ovs/plugins/ovs_quantum_plugin.ini) + +- Make sure it matches your mysql configuration. This file must be updated + with the addresses and credentials to access the database. + +3) Create the agent distribution tarball + +$ make agent-dist + +4) Copy the resulting tarball to your xenserver + +5) Unpack the tarball and run install.sh. This will install all of the +necessary pieces into /etc/xapi.d/plugins. + +6) Run the agent (example below): + +# /etc/xapi.d/plugins/ovs_quantum_agent.py /etc/xapi.d/plugins/ovs_quantum_plugin.ini + +7) Run ovs_quantum_plugin.py via the quantum plugin framework cli. + +- Edit quantum/plugins.ini to point to where the plugin and configuration + files live + +$ PYTHONPATH=$HOME/src/quantum-framework/quantum:$PYTHONPATH python quantum/cli.py + +This will show all of the available commands. diff --git a/quantum/plugins/openvswitch/__init__.py b/quantum/plugins/openvswitch/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/quantum/plugins/openvswitch/agent/install.sh b/quantum/plugins/openvswitch/agent/install.sh new file mode 100644 index 00000000000..2f2b0381f31 --- /dev/null +++ b/quantum/plugins/openvswitch/agent/install.sh @@ -0,0 +1,38 @@ +#!/bin/bash + +CONF_FILE=/etc/xapi.d/plugins/ovs_quantum_plugin.ini + +if [ ! -d /etc/xapi.d/plugins ]; then + echo "Am I on a xenserver? I can't find the plugins directory!" + exit 1 +fi + +# Make sure we have mysql-python +rpm -qa | grep MYyQL-python >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "MySQL-python not found; installing." + yum -y install MySQL-python + if [ $? -ne 0 ]; then + echo "Failed to install MYSQL-python; agent will not work." + exit 1 + fi +fi + +cp ovs_quantum_agent.py /etc/xapi.d/plugins +cp ovs_quantum_plugin.ini /etc/xapi.d/plugins +cp set_external_ids.sh /etc/xapi.d/plugins + +xe network-list name-label="integration-bridge" | grep xapi >/dev/null 2>&1 +if [ $? -ne 0 ]; then + echo "No integration bridge found. Creating." + xe network-create name-label="integration-bridge" +fi + +BR=$(xe network-list name-label="integration-bridge" | grep "bridge.*:" | awk '{print $4}') +CONF_BR=$(grep integration-bridge ${CONF_FILE} | cut -d= -f2) +if [ "X$BR" != "X$CONF_BR" ]; then + echo "Integration bridge doesn't match configuration file; fixing." + sed -i -e "s/^integration-bridge =.*$/integration-bridge = ${BR}/g" $CONF_FILE +fi + +echo "Make sure to edit: $CONF_FILE" diff --git a/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py new file mode 100755 index 00000000000..9769ab93f97 --- /dev/null +++ b/quantum/plugins/openvswitch/agent/ovs_quantum_agent.py @@ -0,0 +1,272 @@ +#!/usr/bin/env python + +import ConfigParser +import logging as LOG +import MySQLdb +import os +import sys +import time + +from optparse import OptionParser +from subprocess import * + +# A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac' +# attributes set). +class VifPort: + def __init__(self, port_name, ofport, vif_id, vif_mac, switch): + self.port_name = port_name + self.ofport = ofport + self.vif_id = vif_id + self.vif_mac = vif_mac + self.switch = switch + def __str__(self): + return "iface-id=" + self.vif_id + ", vif_mac=" + \ + self.vif_mac + ", port_name=" + self.port_name + \ + ", ofport=" + self.ofport + ", bridge name = " + self.switch.br_name + +class OVSBridge: + def __init__(self, br_name): + self.br_name = br_name + + def run_cmd(self, args): + # LOG.debug("## running command: " + " ".join(args)) + return Popen(args, stdout=PIPE).communicate()[0] + + def run_vsctl(self, args): + full_args = ["ovs-vsctl" ] + args + return self.run_cmd(full_args) + + def reset_bridge(self): + self.run_vsctl([ "--" , "--if-exists", "del-br", self.br_name]) + self.run_vsctl(["add-br", self.br_name]) + + def delete_port(self, port_name): + self.run_vsctl([ "--" , "--if-exists", "del-port", self.br_name, + port_name]) + + def set_db_attribute(self, table_name, record, column, value): + args = [ "set", table_name, record, "%s=%s" % (column,value) ] + self.run_vsctl(args) + + def clear_db_attribute(self, table_name,record, column): + args = [ "clear", table_name, record, column ] + self.run_vsctl(args) + + def run_ofctl(self, cmd, args): + full_args = ["ovs-ofctl", cmd, self.br_name ] + args + return self.run_cmd(full_args) + + 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") + + def add_flow(self,**dict): + if "actions" not in dict: + raise Exception("must specify one or more actions") + if "priority" not in dict: + dict["priority"] = "0" + + flow_str = "priority=%s" % dict["priority"] + if "match" in dict: + flow_str += "," + dict["match"] + flow_str += ",actions=%s" % (dict["actions"]) + self.run_ofctl("add-flow", [ flow_str ] ) + + def delete_flows(self,**dict): + all_args = [] + if "priority" in dict: + all_args.append("priority=%s" % dict["priority"]) + if "match" in dict: + all_args.append(dict["match"]) + if "actions" in dict: + all_args.append("actions=%s" % (dict["actions"])) + flow_str = ",".join(all_args) + self.run_ofctl("del-flows", [ flow_str ] ) + + def db_get_map(self, table, record, column): + str = self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r") + return self.db_str_to_map(str) + + def db_get_val(self, table, record, column): + return self.run_vsctl([ "get" , table, record, column ]).rstrip("\n\r") + + def db_str_to_map(self, full_str): + list = full_str.strip("{}").split(", ") + ret = {} + for e in list: + if e.find("=") == -1: + continue + arr = e.split("=") + ret[arr[0]] = arr[1].strip("\"") + return ret + + def get_port_name_list(self): + res = self.run_vsctl([ "list-ports", self.br_name]) + return res.split("\n")[0:-1] + + def get_port_stats(self, port_name): + return self.db_get_map("Interface", port_name, "statistics") + + # returns a VIF object for each VIF port + def get_vif_ports(self): + edge_ports = [] + port_names = self.get_port_name_list() + for name in port_names: + external_ids = self.db_get_map("Interface",name,"external_ids") + if "iface-id" in external_ids and "attached-mac" in external_ids: + ofport = self.db_get_val("Interface",name,"ofport") + p = VifPort(name, ofport, external_ids["iface-id"], + external_ids["attached-mac"], self) + edge_ports.append(p) + else: + # iface-id might not be set. See if we can figure it out and + # set it here. + external_ids = self.db_get_map("Interface",name,"external_ids") + if "attached-mac" not in external_ids: + continue + vif_uuid = external_ids.get("xs-vif-uuid", "") + if len(vif_uuid) == 0: + continue + LOG.debug("iface-id not set, got vif-uuid: %s" % vif_uuid) + res = os.popen("xe vif-param-get param-name=other-config uuid=%s | grep nicira-iface-id | awk '{print $2}'" % vif_uuid).readline() + res = res.strip() + if len(res) == 0: + continue + external_ids["iface-id"] = res + LOG.info("Setting interface \"%s\" iface-id to \"%s\"" % (name, res)) + self.set_db_attribute("Interface", name, + "external-ids:iface-id", res) + 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: + 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)) + + def port_unbound(self, port, still_exists): + if still_exists: + self.int_br.clear_db_attribute("Port", port.port_name,"tag") + + 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 + 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 = {} + old_local_bindings = {} + old_vif_ports = {} + + while True: + cursor = conn.cursor() + cursor.execute("SELECT * FROM network_bindings") + rows = cursor.fetchall() + cursor.close() + all_bindings = {} + for r in rows: + all_bindings[r[2]] = r[1] + + cursor = conn.cursor() + cursor.execute("SELECT * FROM vlan_bindings") + rows = cursor.fetchall() + cursor.close() + vlan_bindings = {} + for r in rows: + vlan_bindings[r[1]] = r[0] + + new_vif_ports = {} + new_local_bindings = {} + vif_ports = self.int_br.get_vif_ports() + for p in vif_ports: + new_vif_ports[p.vif_id] = p + if p.vif_id in all_bindings: + new_local_bindings[p.vif_id] = all_bindings[p.vif_id] + else: + # no binding, put him on the 'dead vlan' + self.int_br.set_db_attribute("Port", p.port_name, "tag", + "4095") + old_b = old_local_bindings.get(p.vif_id,None) + new_b = new_local_bindings.get(p.vif_id,None) + if old_b != new_b: + if old_b is not None: + LOG.info("Removing binding to net-id = %s for %s" + % (old_b, str(p))) + self.port_unbound(p, True) + if new_b is not None: + LOG.info("Adding binding to net-id = %s for %s" \ + % (new_b, str(p))) + # If we don't have a binding we have to stick it on + # the dead vlan + vlan_id = vlan_bindings.get(all_bindings[p.vif_id], + "4095") + self.port_bound(p, vlan_id) + for vif_id in old_vif_ports.keys(): + if vif_id not in new_vif_ports: + LOG.info("Port Disappeared: %s" % vif_id) + if vif_id in old_local_bindings: + old_b = old_local_bindings[vif_id] + self.port_unbound(old_vif_ports[vif_id], False) + + 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__": + usagestr = "%prog [OPTIONS] " + parser = OptionParser(usage=usagestr) + 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() + sys.exit(1) + + config_file = args[0] + config = ConfigParser.ConfigParser() + try: + config.read(config_file) + except Exception, e: + LOG.error("Unable to parse config file \"%s\": %s" % (config_file, + str(e))) + + integ_br = config.get("OVS", "integration-bridge") + + db_name = config.get("DATABASE", "name") + db_user = config.get("DATABASE", "user") + db_pass = config.get("DATABASE", "pass") + db_host = config.get("DATABASE", "host") + conn = None + try: + 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.daemon_loop(conn) + finally: + if conn: + conn.close() + + sys.exit(0) diff --git a/quantum/plugins/openvswitch/agent/set_external_ids.sh b/quantum/plugins/openvswitch/agent/set_external_ids.sh new file mode 100755 index 00000000000..f56dc4bd91c --- /dev/null +++ b/quantum/plugins/openvswitch/agent/set_external_ids.sh @@ -0,0 +1,15 @@ + +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/ovs_db.py b/quantum/plugins/openvswitch/ovs_db.py new file mode 100644 index 00000000000..8b4106215da --- /dev/null +++ b/quantum/plugins/openvswitch/ovs_db.py @@ -0,0 +1,52 @@ +from sqlalchemy.orm import exc + +import quantum.db.api as db +import quantum.db.models as models +import ovs_models + +def get_vlans(): + session = db.get_session() + try: + bindings = session.query(ovs_models.VlanBinding).\ + all() + except exc.NoResultFound: + return [] + res = [] + for x in bindings: + res.append((x.vlan_id, x.network_id)) + return res + +def add_vlan_binding(vlanid, netid): + session = db.get_session() + binding = ovs_models.VlanBinding(vlanid, netid) + session.add(binding) + session.flush() + return binding.vlan_id + +def remove_vlan_binding(netid): + session = db.get_session() + try: + binding = session.query(ovs_models.VlanBinding).\ + filter_by(network_id=netid).\ + one() + session.delete(binding) + except exc.NoResultFound: + pass + session.flush() + +def update_network_binding(netid, ifaceid): + session = db.get_session() + # Add to or delete from the bindings table + if ifaceid == None: + try: + binding = session.query(ovs_models.NetworkBinding).\ + filter_by(network_id=netid).\ + one() + session.delete(binding) + except exc.NoResultFound: + raise Exception("No binding found with network_id = %s" % netid) + else: + binding = ovs_models.NetworkBinding(netid, ifaceid) + session.add(binding) + + session.flush() diff --git a/quantum/plugins/openvswitch/ovs_models.py b/quantum/plugins/openvswitch/ovs_models.py new file mode 100644 index 00000000000..7e8b2f54b6c --- /dev/null +++ b/quantum/plugins/openvswitch/ovs_models.py @@ -0,0 +1,38 @@ +import uuid + +from sqlalchemy import Column, Integer, String, ForeignKey +from sqlalchemy.ext.declarative import declarative_base +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' + + vlan_id = Column(Integer, primary_key=True) + network_id = Column(String(255)) + + def __init__(self, vlan_id, network_id): + self.network_id = network_id + self.vlan_id = vlan_id + + def __repr__(self): + return "" % \ + (self.vlan_id, self.network_id) diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.ini b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini new file mode 100644 index 00000000000..0c75b3fc48a --- /dev/null +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.ini @@ -0,0 +1,9 @@ +[DATABASE] +name = ovs_naas +user = root +pass = foobar +host = 127.0.0.1 +port = 3306 + +[OVS] +integration-bridge = xapi1 diff --git a/quantum/plugins/openvswitch/ovs_quantum_plugin.py b/quantum/plugins/openvswitch/ovs_quantum_plugin.py new file mode 100644 index 00000000000..e8111c65983 --- /dev/null +++ b/quantum/plugins/openvswitch/ovs_quantum_plugin.py @@ -0,0 +1,296 @@ +import ConfigParser +import logging as LOG +import os +import sys +import unittest + +from quantum.quantum_plugin_base import QuantumPluginBase +import quantum.db.api as db +import ovs_db + +# TODO(bgh): Make sure we delete from network bindings when deleting a port, +# network, etc. + +CONF_FILE="ovs_quantum_plugin.ini" + +LOG.basicConfig(level=LOG.DEBUG) +LOG.getLogger("ovs_quantum_plugin") + +def find_config(basepath): + for root, dirs, files in os.walk(basepath): + if CONF_FILE in files: + return os.path.join(root, CONF_FILE) + return None + +class VlanMap(object): + vlans = {} + def __init__(self): + for x in xrange(2, 4094): + self.vlans[x] = None + def set(self, vlan_id, network_id): + self.vlans[vlan_id] = network_id + def acquire(self, network_id): + for x in xrange(2, 4094): + if self.vlans[x] == None: + self.vlans[x] = network_id + # LOG.debug("VlanMap::acquire %s -> %s" % (x, network_id)) + return x + raise Exception("No free vlans..") + def get(self, vlan_id): + return self.vlans[vlan_id] + def release(self, network_id): + for x in self.vlans.keys(): + if self.vlans[x] == network_id: + self.vlans[x] = None + # LOG.debug("VlanMap::release %s" % (x)) + return + raise Exception("No vlan found with network \"%s\"" % network_id) + +class OVSQuantumPlugin(QuantumPluginBase): + def __init__(self, configfile=None): + config = ConfigParser.ConfigParser() + if configfile == None: + if os.path.exists(CONF_FILE): + configfile = CONF_FILE + else: + configfile = find_config(os.path.abspath(os.path.dirname(__file__))) + if configfile == None: + raise Exception("Configuration file \"%s\" doesn't exist" % + (configfile)) + LOG.info("Using configuration file: %s" % configfile) + config.read(configfile) + LOG.debug("Config: %s" % config) + + DB_NAME = config.get("DATABASE", "name") + DB_USER = config.get("DATABASE", "user") + DB_PASS = config.get("DATABASE", "pass") + DB_HOST = config.get("DATABASE", "host") + options = {"sql_connection": "mysql://%s:%s@%s/%s" % (DB_USER, + DB_PASS, DB_HOST, DB_NAME)} + db.configure_db(options) + + self.vmap = VlanMap() + # Populate the map with anything that is already present in the + # database + vlans = ovs_db.get_vlans() + for x in vlans: + vlan_id, network_id = x + # LOG.debug("Adding already populated vlan %s -> %s" % (vlan_id, network_id)) + self.vmap.set(vlan_id, network_id) + + def get_all_networks(self, tenant_id): + nets = [] + for x in db.network_list(tenant_id): + LOG.debug("Adding network: %s" % x.uuid) + d = {} + d["net-id"] = str(x.uuid) + d["net-name"] = x.name + nets.append(d) + return nets + + def create_network(self, tenant_id, net_name): + d = {} + try: + res = db.network_create(tenant_id, net_name) + LOG.debug("Created newtork: %s" % res) + except Exception, e: + LOG.error("Error: %s" % str(e)) + return d + d["net-id"] = str(res.uuid) + d["net-name"] = res.name + vlan_id = self.vmap.acquire(str(res.uuid)) + ovs_db.add_vlan_binding(vlan_id, str(res.uuid)) + return d + + def delete_network(self, tenant_id, net_id): + net = db.network_destroy(net_id) + d = {} + d["net-id"] = net.uuid + ovs_db.remove_vlan_binding(net_id) + self.vmap.release(net_id) + return d + + def get_network_details(self, tenant_id, net_id): + network = db.network_get(net_id) + d = {} + d["net-id"] = str(network.uuid) + d["net-name"] = network.name + d["net-ports"] = self.get_all_ports(tenant_id, net_id) + return d + + def rename_network(self, tenant_id, net_id, new_name): + try: + net = db.network_rename(net_id, tenant_id, new_name) + except Exception, e: + raise Exception("Failed to rename network: %s" % str(e)) + d = {} + d["net-id"] = str(net.uuid) + d["net-name"] = net.name + return d + + def get_all_ports(self, tenant_id, net_id): + ids = [] + ports = db.port_list(net_id) + for x in ports: + LOG.debug("Appending port: %s" % x.uuid) + d = {} + d["port-id"] = str(x.uuid) + ids.append(d) + return ids + + def create_port(self, tenant_id, net_id, port_state=None): + LOG.debug("Creating port with network_id: %s" % net_id) + port = db.port_create(net_id) + d = {} + d["port-id"] = str(port.uuid) + LOG.debug("-> %s" % (port.uuid)) + return d + + def delete_port(self, tenant_id, net_id, port_id): + try: + 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 update_port(self, tenant_id, net_id, port_id, port_state): + """ + Updates the state of a port on the specified Virtual Network. + """ + LOG.debug("update_port() called\n") + port = db.port_get(port_id) + port['port-state'] = port_state + return port + + def get_port_details(self, tenant_id, net_id, port_id): + port = db.port_get(port_id) + rv = {"port-id": port.uuid, "attachment": port.interface_id, + "net-id": port.network_id, "port-state": "UP"} + return rv + + def get_all_attached_interfaces(self, tenant_id, net_id): + ports = db.port_list(net_id) + ifaces = [] + for p in ports: + ifaces.append(p.interface_id) + return ifaces + + 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, "None") + ovs_db.update_network_binding(net_id, remote_iface_id) + + def get_interface_details(self, tenant_id, net_id, port_id): + res = db.port_get(port_id) + return res.interface_id + +class VlanMapTest(unittest.TestCase): + def setUp(self): + self.vmap = VlanMap() + def tearDown(self): + pass + def testAddVlan(self): + vlan_id = self.vmap.acquire("foobar") + self.assertTrue(vlan_id == 2) + def testReleaseVlan(self): + vlan_id = self.vmap.acquire("foobar") + self.vmap.release("foobar") + self.assertTrue(self.vmap.get(vlan_id) == None) + +# TODO(bgh): Make the tests use a sqlite database instead of mysql +class OVSPluginTest(unittest.TestCase): + def setUp(self): + self.quantum = OVSQuantumPlugin() + self.tenant_id = "testtenant" + + def testCreateNetwork(self): + net1 = self.quantum.create_network(self.tenant_id, "plugin_test1") + self.assertTrue(net1["net-name"] == "plugin_test1") + + def testGetNetworks(self): + net1 = self.quantum.create_network(self.tenant_id, "plugin_test1") + net2 = self.quantum.create_network(self.tenant_id, "plugin_test2") + nets = self.quantum.get_all_networks(self.tenant_id) + count = 0 + for x in nets: + print x + if "plugin_test" in x["net-name"]: + count += 1 + self.assertTrue(count == 2) + + def testDeleteNetwork(self): + net = self.quantum.create_network(self.tenant_id, "plugin_test1") + self.quantum.delete_network(self.tenant_id, net["net-id"]) + nets = self.quantum.get_all_networks(self.tenant_id) + count = 0 + for x in nets: + print x + if "plugin_test" in x["net-name"]: + count += 1 + self.assertTrue(count == 0) + + def testRenameNetwork(self): + net = self.quantum.create_network(self.tenant_id, "plugin_test1") + net = self.quantum.rename_network(self.tenant_id, net["net-id"], + "plugin_test_renamed") + self.assertTrue(net["net-name"] == "plugin_test_renamed") + + def testCreatePort(self): + net1 = self.quantum.create_network(self.tenant_id, "plugin_test1") + port = self.quantum.create_port(self.tenant_id, net1["net-id"]) + ports = self.quantum.get_all_ports(self.tenant_id, net1["net-id"]) + count = 0 + for p in ports: + count += 1 + self.assertTrue(count == 1) + + def testDeletePort(self): + pass + + def testGetPorts(self): + pass + + def testPlugInterface(self): + pass + + def testUnPlugInterface(self): + pass + + def tearDown(self): + networks = self.quantum.get_all_networks(self.tenant_id) + print networks + # Clean up any test networks lying around + for net in networks: + id = net["net-id"] + name = net["net-name"] + if "plugin_test" in name: + # Clean up any test ports lying around + ports = self.quantum.get_all_ports(self.tenant_id, id) + print ports + for p in ports: + self.quantum.delete_port(self.tenant_id, id, p["port-id"]) + self.quantum.delete_network(self.tenant_id, id) + +if __name__ == "__main__": + suite = unittest.TestLoader().loadTestsFromTestCase(OVSPluginTest) + unittest.TextTestRunner(verbosity=2).run(suite) + suite = unittest.TestLoader().loadTestsFromTestCase(VlanMapTest) + unittest.TextTestRunner(verbosity=2).run(suite) + + # TODO(bgh) move to unit tets + if False: + quantum.plug_interface(tenant_id, net1, port, "vif1.1") + portdetails = quantum.get_port_details(tenant_id, net1, port) + LOG.DEBUG(portdetails) + LOG.info("=== PORT: %s" % quantum.get_port_details(tenant_id, net1, port)) + assert(portdetails["interface_id"] == "vif1.1") + networks = quantum.get_all_networks(tenant_id) + LOG.debug(networks) + for nid, name in networks.iteritems(): + ports = quantum.get_all_ports(tenant_id, nid) + LOG.debug(ports)