From f7064f2b6c6ba1d0ab5f9872b2d5ad7969a64e7b Mon Sep 17 00:00:00 2001 From: Miguel Lavalle Date: Sat, 23 Jun 2018 22:58:42 -0500 Subject: [PATCH] Add binding activation to the Linuxbridge agent As part of the implementation of multiple port bindings [1], add binding activation support to the linux bridge agent. This will enable the execution with linux bridge agents of the complete sequence of steps outlined in [1] during an instance migration: 1) Create inactive port bindings for destination host 2) Migrate the instance to the destination host and plug its VIFs 3) Activate the port bindings in the destination host 4) Delete the port bindings for the source host [1] https://review.openstack.org/#/c/309416/ Change-Id: I2c937cc0a551e5ce0e8534c4dd4384ec2ca92da1 Partial-Bug: #1580880 --- .../ml2/drivers/agent/_common_agent.py | 3 +++ .../agent/linuxbridge_neutron_agent.py | 16 ++++++++++++-- neutron/plugins/ml2/plugin.py | 2 ++ neutron/plugins/ml2/rpc.py | 19 +++++++++++++++- .../ml2/drivers/agent/test__common_agent.py | 8 +++++++ .../agent/test_linuxbridge_neutron_agent.py | 12 ++++++++++ neutron/tests/unit/plugins/ml2/test_rpc.py | 10 +++++++++ ...ltiple-port-bindings-f16eb47ebdddff2d.yaml | 22 +++++++++++++++++++ 8 files changed, 89 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/add-multiple-port-bindings-f16eb47ebdddff2d.yaml diff --git a/neutron/plugins/ml2/drivers/agent/_common_agent.py b/neutron/plugins/ml2/drivers/agent/_common_agent.py index 4e5b15ac950..bf559a3deb3 100644 --- a/neutron/plugins/ml2/drivers/agent/_common_agent.py +++ b/neutron/plugins/ml2/drivers/agent/_common_agent.py @@ -39,6 +39,7 @@ from neutron.agent import securitygroups_rpc as agent_sg_rpc from neutron.api.rpc.callbacks import resources from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc from neutron.common import config as common_config +from neutron.common import constants as c_const from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb from neutron.plugins.ml2.drivers.agent import capabilities from neutron.plugins.ml2.drivers.agent import config as cagt_config # noqa @@ -311,6 +312,8 @@ class CommonAgentLoop(service.Service): events.AFTER_UPDATE, self, context=self.context, device_details=device_details) + elif c_const.NO_ACTIVE_BINDING in device_details: + LOG.info("Device %s has no active binding in host", device) else: LOG.info("Device %s not defined on plugin", device) diff --git a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py index cbd7986a368..d153f4ecdfb 100644 --- a/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py +++ b/neutron/plugins/ml2/drivers/linuxbridge/agent/linuxbridge_neutron_agent.py @@ -835,7 +835,8 @@ class LinuxBridgeManager(amb.CommonAgentManagerBase): [topics.NETWORK, topics.DELETE], [topics.NETWORK, topics.UPDATE], [topics.SECURITY_GROUP, topics.UPDATE], - [n_topics.PORT_BINDING, n_topics.DEACTIVATE]] + [n_topics.PORT_BINDING, n_topics.DEACTIVATE], + [n_topics.PORT_BINDING, n_topics.ACTIVATE]] if cfg.CONF.VXLAN.l2_population: consumers.append([topics.L2POPULATION, topics.UPDATE]) return consumers @@ -871,7 +872,7 @@ class LinuxBridgeRpcCallbacks( # 1.1 Support Security Group RPC # 1.3 Added param devices_to_update to security_groups_provider_updated # 1.4 Added support for network_update - # 1.5 Added binding_deactivate + # 1.5 Added binding_activate and binding_deactivate target = oslo_messaging.Target(version='1.5') def network_delete(self, context, **kwargs): @@ -914,6 +915,17 @@ class LinuxBridgeRpcCallbacks( 'bridge_name': bridge_name}) self.agent.mgr.remove_interface(bridge_name, interface_name) + def binding_activate(self, context, **kwargs): + if kwargs.get('host') != cfg.CONF.host: + return + # Since the common agent loop treats added and updated the same way, + # just add activated ports to the updated devices list. This way, + # adding binding activation is less disruptive to the existing code + port_id = kwargs.get('port_id') + device_name = self.agent.mgr.get_tap_device_name(port_id) + self.updated_devices.add(device_name) + LOG.debug("Binding activation received for port: %s", port_id) + def network_update(self, context, **kwargs): network_id = kwargs['network']['id'] LOG.debug("network_update message processed for network " diff --git a/neutron/plugins/ml2/plugin.py b/neutron/plugins/ml2/plugin.py index dd33eae0347..7be8cd3ce78 100644 --- a/neutron/plugins/ml2/plugin.py +++ b/neutron/plugins/ml2/plugin.py @@ -2202,6 +2202,8 @@ class Ml2Plugin(db_base_plugin_v2.NeutronDbPluginV2, self.notifier.binding_deactivate(context, port_id, active_binding.host, network['id']) + self.notifier.binding_activate(context, port_id, + inactive_binding.host) return self._make_port_binding_dict(cur_context._binding) raise n_exc.PortBindingError(port_id=port_id, host=host) diff --git a/neutron/plugins/ml2/rpc.py b/neutron/plugins/ml2/rpc.py index 01bf781b89b..8092545f692 100644 --- a/neutron/plugins/ml2/rpc.py +++ b/neutron/plugins/ml2/rpc.py @@ -28,6 +28,7 @@ from sqlalchemy.orm import exc from neutron.agent import _topics as n_topics from neutron.api.rpc.handlers import dvr_rpc from neutron.api.rpc.handlers import securitygroups_rpc as sg_rpc +from neutron.common import constants as c_const from neutron.common import rpc as n_rpc from neutron.db import l3_hamode_db from neutron.db import provisioning_blocks @@ -124,6 +125,15 @@ class RpcCallbacks(type_tunnel.TunnelRpcCallbackMixin): 'vif_type': port_context.vif_type}) return {'device': device} + if (port['device_owner'].startswith( + n_const.DEVICE_OWNER_COMPUTE_PREFIX) and + port[portbindings.HOST_ID] != host): + LOG.debug("Device %(device)s has no active binding in host " + "%(host)s", {'device': device, + 'host': host}) + return {'device': device, + c_const.NO_ACTIVE_BINDING: True} + network_qos_policy_id = port_context.network._network.get( qos_consts.QOS_POLICY_ID) entry = {'device': device, @@ -384,7 +394,7 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, 1.1 - Added get_active_networks_info, create_dhcp_port, update_dhcp_port, and removed get_dhcp_port methods. 1.4 - Added network_update - 1.5 - Added binding_deactivate + 1.5 - Added binding_activate and binding_deactivate """ def __init__(self, topic): @@ -403,6 +413,8 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, topics.UPDATE) self.topic_port_binding_deactivate = topics.get_topic_name( topic, n_topics.PORT_BINDING, n_topics.DEACTIVATE) + self.topic_port_binding_activate = topics.get_topic_name( + topic, n_topics.PORT_BINDING, n_topics.ACTIVATE) target = oslo_messaging.Target(topic=topic, version='1.0') self.client = n_rpc.get_client(target) @@ -435,3 +447,8 @@ class AgentNotifierApi(dvr_rpc.DVRAgentRpcApiMixin, fanout=True, version='1.5') cctxt.cast(context, 'binding_deactivate', port_id=port_id, host=host, network_id=network_id) + + def binding_activate(self, context, port_id, host): + cctxt = self.client.prepare(topic=self.topic_port_binding_activate, + fanout=True, version='1.5') + cctxt.cast(context, 'binding_activate', port_id=port_id, host=host) diff --git a/neutron/tests/unit/plugins/ml2/drivers/agent/test__common_agent.py b/neutron/tests/unit/plugins/ml2/drivers/agent/test__common_agent.py index c7091bbf5cf..4ecc7703a13 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/agent/test__common_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/agent/test__common_agent.py @@ -24,6 +24,7 @@ from oslo_config import cfg import testtools from neutron.agent.linux import bridge_lib +from neutron.common import constants as n_const from neutron.plugins.ml2.drivers.agent import _agent_manager_base as amb from neutron.plugins.ml2.drivers.agent import _common_agent as ca from neutron.tests import base @@ -520,6 +521,13 @@ class TestCommonAgentLoop(base.BaseTestCase): # device exists so it should raise self.agent._process_device_if_exists(mock_details) + def test__process_device_if_exists_no_active_binding_in_host(self): + mock_details = {'device': 'dev123', + n_const.NO_ACTIVE_BINDING: True} + self.agent.mgr = mock.Mock() + self.agent._process_device_if_exists(mock_details) + self.agent.mgr.setup_arp_spoofing_protection.assert_not_called() + def test_set_rpc_timeout(self): self.agent.stop() for rpc_client in (self.agent.plugin_rpc.client, diff --git a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py index a72fe0332d4..9837f0e39d1 100644 --- a/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py +++ b/neutron/tests/unit/plugins/ml2/drivers/linuxbridge/agent/test_linuxbridge_neutron_agent.py @@ -1051,6 +1051,18 @@ class TestLinuxBridgeRpcCallbacks(base.BaseTestCase): get_tap_fn.assert_not_called() rem_intf.assert_not_called() + def test_binding_activate(self): + with mock.patch.object(self.lb_rpc.agent.mgr, + "get_tap_device_name") as get_tap_fun: + get_tap_fun.return_value = "tap456" + self.lb_rpc.binding_activate(mock.ANY, host="host", port_id="456") + self.assertIn("tap456", self.lb_rpc.updated_devices) + + def test_binding_activate_not_for_host(self): + self.lb_rpc.binding_activate(mock.ANY, host="other-host", + port_id="456") + self.assertFalse(self.lb_rpc.updated_devices) + def _test_fdb_add(self, proxy_enabled=False): fdb_entries = {'net_id': {'ports': diff --git a/neutron/tests/unit/plugins/ml2/test_rpc.py b/neutron/tests/unit/plugins/ml2/test_rpc.py index 646e6a5f946..e4e6ccda18d 100644 --- a/neutron/tests/unit/plugins/ml2/test_rpc.py +++ b/neutron/tests/unit/plugins/ml2/test_rpc.py @@ -21,6 +21,7 @@ import collections import mock from neutron_lib.agent import topics +from neutron_lib.api.definitions import portbindings from neutron_lib.callbacks import resources from neutron_lib import constants from neutron_lib.plugins import constants as plugin_constants @@ -31,6 +32,7 @@ from oslo_context import context as oslo_context from sqlalchemy.orm import exc from neutron.agent import rpc as agent_rpc +from neutron.common import constants as n_const from neutron.db import provisioning_blocks from neutron.plugins.ml2 import db as ml2_db from neutron.plugins.ml2.drivers import type_tunnel @@ -154,6 +156,14 @@ class RpcCallbacksTestCase(base.BaseTestCase): res = self.callbacks.get_device_details(mock.Mock(), host='fake') self.assertEqual('test-policy-id', res['network_qos_policy_id']) + def test_get_device_details_port_no_active_in_host(self): + port = collections.defaultdict(lambda: 'fake_port') + self.plugin.get_bound_port_context().current = port + port['device_owner'] = constants.DEVICE_OWNER_COMPUTE_PREFIX + port[portbindings.HOST_ID] = 'other-host' + res = self.callbacks.get_device_details(mock.Mock(), host='host') + self.assertIn(n_const.NO_ACTIVE_BINDING, res) + def test_get_device_details_qos_policy_id_from_port(self): port = collections.defaultdict( lambda: 'fake_port', diff --git a/releasenotes/notes/add-multiple-port-bindings-f16eb47ebdddff2d.yaml b/releasenotes/notes/add-multiple-port-bindings-f16eb47ebdddff2d.yaml new file mode 100644 index 00000000000..1cad21191ab --- /dev/null +++ b/releasenotes/notes/add-multiple-port-bindings-f16eb47ebdddff2d.yaml @@ -0,0 +1,22 @@ +--- +prelude: > + Support multiple bindings for compute owned ports. +features: + - | + In order to better support instance migration, multiple port + bindings can be associated to compute owned ports. + + * Create, update, list, show and activate operations are supported + for port bindings by the ReST API. + * A compute owned port can have one active binding and many + inactive bindings. + * There can be only one binding (active or inactive) per compute + host. + * When the ``activate`` operation is executed, a previously + inactive binding is made active. The previously active binding + becomes inactive. + * As a consequence of the multiple port bindings implementation, + the ``port_binding`` relationship in the SQLAlchemy ``Port`` + object has been renamed ``port_bindings``. Similarly, the + ``binding`` attribute of the ``Port`` OVO has been renamed + ``bindings``.