]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Ryu plugin support for v2 Quantum API
authorYoshihiro Kaneko <ykaneko0929@gmail.com>
Fri, 13 Jul 2012 07:15:23 +0000 (16:15 +0900)
committerYoshihiro Kaneko <ykaneko0929@gmail.com>
Fri, 13 Jul 2012 08:07:17 +0000 (17:07 +0900)
blueprint update-ryu-plugin-for-v2

Ryu plugin using the v2 Quantum API.

Change-Id: I9d42b3aee4b51edbc0b80d8f8372f65eb47df9af

quantum/plugins/ryu/agent/ryu_quantum_agent.py
quantum/plugins/ryu/common/config.py
quantum/plugins/ryu/db/api_v2.py [new file with mode: 0644]
quantum/plugins/ryu/db/models_v2.py [new file with mode: 0644]
quantum/plugins/ryu/nova/vif.py
quantum/plugins/ryu/ryu_quantum_plugin.py

index 74513e18947d52d44fc70d0ea0387f7ee1759669..15056b8381c82a130c9ff09fe4c7a20544906a89 100755 (executable)
@@ -22,9 +22,6 @@
 
 import logging as LOG
 from optparse import OptionParser
-import shlex
-import signal
-from subprocess import PIPE, Popen
 import sys
 import time
 
@@ -32,38 +29,17 @@ from ryu.app import rest_nw_id
 from ryu.app.client import OFPClient
 from sqlalchemy.ext.sqlsoup import SqlSoup
 
-from quantum.agent.linux import utils
+from quantum.agent.linux import ovs_lib
+from quantum.agent.linux.ovs_lib import VifPort
 from quantum.plugins.ryu.common import config
 
 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:
+class OVSBridge(ovs_lib.OVSBridge):
     def __init__(self, br_name, root_helper):
-        self.br_name = br_name
-        self.root_helper = root_helper
+        ovs_lib.OVSBridge.__init__(self, br_name, root_helper)
         self.datapath_id = None
 
     def find_datapath_id(self):
@@ -74,10 +50,6 @@ class OVSBridge:
         dp_id = res.strip().strip('"')
         self.datapath_id = dp_id
 
-    def run_vsctl(self, args):
-        full_args = ["ovs-vsctl", "--timeout=2"] + args
-        return utils.execute(full_args, root_helper=self.root_helper)
-
     def set_controller(self, target):
         methods = ("ssl", "tcp", "unix", "pssl", "ptcp", "punix")
         args = target.split(":")
@@ -85,35 +57,6 @@ class OVSBridge:
             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 utils.execute(["xe", "vif-param-get",
-                              "param-name=other-config",
-                              "param-key=nicira-iface-id",
-                              "uuid=%s" % xs_vif_uuid],
-                             root_helper=self.root_helper).strip()
-
     def _vifport(self, name, external_ids):
         ofport = self.db_get_val("Interface", name, "ofport")
         return VifPort(name, ofport, external_ids["iface-id"],
@@ -186,12 +129,13 @@ def check_ofp_mode(db):
 
 
 class OVSQuantumOFPRyuAgent:
-    def __init__(self, integ_br, db, root_helper):
+    def __init__(self, integ_br, db, root_helper, target_v2_api=False):
         self.root_helper = root_helper
         (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.target_v2_api = target_v2_api
         self._setup_integration_br(integ_br, ofp_controller_addr)
 
     def _setup_integration_br(self, integ_br, ofp_controller_addr):
@@ -206,7 +150,16 @@ class OVSQuantumOFPRyuAgent:
 
     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())
+        if self.target_v2_api:
+            return dict((port.device_id, port) for port in db.ports.all())
+        else:
+            return dict((port.interface_id, port) for port in db.ports.all())
+
+    def _set_port_status(self, port, status):
+        if self.target_v2_api:
+            port.status = status
+        else:
+            port.op_status = status
 
     def daemon_loop(self, db):
         # on startup, register all existing ports
@@ -220,7 +173,7 @@ class OVSQuantumOFPRyuAgent:
                 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
+                self._set_port_status(all_bindings[port.vif_id], OP_STATUS_UP)
                 LOG.info("Updating binding to net-id = %s for %s",
                          net_id, str(port))
         db.commit()
@@ -248,10 +201,12 @@ class OVSQuantumOFPRyuAgent:
                     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
+                        self._set_port_status(all_bindings[port.vif_id],
+                                              OP_STATUS_DOWN)
                 if not new_b:
                     if port.vif_id in all_bindings:
-                        all_bindings[port.vif_id].op_status = OP_STATUS_UP
+                        self._set_port_status(all_bindings[port.vif_id],
+                                              OP_STATUS_UP)
                     LOG.info("Adding binding to net-id = %s for %s",
                              new_b, str(port))
 
@@ -259,7 +214,8 @@ class OVSQuantumOFPRyuAgent:
                 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
+                        self._set_port_status(all_bindings[port.vif_id],
+                                              OP_STATUS_DOWN)
 
             old_vif_ports = new_vif_ports
             old_local_bindings = new_local_bindings
@@ -289,12 +245,13 @@ def main():
     conf = config.parse(config_file)
     integ_br = conf.OVS.integration_bridge
     root_helper = conf.AGENT.root_helper
+    target_v2_api = conf.AGENT.target_v2_api
     options = {"sql_connection": conf.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, root_helper)
+    plugin = OVSQuantumOFPRyuAgent(integ_br, db, root_helper, target_v2_api)
     plugin.daemon_loop(db)
 
     sys.exit(0)
index a18097924eba9d48bf5e19c2541ac03ae6d12c99..075668eb03de4276f26e714cf705839245ba4895 100644 (file)
@@ -29,6 +29,7 @@ ovs_opts = [
 ]
 
 agent_opts = [
+    cfg.BoolOpt('target_v2_api', default=True),
     cfg.IntOpt('polling_interval', default=2),
     cfg.StrOpt('root_helper', default='sudo'),
 ]
diff --git a/quantum/plugins/ryu/db/api_v2.py b/quantum/plugins/ryu/db/api_v2.py
new file mode 100644 (file)
index 0000000..0bd65eb
--- /dev/null
@@ -0,0 +1,33 @@
+# 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.db.models_v2 import Network
+from quantum.plugins.ryu.db import models_v2
+
+
+def set_ofp_servers(hosts):
+    session = db.get_session()
+    session.query(models_v2.OFPServer).delete()
+    for (host_address, host_type) in hosts:
+        host = models_v2.OFPServer(host_address, host_type)
+        session.add(host)
+    session.flush()
+
+
+def network_all_tenant_list():
+    session = db.get_session()
+    return session.query(Network).all()
diff --git a/quantum/plugins/ryu/db/models_v2.py b/quantum/plugins/ryu/db/models_v2.py
new file mode 100644 (file)
index 0000000..ce0a830
--- /dev/null
@@ -0,0 +1,37 @@
+# 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 quantum.db import models_v2
+
+
+class OFPServer(models_v2.model_base.BASEV2):
+    """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)
index 5ccdc6e89eef8a57ceb20c753a77538df9b0096e..0e8810dc22120c86cc58d29e419c2ab9364ed7c4 100644 (file)
@@ -60,14 +60,22 @@ class LibvirtOpenVswitchOFPRyuDriver(libvirt_vif.LibvirtOpenVswitchDriver):
         dev = self.get_dev_name(iface_id)
         return _get_port_no(dev)
 
-    def plug(self, instance, network, mapping):
+    def plug(self, instance, vif):
         result = super(LibvirtOpenVswitchOFPRyuDriver, self).plug(
-            instance, network, mapping)
+            instance, vif)
+        network, mapping = vif
         port_no = self._get_port_no(mapping)
-        self.ryu_client.create_port(network['id'], self.datapath_id, port_no)
+        try:
+            self.ryu_client.create_port(network['id'], self.datapath_id,
+                                        port_no)
+        except httplib.HTTPException as e:
+            res = e.args[0]
+            if res.status != httplib.CONFLICT:
+                raise
         return result
 
-    def unplug(self, instance, network, mapping):
+    def unplug(self, instance, vif):
+        network, mapping = vif
         port_no = self._get_port_no(mapping)
         try:
             self.ryu_client.delete_port(network['id'],
@@ -76,5 +84,4 @@ class LibvirtOpenVswitchOFPRyuDriver(libvirt_vif.LibvirtOpenVswitchDriver):
             res = e.args[0]
             if res.status != httplib.NOT_FOUND:
                 raise
-        super(LibvirtOpenVswitchOFPRyuDriver, self).unplug(instance, network,
-                                                           mapping)
+        super(LibvirtOpenVswitchOFPRyuDriver, self).unplug(instance, vif)
index 4c1a8cfb6cd6b26fdceb3b1b14a077a598bd2d08..b1dbe686f49b2daaba3a6efcc02ed2fb70259c3f 100644 (file)
 #    under the License.
 # @author: Isaku Yamahata
 
+import logging
+import os
+
 from ryu.app import client
 from ryu.app import rest_nw_id
 
 from quantum.common import exceptions as q_exc
 from quantum.common.utils import find_config_file
-import quantum.db.api as db
+from quantum.db import api as db
+from quantum.db import db_base_plugin_v2
+from quantum.db import models_v2
 from quantum.plugins.ryu.db import api as db_api
+from quantum.plugins.ryu.db import api_v2 as db_api_v2
 from quantum.plugins.ryu import ofp_service_type
 from quantum.plugins.ryu import ovs_quantum_plugin_base
+from quantum.plugins.ryu.common import config
 
-
+LOG = logging.getLogger(__name__)
 CONF_FILE = find_config_file({"plugin": "ryu"}, "ryu.ini")
 
 
@@ -65,3 +72,50 @@ class RyuQuantumPlugin(ovs_quantum_plugin_base.OVSQuantumPluginBase):
     def __init__(self, configfile=None):
         super(RyuQuantumPlugin, self).__init__(CONF_FILE, __file__, configfile)
         self.driver = OFPRyuDriver(self.conf)
+
+
+class RyuQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2):
+    def __init__(self, configfile=None):
+        if configfile is None:
+            if os.path.exists(CONF_FILE):
+                configfile = CONF_FILE
+        if configfile is None:
+            raise Exception("Configuration file \"%s\" doesn't exist" %
+                            (configfile))
+        LOG.debug("Using configuration file: %s" % configfile)
+        conf = config.parse(configfile)
+        options = {"sql_connection": conf.DATABASE.sql_connection}
+        options.update({'base': models_v2.model_base.BASEV2})
+        reconnect_interval = conf.DATABASE.reconnect_interval
+        options.update({"reconnect_interval": reconnect_interval})
+        db.configure_db(options)
+
+        ofp_con_host = conf.OVS.openflow_controller
+        ofp_api_host = conf.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_v2.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_api_v2.network_all_tenant_list()
+        for net in networks:
+            self.client.update_network(net.id)
+
+    def create_network(self, context, network):
+        net = super(RyuQuantumPluginV2, self).create_network(context, network)
+        self.client.create_network(net['id'])
+        return net
+
+    def delete_network(self, context, id):
+        self.client.delete_network(id)
+        return super(RyuQuantumPluginV2, self).delete_network(context, id)