include etc/quantum/plugins/cisco/quantum.conf.ciscoext
include etc/quantum/plugins/linuxbridge/*.ini
include etc/quantum/plugins/nicira/*
+include etc/quantum/plugins/ryu/*.ini
include quantum/plugins/*/README
include quantum/plugins/openvswitch/Makefile
include quantum/plugins/openvswitch/agent/xenserver_install.sh
--- /dev/null
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 os
+import sys
+sys.path.insert(0, os.getcwd())
+from quantum.plugins.ryu.agent.ryu_quantum_agent import main
+
+main()
--- /dev/null
+[DATABASE]
+# This line MUST be changed to actually run the plugin.
+# Example: sql_connection = mysql://root:nova@127.0.0.1:3306/ryu_quantum
+#sql_connection = mysql://<user>:<pass>@<IP>:<port>/<dbname>
+sql_connection = sqlite://
+
+[OVS]
+integration-bridge = br-int
+
+# openflow-controller = <host IP address of ofp controller>:<port: 6633>
+# openflow-rest-api = <host IP address of ofp rest api service>:<port: 8080>
+openflow-controller = 127.0.0.1:6633
+openflow-rest-api = 127.0.0.1:8080
return net
+def network_all_tenant_list():
+ session = get_session()
+ return session.query(models.Network).all()
+
+
def network_list(tenant_id):
session = get_session()
return session.query(models.Network).\
--- /dev/null
+Quantum plugin for Ryu Network Operating System
+This directory includes quantum plugin for Ryu Network Operating System.
+
+# -- Installation
+
+For how to install/set up this plugin with Ryu and Open Stack, please refer to
+http://www.osrg.net/ryu/using_with_openstack.html
+
+# -- Ryu General
+
+For general Ryu stuff, please refer to
+http://www.osrg.net/ryu/
+
+Ryu is available at github
+git://github.com/osrg/ryu.git
+https://github.com/osrg/ryu
+
+The mailing is at
+ryu-devel@lists.sourceforge.net
+https://lists.sourceforge.net/lists/listinfo/ryu-devel
+
+# -- unit test
+
+In order to run unit tests for Ryu plugin
+PLUGIN_DIR=quantum/plugins/ryu ./run_tests.sh
+NOTE: In order to run unit tests, sqlite3 is additionally needed.
+
+Enjoy!
--- /dev/null
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# Based on openvswitch agent.
+#
+# Copyright 2011 Nicira Networks, Inc.
+# 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: Isaku Yamahata
+import ConfigParser
+import logging as LOG
+import signal
+import sys
+import time
+from optparse import OptionParser
+from sqlalchemy.ext.sqlsoup import SqlSoup
+from subprocess import PIPE, Popen
+
+from ryu.app import rest_nw_id
+from ryu.app.client import OFPClient
+
+
+OP_STATUS_UP = "UP"
+OP_STATUS_DOWN = "DOWN"
+
+
+class VifPort:
+ """
+ A class to represent a VIF (i.e., a port that has 'iface-id' and 'vif-mac'
+ attributes set).
+ """
+ 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=%s, vif_mac=%s, port_name=%s, ofport=%s, "
+ "bridge name = %s" % (self.vif_id,
+ self.vif_mac,
+ self.port_name,
+ self.ofport,
+ self.switch.br_name))
+
+
+class OVSBridge:
+ def __init__(self, br_name):
+ self.br_name = br_name
+ self.datapath_id = None
+
+ def find_datapath_id(self):
+ # ovs-vsctl get Bridge br-int datapath_id
+ res = self.run_vsctl(["get", "Bridge", self.br_name, "datapath_id"])
+
+ # remove preceding/trailing double quotes
+ dp_id = res.strip().strip('"')
+ self.datapath_id = dp_id
+
+ def run_cmd(self, args):
+ pipe = Popen(args, stdout=PIPE)
+ retval = pipe.communicate()[0]
+ if pipe.returncode == -(signal.SIGALRM):
+ LOG.debug("## timeout running command: " + " ".join(args))
+ return retval
+
+ def run_vsctl(self, args):
+ full_args = ["ovs-vsctl", "--timeout=2"] + args
+ return self.run_cmd(full_args)
+
+ def set_controller(self, target):
+ methods = ("ssl", "tcp", "unix", "pssl", "ptcp", "punix")
+ args = target.split(":")
+ if not args[0] in methods:
+ target = "tcp:" + target
+ self.run_vsctl(["set-controller", self.br_name, target])
+
+ 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")
+
+ @staticmethod
+ def db_str_to_map(full_str):
+ list = full_str.strip("{}").split(", ")
+ ret = {}
+ for elem in list:
+ if elem.find("=") == -1:
+ continue
+ arr = elem.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")[:-1]
+
+ def get_xapi_iface_id(self, xs_vif_uuid):
+ return self.run_cmd(
+ ["xe",
+ "vif-param-get",
+ "param-name=other-config",
+ "param-key=nicira-iface-id",
+ "uuid=%s" % xs_vif_uuid]).strip()
+
+ def _vifport(self, name, external_ids):
+ ofport = self.db_get_val("Interface", name, "ofport")
+ return VifPort(name, ofport, external_ids["iface-id"],
+ external_ids["attached-mac"], self)
+
+ def _get_ports(self, get_port):
+ ports = []
+ port_names = self.get_port_name_list()
+ for name in port_names:
+ port = get_port(name)
+ if port:
+ ports.append(port)
+
+ return ports
+
+ def _get_vif_port(self, name):
+ external_ids = self.db_get_map("Interface", name, "external_ids")
+ if "iface-id" in external_ids and "attached-mac" in external_ids:
+ return self._vifport(name, external_ids)
+ elif ("xs-vif-uuid" in external_ids and
+ "attached-mac" in external_ids):
+ # if this is a xenserver and iface-id is not automatically
+ # synced to OVS from XAPI, we grab it from XAPI directly
+ ofport = self.db_get_val("Interface", name, "ofport")
+ iface_id = self.get_xapi_iface_id(external_ids["xs-vif-uuid"])
+ return VifPort(name, ofport, iface_id,
+ external_ids["attached-mac"], self)
+
+ def get_vif_ports(self):
+ "returns a VIF object for each VIF port"
+ return self._get_ports(self._get_vif_port)
+
+ def _get_external_port(self, name):
+ external_ids = self.db_get_map("Interface", name, "external_ids")
+ if external_ids:
+ return
+
+ ofport = self.db_get_val("Interface", name, "ofport")
+ return VifPort(name, ofport, None, None, self)
+
+ def get_external_ports(self):
+ return self._get_ports(self._get_external_port)
+
+
+def check_ofp_mode(db):
+ LOG.debug("checking db")
+
+ servers = db.ofp_server.all()
+
+ ofp_controller_addr = None
+ ofp_rest_api_addr = None
+ for serv in servers:
+ if serv.host_type == "REST_API":
+ ofp_rest_api_addr = serv.address
+ elif serv.host_type == "controller":
+ ofp_controller_addr = serv.address
+ else:
+ LOG.warn("ignoring unknown server type %s", serv)
+
+ LOG.debug("controller %s", ofp_controller_addr)
+ LOG.debug("api %s", ofp_rest_api_addr)
+ if not ofp_controller_addr:
+ raise RuntimeError("OF controller isn't specified")
+ if not ofp_rest_api_addr:
+ raise RuntimeError("Ryu rest API port isn't specified")
+
+ LOG.debug("going to ofp controller mode %s %s",
+ ofp_controller_addr, ofp_rest_api_addr)
+ return (ofp_controller_addr, ofp_rest_api_addr)
+
+
+class OVSQuantumOFPRyuAgent:
+ def __init__(self, integ_br, db):
+ (ofp_controller_addr, ofp_rest_api_addr) = check_ofp_mode(db)
+
+ self.nw_id_external = rest_nw_id.NW_ID_EXTERNAL
+ self.api = OFPClient(ofp_rest_api_addr)
+ self._setup_integration_br(integ_br, ofp_controller_addr)
+
+ def _setup_integration_br(self, integ_br, ofp_controller_addr):
+ self.int_br = OVSBridge(integ_br)
+ self.int_br.find_datapath_id()
+ self.int_br.set_controller(ofp_controller_addr)
+ for port in self.int_br.get_external_ports():
+ self._port_update(self.nw_id_external, port)
+
+ def _port_update(self, network_id, port):
+ self.api.update_port(network_id, port.switch.datapath_id, port.ofport)
+
+ def _all_bindings(self, db):
+ """return interface id -> port which include network id bindings"""
+ return dict((port.interface_id, port) for port in db.ports.all())
+
+ def daemon_loop(self, db):
+ # on startup, register all existing ports
+ all_bindings = self._all_bindings(db)
+
+ local_bindings = {}
+ vif_ports = {}
+ for port in self.int_br.get_vif_ports():
+ vif_ports[port.vif_id] = port
+ if port.vif_id in all_bindings:
+ net_id = all_bindings[port.vif_id].network_id
+ local_bindings[port.vif_id] = net_id
+ self._port_update(net_id, port)
+ all_bindings[port.vif_id].op_status = OP_STATUS_UP
+ LOG.info("Updating binding to net-id = %s for %s",
+ net_id, str(port))
+ db.commit()
+
+ old_vif_ports = vif_ports
+ old_local_bindings = local_bindings
+
+ while True:
+ all_bindings = self._all_bindings(db)
+
+ new_vif_ports = {}
+ new_local_bindings = {}
+ for port in self.int_br.get_vif_ports():
+ new_vif_ports[port.vif_id] = port
+ if port.vif_id in all_bindings:
+ net_id = all_bindings[port.vif_id].network_id
+ new_local_bindings[port.vif_id] = net_id
+
+ old_b = old_local_bindings.get(port.vif_id)
+ new_b = new_local_bindings.get(port.vif_id)
+ if old_b == new_b:
+ continue
+
+ if not old_b:
+ LOG.info("Removing binding to net-id = %s for %s",
+ old_b, str(port))
+ if port.vif_id in all_bindings:
+ all_bindings[port.vif_id].op_status = OP_STATUS_DOWN
+ if not new_b:
+ if port.vif_id in all_bindings:
+ all_bindings[port.vif_id].op_status = OP_STATUS_UP
+ LOG.info("Adding binding to net-id = %s for %s",
+ new_b, str(port))
+
+ for vif_id in old_vif_ports:
+ if vif_id not in new_vif_ports:
+ LOG.info("Port Disappeared: %s", vif_id)
+ if vif_id in all_bindings:
+ all_bindings[vif_id].op_status = OP_STATUS_DOWN
+
+ old_vif_ports = new_vif_ports
+ old_local_bindings = new_local_bindings
+ db.commit()
+ time.sleep(2)
+
+
+def main():
+ usagestr = "%prog [OPTIONS] <config file>"
+ 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")
+
+ options = {"sql_connection": config.get("DATABASE", "sql_connection")}
+ db = SqlSoup(options["sql_connection"])
+
+ LOG.info("Connecting to database \"%s\" on %s",
+ db.engine.url.database, db.engine.url.host)
+ plugin = OVSQuantumOFPRyuAgent(integ_br, db)
+ plugin.daemon_loop(db)
+
+ sys.exit(0)
+
+
+if __name__ == "__main__":
+ main()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 quantum.db.api as db
+from quantum.plugins.ryu.db import models
+
+
+def set_ofp_servers(hosts):
+ session = db.get_session()
+ session.query(models.OFPServer).delete()
+ for (host_address, host_type) in hosts:
+ host = models.OFPServer(host_address, host_type)
+ session.add(host)
+ session.flush()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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.
+
+from sqlalchemy import Column, Integer, String
+from sqlalchemy.ext.declarative import declarative_base
+
+from quantum.db.models import BASE
+
+
+class OFPServer(BASE):
+ """Openflow Server/API address"""
+ __tablename__ = 'ofp_server'
+
+ id = Column(Integer, primary_key=True, autoincrement=True)
+ address = Column(String(255)) # netloc <host ip address>:<port>
+ host_type = Column(String(255)) # server type
+ # Controller, REST_API
+
+ def __init__(self, address, host_type):
+ self.address = address
+ self.host_type = host_type
+
+ def __repr__(self):
+ return "<OFPServer(%s,%s,%s)>" % (self.id, self.address,
+ self.host_type)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright (c) 2012 Isaku Yamahata <yamahata at private email ne jp>
+# <yamahata at valinux co jp>
+# 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 inspect
+
+from nova.virt import firewall
+
+
+class NopFirewallDriver(firewall.FirewallDriver):
+ def __init__(self, *args, **kwargs):
+ super(NopFirewallDriver, self).__init__()
+ for key, _val in inspect.getmembers(self, inspect.ismethod):
+ if key.startswith('__') or key.endswith('__'):
+ continue
+ setattr(self, key, (lambda _self, *_args, **_kwargs: True))
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# <yamahata at valinux co jp>
+# 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.
+
+from nova import flags
+from nova import log as logging
+from nova import utils
+from nova.openstack.common import cfg
+from ryu.app.client import OFPClient
+
+LOG = logging.getLogger(__name__)
+
+ryu_linux_net_opt = cfg.StrOpt('linuxnet_ovs_ryu_api_host',
+ default='127.0.0.1:8080',
+ help='Openflow Ryu REST API host:port')
+
+FLAGS = flags.FLAGS
+FLAGS.add_option(ryu_linux_net_opt)
+
+
+def _get_datapath_id(bridge_name):
+ out, _err = utils.execute('ovs-vsctl', 'get', 'Bridge',
+ bridge_name, 'datapath_id', run_as_root=True)
+ return out.strip().strip('"')
+
+
+def _get_port_no(dev):
+ out, _err = utils.execute('ovs-vsctl', 'get', 'Interface', dev,
+ 'ofport', run_as_root=True)
+ return int(out.strip())
+
+
+# In order to avoid circular import, dynamically import the base class,
+# nova.network.linux_net.LinuxOVSInterfaceDriver
+# and use composition instead of inheritance.
+# The following inheritance code doesn't work due to circular import.
+# from nova.network import linux_net as nova_linux_net
+# class LinuxOVSRyuInterfaceDriver(nova_linux_net.LinuxOVSInterfaceDriver):
+#
+# nova.network.linux_net imports FLAGS.linuxnet_interface_driver
+# We are being imported from linux_net so that linux_net can't be imported
+# here due to circular import.
+# Another approach would be to factor out nova.network.linux_net so that
+# linux_net doesn't import FLAGS.linuxnet_interface_driver or it loads
+# lazily FLAGS.linuxnet_interface_driver.
+
+
+class LinuxOVSRyuInterfaceDriver(object):
+ def __init__(self):
+ from nova.network import linux_net as nova_linux_net
+ self.parent = nova_linux_net.LinuxOVSInterfaceDriver()
+
+ LOG.debug('ryu rest host %s', FLAGS.linuxnet_ovs_ryu_api_host)
+ self.ryu_client = OFPClient(FLAGS.linuxnet_ovs_ryu_api_host)
+ self.datapath_id = _get_datapath_id(
+ FLAGS.linuxnet_ovs_integration_bridge)
+
+ if nova_linux_net.binary_name == 'nova-network':
+ for tables in [nova_linux_net.iptables_manager.ipv4,
+ nova_linux_net.iptables_manager.ipv6]:
+ tables['filter'].add_rule('FORWARD',
+ '--in-interface gw-+ --out-interface gw-+ -j DROP')
+ nova_linux_net.iptables_manager.apply()
+
+ def plug(self, network, mac_address, gateway=True):
+ LOG.debug("network %s mac_adress %s gateway %s",
+ network, mac_address, gateway)
+ ret = self.parent.plug(network, mac_address, gateway)
+ port_no = _get_port_no(self.get_dev(network))
+ self.ryu_client.create_port(network['uuid'], self.datapath_id, port_no)
+ return ret
+
+ def unplug(self, network):
+ return self.parent.unplug(network)
+
+ def get_dev(self, network):
+ return self.parent.get_dev(network)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# <yamahata at valinux co jp>
+# 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
+
+from nova import flags
+from nova import log as logging
+from nova import utils
+from nova.openstack.common import cfg
+from nova.virt.libvirt import vif as libvirt_vif
+from ryu.app.client import OFPClient
+
+
+LOG = logging.getLogger(__name__)
+
+ryu_libvirt_ovs_driver_opt = cfg.StrOpt('libvirt_ovs_ryu_api_host',
+ default='127.0.0.1:8080',
+ help='Openflow Ryu REST API host:port')
+
+FLAGS = flags.FLAGS
+FLAGS.add_option(ryu_libvirt_ovs_driver_opt)
+
+
+def _get_datapath_id(bridge_name):
+ out, _err = utils.execute('ovs-vsctl', 'get', 'Bridge',
+ bridge_name, 'datapath_id', run_as_root=True)
+ return out.strip().strip('"')
+
+
+def _get_port_no(dev):
+ out, _err = utils.execute('ovs-vsctl', 'get', 'Interface', dev,
+ 'ofport', run_as_root=True)
+ return int(out.strip())
+
+
+class LibvirtOpenVswitchOFPRyuDriver(libvirt_vif.LibvirtOpenVswitchDriver):
+ def __init__(self, **kwargs):
+ super(LibvirtOpenVswitchOFPRyuDriver, self).__init__()
+ LOG.debug('ryu rest host %s', FLAGS.libvirt_ovs_bridge)
+ self.ryu_client = OFPClient(FLAGS.libvirt_ovs_ryu_api_host)
+ self.datapath_id = _get_datapath_id(FLAGS.libvirt_ovs_bridge)
+
+ def _get_port_no(self, mapping):
+ iface_id = mapping['vif_uuid']
+ dev = self.get_dev_name(iface_id)
+ return _get_port_no(dev)
+
+ def plug(self, instance, network, mapping):
+ result = super(LibvirtOpenVswitchOFPRyuDriver, self).plug(
+ instance, network, mapping)
+ port_no = self._get_port_no(mapping)
+ self.ryu_client.create_port(network['id'],
+ self.datapath_id, port_no)
+ return result
+
+ def unplug(self, instance, network, mapping):
+ port_no = self._get_port_no(mapping)
+ try:
+ self.ryu_client.delete_port(network['id'],
+ self.datapath_id, port_no)
+ except httplib.HTTPException as e:
+ res = e.args[0]
+ if res.status != httplib.NOT_FOUND:
+ raise
+ super(LibvirtOpenVswitchOFPRyuDriver, self).unplug(instance, network,
+ mapping)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at valinux co jp>
+# 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: Isaku Yamahata
+
+CONTROLLER = 'controller'
+REST_API = 'REST_API'
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata
+# 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: Isaku Yamahata
+import ConfigParser
+import logging as LOG
+import os
+from abc import ABCMeta, abstractmethod
+
+import quantum.db.api as db
+from quantum.api.api_common import OperationalStatus
+from quantum.common import exceptions as q_exc
+from quantum.manager import find_config
+from quantum.quantum_plugin_base import QuantumPluginBase
+
+
+LOG.getLogger(__name__)
+
+
+class OVSQuantumPluginDriverBase(object):
+ """
+ Base class for OVS quantum plugin driver
+ """
+ __metaclass__ = ABCMeta
+
+ @abstractmethod
+ def create_network(self, net):
+ pass
+
+ @abstractmethod
+ def delete_network(self, net):
+ pass
+
+
+class OVSQuantumPluginBase(QuantumPluginBase):
+ """
+ Base class for OVS-based plugin which referes to a subclass of
+ OVSQuantumPluginDriverBase which is defined above.
+ Subclass of OVSQuantumPluginBase must set self.driver to a subclass of
+ OVSQuantumPluginDriverBase.
+ """
+ def __init__(self, conf_file, mod_file, configfile=None):
+ super(OVSQuantumPluginBase, self).__init__()
+ config = ConfigParser.ConfigParser()
+ if configfile is None:
+ if conf_file and os.path.exists(conf_file):
+ configfile = conf_file
+ else:
+ configfile = find_config(os.path.abspath(
+ os.path.dirname(mod_file)))
+ if configfile is None:
+ raise Exception("Configuration file \"%s\" doesn't exist" %
+ (configfile))
+ LOG.debug("Using configuration file: %s", configfile)
+ config.read(configfile)
+ LOG.debug("Config: %s", config)
+
+ options = {"sql_connection": config.get("DATABASE", "sql_connection")}
+ db.configure_db(options)
+
+ self.config = config
+ # Subclass must set self.driver to its own OVSQuantumPluginDriverBase
+ self.driver = None
+
+ def get_all_networks(self, tenant_id, **kwargs):
+ nets = []
+ for net in db.network_list(tenant_id):
+ LOG.debug("Adding network: %s", net.uuid)
+ nets.append(self._make_net_dict(str(net.uuid), net.name,
+ None, net.op_status))
+ return nets
+
+ def _make_net_dict(self, net_id, net_name, ports, op_status):
+ res = {'net-id': net_id,
+ 'net-name': net_name,
+ 'net-op-status': op_status}
+ if ports:
+ res['net-ports'] = ports
+ return res
+
+ def create_network(self, tenant_id, net_name, **kwargs):
+ net = db.network_create(tenant_id, net_name,
+ op_status=OperationalStatus.UP)
+ LOG.debug("Created network: %s", net)
+ self.driver.create_network(net)
+ return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
+
+ def delete_network(self, tenant_id, net_id):
+ net = db.network_get(net_id)
+
+ # Verify that no attachments are plugged into the network
+ for port in db.port_list(net_id):
+ if port.interface_id:
+ raise q_exc.NetworkInUse(net_id=net_id)
+ net = db.network_destroy(net_id)
+ self.driver.delete_network(net)
+ return self._make_net_dict(str(net.uuid), net.name, [], net.op_status)
+
+ def get_network_details(self, tenant_id, net_id):
+ net = db.network_get(net_id)
+ ports = self.get_all_ports(tenant_id, net_id)
+ return self._make_net_dict(str(net.uuid), net.name,
+ ports, net.op_status)
+
+ def update_network(self, tenant_id, net_id, **kwargs):
+ net = db.network_update(net_id, tenant_id, **kwargs)
+ return self._make_net_dict(str(net.uuid), net.name,
+ None, net.op_status)
+
+ def _make_port_dict(self, port):
+ if port.state == "ACTIVE":
+ op_status = port.op_status
+ else:
+ op_status = OperationalStatus.DOWN
+
+ return {'port-id': str(port.uuid),
+ 'port-state': port.state,
+ 'port-op-status': op_status,
+ 'net-id': port.network_id,
+ 'attachment': port.interface_id}
+
+ def get_all_ports(self, tenant_id, net_id, **kwargs):
+ ports = db.port_list(net_id)
+ # This plugin does not perform filtering at the moment
+ return [{'port-id': str(port.uuid)} for port in ports]
+
+ def create_port(self, tenant_id, net_id, port_state=None, **kwargs):
+ LOG.debug("Creating port with network_id: %s", net_id)
+ port = db.port_create(net_id, port_state,
+ op_status=OperationalStatus.DOWN)
+ return self._make_port_dict(port)
+
+ def delete_port(self, tenant_id, net_id, port_id):
+ port = db.port_destroy(port_id, net_id)
+ return self._make_port_dict(port)
+
+ def update_port(self, tenant_id, net_id, port_id, **kwargs):
+ """
+ Updates the state of a port on the specified Virtual Network.
+ """
+ LOG.debug("update_port() called\n")
+ port = db.port_get(port_id, net_id)
+ db.port_update(port_id, net_id, **kwargs)
+ return self._make_port_dict(port)
+
+ def get_port_details(self, tenant_id, net_id, port_id):
+ port = db.port_get(port_id, net_id)
+ return self._make_port_dict(port)
+
+ def plug_interface(self, tenant_id, net_id, port_id, remote_iface_id):
+ db.port_set_attachment(port_id, net_id, remote_iface_id)
+
+ def unplug_interface(self, tenant_id, net_id, port_id):
+ db.port_set_attachment(port_id, net_id, "")
+ db.port_update(port_id, net_id, op_status=OperationalStatus.DOWN)
+
+ def get_interface_details(self, tenant_id, net_id, port_id):
+ res = db.port_get(port_id, net_id)
+ return res.interface_id
--- /dev/null
+#!/usr/bin/env python
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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.
+
+
+"""Unittest runner for quantum Ryu plugin
+
+This file should be run from the top dir in the quantum directory
+
+To run all tests::
+ PLUGIN_DIR=quantum/plugins/ryu ./run_tests.sh
+"""
+
+import os
+import sys
+
+from nose import config
+from nose import core
+
+sys.path.append(os.getcwd())
+sys.path.append(os.path.dirname(__file__))
+
+
+import quantum.tests.unit
+from quantum.api.api_common import OperationalStatus
+from quantum.common.test_lib import run_tests, test_config
+from quantum.plugins.ryu.tests.unit.utils import patch_fake_ryu_client
+
+
+if __name__ == '__main__':
+ exit_status = False
+
+ # if a single test case was specified,
+ # we should only invoked the tests once
+ invoke_once = len(sys.argv) > 1
+
+ test_config['plugin_name'] = "ryu_quantum_plugin.RyuQuantumPlugin"
+ test_config['default_net_op_status'] = OperationalStatus.UP
+ test_config['default_port_op_status'] = OperationalStatus.DOWN
+
+ cwd = os.getcwd()
+ # patch modules for ryu.app.client and ryu.app.rest_nw_id
+ # With those, plugin can be tested without ryu installed
+ with patch_fake_ryu_client():
+ # to find quantum/etc/plugin/ryu/ryu.ini before chdir()
+ import ryu_quantum_plugin
+
+ c = config.Config(stream=sys.stdout,
+ env=os.environ,
+ verbosity=3,
+ includeExe=True,
+ traverseNamespace=True,
+ plugins=core.DefaultPluginManager())
+ c.configureWhere(quantum.tests.unit.__path__)
+
+ exit_status = run_tests(c)
+
+ if invoke_once:
+ sys.exit(0)
+
+ os.chdir(cwd)
+
+ working_dir = os.path.abspath("quantum/plugins/ryu")
+ c = config.Config(stream=sys.stdout,
+ env=os.environ,
+ verbosity=3,
+ workingDir=working_dir)
+ exit_status = exit_status or run_tests(c)
+
+ sys.exit(exit_status)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# <yamahata at valinux co jp>
+# 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: Isaku Yamahata
+
+import quantum.db.api as db
+from quantum.common import exceptions as q_exc
+from quantum.common.config import find_config_file
+from quantum.plugins.ryu import ofp_service_type
+from quantum.plugins.ryu import ovs_quantum_plugin_base
+from quantum.plugins.ryu.db import api as db_api
+
+
+from ryu.app import client
+from ryu.app import rest_nw_id
+
+
+CONF_FILE = find_config_file({"plugin": "ryu"}, None, "ryu.ini")
+
+
+class OFPRyuDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase):
+ def __init__(self, config):
+ super(OFPRyuDriver, self).__init__()
+ ofp_con_host = config.get("OVS", "openflow-controller")
+ ofp_api_host = config.get("OVS", "openflow-rest-api")
+
+ if ofp_con_host is None or ofp_api_host is None:
+ raise q_exc.Invalid("invalid configuration. check ryu.ini")
+
+ hosts = [(ofp_con_host, ofp_service_type.CONTROLLER),
+ (ofp_api_host, ofp_service_type.REST_API)]
+ db_api.set_ofp_servers(hosts)
+
+ self.client = client.OFPClient(ofp_api_host)
+ self.client.update_network(rest_nw_id.NW_ID_EXTERNAL)
+
+ # register known all network list on startup
+ self._create_all_tenant_network()
+
+ def _create_all_tenant_network(self):
+ networks = db.network_all_tenant_list()
+ for net in networks:
+ self.client.update_network(net.uuid)
+
+ def create_network(self, net):
+ self.client.create_network(net.uuid)
+
+ def delete_network(self, net):
+ self.client.delete_network(net.uuid)
+
+
+class RyuQuantumPlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase):
+ def __init__(self, configfile=None):
+ super(RyuQuantumPlugin, self).__init__(CONF_FILE, __file__, configfile)
+ self.driver = OFPRyuDriver(self.config)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 mox
+import stubout
+import unittest
+
+import quantum.db.api as db
+import quantum.plugins.ryu.db.models # for ryu specific tables
+from quantum.plugins.ryu.tests.unit import utils
+
+
+class BaseRyuTest(unittest.TestCase):
+ """base test class for Ryu unit tests"""
+ def setUp(self):
+ config = utils.get_config()
+ options = {"sql_connection": config.get("DATABASE", "sql_connection")}
+ db.configure_db(options)
+
+ self.config = config
+ self.mox = mox.Mox()
+ self.stubs = stubout.StubOutForTesting()
+
+ def tearDown(self):
+ self.mox.UnsetStubs()
+ self.stubs.UnsetAll()
+ self.stubs.SmartUnsetAll()
+ self.mox.VerifyAll()
+ db.clear_db()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# <yamahata at valinux co jp>
+# 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.
+
+from quantum.plugins.ryu import ovs_quantum_plugin_base
+
+
+class FakePluginDriver(ovs_quantum_plugin_base.OVSQuantumPluginDriverBase):
+ def __init__(self, config):
+ super(FakePluginDriver, self).__init__()
+
+ def create_network(self, net):
+ pass
+
+ def delete_network(self, net):
+ pass
+
+
+class FakePlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase):
+ def __init__(self, configfile=None):
+ super(FakePlugin, self).__init__(None, __file__, configfile)
+ self.driver = FakePluginDriver(self.config)
--- /dev/null
+# Copyright (C) 2011 Nippon Telegraph and Telephone Corporation.
+# Copyright (C) 2011 Isaku Yamahata <yamahata at valinux co jp>
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, version 3 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+NW_ID_EXTERNAL = '__NW_ID_EXTERNAL__'
+NW_ID_UNKNOWN = '__NW_ID_UNKNOWN__'
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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.
+
+
+class OFPClient(object):
+ def __init__(self, address):
+ super(OFPClient, self).__init__()
+ self.address = address
+
+ def get_networks(self):
+ pass
+
+ def create_network(self, network_id):
+ pass
+
+ def update_network(self, network_id):
+ pass
+
+ def delete_network(self, network_id):
+ pass
+
+ def get_ports(self, network_id):
+ pass
+
+ def create_port(self, network_id, dpid, port):
+ pass
+
+ def update_port(self, network_id, dpid, port):
+ pass
+
+ def delete_port(self, network_id, dpid, port):
+ pass
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 mox
+import os
+
+from quantum.plugins.ryu.tests.unit import fake_plugin
+from quantum.plugins.ryu.tests.unit import utils
+from quantum.plugins.ryu.tests.unit.basetest import BaseRyuTest
+
+
+class PluginBaseTest(BaseRyuTest):
+ """Class conisting of OVSQuantumPluginBase unit tests"""
+ def setUp(self):
+ super(PluginBaseTest, self).setUp()
+ self.ini_file = utils.create_fake_ryu_ini()
+
+ def tearDown(self):
+ os.unlink(self.ini_file)
+ super(PluginBaseTest, self).tearDown()
+
+ def test_create_delete_network(self):
+ # mox.StubOutClassWithMocks can't be used for class with metaclass
+ # overrided
+ driver_mock = self.mox.CreateMock(fake_plugin.FakePluginDriver)
+ self.mox.StubOutWithMock(fake_plugin, 'FakePluginDriver',
+ use_mock_anything=True)
+
+ fake_plugin.FakePluginDriver(mox.IgnoreArg()).AndReturn(driver_mock)
+ driver_mock.create_network(mox.IgnoreArg())
+ driver_mock.delete_network(mox.IgnoreArg())
+ self.mox.ReplayAll()
+ plugin = fake_plugin.FakePlugin(configfile=self.ini_file)
+
+ tenant_id = 'tenant_id'
+ net_name = 'net_name'
+ ret = plugin.create_network(tenant_id, net_name)
+
+ plugin.delete_network(tenant_id, ret['net-id'])
+ self.mox.VerifyAll()
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 uuid
+
+import quantum.db.api as db
+from quantum.plugins.ryu.tests.unit import utils
+from quantum.plugins.ryu.tests.unit.basetest import BaseRyuTest
+from quantum.plugins.ryu.tests.unit.utils import patch_fake_ryu_client
+
+
+class RyuDriverTest(BaseRyuTest):
+ """Class conisting of OFPRyuDriver unit tests"""
+ def setUp(self):
+ super(RyuDriverTest, self).setUp()
+
+ # fake up ryu.app.client and ryu.app.rest_nw_id
+ # With those, plugin can be tested without ryu installed
+ self.module_patcher = patch_fake_ryu_client()
+ self.module_patcher.start()
+
+ def tearDown(self):
+ self.module_patcher.stop()
+ super(RyuDriverTest, self).tearDown()
+
+ def test_ryu_driver(self):
+ from ryu.app import client as client_mod
+ from ryu.app import rest_nw_id as rest_nw_id_mod
+
+ self.mox.StubOutClassWithMocks(client_mod, 'OFPClient')
+ client_mock = client_mod.OFPClient(utils.FAKE_REST_ADDR)
+
+ self.mox.StubOutWithMock(client_mock, 'update_network')
+ self.mox.StubOutWithMock(client_mock, 'create_network')
+ self.mox.StubOutWithMock(client_mock, 'delete_network')
+ client_mock.update_network(rest_nw_id_mod.NW_ID_EXTERNAL)
+ uuid0 = '01234567-89ab-cdef-0123-456789abcdef'
+
+ def fake_uuid4():
+ return uuid0
+
+ self.stubs.Set(uuid, 'uuid4', fake_uuid4)
+ uuid1 = '12345678-9abc-def0-1234-56789abcdef0'
+ net1 = utils.Net(uuid1)
+
+ client_mock.update_network(uuid0)
+ client_mock.create_network(uuid1)
+ client_mock.delete_network(uuid1)
+ self.mox.ReplayAll()
+
+ db.network_create('test', uuid0)
+
+ from quantum.plugins.ryu import ryu_quantum_plugin
+ ryu_driver = ryu_quantum_plugin.OFPRyuDriver(self.config)
+ ryu_driver.create_network(net1)
+ ryu_driver.delete_network(net1)
+ self.mox.VerifyAll()
+
+ db.network_destroy(uuid0)
--- /dev/null
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+#
+# Copyright 2012 Isaku Yamahata <yamahata at private email ne jp>
+# 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 ConfigParser
+import imp
+import os
+import tempfile
+from StringIO import StringIO
+
+import mock
+
+from quantum.plugins.ryu.tests.unit import fake_rest_nw_id
+from quantum.plugins.ryu.tests.unit import fake_ryu_client
+
+FAKE_CONTROLLER_ADDR = '127.0.0.1:6633'
+FAKE_REST_ADDR = '127.0.0.1:8080'
+FAKE_RYU_INI_TEMPLATE = """
+[DATABASE]
+sql_connection = sqlite:///:memory:
+
+[OVS]
+integration-bridge = br-int
+openflow-controller = %s
+openflow-rest-api = %s
+""" % (FAKE_CONTROLLER_ADDR, FAKE_REST_ADDR)
+
+
+def create_fake_ryu_ini():
+ fd, file_name = tempfile.mkstemp(suffix='.ini')
+ tmp_file = os.fdopen(fd, 'w')
+ tmp_file.write(FAKE_RYU_INI_TEMPLATE)
+ tmp_file.close()
+ return file_name
+
+
+def get_config():
+ config = ConfigParser.ConfigParser()
+ buf_file = StringIO(FAKE_RYU_INI_TEMPLATE)
+ config.readfp(buf_file)
+ buf_file.close()
+ return config
+
+
+def patch_fake_ryu_client():
+ ryu_mod = imp.new_module('ryu')
+ ryu_app_mod = imp.new_module('ryu.app')
+ ryu_mod.app = ryu_app_mod
+ ryu_app_mod.client = fake_ryu_client
+ ryu_app_mod.rest_nw_id = fake_rest_nw_id
+ return mock.patch.dict('sys.modules',
+ {'ryu': ryu_mod,
+ 'ryu.app': ryu_app_mod,
+ 'ryu.app.client': fake_ryu_client,
+ 'ryu.app.rest_nw_id': fake_rest_nw_id})
+
+
+class Net(object):
+ def __init__(self, uuid):
+ self.uuid = uuid
cisco_plugin_config_path = 'etc/quantum/plugins/cisco'
linuxbridge_plugin_config_path = 'etc/quantum/plugins/linuxbridge'
nvp_plugin_config_path = 'etc/quantum/plugins/nicira'
+ryu_plugin_config_path = 'etc/quantum/plugins/ryu'
DataFiles = [
(config_path,
['etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini']),
(nvp_plugin_config_path,
['etc/quantum/plugins/nicira/nvp.ini']),
+ (ryu_plugin_config_path, ['etc/quantum/plugins/ryu/ryu.ini']),
]
setup(
eager_resources=EagerResources,
entry_points={
'console_scripts': [
- 'quantum-linuxbridge-agent = \
-quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent:main',
- 'quantum-openvswitch-agent = \
-quantum.plugins.openvswitch.agent.ovs_quantum_agent:main',
+ 'quantum-linuxbridge-agent =' \
+ 'quantum.plugins.linuxbridge.agent.linuxbridge_quantum_agent:main',
+ 'quantum-openvswitch-agent =' \
+ 'quantum.plugins.openvswitch.agent.ovs_quantum_agent:main',
+ 'quantum-ryu-agent = ' \
+ 'quantum.plugins.ryu.agent.ryu_quantum_agent:main',
'quantum-server = quantum.server:main',
]
},
distribute>=0.6.24
coverage
+mock>=0.7.1
nose
nosexcover
pep8==0.6.1