Merge "Filter subnets on fixed_ips segment" into stable/train
This commit is contained in:
commit
7234f711d2
|
@ -642,14 +642,15 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
|
|||
return fixed_ip_list
|
||||
|
||||
def _ipam_get_subnets(self, context, network_id, host, service_type=None,
|
||||
fixed_configured=False):
|
||||
fixed_configured=False, fixed_ips=None):
|
||||
"""Return eligible subnets
|
||||
|
||||
If no eligible subnets are found, determine why and potentially raise
|
||||
an appropriate error.
|
||||
"""
|
||||
subnets = subnet_obj.Subnet.find_candidate_subnets(
|
||||
context, network_id, host, service_type, fixed_configured)
|
||||
context, network_id, host, service_type, fixed_configured,
|
||||
fixed_ips)
|
||||
if subnets:
|
||||
subnet_dicts = [self._make_subnet_dict(subnet, context=context)
|
||||
for subnet in subnets]
|
||||
|
|
|
@ -230,11 +230,13 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
"""
|
||||
p = port['port']
|
||||
fixed_configured = p['fixed_ips'] is not constants.ATTR_NOT_SPECIFIED
|
||||
fixed_ips = p['fixed_ips'] if fixed_configured else []
|
||||
subnets = self._ipam_get_subnets(context,
|
||||
network_id=p['network_id'],
|
||||
host=p.get(portbindings.HOST_ID),
|
||||
service_type=p.get('device_owner'),
|
||||
fixed_configured=fixed_configured)
|
||||
fixed_configured=fixed_configured,
|
||||
fixed_ips=fixed_ips)
|
||||
|
||||
v4, v6_stateful, v6_stateless = self._classify_subnets(
|
||||
context, subnets)
|
||||
|
@ -345,7 +347,8 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
try:
|
||||
subnets = self._ipam_get_subnets(
|
||||
context, network_id=port['network_id'], host=host,
|
||||
service_type=port.get('device_owner'), fixed_configured=True)
|
||||
service_type=port.get('device_owner'), fixed_configured=True,
|
||||
fixed_ips=changes.add + changes.original)
|
||||
except ipam_exc.DeferIpam:
|
||||
subnets = []
|
||||
|
||||
|
|
|
@ -298,7 +298,7 @@ class Subnet(base.NeutronDbObject):
|
|||
|
||||
@classmethod
|
||||
def find_candidate_subnets(cls, context, network_id, host, service_type,
|
||||
fixed_configured):
|
||||
fixed_configured, fixed_ips):
|
||||
"""Find canditate subnets for the network, host, and service_type"""
|
||||
query = cls.query_subnets_on_network(context, network_id)
|
||||
query = SubnetServiceType.query_filter_service_subnets(
|
||||
|
@ -311,7 +311,8 @@ class Subnet(base.NeutronDbObject):
|
|||
# the network are candidates. Host/Segment will be validated
|
||||
# on port update with binding:host_id set. Allocation _cannot_
|
||||
# be deferred as requested fixed_ips would then be lost.
|
||||
return query.all()
|
||||
return cls._query_filter_by_fixed_ips_segment(
|
||||
query, fixed_ips).all()
|
||||
# If the host isn't known, we can't allocate on a routed network.
|
||||
# So, exclude any subnets attached to segments.
|
||||
return cls._query_exclude_subnets_on_segments(query).all()
|
||||
|
@ -332,6 +333,47 @@ class Subnet(base.NeutronDbObject):
|
|||
|
||||
return [subnet for subnet, _mapping in results]
|
||||
|
||||
@classmethod
|
||||
def _query_filter_by_fixed_ips_segment(cls, query, fixed_ips):
|
||||
"""Excludes subnets not on the same segment as fixed_ips
|
||||
|
||||
:raises: FixedIpsSubnetsNotOnSameSegment
|
||||
"""
|
||||
segment_ids = []
|
||||
for fixed_ip in fixed_ips:
|
||||
subnet = None
|
||||
if 'subnet_id' in fixed_ip:
|
||||
try:
|
||||
subnet = query.filter(
|
||||
cls.db_model.id == fixed_ip['subnet_id']).all()[0]
|
||||
except IndexError:
|
||||
# NOTE(hjensas): The subnet is invalid for the network,
|
||||
# return all subnets. This will be detected in following
|
||||
# IPAM code and some exception will be raised.
|
||||
return query
|
||||
elif 'ip_address' in fixed_ip:
|
||||
ip = netaddr.IPNetwork(fixed_ip['ip_address'])
|
||||
|
||||
for s in query.all():
|
||||
if ip in netaddr.IPNetwork(s.cidr):
|
||||
subnet = s
|
||||
break
|
||||
if not subnet:
|
||||
# NOTE(hjensas): The ip address is invalid, return all
|
||||
# subnets. This will be detected in following IPAM code
|
||||
# and some exception will be raised.
|
||||
return query
|
||||
|
||||
if subnet and subnet.segment_id not in segment_ids:
|
||||
segment_ids.append(subnet.segment_id)
|
||||
|
||||
if 1 < len(segment_ids):
|
||||
raise segment_exc.FixedIpsSubnetsNotOnSameSegment()
|
||||
|
||||
segment_id = False if not segment_ids else segment_ids[0]
|
||||
|
||||
return query.filter(cls.db_model.segment_id == segment_id)
|
||||
|
||||
@classmethod
|
||||
def _query_filter_by_segment_host_mapping(cls, query, host):
|
||||
# TODO(tuanvu): find OVO-like solution for handling "join queries" and
|
||||
|
|
|
@ -69,3 +69,7 @@ class HostNotCompatibleWithFixedIps(exceptions.Conflict):
|
|||
|
||||
class SegmentInUse(exceptions.InUse):
|
||||
message = _("Segment '%(segment_id)s' cannot be deleted: %(reason)s.")
|
||||
|
||||
|
||||
class FixedIpsSubnetsNotOnSameSegment(exceptions.BadRequest):
|
||||
message = _("Cannot allocate addresses from different segments.")
|
||||
|
|
|
@ -689,7 +689,8 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
mocks['driver'].get_address_request_factory.assert_called_once_with()
|
||||
mocks['ipam']._ipam_get_subnets.assert_called_once_with(
|
||||
context, network_id=port_dict['network_id'], fixed_configured=True,
|
||||
host=None, service_type=port_dict['device_owner'])
|
||||
fixed_ips=[ip_dict], host=None,
|
||||
service_type=port_dict['device_owner'])
|
||||
# Validate port_dict is passed into address_factory
|
||||
address_factory.get_request.assert_called_once_with(context,
|
||||
port_dict,
|
||||
|
|
|
@ -1007,6 +1007,19 @@ class SegmentAwareIpamTestCase(SegmentTestCase):
|
|||
is_adjacent=False)
|
||||
return subnet
|
||||
|
||||
def _create_test_slaac_subnet_with_segment(
|
||||
self, network, segment, cidr='2001:db8:0:0::/64'):
|
||||
with self.subnet(network=network,
|
||||
segment_id=segment['segment']['id'],
|
||||
ip_version=constants.IP_VERSION_6,
|
||||
ipv6_ra_mode=constants.IPV6_SLAAC,
|
||||
ipv6_address_mode=constants.IPV6_SLAAC,
|
||||
cidr=cidr,
|
||||
allocation_pools=None) as subnet:
|
||||
self._validate_l2_adjacency(network['network']['id'],
|
||||
is_adjacent=False)
|
||||
return subnet
|
||||
|
||||
def _validate_l2_adjacency(self, network_id, is_adjacent):
|
||||
request = self.new_show_request('networks', network_id)
|
||||
response = self.deserialize(self.fmt, request.get_response(self.api))
|
||||
|
@ -1029,6 +1042,27 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase):
|
|||
subnets.append(subnet)
|
||||
return network, segments, subnets
|
||||
|
||||
def _create_net_two_segments_four_slaac_subnets(self):
|
||||
with self.network() as network:
|
||||
segment_a = self._test_create_segment(
|
||||
network_id=network['network']['id'],
|
||||
physical_network='physnet_a',
|
||||
network_type=constants.TYPE_FLAT)
|
||||
segment_b = self._test_create_segment(
|
||||
network_id=network['network']['id'],
|
||||
physical_network='physnet_b',
|
||||
network_type=constants.TYPE_FLAT)
|
||||
subnet_a0 = self._create_test_slaac_subnet_with_segment(
|
||||
network, segment_a, '2001:db8:a:0::/64')
|
||||
subnet_a1 = self._create_test_slaac_subnet_with_segment(
|
||||
network, segment_a, '2001:db8:a:1::/64')
|
||||
subnet_b0 = self._create_test_slaac_subnet_with_segment(
|
||||
network, segment_b, '2001:db8:b:0::/64')
|
||||
subnet_b1 = self._create_test_slaac_subnet_with_segment(
|
||||
network, segment_b, '2001:db8:b:1::/64')
|
||||
return (network, segment_a, segment_b, subnet_a0, subnet_a1,
|
||||
subnet_b0, subnet_b1)
|
||||
|
||||
def test_port_create_with_segment_subnets(self):
|
||||
"""No binding information is provided, defer IP allocation"""
|
||||
network, segment, subnet = self._create_test_segment_with_subnet()
|
||||
|
@ -1628,6 +1662,63 @@ class TestSegmentAwareIpam(SegmentAwareIpamTestCase):
|
|||
self.assertEqual(webob.exc.HTTPOk.code, response.status_int)
|
||||
self._assert_one_ip_in_subnet(response, subnet['subnet']['cidr'])
|
||||
|
||||
def test_slaac_segment_aware_no_binding_info(self):
|
||||
(network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0,
|
||||
subnet_b1) = self._create_net_two_segments_four_slaac_subnets()
|
||||
|
||||
# Create a port with no IP address (since there is no subnet)
|
||||
port_deferred = self._create_deferred_ip_port(network)
|
||||
self._validate_deferred_ip_allocation(port_deferred['port']['id'])
|
||||
|
||||
def test_slaac_segment_aware_immediate_fixed_ips_no_binding_info_(self):
|
||||
(network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0,
|
||||
subnet_b1) = self._create_net_two_segments_four_slaac_subnets()
|
||||
|
||||
# Create two ports, port_a with subnet_a0 in fixed_ips and port_b
|
||||
# with subnet_b0 in fixed_ips
|
||||
port_a = self._create_port_and_show(
|
||||
network, fixed_ips=[{'subnet_id': subnet_a0['subnet']['id']}])
|
||||
port_b = self._create_port_and_show(
|
||||
network, fixed_ips=[{'subnet_id': subnet_b0['subnet']['id']}])
|
||||
self._validate_immediate_ip_allocation(port_a['port']['id'])
|
||||
self._validate_immediate_ip_allocation(port_b['port']['id'])
|
||||
self.assertEqual(2, len(port_a['port']['fixed_ips']))
|
||||
self.assertEqual(2, len(port_b['port']['fixed_ips']))
|
||||
port_a_snet_ids = [f['subnet_id'] for f in port_a['port']['fixed_ips']]
|
||||
port_b_snet_ids = [f['subnet_id'] for f in port_b['port']['fixed_ips']]
|
||||
self.assertIn(subnet_a0['subnet']['id'], port_a_snet_ids)
|
||||
self.assertIn(subnet_a1['subnet']['id'], port_a_snet_ids)
|
||||
self.assertIn(subnet_b0['subnet']['id'], port_b_snet_ids)
|
||||
self.assertIn(subnet_b1['subnet']['id'], port_b_snet_ids)
|
||||
self.assertNotIn(subnet_a0['subnet']['id'], port_b_snet_ids)
|
||||
self.assertNotIn(subnet_a1['subnet']['id'], port_b_snet_ids)
|
||||
self.assertNotIn(subnet_b0['subnet']['id'], port_a_snet_ids)
|
||||
self.assertNotIn(subnet_b1['subnet']['id'], port_a_snet_ids)
|
||||
|
||||
def test_slaac_segment_aware_immediate_with_binding_info(self):
|
||||
(network, segment_a, segment_b, subnet_a0, subnet_a1, subnet_b0,
|
||||
subnet_b1) = self._create_net_two_segments_four_slaac_subnets()
|
||||
|
||||
self._setup_host_mappings([(segment_a['segment']['id'], 'fakehost_a')])
|
||||
|
||||
# Create a port with host ID, validate immediate allocation on subnets
|
||||
# with correct segment_id.
|
||||
response = self._create_port(self.fmt,
|
||||
net_id=network['network']['id'],
|
||||
tenant_id=network['network']['tenant_id'],
|
||||
arg_list=(portbindings.HOST_ID,),
|
||||
**{portbindings.HOST_ID: 'fakehost_a'})
|
||||
res = self.deserialize(self.fmt, response)
|
||||
self._validate_immediate_ip_allocation(res['port']['id'])
|
||||
# Since host mapped to segment_a, IP's must come from subnets:
|
||||
# subnet_a0 and subnet_a1
|
||||
self.assertEqual(2, len(res['port']['fixed_ips']))
|
||||
res_subnet_ids = [f['subnet_id'] for f in res['port']['fixed_ips']]
|
||||
self.assertIn(subnet_a0['subnet']['id'], res_subnet_ids)
|
||||
self.assertIn(subnet_a1['subnet']['id'], res_subnet_ids)
|
||||
self.assertNotIn(subnet_b0['subnet']['id'], res_subnet_ids)
|
||||
self.assertNotIn(subnet_b1['subnet']['id'], res_subnet_ids)
|
||||
|
||||
|
||||
class TestSegmentAwareIpamML2(TestSegmentAwareIpam):
|
||||
|
||||
|
|
Loading…
Reference in New Issue