DVR: Provide options for DVR North/South routing centralized

DVR supports both East/West and North/South routing. While the
SNAT is centralized the DNAT is mostly distributed. There are
certain circumstances where the DNAT might be centralized when
the ports are unbound.

In order to have a well defined behavior and when there are
no external network connectivity available in the compute host,
the DNAT functionality is centralized.
In order to achieve this we are introducing a new agent type
option 'dvr_no_external' to centralize the DNAT.

This new L3 agent type ('dvr_no_external') would only allow the East/West
routing to occur in the compute host and the DNAT or Floating IP will be
configured in the centralized network node.

Change-Id: Ia5d7336e478e0fa5ba62b7ae5ed0c56656116d94
Partial-Bug: #1667877
(cherry picked from commit 9515c771e7)
This commit is contained in:
Swaminathan Vasudevan 2017-07-19 12:13:43 -07:00 committed by Armando Migliaccio
parent 7a89752e2f
commit 5524bf8434
11 changed files with 342 additions and 62 deletions

View File

@ -591,8 +591,9 @@ class L3NATAgent(ha.AgentMixin,
chunk = []
is_snat_agent = (self.conf.agent_mode ==
lib_const.L3_AGENT_MODE_DVR_SNAT)
is_dvr_only_agent = (self.conf.agent_mode ==
lib_const.L3_AGENT_MODE_DVR)
is_dvr_only_agent = (self.conf.agent_mode in
[lib_const.L3_AGENT_MODE_DVR,
l3_constants.L3_AGENT_MODE_DVR_NO_EXTERNAL])
try:
router_ids = self.plugin_rpc.get_router_ids(context)
# We set HA network port status to DOWN to let l2 agent update it

View File

@ -95,7 +95,7 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
"""
def floating_ip_added_dist(self, fip, fip_cidr):
"""Add floating IP to FIP namespace."""
"""Add floating IP to respective namespace based on agent mode."""
if fip.get(n_const.DVR_SNAT_BOUND):
floating_ip_status = self.add_centralized_floatingip(fip, fip_cidr)
if floating_ip_status == lib_constants.FLOATINGIP_STATUS_ACTIVE:
@ -552,10 +552,12 @@ class DvrLocalRouter(dvr_router_base.DvrRouterBase):
i.get('dest_host') == self.host)]
def process_external(self):
ex_gw_port = self.get_ex_gw_port()
if ex_gw_port:
self.create_dvr_external_gateway_on_agent(ex_gw_port)
self.connect_rtr_2_fip()
if self.agent_conf.agent_mode != (
n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL):
ex_gw_port = self.get_ex_gw_port()
if ex_gw_port:
self.create_dvr_external_gateway_on_agent(ex_gw_port)
self.connect_rtr_2_fip()
super(DvrLocalRouter, self).process_external()
def connect_rtr_2_fip(self):

View File

@ -29,6 +29,7 @@ METERING_LABEL_KEY = '_metering_labels'
FLOATINGIP_AGENT_INTF_KEY = '_floatingip_agent_interfaces'
SNAT_ROUTER_INTF_KEY = '_snat_router_interfaces'
DVR_SNAT_BOUND = 'dvr_snat_bound'
L3_AGENT_MODE_DVR_NO_EXTERNAL = 'dvr_no_external'
HA_NETWORK_NAME = 'HA network tenant %s'
HA_SUBNET_NAME = 'HA subnet tenant %s'

View File

@ -18,6 +18,7 @@ from neutron_lib import constants
from oslo_config import cfg
from neutron._i18n import _
from neutron.common import constants as common_consts
from neutron.conf.agent import common as config
@ -25,7 +26,8 @@ OPTS = [
cfg.StrOpt('agent_mode', default=constants.L3_AGENT_MODE_LEGACY,
choices=(constants.L3_AGENT_MODE_DVR,
constants.L3_AGENT_MODE_DVR_SNAT,
constants.L3_AGENT_MODE_LEGACY),
constants.L3_AGENT_MODE_LEGACY,
common_consts.L3_AGENT_MODE_DVR_NO_EXTERNAL),
help=_("The working mode for the agent. Allowed modes are: "
"'legacy' - this preserves the existing behavior "
"where the L3 agent is deployed on a centralized "
@ -37,7 +39,14 @@ OPTS = [
"enables centralized SNAT support in conjunction "
"with DVR. This mode must be used for an L3 agent "
"running on a centralized node (or in single-host "
"deployments, e.g. devstack)")),
"deployments, e.g. devstack). "
"'dvr_no_external' - this mode enables only East/West "
"DVR routing functionality for a L3 agent that runs on "
"a compute host, the North/South functionality such "
"as DNAT and SNAT will be provided by the centralized "
"network node that is running in 'dvr_snat' mode. "
"This mode should be used when there is no "
"external network connectivity on the compute host.")),
cfg.PortOpt('metadata_port',
default=9697,
help=_("TCP Port used by Neutron metadata namespace proxy.")),

View File

@ -24,6 +24,7 @@ from sqlalchemy import or_
from neutron._i18n import _, _LI
from neutron.agent.common import utils as agent_utils
from neutron.common import constants as l_consts
from neutron.common import utils as n_utils
from neutron.db import agentschedulers_db
from neutron.db.models import agent as agent_model
@ -108,7 +109,8 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
agent_mode = self._get_agent_mode(agent)
if agent_mode == constants.L3_AGENT_MODE_DVR:
if agent_mode in [constants.L3_AGENT_MODE_DVR,
l_consts.L3_AGENT_MODE_DVR_NO_EXTERNAL]:
raise l3agentscheduler.DVRL3CannotAssignToDvrAgent()
if (agent_mode == constants.L3_AGENT_MODE_LEGACY and
@ -194,11 +196,11 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
"""
agent = self._get_agent(context, agent_id)
agent_mode = self._get_agent_mode(agent)
if agent_mode == constants.L3_AGENT_MODE_DVR:
if agent_mode in [constants.L3_AGENT_MODE_DVR,
l_consts.L3_AGENT_MODE_DVR_NO_EXTERNAL]:
raise l3agentscheduler.DVRL3CannotRemoveFromDvrAgent()
self._unbind_router(context, router_id, agent_id)
router = self.get_router(context, router_id)
plugin = directory.get_plugin(plugin_constants.L3)
if router.get('ha'):
@ -416,8 +418,9 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
ignore_admin_state=False):
"""Get the valid l3 agents for the router from a list of l3_agents.
It will not return agents in 'dvr' mode for a dvr router as dvr
routers are not explicitly scheduled to l3 agents on compute nodes
It will not return agents in 'dvr' mode or in 'dvr_no_external' mode
for a dvr router as dvr routers are not explicitly scheduled to l3
agents on compute nodes
"""
candidates = []
is_router_distributed = sync_router.get('distributed', False)
@ -431,6 +434,7 @@ class L3AgentSchedulerDbMixin(l3agentscheduler.L3AgentSchedulerPluginBase,
agent_mode = agent_conf.get(constants.L3_AGENT_MODE,
constants.L3_AGENT_MODE_LEGACY)
if (agent_mode == constants.L3_AGENT_MODE_DVR or
agent_mode == l_consts.L3_AGENT_MODE_DVR_NO_EXTERNAL or
(agent_mode == constants.L3_AGENT_MODE_LEGACY and
is_router_distributed)):
continue

View File

@ -714,22 +714,56 @@ class _DVRAgentInterfaceMixin(object):
# All unbound ports with floatingip irrespective of
# the device owner should be included as valid ports
# and updated.
if (port[portbindings.HOST_ID] == host or port_in_migration or
port_host = port[portbindings.HOST_ID]
if (port_host == host or port_in_migration or
self._is_unbound_port(port)):
port_dict.update({port['id']: port})
if port_host and port_host != host:
# Consider the ports where the portbinding host and
# request host does not match.
l3_agent_on_host = self.get_l3_agents(
context,
filters={'host': [port_host]})
if len(l3_agent_on_host):
l3_agent_mode = self._get_agent_mode(
l3_agent_on_host[0])
# If the agent requesting is dvr_snat but
# the portbinding host resides in dvr_no_external
# agent then include the port.
requesting_agent_mode = self._get_agent_mode(agent)
if (l3_agent_mode == (
l3_const.L3_AGENT_MODE_DVR_NO_EXTERNAL) and
requesting_agent_mode == (
const.L3_AGENT_MODE_DVR_SNAT)):
port['agent'] = (
l3_const.L3_AGENT_MODE_DVR_NO_EXTERNAL)
port_dict.update({port['id']: port})
# Add the port binding host to the floatingip dictionary
for fip in floating_ips:
vm_port = port_dict.get(fip['port_id'], None)
if vm_port:
fip['host'] = self._get_dvr_service_port_hostid(
context, fip['port_id'], port=vm_port)
fip['dest_host'] = (
self._get_dvr_migrating_service_port_hostid(
context, fip['port_id'], port=vm_port))
port_host = vm_port[portbindings.HOST_ID]
if port_host:
fip['host'] = port_host
fip['dest_host'] = (
self._get_dvr_migrating_service_port_hostid(
context, fip['port_id'], port=vm_port))
vm_port_agent_mode = vm_port.get('agent', None)
if vm_port_agent_mode == (
l3_const.L3_AGENT_MODE_DVR_NO_EXTERNAL):
# For floatingip configured on ports that
# reside on 'dvr_no_external' agent, get rid of
# the fip host binding since it would be created
# in the 'dvr_snat' agent.
fip['host'] = None
else:
# If no port-binding assign the fip['host']
# value to None.
fip['host'] = None
# Handle the case were there is no host binding
# for the private ports that are associated with
# floating ip.
if not fip['host']:
if not fip['host'] or fip['host'] is None:
fip[l3_const.DVR_SNAT_BOUND] = True
routers_dict = self._process_routers(context, routers, agent)
self._process_floating_ips_dvr(context, routers_dict,
@ -961,6 +995,10 @@ class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin,
self._notify_floating_ip_change(context, floating_ip)
return floating_ip
def get_dvr_agent_on_host(self, context, fip_host):
agent_filters = {'host': [fip_host]}
return self.get_l3_agents(context, filters=agent_filters)
def _notify_floating_ip_change(self, context, floating_ip):
router_id = floating_ip['router_id']
fixed_port_id = floating_ip['port_id']
@ -981,6 +1019,17 @@ class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin,
host = self._get_dvr_service_port_hostid(context, fixed_port_id)
dest_host = self._get_dvr_migrating_service_port_hostid(
context, fixed_port_id)
if host is not None:
l3_agent_on_host = self.get_dvr_agent_on_host(
context, host)
agent_mode = self._get_agent_mode(l3_agent_on_host[0])
if agent_mode == l3_const.L3_AGENT_MODE_DVR_NO_EXTERNAL:
# If the agent hosting the fixed port is in
# 'dvr_no_external' mode, then set the host to None,
# since we would be centralizing the floatingip for
# those fixed_ports.
host = None
if host is not None:
self.l3_rpc_notifier.routers_updated_on_host(
context, [router_id], host)
@ -988,7 +1037,11 @@ class L3_NAT_with_dvr_db_mixin(_DVRAgentInterfaceMixin,
self.l3_rpc_notifier.routers_updated_on_host(
context, [router_id], dest_host)
else:
self.notify_router_updated(context, router_id)
centralized_agent_list = self.list_l3_agents_hosting_router(
context, router_id)['agents']
for agent in centralized_agent_list:
self.l3_rpc_notifier.routers_updated_on_host(
context, [router_id], agent['host'])
else:
self.notify_router_updated(context, router_id)

View File

@ -23,6 +23,7 @@ from neutron_lib.plugins import directory
from oslo_log import log as logging
from sqlalchemy import or_
from neutron.common import constants as l3_consts
from neutron.common import utils as n_utils
from neutron.db import agentschedulers_db
@ -44,7 +45,9 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
distributed manner across Compute Nodes without a centralized element.
This includes E/W traffic between VMs on the same Compute Node.
- North/South traffic for Floating IPs (FIP N/S): this is supported on the
distributed routers on Compute Nodes without any centralized element.
distributed routers on Compute Nodes when there is external network
connectivity and on centralized nodes when the port is not bound or when
the agent is configured as 'dvr_no_external'.
- North/South traffic for SNAT (SNAT N/S): this is supported via a
centralized element that handles the SNAT traffic.
@ -288,8 +291,10 @@ class L3_DVRsch_db_mixin(l3agent_sch_db.L3AgentSchedulerDbMixin):
# dvr routers are not explicitly scheduled to agents on hosts with
# dvr serviceable ports, so need special handling
if self._get_agent_mode(agent_db) in [n_const.L3_AGENT_MODE_DVR,
n_const.L3_AGENT_MODE_DVR_SNAT]:
if (self._get_agent_mode(agent_db) in
[n_const.L3_AGENT_MODE_DVR,
l3_consts.L3_AGENT_MODE_DVR_NO_EXTERNAL,
n_const.L3_AGENT_MODE_DVR_SNAT]):
if not router_ids:
result_set |= set(self._get_dvr_router_ids_for_host(
context, agent_db['host']))

View File

@ -641,19 +641,22 @@ class L3_HA_NAT_db_mixin(l3_dvr_db.L3_NAT_with_dvr_db_mixin,
self._populate_mtu_and_subnets_for_ports(context, interfaces)
# If this is a DVR+HA router, but the agent is question is in 'dvr'
# mode (as opposed to 'dvr_snat'), then we want to always return it
# even though it's missing the '_ha_interface' key.
# If this is a DVR+HA router, but the agent in question is in 'dvr'
# or 'dvr_no_external' mode (as opposed to 'dvr_snat'), then we want to
# always return it even though it's missing the '_ha_interface' key.
return [r for r in list(routers_dict.values())
if (agent_mode == constants.L3_AGENT_MODE_DVR or
agent_mode == n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL or
not r.get('ha') or r.get(constants.HA_INTERFACE_KEY))]
@log_helpers.log_method_call
def get_ha_sync_data_for_host(self, context, host, agent,
router_ids=None, active=None):
agent_mode = self._get_agent_mode(agent)
dvr_agent_mode = (agent_mode in [constants.L3_AGENT_MODE_DVR_SNAT,
constants.L3_AGENT_MODE_DVR])
dvr_agent_mode = (
agent_mode in [constants.L3_AGENT_MODE_DVR_SNAT,
constants.L3_AGENT_MODE_DVR,
n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL])
if (dvr_agent_mode and n_utils.is_extension_supported(
self, constants.L3_DISTRIBUTED_EXT_ALIAS)):
# DVR has to be handled differently

View File

@ -449,6 +449,7 @@ class TestDvrRouter(framework.L3AgentTestFramework):
agent=None,
extra_routes=False,
enable_floating_ip=True,
enable_centralized_fip=False,
**kwargs):
if not agent:
agent = self.agent
@ -469,6 +470,11 @@ class TestDvrRouter(framework.L3AgentTestFramework):
if snat_bound_fip:
floating_ip[n_const.DVR_SNAT_BOUND] = True
if enable_floating_ip and enable_centralized_fip:
# For centralizing the fip, we are emulating the legacy
# router behavior were the fip dict does not contain any
# host information.
floating_ip['host'] = None
if enable_gw:
external_gw_port = router['gw_port']
router['gw_port'][portbindings.HOST_ID] = agent.conf.host
@ -479,7 +485,6 @@ class TestDvrRouter(framework.L3AgentTestFramework):
# dependent on the agent_type.
if enable_floating_ip:
floating_ip = router['_floatingips'][0]
floating_ip['host'] = agent.conf.host
floating_ip['floating_network_id'] = (
external_gw_port['network_id'])
floating_ip['port_id'] = internal_ports[0]['id']
@ -686,7 +691,8 @@ class TestDvrRouter(framework.L3AgentTestFramework):
self.assertTrue(ip_lib.device_exists(
device_name, namespace=router.ns_name))
# In the router namespace, check the iptables rules are set correctly
# In the router namespace, check the iptables rules are set
# correctly
for fip in floating_ips:
expected_rules = router.floating_forward_rules(fip)
self._assert_iptables_rules_exist(
@ -1013,6 +1019,27 @@ class TestDvrRouter(framework.L3AgentTestFramework):
self._assert_iptables_rules_exist(
router1.snat_iptables_manager, 'nat', expected_rules)
def test_floating_ip_not_deployed_on_dvr_no_external_agent(self):
"""Test to check floating ips not configured for dvr_no_external."""
self.agent.conf.agent_mode = n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL
router_info = self.generate_dvr_router_info(
enable_floating_ip=True, enable_centralized_fip=True)
router1 = self.manage_router(self.agent, router_info)
centralized_floatingips = router_info[lib_constants.FLOATINGIP_KEY]
# For private ports hosted in dvr_no_fip agent, the floatingip
# dict will contain the fip['host'] key, but the value will always
# be None to emulate the legacy router.
self.assertIsNone(centralized_floatingips[0]['host'])
self.assertTrue(self._namespace_exists(router1.ns_name))
fip_ns = router1.fip_ns.get_name()
self.assertFalse(self._namespace_exists(fip_ns))
# If fips are centralized then, the DNAT rules are only
# configured in the SNAT Namespace and not in the router-ns.
for fip in centralized_floatingips:
expected_rules = router1.floating_forward_rules(fip)
self.assertFalse(self._assert_iptables_rules_exist(
router1.iptables_manager, 'nat', expected_rules))
def test_dvr_router_snat_namespace_with_interface_remove(self):
"""Test to validate the snat namespace with interface remove.

View File

@ -248,7 +248,8 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.l3_plugin._get_agent_gw_ports_exist_for_network(
self.context, ext_net_id, "", self.l3_agent['id']))
def _test_create_floating_ip_agent_notification(self, dvr=True):
def _test_create_floating_ip_agent_notification(
self, dvr=True, test_agent_mode=constants.L3_AGENT_MODE_DVR):
with self.subnet() as ext_subnet,\
self.subnet(cidr='20.0.0.0/24') as int_subnet,\
self.port(subnet=int_subnet,
@ -258,7 +259,7 @@ class L3DvrTestCase(L3DvrTestCaseBase):
{'port': {portbindings.HOST_ID: 'host1'}})
# and create l3 agents on corresponding hosts
helpers.register_l3_agent(host='host1',
agent_mode=constants.L3_AGENT_MODE_DVR)
agent_mode=test_agent_mode)
# make net external
ext_net_id = ext_subnet['subnet']['network_id']
@ -273,7 +274,6 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': int_subnet['subnet']['id']})
floating_ip = {'floating_network_id': ext_net_id,
'router_id': router['id'],
'port_id': int_port['port']['id'],
@ -284,10 +284,27 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.l3_plugin.create_floatingip(
self.context, {'floatingip': floating_ip})
if dvr:
l3_notif.routers_updated_on_host.assert_called_once_with(
self.context, [router['id']],
'host1')
self.assertFalse(l3_notif.routers_updated.called)
if test_agent_mode == (
n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL):
if router['ha']:
expected_calls = [
mock.call(self.context,
[router['id']], 'host0'),
mock.call(self.context,
[router['id']], 'standby')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls, any_order=True)
self.assertFalse(l3_notif.routers_updated.called)
if not router['ha']:
l3_notif.routers_updated_on_host.\
assert_called_once_with(
self.context, [router['id']], 'host0')
self.assertFalse(l3_notif.routers_updated.called)
else:
l3_notif.routers_updated_on_host.\
assert_called_once_with(
self.context, [router['id']], 'host1')
self.assertFalse(l3_notif.routers_updated.called)
else:
l3_notif.routers_updated.assert_called_once_with(
self.context, [router['id']], None)
@ -297,10 +314,17 @@ class L3DvrTestCase(L3DvrTestCaseBase):
def test_create_floating_ip_agent_notification(self):
self._test_create_floating_ip_agent_notification()
def test_create_floating_ip_agent_notification_for_dvr_no_external_agent(
self):
agent_mode = n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL
self._test_create_floating_ip_agent_notification(
test_agent_mode=agent_mode)
def test_create_floating_ip_agent_notification_non_dvr(self):
self._test_create_floating_ip_agent_notification(dvr=False)
def _test_update_floating_ip_agent_notification(self, dvr=True):
def _test_update_floating_ip_agent_notification(
self, dvr=True, test_agent_mode=constants.L3_AGENT_MODE_DVR):
with self.subnet() as ext_subnet,\
self.subnet(cidr='20.0.0.0/24') as int_subnet1,\
self.subnet(cidr='30.0.0.0/24') as int_subnet2,\
@ -317,9 +341,9 @@ class L3DvrTestCase(L3DvrTestCaseBase):
{'port': {portbindings.HOST_ID: 'host2'}})
# and create l3 agents on corresponding hosts
helpers.register_l3_agent(host='host1',
agent_mode=constants.L3_AGENT_MODE_DVR)
agent_mode=test_agent_mode)
helpers.register_l3_agent(host='host2',
agent_mode=constants.L3_AGENT_MODE_DVR)
agent_mode=test_agent_mode)
# make net external
ext_net_id = ext_subnet['subnet']['network_id']
@ -356,14 +380,45 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.context, floating_ip['id'],
{'floatingip': updated_floating_ip})
if dvr:
self.assertEqual(
2, l3_notif.routers_updated_on_host.call_count)
expected_calls = [
mock.call(self.context, [router1['id']], 'host1'),
mock.call(self.context, [router2['id']], 'host2')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls)
self.assertFalse(l3_notif.routers_updated.called)
if test_agent_mode == (
n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL):
if router1['ha'] and router2['ha']:
self.assertEqual(
4,
l3_notif.routers_updated_on_host.call_count)
expected_calls = [
mock.call(self.context,
[router1['id']], 'host0'),
mock.call(self.context,
[router1['id']], 'standby'),
mock.call(self.context,
[router2['id']], 'host0'),
mock.call(self.context,
[router2['id']], 'standby')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls, any_order=True)
self.assertFalse(l3_notif.routers_updated.called)
else:
self.assertEqual(
2,
l3_notif.routers_updated_on_host.call_count)
expected_calls = [
mock.call(self.context,
[router1['id']], 'host0'),
mock.call(self.context,
[router2['id']], 'host0')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls)
self.assertFalse(l3_notif.routers_updated.called)
else:
self.assertEqual(
2, l3_notif.routers_updated_on_host.call_count)
expected_calls = [
mock.call(self.context, [router1['id']], 'host1'),
mock.call(self.context, [router2['id']], 'host2')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls)
self.assertFalse(l3_notif.routers_updated.called)
else:
self.assertEqual(
2, l3_notif.routers_updated.call_count)
@ -377,10 +432,17 @@ class L3DvrTestCase(L3DvrTestCaseBase):
def test_update_floating_ip_agent_notification(self):
self._test_update_floating_ip_agent_notification()
def test_update_floating_ip_agent_notification_with_dvr_no_external_agents(
self):
agent_mode = n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL
self._test_update_floating_ip_agent_notification(
test_agent_mode=agent_mode)
def test_update_floating_ip_agent_notification_non_dvr(self):
self._test_update_floating_ip_agent_notification(dvr=False)
def _test_delete_floating_ip_agent_notification(self, dvr=True):
def _test_delete_floating_ip_agent_notification(
self, dvr=True, test_agent_mode=constants.L3_AGENT_MODE_DVR):
with self.subnet() as ext_subnet,\
self.subnet(cidr='20.0.0.0/24') as int_subnet,\
self.port(subnet=int_subnet,
@ -390,7 +452,7 @@ class L3DvrTestCase(L3DvrTestCaseBase):
{'port': {portbindings.HOST_ID: 'host1'}})
# and create l3 agents on corresponding hosts
helpers.register_l3_agent(host='host1',
agent_mode=constants.L3_AGENT_MODE_DVR)
agent_mode=test_agent_mode)
# make net external
ext_net_id = ext_subnet['subnet']['network_id']
self._update('networks', ext_net_id,
@ -417,10 +479,27 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.l3_plugin.delete_floatingip(
self.context, floating_ip['id'])
if dvr:
l3_notif.routers_updated_on_host.assert_called_once_with(
self.context, [router['id']],
'host1')
self.assertFalse(l3_notif.routers_updated.called)
if test_agent_mode == (
n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL):
if router['ha']:
expected_calls = [
mock.call(self.context,
[router['id']], 'host0'),
mock.call(self.context,
[router['id']], 'standby')]
l3_notif.routers_updated_on_host.assert_has_calls(
expected_calls, any_order=True)
self.assertFalse(l3_notif.routers_updated.called)
else:
l3_notif.routers_updated_on_host.\
assert_called_once_with(
self.context, [router['id']], 'host0')
self.assertFalse(l3_notif.routers_updated.called)
else:
l3_notif.routers_updated_on_host.\
assert_called_once_with(
self.context, [router['id']], 'host1')
self.assertFalse(l3_notif.routers_updated.called)
else:
l3_notif.routers_updated.assert_called_once_with(
self.context, [router['id']], None)
@ -430,6 +509,12 @@ class L3DvrTestCase(L3DvrTestCaseBase):
def test_delete_floating_ip_agent_notification(self):
self._test_delete_floating_ip_agent_notification()
def test_delete_floating_ip_agent_notification_with_dvr_no_external_agents(
self):
agent_mode = n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL
self._test_delete_floating_ip_agent_notification(
test_agent_mode=agent_mode)
def test_delete_floating_ip_agent_notification_non_dvr(self):
self._test_delete_floating_ip_agent_notification(dvr=False)
@ -595,10 +680,77 @@ class L3DvrTestCase(L3DvrTestCaseBase):
self.context, {'floatingip': floating_ip})
expected_routers_updated_calls = [
mock.call(self.context, mock.ANY, HOST1),
mock.call(self.context, mock.ANY, HOST2)]
mock.call(self.context, mock.ANY, HOST2),
mock.call(self.context, mock.ANY, 'host0')]
l3_notifier.routers_updated_on_host.assert_has_calls(
expected_routers_updated_calls)
self.assertTrue(l3_notifier.routers_updated.called)
self.assertFalse(l3_notifier.routers_updated.called)
router_info = (
self.l3_plugin.list_active_sync_routers_on_active_l3_agent(
self.context, self.l3_agent['host'], [router['id']]))
floatingips = router_info[0][constants.FLOATINGIP_KEY]
self.assertTrue(floatingips[0][n_const.DVR_SNAT_BOUND])
def test_dvr_router_centralized_floating_ip(self):
HOST1 = 'host1'
helpers.register_l3_agent(
host=HOST1, agent_mode=n_const.L3_AGENT_MODE_DVR_NO_EXTERNAL)
router = self._create_router(ha=False)
private_net1 = self._make_network(self.fmt, 'net1', True)
kwargs = {'arg_list': (external_net.EXTERNAL,),
external_net.EXTERNAL: True}
ext_net = self._make_network(self.fmt, '', True, **kwargs)
self._make_subnet(
self.fmt, ext_net, '10.20.0.1', '10.20.0.0/24',
ip_version=4, enable_dhcp=True)
self.l3_plugin.schedule_router(self.context,
router['id'],
candidates=[self.l3_agent])
# Set gateway to router
self.l3_plugin._update_router_gw_info(
self.context, router['id'],
{'network_id': ext_net['network']['id']})
private_subnet1 = self._make_subnet(
self.fmt,
private_net1,
'10.1.0.1',
cidr='10.1.0.0/24',
ip_version=4,
enable_dhcp=True)
with self.port(
subnet=private_subnet1,
device_owner=DEVICE_OWNER_COMPUTE) as int_port1:
self.l3_plugin.add_router_interface(
self.context, router['id'],
{'subnet_id': private_subnet1['subnet']['id']})
router_handle = (
self.l3_plugin.list_active_sync_routers_on_active_l3_agent(
self.context, self.l3_agent['host'], [router['id']]))
self.assertEqual(self.l3_agent['host'],
router_handle[0]['gw_port_host'])
with mock.patch.object(self.l3_plugin,
'_l3_rpc_notifier') as l3_notifier:
vm_port = self.core_plugin.update_port(
self.context, int_port1['port']['id'],
{'port': {portbindings.HOST_ID: HOST1}})
self.assertEqual(
1, l3_notifier.routers_updated_on_host.call_count)
# Next we can try to associate the floatingip to the
# VM port
floating_ip = {'floating_network_id': ext_net['network']['id'],
'router_id': router['id'],
'port_id': vm_port['id'],
'tenant_id': vm_port['tenant_id']}
floating_ip = self.l3_plugin.create_floatingip(
self.context, {'floatingip': floating_ip})
expected_routers_updated_calls = [
mock.call(self.context, mock.ANY, HOST1),
mock.call(self.context, mock.ANY, 'host0')]
l3_notifier.routers_updated_on_host.assert_has_calls(
expected_routers_updated_calls)
self.assertFalse(l3_notifier.routers_updated.called)
router_info = (
self.l3_plugin.list_active_sync_routers_on_active_l3_agent(
self.context, self.l3_agent['host'], [router['id']]))
@ -733,10 +885,11 @@ class L3DvrTestCase(L3DvrTestCaseBase):
expected_calls)
expected_routers_updated_calls = [
mock.call(self.context, mock.ANY, HOST1),
mock.call(self.context, mock.ANY, HOST2)]
mock.call(self.context, mock.ANY, HOST2),
mock.call(self.context, mock.ANY, 'host0')]
l3_notifier.routers_updated_on_host.assert_has_calls(
expected_routers_updated_calls)
self.assertTrue(l3_notifier.routers_updated.called)
self.assertFalse(l3_notifier.routers_updated.called)
router_info = (
self.l3_plugin.list_active_sync_routers_on_active_l3_agent(
self.context, self.l3_agent['host'], [router['id']]))
@ -875,10 +1028,11 @@ class L3DvrTestCase(L3DvrTestCaseBase):
l3_notifier.add_arp_entry.assert_has_calls(
expected_calls)
expected_routers_updated_calls = [
mock.call(self.context, mock.ANY, HOST1)]
mock.call(self.context, mock.ANY, HOST1),
mock.call(self.context, mock.ANY, 'host0')]
l3_notifier.routers_updated_on_host.assert_has_calls(
expected_routers_updated_calls)
self.assertTrue(l3_notifier.routers_updated.called)
self.assertFalse(l3_notifier.routers_updated.called)
def test_update_vm_port_host_router_update(self):
# register l3 agents in dvr mode in addition to existing dvr_snat agent

View File

@ -0,0 +1,21 @@
---
prelude: >
A new agent_mode(``dvr_no_external``) for DVR routers has been added
to allow the server to configure Floating IPs associated with DVR at
the centralized node.
features:
- |
A new DVR agent type ``dvr_no_external`` has been introduced with this
release. This agent type allows the Floating IPs (DNAT/North-South routing)
to be centralized while the East/West routing is still distributed.
issues:
- |
There can be a mixture of ``dvr`` agents and ``dvr_no_external`` agents.
But please avoid any VM with Floating IP migration between a ``dvr`` agent
and a ``dvr_no_external`` agent. All VM ports with Floating IPs should be
migrated to same agent_mode.
This would be one of the restrictions.
upgrade:
- |
A new DVR agent mode of ``dvr_no_external`` was added. Changing between
this mode and ``dvr`` is a disruptive operation to the dataplane.