Routed Networks - peer-subnet/segment host-routes (2/2)
Ensure that host routes are maintained for each subnet within a network. Subnets associated with different segments on the same network get host_routes entries added/removed as subnets are created, deleted or updated. This change handle the host_routes for the peer subnets on the same network when a subnet is created or deleted. Also adds a shim api extension. APIImpact: Host routes are now calculated for routed networks. Closes-Bug: #1766380 Change-Id: Iafbabe6352283e7f1a535a7b147bd81fb32f0ed1
This commit is contained in:
32
neutron/extensions/_segments_peer_subnet_host_routes_lib.py
Normal file
32
neutron/extensions/_segments_peer_subnet_host_routes_lib.py
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
"""
|
||||||
|
TODO(hjensas): This module should be deleted once neutron-lib containing
|
||||||
|
Change-Id: Ibd1b565a04a6d979b6e56ca5469af644894d6b4c is released.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from neutron_lib.api.definitions import segment
|
||||||
|
|
||||||
|
|
||||||
|
ALIAS = 'segments-peer-subnet-host-routes'
|
||||||
|
IS_SHIM_EXTENSION = True
|
||||||
|
IS_STANDARD_ATTR_EXTENSION = False
|
||||||
|
NAME = 'Segments peer-subnet host routes'
|
||||||
|
DESCRIPTION = 'Add host routes to subnets on a routed network (segments)'
|
||||||
|
UPDATED_TIMESTAMP = '2018-06-12T10:00:00-00:00'
|
||||||
|
RESOURCE_ATTRIBUTE_MAP = {}
|
||||||
|
SUB_RESOURCE_ATTRIBUTE_MAP = {}
|
||||||
|
ACTION_MAP = {}
|
||||||
|
REQUIRED_EXTENSIONS = [segment.ALIAS]
|
||||||
|
OPTIONAL_EXTENSIONS = []
|
||||||
|
ACTION_STATUS = {}
|
18
neutron/extensions/segments_peer_subnet_host_routes.py
Normal file
18
neutron/extensions/segments_peer_subnet_host_routes.py
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from neutron.extensions import _segments_peer_subnet_host_routes_lib as apidef
|
||||||
|
from neutron_lib.api import extensions
|
||||||
|
|
||||||
|
|
||||||
|
class Segments_peer_subnet_host_routes(extensions.APIExtensionDescriptor):
|
||||||
|
api_definition = apidef
|
@@ -38,7 +38,6 @@ from oslo_utils import excutils
|
|||||||
|
|
||||||
from neutron._i18n import _
|
from neutron._i18n import _
|
||||||
from neutron.db import _resource_extend as resource_extend
|
from neutron.db import _resource_extend as resource_extend
|
||||||
from neutron.db import models_v2
|
|
||||||
from neutron.extensions import segment
|
from neutron.extensions import segment
|
||||||
from neutron.notifiers import batch_notifier
|
from neutron.notifiers import batch_notifier
|
||||||
from neutron.objects import network as net_obj
|
from neutron.objects import network as net_obj
|
||||||
@@ -64,7 +63,8 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
|
|||||||
supported_extension_aliases = ["segment", "ip_allocation",
|
supported_extension_aliases = ["segment", "ip_allocation",
|
||||||
l2adj_apidef.ALIAS,
|
l2adj_apidef.ALIAS,
|
||||||
"standard-attr-segment",
|
"standard-attr-segment",
|
||||||
"subnet-segmentid-writable"]
|
"subnet-segmentid-writable",
|
||||||
|
'segments-peer-subnet-host-routes']
|
||||||
|
|
||||||
__native_pagination_support = True
|
__native_pagination_support = True
|
||||||
__native_sorting_support = True
|
__native_sorting_support = True
|
||||||
@@ -437,12 +437,11 @@ class NovaSegmentNotifier(object):
|
|||||||
@registry.has_registry_receivers
|
@registry.has_registry_receivers
|
||||||
class SegmentHostRoutes(object):
|
class SegmentHostRoutes(object):
|
||||||
|
|
||||||
def _get_network(self, context, network_id):
|
def _get_subnets(self, context, network_id):
|
||||||
return context.session.query(models_v2.Network).filter(
|
return subnet_obj.Subnet.get_objects(context, network_id=network_id)
|
||||||
models_v2.Network.id == network_id).one()
|
|
||||||
|
|
||||||
def _calculate_routed_network_host_routes(self, context, ip_version,
|
def _calculate_routed_network_host_routes(self, context, ip_version,
|
||||||
network=None, subnet_id=None,
|
network_id=None, subnet_id=None,
|
||||||
segment_id=None,
|
segment_id=None,
|
||||||
host_routes=None,
|
host_routes=None,
|
||||||
gateway_ip=None,
|
gateway_ip=None,
|
||||||
@@ -456,7 +455,7 @@ class SegmentHostRoutes(object):
|
|||||||
and AFTER_DELETE.
|
and AFTER_DELETE.
|
||||||
|
|
||||||
:param ip_version: IP version (4/6).
|
:param ip_version: IP version (4/6).
|
||||||
:param network: Network.
|
:param network_id: Network ID.
|
||||||
:param subnet_id: UUID of the subnet.
|
:param subnet_id: UUID of the subnet.
|
||||||
:param segment_id: Segement ID associated with the subnet.
|
:param segment_id: Segement ID associated with the subnet.
|
||||||
:param host_routes: Current host_routes of the subnet.
|
:param host_routes: Current host_routes of the subnet.
|
||||||
@@ -479,13 +478,13 @@ class SegmentHostRoutes(object):
|
|||||||
if delete_route in host_routes:
|
if delete_route in host_routes:
|
||||||
host_routes.remove(delete_route)
|
host_routes.remove(delete_route)
|
||||||
|
|
||||||
for subnet in network.subnets:
|
for subnet in self._get_subnets(context, network_id):
|
||||||
if (subnet.id == subnet_id or subnet.segment_id == segment_id or
|
if (subnet.id == subnet_id or subnet.segment_id == segment_id or
|
||||||
subnet.ip_version != ip_version):
|
subnet.ip_version != ip_version):
|
||||||
continue
|
continue
|
||||||
subnet_ip_net = netaddr.IPNetwork(subnet.cidr)
|
subnet_ip_net = netaddr.IPNetwork(subnet.cidr)
|
||||||
if old_gateway_ip:
|
if old_gateway_ip:
|
||||||
old_route = {'destination': subnet.cidr,
|
old_route = {'destination': str(subnet.cidr),
|
||||||
'nexthop': old_gateway_ip}
|
'nexthop': old_gateway_ip}
|
||||||
if old_route in host_routes:
|
if old_route in host_routes:
|
||||||
host_routes.remove(old_route)
|
host_routes.remove(old_route)
|
||||||
@@ -514,6 +513,38 @@ class SegmentHostRoutes(object):
|
|||||||
set((route['destination'],
|
set((route['destination'],
|
||||||
route['nexthop']) for route in calc_host_routes)))
|
route['nexthop']) for route in calc_host_routes)))
|
||||||
|
|
||||||
|
def _update_routed_network_host_routes(self, context, network_id,
|
||||||
|
deleted_cidr=None):
|
||||||
|
"""Update host routes on subnets on a routed network after event
|
||||||
|
|
||||||
|
Host routes on the subnets on a routed network may need updates after
|
||||||
|
any CREATE or DELETE event.
|
||||||
|
|
||||||
|
:param network_id: Network ID
|
||||||
|
:param deleted_cidr: The cidr of a deleted subnet.
|
||||||
|
"""
|
||||||
|
for subnet in self._get_subnets(context, network_id):
|
||||||
|
host_routes = [{'destination': str(route.destination),
|
||||||
|
'nexthop': route.nexthop}
|
||||||
|
for route in subnet.host_routes]
|
||||||
|
calc_host_routes = self._calculate_routed_network_host_routes(
|
||||||
|
context=context,
|
||||||
|
ip_version=subnet.ip_version,
|
||||||
|
network_id=subnet.network_id,
|
||||||
|
subnet_id=subnet.id,
|
||||||
|
segment_id=subnet.segment_id,
|
||||||
|
host_routes=copy.deepcopy(host_routes),
|
||||||
|
gateway_ip=subnet.gateway_ip,
|
||||||
|
deleted_cidr=deleted_cidr)
|
||||||
|
if self._host_routes_need_update(host_routes, calc_host_routes):
|
||||||
|
LOG.debug(
|
||||||
|
"Updating host routes for subnet %s on routed network %s",
|
||||||
|
(subnet.id, subnet.network_id))
|
||||||
|
plugin = directory.get_plugin()
|
||||||
|
plugin.update_subnet(context, subnet.id,
|
||||||
|
{'subnet': {
|
||||||
|
'host_routes': calc_host_routes}})
|
||||||
|
|
||||||
@registry.receives(resources.SUBNET, [events.BEFORE_CREATE])
|
@registry.receives(resources.SUBNET, [events.BEFORE_CREATE])
|
||||||
def host_routes_before_create(self, resource, event, trigger, context,
|
def host_routes_before_create(self, resource, event, trigger, context,
|
||||||
subnet, **kwargs):
|
subnet, **kwargs):
|
||||||
@@ -524,11 +555,10 @@ class SegmentHostRoutes(object):
|
|||||||
else:
|
else:
|
||||||
host_routes = []
|
host_routes = []
|
||||||
if segment_id is not None and validators.is_attr_set(gateway_ip):
|
if segment_id is not None and validators.is_attr_set(gateway_ip):
|
||||||
network = self._get_network(context, subnet['network_id'])
|
|
||||||
calc_host_routes = self._calculate_routed_network_host_routes(
|
calc_host_routes = self._calculate_routed_network_host_routes(
|
||||||
context=context,
|
context=context,
|
||||||
ip_version=netaddr.IPNetwork(subnet['cidr']).version,
|
ip_version=netaddr.IPNetwork(subnet['cidr']).version,
|
||||||
network=network,
|
network_id=subnet['network_id'],
|
||||||
segment_id=subnet['segment_id'],
|
segment_id=subnet['segment_id'],
|
||||||
host_routes=copy.deepcopy(host_routes),
|
host_routes=copy.deepcopy(host_routes),
|
||||||
gateway_ip=gateway_ip)
|
gateway_ip=gateway_ip)
|
||||||
@@ -546,11 +576,10 @@ class SegmentHostRoutes(object):
|
|||||||
host_routes = subnet.get('host_routes', original_subnet['host_routes'])
|
host_routes = subnet.get('host_routes', original_subnet['host_routes'])
|
||||||
if (segment_id and (host_routes != original_subnet['host_routes'] or
|
if (segment_id and (host_routes != original_subnet['host_routes'] or
|
||||||
gateway_ip != original_subnet['gateway_ip'])):
|
gateway_ip != original_subnet['gateway_ip'])):
|
||||||
network = self._get_network(context, original_subnet['network_id'])
|
|
||||||
calc_host_routes = self._calculate_routed_network_host_routes(
|
calc_host_routes = self._calculate_routed_network_host_routes(
|
||||||
context=context,
|
context=context,
|
||||||
ip_version=netaddr.IPNetwork(original_subnet['cidr']).version,
|
ip_version=netaddr.IPNetwork(original_subnet['cidr']).version,
|
||||||
network=network,
|
network_id=original_subnet['network_id'],
|
||||||
segment_id=segment_id,
|
segment_id=segment_id,
|
||||||
host_routes=copy.deepcopy(host_routes),
|
host_routes=copy.deepcopy(host_routes),
|
||||||
gateway_ip=gateway_ip,
|
gateway_ip=gateway_ip,
|
||||||
@@ -558,3 +587,23 @@ class SegmentHostRoutes(object):
|
|||||||
gateway_ip != original_subnet['gateway_ip']) else None)
|
gateway_ip != original_subnet['gateway_ip']) else None)
|
||||||
if self._host_routes_need_update(host_routes, calc_host_routes):
|
if self._host_routes_need_update(host_routes, calc_host_routes):
|
||||||
subnet['host_routes'] = calc_host_routes
|
subnet['host_routes'] = calc_host_routes
|
||||||
|
|
||||||
|
@registry.receives(resources.SUBNET, [events.AFTER_CREATE])
|
||||||
|
def host_routes_after_create(self, resource, event, trigger, **kwargs):
|
||||||
|
context = kwargs['context']
|
||||||
|
subnet = kwargs['subnet']
|
||||||
|
# If there are other subnets on the network and subnet has segment_id
|
||||||
|
# ensure host routes for all subnets are updated.
|
||||||
|
if (len(self._get_subnets(context, subnet['network_id'])) > 1 and
|
||||||
|
subnet.get('segment_id')):
|
||||||
|
self._update_routed_network_host_routes(context,
|
||||||
|
subnet['network_id'])
|
||||||
|
|
||||||
|
@registry.receives(resources.SUBNET, [events.AFTER_DELETE])
|
||||||
|
def host_routes_after_delete(self, resource, event, trigger, context,
|
||||||
|
subnet, **kwargs):
|
||||||
|
# If this is a routed network, remove any routes to this subnet on
|
||||||
|
# this networks remaining subnets.
|
||||||
|
if subnet.get('segment_id'):
|
||||||
|
self._update_routed_network_host_routes(
|
||||||
|
context, subnet['network_id'], deleted_cidr=subnet['cidr'])
|
||||||
|
@@ -42,6 +42,7 @@ NETWORK_API_EXTENSIONS+=",router_availability_zone"
|
|||||||
NETWORK_API_EXTENSIONS+=",security-group"
|
NETWORK_API_EXTENSIONS+=",security-group"
|
||||||
NETWORK_API_EXTENSIONS+=",port-security-groups-filtering"
|
NETWORK_API_EXTENSIONS+=",port-security-groups-filtering"
|
||||||
NETWORK_API_EXTENSIONS+=",segment"
|
NETWORK_API_EXTENSIONS+=",segment"
|
||||||
|
NETWORK_API_EXTENSIONS+=",segments-peer-subnet-host-routes"
|
||||||
NETWORK_API_EXTENSIONS+=",service-type"
|
NETWORK_API_EXTENSIONS+=",service-type"
|
||||||
NETWORK_API_EXTENSIONS+=",sorting"
|
NETWORK_API_EXTENSIONS+=",sorting"
|
||||||
NETWORK_API_EXTENSIONS+=",standard-attr-description"
|
NETWORK_API_EXTENSIONS+=",standard-attr-description"
|
||||||
|
@@ -2498,8 +2498,6 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
|||||||
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
||||||
self.assertIn(sub_res['cidr'], cidrs)
|
self.assertIn(sub_res['cidr'], cidrs)
|
||||||
self.assertIn(sub_res['gateway_ip'], gateway_ips)
|
self.assertIn(sub_res['gateway_ip'], gateway_ips)
|
||||||
# TODO(hjensas): Remove the conditinal in next patch in series.
|
|
||||||
if len(sub_res['host_routes']) > 0:
|
|
||||||
self.assertIn(sub_res['host_routes'][0], host_routes)
|
self.assertIn(sub_res['host_routes'][0], host_routes)
|
||||||
|
|
||||||
def test_host_routes_two_subnets_with_same_segment_association(self):
|
def test_host_routes_two_subnets_with_same_segment_association(self):
|
||||||
@@ -2540,6 +2538,33 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
|||||||
self.assertEqual([], res_subnet0['subnet']['host_routes'])
|
self.assertEqual([], res_subnet0['subnet']['host_routes'])
|
||||||
self.assertEqual([], res_subnet1['subnet']['host_routes'])
|
self.assertEqual([], res_subnet1['subnet']['host_routes'])
|
||||||
|
|
||||||
|
def test_host_routes_create_two_subnets_then_delete_one(self):
|
||||||
|
"""Delete subnet after creating two subnets associated same segment.
|
||||||
|
|
||||||
|
Host routes with destination to the subnet that is deleted are removed
|
||||||
|
from the remaining subnets.
|
||||||
|
"""
|
||||||
|
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||||
|
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||||
|
net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips,
|
||||||
|
cidrs)
|
||||||
|
|
||||||
|
sh_req = self.new_show_request('subnets', subnet1['id'])
|
||||||
|
raw_res = sh_req.get_response(self.api)
|
||||||
|
sub_res = self.deserialize(self.fmt, raw_res)
|
||||||
|
self.assertEqual([{'destination': cidrs[0],
|
||||||
|
'nexthop': gateway_ips[1]}],
|
||||||
|
sub_res['subnet']['host_routes'])
|
||||||
|
|
||||||
|
del_req = self.new_delete_request('subnets', subnet0['id'])
|
||||||
|
del_req.get_response(self.api)
|
||||||
|
|
||||||
|
sh_req = self.new_show_request('subnets', subnet1['id'])
|
||||||
|
raw_res = sh_req.get_response(self.api)
|
||||||
|
sub_res = self.deserialize(self.fmt, raw_res)
|
||||||
|
|
||||||
|
self.assertEqual([], sub_res['subnet']['host_routes'])
|
||||||
|
|
||||||
def test_host_routes_two_subnets_then_change_gateway_ip(self):
|
def test_host_routes_two_subnets_then_change_gateway_ip(self):
|
||||||
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||||
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||||
@@ -2557,8 +2582,6 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
|||||||
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
||||||
self.assertIn(sub_res['cidr'], cidrs)
|
self.assertIn(sub_res['cidr'], cidrs)
|
||||||
self.assertIn(sub_res['gateway_ip'], gateway_ips)
|
self.assertIn(sub_res['gateway_ip'], gateway_ips)
|
||||||
# TODO(hjensas): Remove the conditinal in next patch in series.
|
|
||||||
if len(sub_res['host_routes']) > 0:
|
|
||||||
self.assertIn(sub_res['host_routes'][0], host_routes)
|
self.assertIn(sub_res['host_routes'][0], host_routes)
|
||||||
|
|
||||||
new_gateway_ip = '10.0.1.254'
|
new_gateway_ip = '10.0.1.254'
|
||||||
|
@@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- |
|
||||||
|
Adds host routes for subnets on the same network when using routed
|
||||||
|
networks. Static routes will be configured for subnets associated with
|
||||||
|
other segments on the same network. This ensures that traffic within an L3
|
||||||
|
routed network stays within the network even when the default route is on
|
||||||
|
a different interface.
|
Reference in New Issue
Block a user