From 84996a6039eb173529857eeb51a34992913fb2f7 Mon Sep 17 00:00:00 2001 From: Isaku Yamahata Date: Sat, 28 Jul 2012 00:27:30 +0900 Subject: [PATCH] dhcp-agent: Ryu plugin support for dhcp agent 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 --- etc/dhcp_agent.ini | 5 ++ quantum/agent/linux/interface.py | 27 +++++++++ quantum/agent/linux/ovs_lib.py | 4 ++ quantum/tests/unit/test_linux_interface.py | 69 ++++++++++++++++++++++ quantum/tests/unit/test_ovs_lib.py | 10 ++++ 5 files changed, 115 insertions(+) diff --git a/etc/dhcp_agent.ini b/etc/dhcp_agent.ini index 7fea80a982..e9c458c30e 100644 --- a/etc/dhcp_agent.ini +++ b/etc/dhcp_agent.ini @@ -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 diff --git a/quantum/agent/linux/interface.py b/quantum/agent/linux/interface.py index 3ff13d8311..4f7d9bec63 100644 --- a/quantum/agent/linux/interface.py +++ b/quantum/agent/linux/interface.py @@ -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) diff --git a/quantum/agent/linux/ovs_lib.py b/quantum/agent/linux/ovs_lib.py index 4e8c2c06bb..cfb2d784ae 100644 --- a/quantum/agent/linux/ovs_lib.py +++ b/quantum/agent/linux/ovs_lib.py @@ -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) diff --git a/quantum/tests/unit/test_linux_interface.py b/quantum/tests/unit/test_linux_interface.py index c85899c3ec..5b70b0f942 100644 --- a/quantum/tests/unit/test_linux_interface.py +++ b/quantum/tests/unit/test_linux_interface.py @@ -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) diff --git a/quantum/tests/unit/test_ovs_lib.py b/quantum/tests/unit/test_ovs_lib.py index 2cd6fdc087..7172597c0e 100644 --- a/quantum/tests/unit/test_ovs_lib.py +++ b/quantum/tests/unit/test_ovs_lib.py @@ -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'