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
This commit is contained in:
parent
55a7ab79ff
commit
9515c771e7
@ -572,8 +572,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
|
||||
|
@ -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):
|
||||
|
@ -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'
|
||||
|
@ -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.")),
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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']))
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
@ -1001,6 +1007,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.
|
||||
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user