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 to543e150d6d
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 toa945d1a304
- 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 tod6bf2b7616
- catch upd6bf2b7616
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 to04d144ae0b
- 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 to1eb3c693b5
- 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 to9c5c2caef1
- some clean ups Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp> Change-Id: Ia9fe87525cebccc87b7c18a533f48607272cd97f
This commit is contained in:
parent
d85ea92041
commit
9488df9779
@ -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
24
bin/quantum-ryu-agent
Executable 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()
|
13
etc/quantum/plugins/ryu/ryu.ini
Normal file
13
etc/quantum/plugins/ryu/ryu.ini
Normal 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
|
@ -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).\
|
||||
|
28
quantum/plugins/ryu/README
Normal file
28
quantum/plugins/ryu/README
Normal 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!
|
0
quantum/plugins/ryu/__init__.py
Normal file
0
quantum/plugins/ryu/__init__.py
Normal file
0
quantum/plugins/ryu/agent/__init__.py
Normal file
0
quantum/plugins/ryu/agent/__init__.py
Normal file
312
quantum/plugins/ryu/agent/ryu_quantum_agent.py
Executable file
312
quantum/plugins/ryu/agent/ryu_quantum_agent.py
Executable 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()
|
0
quantum/plugins/ryu/db/__init__.py
Normal file
0
quantum/plugins/ryu/db/__init__.py
Normal file
27
quantum/plugins/ryu/db/api.py
Normal file
27
quantum/plugins/ryu/db/api.py
Normal 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()
|
38
quantum/plugins/ryu/db/models.py
Normal file
38
quantum/plugins/ryu/db/models.py
Normal 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)
|
0
quantum/plugins/ryu/nova/__init__.py
Normal file
0
quantum/plugins/ryu/nova/__init__.py
Normal file
29
quantum/plugins/ryu/nova/firewall.py
Normal file
29
quantum/plugins/ryu/nova/firewall.py
Normal 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))
|
90
quantum/plugins/ryu/nova/linux_net.py
Normal file
90
quantum/plugins/ryu/nova/linux_net.py
Normal 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)
|
80
quantum/plugins/ryu/nova/vif.py
Normal file
80
quantum/plugins/ryu/nova/vif.py
Normal 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)
|
19
quantum/plugins/ryu/ofp_service_type.py
Normal file
19
quantum/plugins/ryu/ofp_service_type.py
Normal 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'
|
172
quantum/plugins/ryu/ovs_quantum_plugin_base.py
Normal file
172
quantum/plugins/ryu/ovs_quantum_plugin_base.py
Normal 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
|
84
quantum/plugins/ryu/run_tests.py
Normal file
84
quantum/plugins/ryu/run_tests.py
Normal 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)
|
68
quantum/plugins/ryu/ryu_quantum_plugin.py
Normal file
68
quantum/plugins/ryu/ryu_quantum_plugin.py
Normal 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)
|
0
quantum/plugins/ryu/tests/__init__.py
Normal file
0
quantum/plugins/ryu/tests/__init__.py
Normal file
0
quantum/plugins/ryu/tests/unit/__init__.py
Normal file
0
quantum/plugins/ryu/tests/unit/__init__.py
Normal file
43
quantum/plugins/ryu/tests/unit/basetest.py
Normal file
43
quantum/plugins/ryu/tests/unit/basetest.py
Normal 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()
|
35
quantum/plugins/ryu/tests/unit/fake_plugin.py
Normal file
35
quantum/plugins/ryu/tests/unit/fake_plugin.py
Normal 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)
|
17
quantum/plugins/ryu/tests/unit/fake_rest_nw_id.py
Normal file
17
quantum/plugins/ryu/tests/unit/fake_rest_nw_id.py
Normal 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__'
|
46
quantum/plugins/ryu/tests/unit/fake_ryu_client.py
Normal file
46
quantum/plugins/ryu/tests/unit/fake_ryu_client.py
Normal 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
|
54
quantum/plugins/ryu/tests/unit/test_plugin_base.py
Normal file
54
quantum/plugins/ryu/tests/unit/test_plugin_base.py
Normal 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()
|
73
quantum/plugins/ryu/tests/unit/test_ryu_driver.py
Normal file
73
quantum/plugins/ryu/tests/unit/test_ryu_driver.py
Normal 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)
|
73
quantum/plugins/ryu/tests/unit/utils.py
Normal file
73
quantum/plugins/ryu/tests/unit/utils.py
Normal 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
|
12
setup.py
12
setup.py
@ -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',
|
||||
]
|
||||
},
|
||||
|
@ -13,6 +13,7 @@ webtest
|
||||
distribute>=0.6.24
|
||||
|
||||
coverage
|
||||
mock>=0.7.1
|
||||
nose
|
||||
nosexcover
|
||||
pep8==0.6.1
|
||||
|
Loading…
Reference in New Issue
Block a user