From 56cac98626f1b25fd375b9c8d00294d6fb84496b Mon Sep 17 00:00:00 2001 From: Bob Kukura Date: Tue, 14 May 2013 17:35:08 -0400 Subject: [PATCH] Fix linuxbridge RPC message format The linuxbridge, openvswitch, and hyperv plugins all use the same basic RPC interface between their plugins and L2 agents. But the attributes describing a virtual network passed from the plugin to the agent over this interface differed for historical reasons. The openvswitch and hyperv plugins each pass network_type, physical_network, and segmentation_id attributes, whereas the linuxbridge plugin previously passed vlan_id and physical_network attributes, using special vlan_id values to indicate flat or local network types. This patch changes the linuxbridge plugin to pass network_type and segmentation_id attributes instead of the vlan_id attribute, bringing its message formats into sync with the other plugins. RPC compatibility is required for blueprint modular-l2 so that the ml2 plugin can work with all three existing types of L2 agent. This RPC message format change is also required for blueprint vxlan-linuxbridge. Unlike the vxlan-linuxbridge patch on which it is based (see https://review.openstack.org/#/c/26516/), this patch does not bump the linuxbridge RPC version number, as the ml2 plugin will require all three L2 agents to use the same RPC version. Instead, the updated linuxbridge agent maintains compatibility with old linuxbridge plugins by accepting either the old or new attributes. There is also a configuration option, currently turned on by default, to enable the updated linuxbridge plugin to pass the vlan_id attribute expected by old linuxbridge agents along with the new attributes. These message format compatibility mechanisms are intended to aid during upgrades, and can eventually be removed. Change-Id: I7cc1c9f96b09db6bab2c7d9f2b30b79fa4dab919 --- .../plugins/linuxbridge/linuxbridge_conf.ini | 6 ++ .../agent/linuxbridge_quantum_agent.py | 53 ++++++++---- quantum/plugins/linuxbridge/common/config.py | 3 + .../plugins/linuxbridge/common/constants.py | 13 +++ .../plugins/linuxbridge/lb_quantum_plugin.py | 21 +++-- .../tests/unit/linuxbridge/test_defaults.py | 2 + .../unit/linuxbridge/test_lb_quantum_agent.py | 81 +++++++++++++++---- quantum/tests/unit/linuxbridge/test_rpcapi.py | 38 +++++++-- 8 files changed, 176 insertions(+), 41 deletions(-) diff --git a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini index c9fe85c9f..be0bb22a0 100644 --- a/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini +++ b/etc/quantum/plugins/linuxbridge/linuxbridge_conf.ini @@ -58,6 +58,12 @@ reconnect_interval = 2 # Agent's polling interval in seconds polling_interval = 2 +# (BoolOpt) Enable server RPC compatibility with old (pre-havana) +# agents. +# +# Default: rpc_support_old_agents = True +# Example: rpc_support_old_agents = False + [SECURITYGROUP] # Firewall driver for realizing quantum security group function firewall_driver = quantum.agent.linux.iptables_firewall.IptablesFirewallDriver diff --git a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py index ef5cec669..7e6bce517 100755 --- a/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py +++ b/quantum/plugins/linuxbridge/agent/linuxbridge_quantum_agent.py @@ -253,21 +253,26 @@ class LinuxBridgeManager: return bridge_name def ensure_physical_in_bridge(self, network_id, + network_type, physical_network, - vlan_id): + segmentation_id): physical_interface = self.interface_mappings.get(physical_network) if not physical_interface: LOG.error(_("No mapping for physical network %s"), physical_network) return - if int(vlan_id) == lconst.FLAT_VLAN_ID: + if network_type == lconst.TYPE_FLAT: return self.ensure_flat_bridge(network_id, physical_interface) - else: + elif network_type == lconst.TYPE_VLAN: return self.ensure_vlan_bridge(network_id, physical_interface, - vlan_id) + segmentation_id) + else: + LOG.error(_("Unknown network_type %(network_type)s for network " + "%(network_id)s."), {network_type: network_type, + network_id: network_id}) - def add_tap_interface(self, network_id, physical_network, vlan_id, - tap_device_name): + def add_tap_interface(self, network_id, network_type, physical_network, + segmentation_id, tap_device_name): """Add tap interface. If a VIF has been plugged into a network, this function will @@ -279,11 +284,12 @@ class LinuxBridgeManager: return False bridge_name = self.get_bridge_name(network_id) - if int(vlan_id) == lconst.LOCAL_VLAN_ID: + if network_type == lconst.TYPE_LOCAL: self.ensure_local_bridge(network_id) elif not self.ensure_physical_in_bridge(network_id, + network_type, physical_network, - vlan_id): + segmentation_id): return False # Check if device needs to be added to bridge @@ -305,11 +311,11 @@ class LinuxBridgeManager: LOG.debug(msg) return True - def add_interface(self, network_id, physical_network, vlan_id, - port_id): + def add_interface(self, network_id, network_type, physical_network, + segmentation_id, port_id): tap_device_name = self.get_tap_device_name(port_id) - return self.add_tap_interface(network_id, - physical_network, vlan_id, + return self.add_tap_interface(network_id, network_type, + physical_network, segmentation_id, tap_device_name) def delete_vlan_bridge(self, bridge_name): @@ -433,12 +439,20 @@ class LinuxBridgeRpcCallbacks(sg_rpc.SecurityGroupAgentRpcCallbackMixin): self.sg_agent.refresh_firewall() if port['admin_state_up']: - vlan_id = kwargs.get('vlan_id') + network_type = kwargs.get('network_type') + if network_type: + segmentation_id = kwargs.get('segmentation_id') + else: + # compatibility with pre-Havana RPC vlan_id encoding + vlan_id = kwargs.get('vlan_id') + (network_type, + segmentation_id) = lconst.interpret_vlan_id(vlan_id) physical_network = kwargs.get('physical_network') # create the networking for the port self.agent.br_mgr.add_interface(port['network_id'], + network_type, physical_network, - vlan_id, + segmentation_id, port['id']) # update plugin about port status self.agent.plugin_rpc.update_device_up(self.context, @@ -569,9 +583,18 @@ class LinuxBridgeQuantumAgentRPC(sg_rpc.SecurityGroupAgentRpcMixin): {'device': device, 'details': details}) if details['admin_state_up']: # create the networking for the port + network_type = details.get('network_type') + if network_type: + segmentation_id = details.get('segmentation_id') + else: + # compatibility with pre-Havana RPC vlan_id encoding + vlan_id = details.get('vlan_id') + (network_type, + segmentation_id) = lconst.interpret_vlan_id(vlan_id) self.br_mgr.add_interface(details['network_id'], + network_type, details['physical_network'], - details['vlan_id'], + segmentation_id, details['port_id']) else: self.remove_port_binding(details['network_id'], diff --git a/quantum/plugins/linuxbridge/common/config.py b/quantum/plugins/linuxbridge/common/config.py index dea8d685d..99eccc20b 100644 --- a/quantum/plugins/linuxbridge/common/config.py +++ b/quantum/plugins/linuxbridge/common/config.py @@ -46,6 +46,9 @@ agent_opts = [ cfg.IntOpt('polling_interval', default=2, help=_("The number of seconds the agent will wait between " "polling for local device changes.")), + #TODO(rkukura): Change default to False before havana rc1 + cfg.BoolOpt('rpc_support_old_agents', default=True, + help=_("Enable server RPC compatibility with old agents")), ] diff --git a/quantum/plugins/linuxbridge/common/constants.py b/quantum/plugins/linuxbridge/common/constants.py index 2ae1412ab..24614af8c 100644 --- a/quantum/plugins/linuxbridge/common/constants.py +++ b/quantum/plugins/linuxbridge/common/constants.py @@ -25,3 +25,16 @@ TYPE_FLAT = 'flat' TYPE_VLAN = 'vlan' TYPE_LOCAL = 'local' TYPE_NONE = 'none' + + +# TODO(rkukura): Eventually remove this function, which provides +# temporary backward compatibility with pre-Havana RPC and DB vlan_id +# encoding. +def interpret_vlan_id(vlan_id): + """Return (network_type, segmentation_id) tuple for encoded vlan_id.""" + if vlan_id == LOCAL_VLAN_ID: + return (TYPE_LOCAL, None) + elif vlan_id == FLAT_VLAN_ID: + return (TYPE_FLAT, None) + else: + return (TYPE_VLAN, vlan_id) diff --git a/quantum/plugins/linuxbridge/lb_quantum_plugin.py b/quantum/plugins/linuxbridge/lb_quantum_plugin.py index 57a83c5ef..48588132d 100644 --- a/quantum/plugins/linuxbridge/lb_quantum_plugin.py +++ b/quantum/plugins/linuxbridge/lb_quantum_plugin.py @@ -87,12 +87,17 @@ class LinuxBridgeRpcCallbacks(dhcp_rpc_base.DhcpRpcCallbackMixin, if port: binding = db.get_network_binding(db_api.get_session(), port['network_id']) + (network_type, + segmentation_id) = constants.interpret_vlan_id(binding.vlan_id) entry = {'device': device, + 'network_type': network_type, 'physical_network': binding.physical_network, - 'vlan_id': binding.vlan_id, + 'segmentation_id': segmentation_id, 'network_id': port['network_id'], 'port_id': port['id'], 'admin_state_up': port['admin_state_up']} + if cfg.CONF.AGENT.rpc_support_old_agents: + entry['vlan_id'] = binding.vlan_id new_status = (q_const.PORT_STATUS_ACTIVE if port['admin_state_up'] else q_const.PORT_STATUS_DOWN) if port['status'] != new_status: @@ -166,11 +171,15 @@ class AgentNotifierApi(proxy.RpcProxy, topic=self.topic_network_delete) def port_update(self, context, port, physical_network, vlan_id): - self.fanout_cast(context, - self.make_msg('port_update', - port=port, - physical_network=physical_network, - vlan_id=vlan_id), + network_type, segmentation_id = constants.interpret_vlan_id(vlan_id) + kwargs = {'port': port, + 'network_type': network_type, + 'physical_network': physical_network, + 'segmentation_id': segmentation_id} + if cfg.CONF.AGENT.rpc_support_old_agents: + kwargs['vlan_id'] = vlan_id + msg = self.make_msg('port_update', **kwargs) + self.fanout_cast(context, msg, topic=self.topic_port_update) diff --git a/quantum/tests/unit/linuxbridge/test_defaults.py b/quantum/tests/unit/linuxbridge/test_defaults.py index d58bfbceb..c33e9549e 100644 --- a/quantum/tests/unit/linuxbridge/test_defaults.py +++ b/quantum/tests/unit/linuxbridge/test_defaults.py @@ -24,6 +24,8 @@ class ConfigurationTest(base.BaseTestCase): def test_defaults(self): self.assertEqual(2, cfg.CONF.AGENT.polling_interval) + self.assertEqual(True, + cfg.CONF.AGENT.rpc_support_old_agents) self.assertEqual('sudo', cfg.CONF.AGENT.root_helper) self.assertEqual('local', diff --git a/quantum/tests/unit/linuxbridge/test_lb_quantum_agent.py b/quantum/tests/unit/linuxbridge/test_lb_quantum_agent.py index f399fee25..3a8923a1f 100644 --- a/quantum/tests/unit/linuxbridge/test_lb_quantum_agent.py +++ b/quantum/tests/unit/linuxbridge/test_lb_quantum_agent.py @@ -41,6 +41,7 @@ class TestLinuxBridge(base.BaseTestCase): def test_ensure_physical_in_bridge_invalid(self): result = self.linux_bridge.ensure_physical_in_bridge('network_id', + lconst.TYPE_VLAN, 'physnetx', 7) self.assertFalse(result) @@ -49,14 +50,14 @@ class TestLinuxBridge(base.BaseTestCase): with mock.patch.object(self.linux_bridge, 'ensure_flat_bridge') as flat_bridge_func: self.linux_bridge.ensure_physical_in_bridge( - 'network_id', 'physnet1', lconst.FLAT_VLAN_ID) + 'network_id', lconst.TYPE_FLAT, 'physnet1', None) self.assertTrue(flat_bridge_func.called) def test_ensure_physical_in_bridge_vlan(self): with mock.patch.object(self.linux_bridge, 'ensure_vlan_bridge') as vlan_bridge_func: self.linux_bridge.ensure_physical_in_bridge( - 'network_id', 'physnet1', 7) + 'network_id', lconst.TYPE_VLAN, 'physnet1', 7) self.assertTrue(vlan_bridge_func.called) @@ -338,16 +339,19 @@ class TestLinuxBridgeManager(base.BaseTestCase): def test_ensure_physical_in_bridge(self): self.assertFalse( - self.lbm.ensure_physical_in_bridge("123", "phys", "1") + self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VLAN, + "phys", "1") ) with mock.patch.object(self.lbm, "ensure_flat_bridge") as flbr_fn: self.assertTrue( - self.lbm.ensure_physical_in_bridge("123", "physnet1", "-1") + self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_FLAT, + "physnet1", None) ) self.assertTrue(flbr_fn.called) with mock.patch.object(self.lbm, "ensure_vlan_bridge") as vlbr_fn: self.assertTrue( - self.lbm.ensure_physical_in_bridge("123", "physnet1", "1") + self.lbm.ensure_physical_in_bridge("123", lconst.TYPE_VLAN, + "physnet1", "1") ) self.assertTrue(vlbr_fn.called) @@ -355,7 +359,8 @@ class TestLinuxBridgeManager(base.BaseTestCase): with mock.patch.object(self.lbm, "device_exists") as de_fn: de_fn.return_value = False self.assertFalse( - self.lbm.add_tap_interface("123", "physnet1", "1", "tap1") + self.lbm.add_tap_interface("123", lconst.TYPE_VLAN, + "physnet1", "1", "tap1") ) de_fn.return_value = True @@ -366,25 +371,33 @@ class TestLinuxBridgeManager(base.BaseTestCase): ) as (en_fn, exec_fn, get_br): exec_fn.return_value = False get_br.return_value = True - self.assertTrue(self.lbm.add_tap_interface("123", "physnet1", - "-2", "tap1")) + self.assertTrue(self.lbm.add_tap_interface("123", + lconst.TYPE_LOCAL, + "physnet1", None, + "tap1")) en_fn.assert_called_with("123") get_br.return_value = False exec_fn.return_value = True - self.assertFalse(self.lbm.add_tap_interface("123", "physnet1", - "-2", "tap1")) + self.assertFalse(self.lbm.add_tap_interface("123", + lconst.TYPE_LOCAL, + "physnet1", None, + "tap1")) with mock.patch.object(self.lbm, "ensure_physical_in_bridge") as ens_fn: ens_fn.return_value = False - self.assertFalse(self.lbm.add_tap_interface("123", "physnet1", - "1", "tap1")) + self.assertFalse(self.lbm.add_tap_interface("123", + lconst.TYPE_VLAN, + "physnet1", "1", + "tap1")) def test_add_interface(self): with mock.patch.object(self.lbm, "add_tap_interface") as add_tap: - self.lbm.add_interface("123", "physnet-1", "1", "234") - add_tap.assert_called_with("123", "physnet-1", "1", "tap234") + self.lbm.add_interface("123", lconst.TYPE_VLAN, "physnet-1", + "1", "234") + add_tap.assert_called_with("123", lconst.TYPE_VLAN, "physnet-1", + "1", "tap234") def test_delete_vlan_bridge(self): with contextlib.nested( @@ -508,9 +521,47 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): self.lb_rpc.port_update("unused_context", port=port, vlan_id="1", physical_network="physnet1") self.assertFalse(reffw_fn.called) - addif_fn.assert_called_with(port["network_id"], + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_VLAN, "physnet1", "1", port["id"]) + self.lb_rpc.port_update("unused_context", port=port, + network_type=lconst.TYPE_VLAN, + segmentation_id="2", + physical_network="physnet1") + self.assertFalse(reffw_fn.called) + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_VLAN, + "physnet1", "2", port["id"]) + + self.lb_rpc.port_update("unused_context", port=port, + vlan_id=lconst.FLAT_VLAN_ID, + physical_network="physnet1") + self.assertFalse(reffw_fn.called) + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_FLAT, + "physnet1", None, port["id"]) + + self.lb_rpc.port_update("unused_context", port=port, + network_type=lconst.TYPE_FLAT, + segmentation_id=None, + physical_network="physnet1") + self.assertFalse(reffw_fn.called) + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_FLAT, + "physnet1", None, port["id"]) + + self.lb_rpc.port_update("unused_context", port=port, + vlan_id=lconst.LOCAL_VLAN_ID, + physical_network=None) + self.assertFalse(reffw_fn.called) + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_LOCAL, + None, None, port["id"]) + + self.lb_rpc.port_update("unused_context", port=port, + network_type=lconst.TYPE_LOCAL, + segmentation_id=None, + physical_network=None) + self.assertFalse(reffw_fn.called) + addif_fn.assert_called_with(port["network_id"], lconst.TYPE_LOCAL, + None, None, port["id"]) + port["admin_state_up"] = False port["security_groups"] = True getbr_fn.return_value = "br0" diff --git a/quantum/tests/unit/linuxbridge/test_rpcapi.py b/quantum/tests/unit/linuxbridge/test_rpcapi.py index 939a54143..1a54b0f14 100644 --- a/quantum/tests/unit/linuxbridge/test_rpcapi.py +++ b/quantum/tests/unit/linuxbridge/test_rpcapi.py @@ -18,6 +18,7 @@ Unit Tests for linuxbridge rpc """ +from oslo.config import cfg import stubout from quantum.agent import rpc as agent_rpc @@ -29,11 +30,12 @@ from quantum.tests import base class rpcApiTestCase(base.BaseTestCase): - - def _test_lb_api(self, rpcapi, topic, method, rpc_method, **kwargs): + def _test_lb_api(self, rpcapi, topic, method, rpc_method, + expected_msg=None, **kwargs): ctxt = context.RequestContext('fake_user', 'fake_project') expected_retval = 'foo' if method == 'call' else None - expected_msg = rpcapi.make_msg(method, **kwargs) + if not expected_msg: + expected_msg = rpcapi.make_msg(method, **kwargs) expected_msg['version'] = rpcapi.BASE_RPC_API_VERSION if rpc_method == 'cast' and method == 'run_instance': kwargs['call'] = False @@ -52,11 +54,11 @@ class rpcApiTestCase(base.BaseTestCase): retval = getattr(rpcapi, method)(ctxt, **kwargs) - self.assertEqual(retval, expected_retval) + self.assertEqual(expected_retval, retval) expected_args = [ctxt, topic, expected_msg] for arg, expected_arg in zip(self.fake_args, expected_args): - self.assertEqual(arg, expected_arg) + self.assertEqual(expected_arg, arg) def test_delete_network(self): rpcapi = plb.AgentNotifierApi(topics.AGENT) @@ -68,12 +70,38 @@ class rpcApiTestCase(base.BaseTestCase): network_id='fake_request_spec') def test_port_update(self): + cfg.CONF.set_override('rpc_support_old_agents', False, 'AGENT') rpcapi = plb.AgentNotifierApi(topics.AGENT) + expected_msg = rpcapi.make_msg('port_update', + port='fake_port', + network_type='vlan', + physical_network='fake_net', + segmentation_id='fake_vlan_id') self._test_lb_api(rpcapi, topics.get_topic_name(topics.AGENT, topics.PORT, topics.UPDATE), 'port_update', rpc_method='fanout_cast', + expected_msg=expected_msg, + port='fake_port', + physical_network='fake_net', + vlan_id='fake_vlan_id') + + def test_port_update_old_agent(self): + cfg.CONF.set_override('rpc_support_old_agents', True, 'AGENT') + rpcapi = plb.AgentNotifierApi(topics.AGENT) + expected_msg = rpcapi.make_msg('port_update', + port='fake_port', + network_type='vlan', + physical_network='fake_net', + segmentation_id='fake_vlan_id', + vlan_id='fake_vlan_id') + self._test_lb_api(rpcapi, + topics.get_topic_name(topics.AGENT, + topics.PORT, + topics.UPDATE), + 'port_update', rpc_method='fanout_cast', + expected_msg=expected_msg, port='fake_port', physical_network='fake_net', vlan_id='fake_vlan_id')