Merge "Routed Networks - peer-subnet/segment host-routes (1/2)"
This commit is contained in:
commit
885c1213f9
@ -14,6 +14,8 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
from keystoneauth1 import loading as ks_loading
|
||||
import netaddr
|
||||
from neutron_lib.api.definitions import ip_allocation as ipalloc_apidef
|
||||
@ -21,6 +23,7 @@ from neutron_lib.api.definitions import l2_adjacency as l2adj_apidef
|
||||
from neutron_lib.api.definitions import network as net_def
|
||||
from neutron_lib.api.definitions import port as port_def
|
||||
from neutron_lib.api.definitions import subnet as subnet_def
|
||||
from neutron_lib.api import validators
|
||||
from neutron_lib.callbacks import events
|
||||
from neutron_lib.callbacks import registry
|
||||
from neutron_lib.callbacks import resources
|
||||
@ -35,6 +38,7 @@ 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
|
||||
@ -67,6 +71,7 @@ class Plugin(db.SegmentDbMixin, segment.SegmentPluginBase):
|
||||
|
||||
def __init__(self):
|
||||
self.nova_updater = NovaSegmentNotifier()
|
||||
self.segment_host_routes = SegmentHostRoutes()
|
||||
|
||||
@staticmethod
|
||||
@resource_extend.extends([net_def.COLLECTION_NAME])
|
||||
@ -427,3 +432,129 @@ class NovaSegmentNotifier(object):
|
||||
ip['ip_address']).version == constants.IP_VERSION_4:
|
||||
ipv4_subnet_ids.append(ip['subnet_id'])
|
||||
return ipv4_subnet_ids
|
||||
|
||||
|
||||
@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 _calculate_routed_network_host_routes(self, context, ip_version,
|
||||
network=None, subnet_id=None,
|
||||
segment_id=None,
|
||||
host_routes=None,
|
||||
gateway_ip=None,
|
||||
old_gateway_ip=None,
|
||||
deleted_cidr=None):
|
||||
"""Calculate host routes for routed network.
|
||||
|
||||
This method is used to calculate the host routes for routed networks
|
||||
both when handling the user create or update request and when making
|
||||
updates to subnets on the network in response to events: AFTER_CREATE
|
||||
and AFTER_DELETE.
|
||||
|
||||
:param ip_version: IP version (4/6).
|
||||
:param network: Network.
|
||||
: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.
|
||||
:param gateway_ip: The subnets gateway IP address.
|
||||
:param old_gateway_ip: The old gateway IP address of the subnet when it
|
||||
is changed on update.
|
||||
:param deleted_cidr: The cidr of a deleted subnet.
|
||||
:returns Host routes with routes for the other subnet's on the routed
|
||||
network appended unless a route to the destination already
|
||||
exists.
|
||||
"""
|
||||
if host_routes is None:
|
||||
host_routes = []
|
||||
dest_ip_nets = [netaddr.IPNetwork(route['destination']) for
|
||||
route in host_routes]
|
||||
|
||||
# Drop routes to the deleted cidr, when the subnet was deleted.
|
||||
if deleted_cidr:
|
||||
delete_route = {'destination': deleted_cidr, 'nexthop': gateway_ip}
|
||||
if delete_route in host_routes:
|
||||
host_routes.remove(delete_route)
|
||||
|
||||
for subnet in network.subnets:
|
||||
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,
|
||||
'nexthop': old_gateway_ip}
|
||||
if old_route in host_routes:
|
||||
host_routes.remove(old_route)
|
||||
dest_ip_nets.remove(subnet_ip_net)
|
||||
if gateway_ip:
|
||||
# Use netaddr here in case the user provided a summary route
|
||||
# (supernet route). I.e subnet.cidr = 10.0.1.0/24 and
|
||||
# the user provided a host route for 10.0.0.0/16. We don't
|
||||
# need to append a route in this case.
|
||||
if not any(subnet_ip_net in ip_net for ip_net in dest_ip_nets):
|
||||
host_routes.append({'destination': subnet.cidr,
|
||||
'nexthop': gateway_ip})
|
||||
|
||||
return host_routes
|
||||
|
||||
def _host_routes_need_update(self, host_routes, calc_host_routes):
|
||||
"""Compare host routes and calculated host routes
|
||||
|
||||
:param host_routes: Current host routes
|
||||
:param calc_host_routes: Host routes + calculated host routes for
|
||||
routed network
|
||||
:returns True if host_routes and calc_host_routes are not equal
|
||||
"""
|
||||
return ((set((route['destination'],
|
||||
route['nexthop']) for route in host_routes) !=
|
||||
set((route['destination'],
|
||||
route['nexthop']) for route in calc_host_routes)))
|
||||
|
||||
@registry.receives(resources.SUBNET, [events.BEFORE_CREATE])
|
||||
def host_routes_before_create(self, resource, event, trigger, context,
|
||||
subnet, **kwargs):
|
||||
segment_id = subnet.get('segment_id')
|
||||
gateway_ip = subnet.get('gateway_ip')
|
||||
if validators.is_attr_set(subnet.get('host_routes')):
|
||||
host_routes = subnet.get('host_routes')
|
||||
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,
|
||||
segment_id=subnet['segment_id'],
|
||||
host_routes=copy.deepcopy(host_routes),
|
||||
gateway_ip=gateway_ip)
|
||||
if (not host_routes or
|
||||
self._host_routes_need_update(host_routes,
|
||||
calc_host_routes)):
|
||||
subnet['host_routes'] = calc_host_routes
|
||||
|
||||
@registry.receives(resources.SUBNET, [events.BEFORE_UPDATE])
|
||||
def host_routes_before_update(self, resource, event, trigger, **kwargs):
|
||||
context = kwargs['context']
|
||||
subnet, original_subnet = kwargs['request'], kwargs['original_subnet']
|
||||
segment_id = subnet.get('segment_id', original_subnet['segment_id'])
|
||||
gateway_ip = subnet.get('gateway_ip', original_subnet['gateway_ip'])
|
||||
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,
|
||||
segment_id=segment_id,
|
||||
host_routes=copy.deepcopy(host_routes),
|
||||
gateway_ip=gateway_ip,
|
||||
old_gateway_ip=original_subnet['gateway_ip'] if (
|
||||
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
|
||||
|
@ -2428,3 +2428,190 @@ class PlacementAPIClientTestCase(base.DietTestCase):
|
||||
self.mock_request.side_effect = ks_exc.EndpointNotFound
|
||||
self.assertRaises(placement_exc.PlacementEndpointNotFound,
|
||||
self.client.list_aggregates, rp_uuid)
|
||||
|
||||
|
||||
class TestSegmentHostRoutes(TestSegmentML2):
|
||||
|
||||
VLAN_MIN = 200
|
||||
VLAN_MAX = 209
|
||||
|
||||
def setUp(self):
|
||||
# NOTE(mlavalle): ml2_type_vlan requires to be registered before used.
|
||||
# This piece was refactored and removed from .config, so it causes
|
||||
# a problem, when tests are executed with pdb.
|
||||
# There is no problem when tests are running without debugger.
|
||||
driver_type.register_ml2_drivers_vlan_opts()
|
||||
cfg.CONF.set_override(
|
||||
'network_vlan_ranges',
|
||||
['physnet:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX),
|
||||
'physnet0:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX),
|
||||
'physnet1:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX),
|
||||
'physnet2:%s:%s' % (self.VLAN_MIN, self.VLAN_MAX)],
|
||||
group='ml2_type_vlan')
|
||||
super(TestSegmentHostRoutes, self).setUp()
|
||||
|
||||
def _create_subnets_segments(self, gateway_ips, cidrs):
|
||||
with self.network() as network:
|
||||
net = network['network']
|
||||
segment0 = self._test_create_segment(
|
||||
network_id=net['id'],
|
||||
physical_network='physnet1',
|
||||
network_type=constants.TYPE_VLAN,
|
||||
segmentation_id=201)['segment']
|
||||
segment1 = self._test_create_segment(
|
||||
network_id=net['id'],
|
||||
physical_network='physnet2',
|
||||
network_type=constants.TYPE_VLAN,
|
||||
segmentation_id=202)['segment']
|
||||
|
||||
with self.subnet(network=network,
|
||||
segment_id=segment0['id'],
|
||||
gateway_ip=gateway_ips[0],
|
||||
cidr=cidrs[0]) as subnet0, \
|
||||
self.subnet(network=network,
|
||||
segment_id=segment1['id'],
|
||||
gateway_ip=gateway_ips[1],
|
||||
cidr=cidrs[1]) as subnet1:
|
||||
pass
|
||||
|
||||
return net, subnet0['subnet'], subnet1['subnet']
|
||||
|
||||
def test_host_routes_two_subnets_with_segments_association(self):
|
||||
"""Creates two subnets associated to different segments.
|
||||
|
||||
Since the two subnets are associated with different segments on the
|
||||
same network host routes will be created.
|
||||
"""
|
||||
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||
host_routes = [{'destination': cidrs[1], 'nexthop': gateway_ips[0]},
|
||||
{'destination': cidrs[0], 'nexthop': gateway_ips[1]}]
|
||||
net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips,
|
||||
cidrs)
|
||||
|
||||
net_req = self.new_show_request('networks', net['id'])
|
||||
raw_res = net_req.get_response(self.api)
|
||||
net_res = self.deserialize(self.fmt, raw_res)
|
||||
for subnet_id in net_res['network']['subnets']:
|
||||
sub_req = self.new_show_request('subnets', subnet_id)
|
||||
raw_res = sub_req.get_response(self.api)
|
||||
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)
|
||||
|
||||
def test_host_routes_two_subnets_with_same_segment_association(self):
|
||||
"""Creates two subnets associated to the same segment.
|
||||
|
||||
Since the two subnets are both associated with the same segment no host
|
||||
routes will be created.
|
||||
"""
|
||||
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||
with self.network() as network:
|
||||
net = network['network']
|
||||
segment = self._test_create_segment(
|
||||
network_id=net['id'],
|
||||
physical_network='physnet1',
|
||||
network_type=constants.TYPE_VLAN,
|
||||
segmentation_id=201)['segment']
|
||||
|
||||
with self.subnet(network=network,
|
||||
segment_id=segment['id'],
|
||||
gateway_ip=gateway_ips[0],
|
||||
cidr=cidrs[0]) as subnet0, \
|
||||
self.subnet(network=network,
|
||||
segment_id=segment['id'],
|
||||
gateway_ip=gateway_ips[1],
|
||||
cidr=cidrs[1]) as subnet1:
|
||||
subnet0 = subnet0['subnet']
|
||||
subnet1 = subnet1['subnet']
|
||||
|
||||
req = self.new_show_request('subnets', subnet0['id'])
|
||||
res = req.get_response(self.api)
|
||||
res_subnet0 = self.deserialize(self.fmt, res)
|
||||
|
||||
req = self.new_show_request('subnets', subnet1['id'])
|
||||
res = req.get_response(self.api)
|
||||
res_subnet1 = self.deserialize(self.fmt, res)
|
||||
|
||||
self.assertEqual([], res_subnet0['subnet']['host_routes'])
|
||||
self.assertEqual([], res_subnet1['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']
|
||||
host_routes = [{'destination': cidrs[1], 'nexthop': gateway_ips[0]},
|
||||
{'destination': cidrs[0], 'nexthop': gateway_ips[1]}]
|
||||
net, subnet0, subnet1 = self._create_subnets_segments(gateway_ips,
|
||||
cidrs)
|
||||
|
||||
net_req = self.new_show_request('networks', net['id'])
|
||||
raw_res = net_req.get_response(self.api)
|
||||
net_res = self.deserialize(self.fmt, raw_res)
|
||||
for subnet_id in net_res['network']['subnets']:
|
||||
sub_req = self.new_show_request('subnets', subnet_id)
|
||||
raw_res = sub_req.get_response(self.api)
|
||||
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)
|
||||
|
||||
new_gateway_ip = '10.0.1.254'
|
||||
data = {'subnet': {'gateway_ip': new_gateway_ip,
|
||||
'allocation_pools': [{'start': '10.0.1.1',
|
||||
'end': '10.0.1.253'}]}}
|
||||
self.new_update_request(
|
||||
'subnets', data, subnet0['id']).get_response(self.api)
|
||||
|
||||
sh_req = self.new_show_request('subnets', subnet0['id'])
|
||||
raw_res = sh_req.get_response(self.api)
|
||||
sub_res = self.deserialize(self.fmt, raw_res)
|
||||
|
||||
self.assertEqual([{'destination': cidrs[1],
|
||||
'nexthop': new_gateway_ip}],
|
||||
sub_res['subnet']['host_routes'])
|
||||
|
||||
def test_host_routes_two_subnets_summary_route_in_request(self):
|
||||
gateway_ips = ['10.0.1.1', '10.0.2.1']
|
||||
cidrs = ['10.0.1.0/24', '10.0.2.0/24']
|
||||
summary_net = '10.0.0.0/16'
|
||||
host_routes = [{'destination': summary_net, 'nexthop': gateway_ips[0]},
|
||||
{'destination': summary_net, 'nexthop': gateway_ips[1]}]
|
||||
|
||||
with self.network() as network:
|
||||
net = network['network']
|
||||
segment0 = self._test_create_segment(
|
||||
network_id=net['id'],
|
||||
physical_network='physnet1',
|
||||
network_type=constants.TYPE_VLAN,
|
||||
segmentation_id=201)['segment']
|
||||
segment1 = self._test_create_segment(
|
||||
network_id=net['id'],
|
||||
physical_network='physnet2',
|
||||
network_type=constants.TYPE_VLAN,
|
||||
segmentation_id=202)['segment']
|
||||
|
||||
self.subnet(network=network, segment_id=segment0['id'],
|
||||
gateway_ip=gateway_ips[0], cidr=cidrs[0],
|
||||
host_routes=[host_routes[0]])
|
||||
self.subnet(network=network, segment_id=segment1['id'],
|
||||
gateway_ip=gateway_ips[1],
|
||||
cidr=cidrs[1], host_routes=[host_routes[1]])
|
||||
|
||||
net_req = self.new_show_request('networks', net['id'])
|
||||
raw_res = net_req.get_response(self.api)
|
||||
net_res = self.deserialize(self.fmt, raw_res)
|
||||
|
||||
for subnet_id in net_res['network']['subnets']:
|
||||
sub_req = self.new_show_request('subnets', subnet_id)
|
||||
raw_res = sub_req.get_response(self.api)
|
||||
sub_res = self.deserialize(self.fmt, raw_res)['subnet']
|
||||
self.assertIn(sub_res['cidr'], cidrs)
|
||||
self.assertIn(sub_res['gateway_ip'], gateway_ips)
|
||||
self.assertEqual(len(sub_res['host_routes']), 1)
|
||||
self.assertIn(sub_res['host_routes'][0], host_routes)
|
||||
|
Loading…
Reference in New Issue
Block a user