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
This commit is contained in:
parent
5c3bf12496
commit
f7064f2b6c
|
@ -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)
|
||||
|
||||
|
|
|
@ -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 "
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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':
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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``.
|
Loading…
Reference in New Issue