plugin: introduce ryu plugin

blueprint ovs-driver-extention
This patch implements the blueprint ovs-driver-extention
https://blueprints.launchpad.net/quantum/+spec/ovs-driver-extension

This patch factors out ovs common logic from ovs plugin into ovscommon
and adds Ryu NOS plugin.
This patch enhances ovs plugin for generic OVS controller support and

This patch is to add ofp controller support to OVS.
Store ofp controller address in ovs quantum data base.
- nova firewall_driver
- nova linuxnet_interface_driver

There may be ports unmanaged by nova/quantum. Those ports are used
to connect vm to outside of physical machine. They needs special care.

---
Changes 12 -> 13:
- rebased to 543e150d6d
  changed files are only MANIFEST.in, setup.py, tools/pip-requres

Changes 11 -> 12:
- ryu agent
  eliminated from quantum.common import exceptions as exc
- ryu.db.api
  eliminated ofp_has_servers
- ryu.nova
  eliminated from quantum.plugins.ryu.nova import ovs_utils
  and eliminate ovs_utils

Chnages 10 -> 11:
- rebased to a945d1a304
- more Maru's review
- setup.py: fix setup() argument
  This isn't directly related to ryu plugin though
- improve fake ini file when unit test
  remove fake ini file after unit tests.
  use StringIO when no file is required.
- LOG: don't use %

Chnages 8 -> 9 -> 10:
- minor fixes: forgot to commit some hunks

Chnages 7 -> 8:
- rebased to d6bf2b7616
- catch up d6bf2b7616 change
  introduced bin/quantum_ryu_agent
- addressed Maru's review
  - avoid custom patching, use mock for test
    and added mox and mock to pip-requires
  - more pep8
  - avoid \ for line continuation
  - avoid single char variables
  - db.api: first() -> one()
  - utilize implicit conversion
    var is not None -> var
  - and more...

Changes 6 -> 7:
- update comment in ryu/run_tests.py
- make unit tests pass without ryu installed
  i.e.
  PLUGIN_DIR=quantum/plugins/ryu/ ./run_tests.sh
  works now

Chages 5 -> 6:
- remove comment

Change 4 -> 5:
- eliminate relative imports
- copyright
- doc string
- naming (s/CONF_FILE/conf_file/g)
- add " check to ryu/nova/ovs_utils
- ryu/nova/linux_net: comment
- ryu agent: eliminated unused methods
- updated ryu/README: add http://www.osrg.net/ryu/using_with_openstack.html
- added unit tests

Changes 3 -> 4:
- reflected Dan's review
- on-OVS in ryu.ini
- update @author
- some naming

Changes 2 -> 3:
- rebased to 04d144ae0b
- abandoned to share code and duplicated codes from openvswitch plugin
  for ovs plugin stability.
- dropped setup_ryu.sh and added README
- update nova driver to catch up upstream change (gflags -> cfg)

Changes 1 -> 2:
- unbreak openvswtich unit test
- MANIFEST.in

Changes 3 -> new 1:
- rebased to 1eb3c693b5
- factor out common loginc from openvswitch plugin into ovscommon
- Introduced a new independent ryu plugin
- try new review due to the previous effort was marked abandoned.
  > https://review.openstack.org/#change,3055
  > Change-Id: I17801a7a74d4087838a8a26c1b1f97f28c2dcef3

Changes:
- rebased to 9c5c2caef1
- some clean ups

Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
Change-Id: Ia9fe87525cebccc87b7c18a533f48607272cd97f
This commit is contained in:
Isaku Yamahata 2011-11-19 18:17:03 +09:00
parent 543e150d6d
commit a5f97ccc3b
30 changed files with 1340 additions and 4 deletions

View File

@ -9,6 +9,7 @@ include etc/quantum/plugins/cisco/*.ini
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

24
bin/quantum-ryu-agent Executable file
View File

@ -0,0 +1,24 @@
#!/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()

View File

@ -0,0 +1,13 @@
[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

View File

@ -91,6 +91,11 @@ def network_create(tenant_id, name, op_status=OperationalStatus.UNKNOWN):
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).\

View File

@ -0,0 +1,28 @@
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!

View File

View File

View File

@ -0,0 +1,312 @@
#!/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()

View File

View File

@ -0,0 +1,27 @@
# 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()

View File

@ -0,0 +1,38 @@
# 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)

View File

View File

@ -0,0 +1,29 @@
# 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))

View File

@ -0,0 +1,90 @@
# 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)

View File

@ -0,0 +1,80 @@
# 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)

View File

@ -0,0 +1,19 @@
# 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'

View File

@ -0,0 +1,172 @@
# 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

View File

@ -0,0 +1,84 @@
#!/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)

View File

@ -0,0 +1,68 @@
# 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)

View File

View File

@ -0,0 +1,43 @@
# 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()

View File

@ -0,0 +1,35 @@
# 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)

View File

@ -0,0 +1,17 @@
# 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__'

View File

@ -0,0 +1,46 @@
# 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

View File

@ -0,0 +1,54 @@
# 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()

View File

@ -0,0 +1,73 @@
# 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)

View File

@ -0,0 +1,73 @@
# 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

View File

@ -72,6 +72,7 @@ ovs_plugin_config_path = 'etc/quantum/plugins/openvswitch'
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,
@ -90,6 +91,7 @@ DataFiles = [
['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(
@ -109,10 +111,12 @@ 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',
]
},

View File

@ -13,6 +13,7 @@ webtest
distribute>=0.6.24
coverage
mock>=0.7.1
nose
nosexcover
pep8==0.6.1