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 to543e150d6dchanged 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 upd6bf2b7616change 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
313 lines
11 KiB
Python
Executable File
313 lines
11 KiB
Python
Executable File
#!/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()
|