Add address scope to floating IPs in RPC response to L3 agent

The L3 agent needs to know the address scope of the fixed ip of each
floating ip because floating ips are a way to cross scope boundaries.
Without the scope information, there could be ambiguity and no way to
know which scope to send it to.

[1] https://review.openstack.org/#/c/189741/

Change-Id: Id9f8c12954a6efbf4d9b99c011652eefbe5f5145
Partially-Implements: blueprint address-scopes
This commit is contained in:
Carl Baldwin 2015-12-23 10:16:15 -07:00
parent dbc541be54
commit 02439b6730
5 changed files with 101 additions and 5 deletions

View File

@ -100,8 +100,9 @@ Routing
The reference implementation honors address scopes. Within an address scope,
addresses route freely (barring any FW rules or other external restrictions).
Between scopes, routed is prevented unless address translation is used. Future
patches will expand on this.
Between scopes, routed is prevented unless address translation is used. For
now, floating IPs are the only place where traffic crosses scope boundaries.
The 1-1 NAT allows this to happen.
.. TODO (Carl) Implement NAT for floating ips crossing scopes
.. TODO (Carl) Implement SNAT for crossing scopes
@ -135,6 +136,23 @@ Here is an example of how the json will look in the context of a router port::
"6": null
},
To implement floating IPs crossing scope boundaries, the L3 agent needs to know
the target scope of the floating ip. The fixed address is not enough to
disambiguate because, theoritically, there could be overlapping addresses from
different scopes. The scope is computed [#]_ from the floating ip fixed port
and attached to the floating ip dict under the 'fixed_ip_address_scope'
attribute. Here's what the json looks like (trimmed)::
{
...
"floating_ip_address": "172.24.4.4",
"fixed_ip_address": "172.16.0.3",
"fixed_ip_address_scope": "d010a0ea-660e-4df4-86ca-ae2ed96da5c1",
...
}
.. [#] neutron/db/l3_db.py (_get_sync_floating_ips)
Model
~~~~~

View File

@ -82,6 +82,7 @@ class L3PluginApi(object):
1.6 - Added process_prefix_update
1.7 - DVR support: new L3 plugin methods added.
- delete_agent_gateway_port
1.8 - Added address scope information
"""
def __init__(self, topic, host):

View File

@ -45,7 +45,8 @@ class L3RpcCallback(object):
# 1.5 Added update_ha_routers_states
# 1.6 Added process_prefix_update to support IPv6 Prefix Delegation
# 1.7 Added method delete_agent_gateway_port for DVR Routers
target = oslo_messaging.Target(version='1.7')
# 1.8 Added address scope information
target = oslo_messaging.Target(version='1.8')
@property
def plugin(self):

View File

@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import itertools
import netaddr
from oslo_log import log as logging
from oslo_utils import uuidutils
@ -1180,11 +1181,52 @@ class L3_NAT_dbonly_mixin(l3.RouterPluginBase):
if r.get('gw_port'))
return self._build_routers_list(context, router_dicts, gw_ports)
@staticmethod
def _unique_floatingip_iterator(query):
"""Iterates over only one row per floating ip. Ignores others."""
# Group rows by fip id. They must be sorted by same.
q = query.order_by(FloatingIP.id)
keyfunc = lambda row: row[0]['id']
group_iterator = itertools.groupby(q, keyfunc)
# Just hit the first row of each group
for key, value in group_iterator:
yield six.next(value)
def _make_floatingip_dict_with_scope(self, floatingip_db, scope_id):
d = self._make_floatingip_dict(floatingip_db)
d['fixed_ip_address_scope'] = scope_id
return d
def _get_sync_floating_ips(self, context, router_ids):
"""Query floating_ips that relate to list of router_ids."""
"""Query floating_ips that relate to list of router_ids with scope.
This is different than the regular get_floatingips in that it finds the
address scope of the fixed IP. The router needs to know this to
distinguish it from other scopes.
There are a few redirections to go through to discover the address
scope from the floating ip.
"""
if not router_ids:
return []
return self.get_floatingips(context, {'router_id': router_ids})
query = context.session.query(FloatingIP,
models_v2.SubnetPool.address_scope_id)
query = query.join(models_v2.Port,
FloatingIP.fixed_port_id == models_v2.Port.id)
# Outer join of Subnet can cause each ip to have more than one row.
query = query.outerjoin(models_v2.Subnet,
models_v2.Subnet.network_id == models_v2.Port.network_id)
query = query.filter(models_v2.Subnet.ip_version == 4)
query = query.outerjoin(models_v2.SubnetPool,
models_v2.Subnet.subnetpool_id == models_v2.SubnetPool.id)
# Filter out on router_ids
query = query.filter(FloatingIP.router_id.in_(router_ids))
return [self._make_floatingip_dict_with_scope(*row)
for row in self._unique_floatingip_iterator(query)]
def _get_sync_interfaces(self, context, router_ids, device_owners=None):
"""Query router interfaces that relate to list of router_ids."""

View File

@ -96,3 +96,37 @@ class TestL3_NAT_dbonly_mixin(base.BaseTestCase):
'network_id': 'net_id',
'subnets': [{k: subnet[k] for k in keys}],
'address_scopes': address_scopes}], ports)
def test__get_sync_floating_ips_no_query(self):
"""Basic test that no query is performed if no router ids are passed"""
db = l3_db.L3_NAT_dbonly_mixin()
context = mock.Mock()
db._get_sync_floating_ips(context, [])
self.assertFalse(context.session.query.called)
@mock.patch.object(l3_db.L3_NAT_dbonly_mixin, '_make_floatingip_dict')
def test__make_floatingip_dict_with_scope(self, make_fip_dict):
db = l3_db.L3_NAT_dbonly_mixin()
make_fip_dict.return_value = {'id': mock.sentinel.fip_ip}
result = db._make_floatingip_dict_with_scope(
mock.sentinel.floating_ip_db, mock.sentinel.address_scope_id)
self.assertEqual({
'fixed_ip_address_scope': mock.sentinel.address_scope_id,
'id': mock.sentinel.fip_ip}, result)
def test__unique_floatingip_iterator(self):
query = mock.MagicMock()
query.order_by().__iter__.return_value = [
({'id': 'id1'}, 'scope1'),
({'id': 'id1'}, 'scope1'),
({'id': 'id2'}, 'scope2'),
({'id': 'id2'}, 'scope2'),
({'id': 'id2'}, 'scope2'),
({'id': 'id3'}, 'scope3')]
query.reset_mock()
result = list(
l3_db.L3_NAT_dbonly_mixin._unique_floatingip_iterator(query))
query.order_by.assert_called_once_with(l3_db.FloatingIP.id)
self.assertEqual([({'id': 'id1'}, 'scope1'),
({'id': 'id2'}, 'scope2'),
({'id': 'id3'}, 'scope3')], result)