]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
dhcp-agent: Ryu plugin support for dhcp agent
authorIsaku Yamahata <yamahata@valinux.co.jp>
Fri, 27 Jul 2012 15:27:30 +0000 (00:27 +0900)
committerIsaku Yamahata <yamahata@valinux.co.jp>
Thu, 9 Aug 2012 13:48:38 +0000 (22:48 +0900)
This patch adds Ryu support to dhcp-agent.
fixes bug 1030830
Ryu devstack support is available at https://review.openstack.org/#/c/10117/

Change-Id: I3f5fbe8600b4b674834e317e158bac1856b0349c
Signed-off-by: Isaku Yamahata <yamahata@valinux.co.jp>
etc/dhcp_agent.ini
quantum/agent/linux/interface.py
quantum/agent/linux/ovs_lib.py
quantum/tests/unit/test_linux_interface.py
quantum/tests/unit/test_ovs_lib.py

index 7fea80a98238371cba202654f48b443c62e4a86d..e9c458c30e2b41952dee4475323b33ab9098f887 100644 (file)
@@ -15,6 +15,8 @@ state_path = /opt/stack/data
 interface_driver = quantum.agent.linux.interface.OVSInterfaceDriver
 # LinuxBridge
 #interface_driver = quantum.agent.linux.interface.BridgeInterfaceDriver
+# Ryu
+#interface_driver = quantum.agent.linux.interface.RyuInterfaceDriver
 
 # The agent can use other DHCP drivers.  Dnsmasq is the simplest and requires
 # no additional setup of the DHCP server.
@@ -29,6 +31,9 @@ db_connection = mysql://root:password@localhost/ovs_quantum?charset=utf8
 # The database used by the LinuxBridge Quantum plugin
 #db_connection = mysql://root:password@localhost/quantum_linux_bridge
 
+# The database used by the Ryu Quantum plugin
+#db_connection = mysql://root:password@localhost/ryu_quantum
+
 # The Quantum user information for accessing the Quantum API.
 auth_url = http://localhost:35357/v2.0
 auth_region = RegionOne
index 3ff13d8311cd2448bddcb16a0d62d9cdb164c59a..4f7d9bec63d17971e888a01ac4c737a67809113d 100644 (file)
@@ -34,6 +34,9 @@ OPTS = [
                help='Name of Open vSwitch bridge to use'),
     cfg.StrOpt('network_device_mtu',
                help='MTU setting for device.'),
+    cfg.StrOpt('ryu_api_host',
+               default='127.0.0.1:8080',
+               help='Openflow Ryu REST API host:port')
 ]
 
 
@@ -164,3 +167,27 @@ class BridgeInterfaceDriver(LinuxInterfaceDriver):
         except RuntimeError:
             LOG.error(_("Failed unplugging interface '%s'") %
                       device_name)
+
+
+class RyuInterfaceDriver(OVSInterfaceDriver):
+    """Driver for creating a Ryu OVS interface."""
+
+    def __init__(self, conf):
+        super(RyuInterfaceDriver, self).__init__(conf)
+
+        from ryu.app.client import OFPClient
+        LOG.debug('ryu rest host %s', self.conf.ryu_api_host)
+        self.ryu_client = OFPClient(self.conf.ryu_api_host)
+
+        self.check_bridge_exists(self.conf.ovs_integration_bridge)
+        self.ovs_br = ovs_lib.OVSBridge(self.conf.ovs_integration_bridge,
+                                        self.conf.root_helper)
+        self.datapath_id = self.ovs_br.get_datapath_id()
+
+    def plug(self, network_id, port_id, device_name, mac_address):
+        """Plug in the interface."""
+        super(RyuInterfaceDriver, self).plug(network_id, port_id, device_name,
+                                             mac_address)
+
+        port_no = self.ovs_br.get_port_ofport(device_name)
+        self.ryu_client.create_port(network_id, self.datapath_id, port_no)
index 4e8c2c06bb778cdc3a2ac0010cf4809a92ae1f62..cfb2d784ae70ac29d6c02b6d6d64ced1e5ae9b64 100644 (file)
@@ -82,6 +82,10 @@ class OVSBridge:
     def get_port_ofport(self, port_name):
         return self.db_get_val("Interface", port_name, "ofport")
 
+    def get_datapath_id(self):
+        return self.db_get_val('Bridge',
+                               self.br_name, 'datapath_id').strip('"')
+
     def _build_flow_expr_arr(self, **kwargs):
         flow_expr_arr = []
         is_delete_expr = kwargs.get('delete', False)
index c85899c3ec2ef2b349064c112b9ef9dd4af69d9b..5b70b0f942bfeaf436e447c256f0c4b04815341e 100644 (file)
@@ -217,3 +217,72 @@ class TestBridgeInterfaceDriver(TestBase):
 
         self.ip_dev.assert_has_calls([mock.call('tap0', 'sudo'),
                                       mock.call().link.delete()])
+
+
+class TestRyuInterfaceDriver(TestBase):
+    def setUp(self):
+        super(TestRyuInterfaceDriver, self).setUp()
+        self.ryu_mod = mock.Mock()
+        self.ryu_app_mod = self.ryu_mod.app
+        self.ryu_app_client = self.ryu_app_mod.client
+        self.ryu_mod_p = mock.patch.dict('sys.modules',
+                                         {'ryu': self.ryu_mod,
+                                          'ryu.app': self.ryu_app_mod,
+                                          'ryu.app.client':
+                                          self.ryu_app_client})
+        self.ryu_mod_p.start()
+
+    def tearDown(self):
+        self.ryu_mod_p.stop()
+        super(TestRyuInterfaceDriver, self).tearDown()
+
+    @staticmethod
+    def _device_exists(dev, root_helper=None):
+        return dev == 'br-int'
+
+    _vsctl_cmd_init = ['ovs-vsctl', '--timeout=2',
+                       'get', 'Bridge', 'br-int', 'datapath_id']
+
+    def test_init(self):
+        with mock.patch.object(utils, 'execute') as execute:
+            self.device_exists.side_effect = self._device_exists
+            interface.RyuInterfaceDriver(self.conf)
+            execute.assert_called_once_with(self._vsctl_cmd_init,
+                                            root_helper='sudo')
+
+        self.ryu_app_client.OFPClient.assert_called_once_with('127.0.0.1:8080')
+
+    def test_plug(self):
+        vsctl_cmd_plug = ['ovs-vsctl', '--', '--may-exist', 'add-port',
+                          'br-int', 'tap0', '--', 'set', 'Interface', 'tap0',
+                          'type=internal', '--', 'set', 'Interface', 'tap0',
+                          'external-ids:iface-id=port-1234', '--', 'set',
+                          'Interface', 'tap0',
+                          'external-ids:iface-status=active', '--', 'set',
+                          'Interface', 'tap0',
+                          'external-ids:attached-mac=aa:bb:cc:dd:ee:ff']
+        vsctl_cmd_ofport = ['ovs-vsctl', '--timeout=2',
+                            'get', 'Interface', 'tap0', 'ofport']
+
+        with mock.patch.object(utils, 'execute') as execute:
+            self.device_exists.side_effect = self._device_exists
+            ryu = interface.RyuInterfaceDriver(self.conf)
+
+            ryu.plug('01234567-1234-1234-99',
+                     'port-1234',
+                     'tap0',
+                     'aa:bb:cc:dd:ee:ff')
+
+            execute.assert_has_calls([mock.call(self._vsctl_cmd_init,
+                                                root_helper='sudo')])
+            execute.assert_has_calls([mock.call(vsctl_cmd_plug, 'sudo')])
+            execute.assert_has_calls([mock.call(vsctl_cmd_ofport,
+                                                root_helper='sudo')])
+
+        self.ryu_app_client.OFPClient.assert_called_once_with('127.0.0.1:8080')
+
+        expected = [mock.call('sudo'),
+                    mock.call().device('tap0'),
+                    mock.call().device().link.set_address('aa:bb:cc:dd:ee:ff'),
+                    mock.call().device().link.set_up()]
+        self.ip.assert_has_calls(expected)
index 2cd6fdc087f0e8e0c4d5c45ff4ed308fec366bbd..7172597c0e6fdf0141976013d6eab8b54640e94a 100644 (file)
@@ -144,6 +144,16 @@ class OVS_Lib_Test(unittest.TestCase):
         self.assertEqual(self.br.get_port_ofport(pname), ofport)
         self.mox.VerifyAll()
 
+    def test_get_datapath_id(self):
+        datapath_id = '"0000b67f4fbcc149"'
+        utils.execute(["ovs-vsctl", self.TO, "get",
+                       "Bridge", self.BR_NAME, "datapath_id"],
+                      root_helper=self.root_helper).AndReturn(datapath_id)
+        self.mox.ReplayAll()
+
+        self.assertEqual(self.br.get_datapath_id(), datapath_id.strip('"'))
+        self.mox.VerifyAll()
+
     def test_count_flows(self):
         utils.execute(["ovs-ofctl", "dump-flows", self.BR_NAME],
                       root_helper=self.root_helper).AndReturn('ignore'