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:
parent
885c1213f9
commit
8361b8b5ae
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.db import _resource_extend as resource_extend
|
||||
from neutron.db import models_v2
|
||||
from neutron.extensions import segment
|
||||
from neutron.notifiers import batch_notifier
|
||||
from neutron.objects import network as net_obj
|
||||
@ -64,7 +63,8 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
|
||||
supported_extension_aliases = ["segment", "ip_allocation",
|
||||
l2adj_apidef.ALIAS,
|
||||
"standard-attr-segment",
|
||||
"subnet-segmentid-writable"]
|
||||
"subnet-segmentid-writable",
|
||||
'segments-peer-subnet-host-routes']
|
||||
|
||||
__native_pagination_support = True
|
||||
__native_sorting_support = True
|
||||
@ -437,12 +437,11 @@ class NovaSegmentNotifier(object):
|
||||
@registry.has_registry_receivers
|
||||
class SegmentHostRoutes(object):
|
||||
|
||||
def _get_network(self, context, network_id):
|
||||
return context.session.query(models_v2.Network).filter(
|
||||
models_v2.Network.id == network_id).one()
|
||||
def _get_subnets(self, context, network_id):
|
||||
return subnet_obj.Subnet.get_objects(context, network_id=network_id)
|
||||
|
||||
def _calculate_routed_network_host_routes(self, context, ip_version,
|
||||
network=None, subnet_id=None,
|
||||
network_id=None, subnet_id=None,
|
||||
segment_id=None,
|
||||
host_routes=None,
|
||||
gateway_ip=None,
|
||||
@ -456,7 +455,7 @@ class SegmentHostRoutes(object):
|
||||
and AFTER_DELETE.
|
||||
|
||||
:param ip_version: IP version (4/6).
|
||||
:param network: Network.
|
||||
:param network_id: Network ID.
|
||||
:param subnet_id: UUID of the subnet.
|
||||
:param segment_id: Segement ID associated with the subnet.
|
||||
:param host_routes: Current host_routes of the subnet.
|
||||
@ -479,13 +478,13 @@ class SegmentHostRoutes(object):
|
||||
if delete_route in host_routes:
|
||||
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
|
||||
subnet.ip_version != ip_version):
|
||||
continue
|
||||
subnet_ip_net = netaddr.IPNetwork(subnet.cidr)
|
||||
if old_gateway_ip:
|
||||
old_route = {'destination': subnet.cidr,
|
||||
old_route = {'destination': str(subnet.cidr),
|
||||
'nexthop': old_gateway_ip}
|
||||
if old_route in host_routes:
|
||||
host_routes.remove(old_route)
|
||||
@ -514,6 +513,38 @@ class SegmentHostRoutes(object):
|
||||
set((route['destination'],
|
||||
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])
|
||||
def host_routes_before_create(self, resource, event, trigger, context,
|
||||
subnet, **kwargs):
|
||||
@ -524,11 +555,10 @@ class SegmentHostRoutes(object):
|
||||
else:
|
||||
host_routes = []
|
||||
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(
|
||||
context=context,
|
||||
ip_version=netaddr.IPNetwork(subnet['cidr']).version,
|
||||
network=network,
|
||||
network_id=subnet['network_id'],
|
||||
segment_id=subnet['segment_id'],
|
||||
host_routes=copy.deepcopy(host_routes),
|
||||
gateway_ip=gateway_ip)
|
||||
@ -546,11 +576,10 @@ class SegmentHostRoutes(object):
|
||||
host_routes = subnet.get('host_routes', original_subnet['host_routes'])
|
||||
if (segment_id and (host_routes != original_subnet['host_routes'] or
|
||||
gateway_ip != original_subnet['gateway_ip'])):
|
||||
network = self._get_network(context, original_subnet['network_id'])
|
||||
calc_host_routes = self._calculate_routed_network_host_routes(
|
||||
context=context,
|
||||
ip_version=netaddr.IPNetwork(original_subnet['cidr']).version,
|
||||
network=network,
|
||||
network_id=original_subnet['network_id'],
|
||||
segment_id=segment_id,
|
||||
host_routes=copy.deepcopy(host_routes),
|
||||
gateway_ip=gateway_ip,
|
||||
@ -558,3 +587,23 @@ class SegmentHostRoutes(object):
|
||||
gateway_ip != original_subnet['gateway_ip']) else None)
|
||||
if self._host_routes_need_update(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+=",port-security-groups-filtering"
|
||||
NETWORK_API_EXTENSIONS+=",segment"
|
||||
NETWORK_API_EXTENSIONS+=",segments-peer-subnet-host-routes"
|
||||
NETWORK_API_EXTENSIONS+=",service-type"
|
||||
NETWORK_API_EXTENSIONS+=",sorting"
|
||||
NETWORK_API_EXTENSIONS+=",standard-attr-description"
|
||||
|
@ -2498,9 +2498,7 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
||||
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
||||
self.assertIn(sub_res['cidr'], cidrs)
|
||||
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):
|
||||
"""Creates two subnets associated to the same segment.
|
||||
@ -2540,6 +2538,33 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
||||
self.assertEqual([], res_subnet0['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):
|
||||
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||
@ -2557,9 +2582,7 @@ class TestSegmentHostRoutes(TestSegmentML2):
|
||||
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
||||
self.assertIn(sub_res['cidr'], cidrs)
|
||||
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'
|
||||
data = {'subnet': {'gateway_ip': new_gateway_ip,
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user