DVR: Always initialize floating IP host

With a recent change to the neutron server code, the server was
processing floating IPs that were not bound to the respective
agent during fullsync operation.

Change to always initialize floating IP host info so callers
can determine if info should be sent to an agent or not.

Also changed the logic that decides when the server should
send a floating IP to an agent to be easier to understand.

Closes-bug: #1713927
Change-Id: Ic916225e0a11c3fb8cd94437ca063e0d3295a569
(cherry picked from commit 7bff99ac4a)
This commit is contained in:
Brian Haley 2017-09-01 13:52:51 -04:00 committed by Swaminathan Vasudevan
parent b854f0f1f4
commit 6051e3d672
4 changed files with 134 additions and 51 deletions

View File

@ -172,6 +172,10 @@ VALID_FLOATINGIP_STATUS = (lib_constants.FLOATINGIP_STATUS_ACTIVE,
lib_constants.FLOATINGIP_STATUS_DOWN,
lib_constants.FLOATINGIP_STATUS_ERROR)
# Floating IP host binding states
FLOATING_IP_HOST_UNBOUND = "FLOATING_IP_HOST_UNBOUND"
FLOATING_IP_HOST_NEEDS_BINDING = "FLOATING_IP_HOST_NEEDS_BINDING"
# Possible types of values (e.g. in QoS rule types)
VALUES_TYPE_CHOICES = "choices"
VALUES_TYPE_RANGE = "range"

View File

@ -629,57 +629,55 @@ class _DVRAgentInterfaceMixin(object):
return routers_dict
@staticmethod
def _get_floating_ip_host(floating_ip):
"""Function to return floating IP host binding state
By default, floating IPs are not bound to any host. Instead
of using the empty string to denote this use a constant.
"""
return floating_ip.get('host', l3_const.FLOATING_IP_HOST_UNBOUND)
def _process_floating_ips_dvr(self, context, routers_dict,
floating_ips, host, agent):
LOG.debug("FIP Agent : %s ", agent.id)
for floating_ip in floating_ips:
router = routers_dict.get(floating_ip['router_id'])
if router:
router_floatingips = router.get(const.FLOATINGIP_KEY, [])
if router['distributed']:
fip_host = floating_ip.get('host')
fip_dest_host = floating_ip.get('dest_host')
# Skip if floatingip need not be processed for the
# given agent.
if self._should_skip_floating_ip_processed_for_given_agent(
floating_ip, fip_host, fip_dest_host, agent):
if self._skip_floating_ip_for_mismatched_agent_or_host(
floating_ip, agent, host):
continue
# Also skip floatingip if the fip port have a host defined
# and if the host does not match.
if self._check_floating_ip_not_valid_for_given_host(
fip_host, fip_dest_host, host):
continue
LOG.debug("Floating IP host: %s", fip_host)
router_floatingips = router.get(const.FLOATINGIP_KEY, [])
router_floatingips.append(floating_ip)
router[const.FLOATINGIP_KEY] = router_floatingips
def _check_floating_ip_not_valid_for_given_host(
self, fip_host, fip_dest_host, host):
"""Function to check if floatingip host match for the given agent.
def _skip_floating_ip_for_mismatched_agent_or_host(self, floating_ip,
agent, host):
"""Function to check if floating IP processing can be skipped."""
fip_host = self._get_floating_ip_host(floating_ip)
Check if the given floatingip host matches with the requesting
host when floatingip dest_host is None.
If floatingip dest_host is not None it means that the floatingip
is migrating to a new compute host and the original host will not
match.
"""
host_mismatch = (
fip_host != host and fip_dest_host is None)
return (fip_host is not None and host_mismatch)
# Skip if it is unbound
if fip_host == l3_const.FLOATING_IP_HOST_UNBOUND:
return True
def _should_skip_floating_ip_processed_for_given_agent(
self, floating_ip, fip_host, fip_dest_host, agent):
"""Function to check if floatingip need to be processed or skipped.
Skip if host and dest_host is none and the agent
requesting is not dvr_snat agent, and the fip has
not already been assigned 'dvr_snat_bound' state.
"""
# Skip if the given agent is in the wrong mode - SNAT bound
# requires DVR_SNAT agent.
agent_mode = self._get_agent_mode(agent)
return (fip_host is None and (fip_dest_host is None) and
agent_mode in [const.L3_AGENT_MODE_LEGACY,
const.L3_AGENT_MODE_DVR] and
not floating_ip.get(l3_const.DVR_SNAT_BOUND))
if (agent_mode in [const.L3_AGENT_MODE_LEGACY,
const.L3_AGENT_MODE_DVR] and
floating_ip.get(l3_const.DVR_SNAT_BOUND)):
return True
# Skip if it is bound, but not to the given host
fip_dest_host = floating_ip.get('dest_host')
if (fip_host != l3_const.FLOATING_IP_HOST_NEEDS_BINDING and
fip_host != host and fip_dest_host is None):
return True
# not being skipped, log host
LOG.debug("Floating IP host: %s", fip_host)
return False
def _get_fip_agent_gw_ports(self, context, fip_agent_id):
"""Return list of floating agent gateway ports for the agent."""
@ -743,30 +741,29 @@ class _DVRAgentInterfaceMixin(object):
port_dict.update({port['id']: port})
# Add the port binding host to the floatingip dictionary
for fip in floating_ips:
# Assume no host binding required
fip['host'] = l3_const.FLOATING_IP_HOST_UNBOUND
vm_port = port_dict.get(fip['port_id'], None)
if vm_port:
# Default value if host port-binding required
fip['host'] = l3_const.FLOATING_IP_HOST_NEEDS_BINDING
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 == (
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
# For floatingip configured on ports that do not
# reside on a 'dvr_no_external' agent, add the
# fip host binding, else it will 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
fip['host'] = port_host
# Handle the case were there is no host binding
# for the private ports that are associated with
# floating ip.
if not fip['host'] or fip['host'] is None:
if fip['host'] == l3_const.FLOATING_IP_HOST_NEEDS_BINDING:
fip[l3_const.DVR_SNAT_BOUND] = True
routers_dict = self._process_routers(context, routers, agent)
self._process_floating_ips_dvr(context, routers_dict,

View File

@ -691,6 +691,87 @@ class L3DvrTestCase(L3DvrTestCaseBase):
floatingips = router_info[0][constants.FLOATINGIP_KEY]
self.assertTrue(floatingips[0][n_const.DVR_SNAT_BOUND])
def test_dvr_process_floatingips_for_dvr_on_full_sync(self):
HOST1 = 'host1'
helpers.register_l3_agent(
host=HOST1, agent_mode=constants.L3_AGENT_MODE_DVR)
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)
# Schedule the router to the dvr_snat node
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.port(
subnet=private_subnet1) as int_port2:
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:
self.core_plugin.update_port(
self.context, int_port1['port']['id'],
{'port': {portbindings.HOST_ID: HOST1}})
# Now let us assign the floatingip to the bound port
# and unbound port.
fip1 = {'floating_network_id': ext_net['network']['id'],
'router_id': router['id'],
'port_id': int_port1['port']['id'],
'tenant_id': int_port1['port']['tenant_id']}
self.l3_plugin.create_floatingip(
self.context, {'floatingip': fip1})
expected_routers_updated_calls = [
mock.call(self.context, mock.ANY, HOST1)]
l3_notifier.routers_updated_on_host.assert_has_calls(
expected_routers_updated_calls)
self.assertFalse(l3_notifier.routers_updated.called)
fip2 = {'floating_network_id': ext_net['network']['id'],
'router_id': router['id'],
'port_id': int_port2['port']['id'],
'tenant_id': int_port2['port']['tenant_id']}
self.l3_plugin.create_floatingip(
self.context, {'floatingip': fip2})
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.assertEqual(1, len(floatingips))
self.assertTrue(floatingips[0][n_const.DVR_SNAT_BOUND])
self.assertEqual(n_const.FLOATING_IP_HOST_NEEDS_BINDING,
floatingips[0]['host'])
router1_info = (
self.l3_plugin.list_active_sync_routers_on_active_l3_agent(
self.context, HOST1, [router['id']]))
floatingips = router1_info[0][constants.FLOATINGIP_KEY]
self.assertEqual(1, len(floatingips))
self.assertEqual(HOST1, floatingips[0]['host'])
self.assertIsNone(floatingips[0]['dest_host'])
def test_dvr_router_centralized_floating_ip(self):
HOST1 = 'host1'
helpers.register_l3_agent(

View File

@ -362,9 +362,10 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
'id': _uuid(),
'port_id': _uuid(),
'router_id': 'foo_router_id',
'host': hostid
}
if not hostid:
if hostid is not None:
floatingip['host'] = hostid
else:
hostid = 'not_my_host_id'
routers = {
'foo_router_id': router
@ -388,7 +389,7 @@ class L3DvrTestCase(test_db_base_plugin_v2.NeutronDbPluginV2TestCase):
hostid, agent)
return (router, floatingip)
def test_floatingip_on_port_not_host(self):
def test_floatingip_on_port_no_host_key(self):
router, fip = self._floatingip_on_port_test_setup(None)
self.assertNotIn(const.FLOATINGIP_KEY, router)