Merge "[OVN] Allow IP allocation with different segments for OVN service ports" into stable/train
This commit is contained in:
commit
cc7a29c4b7
|
@ -27,7 +27,10 @@ A routed provider network enables a single provider network to represent
|
|||
multiple layer-2 networks (broadcast domains) or segments and enables the
|
||||
operator to present one network to users. However, the particular IP
|
||||
addresses available to an instance depend on the segment of the network
|
||||
available on the particular compute node.
|
||||
available on the particular compute node. Neutron port could be associated
|
||||
with only one network segment, but there is an exception for OVN distributed
|
||||
services like OVN Metadata.
|
||||
|
||||
|
||||
Similar to conventional networking, layer-2 (switching) handles transit of
|
||||
traffic between ports on the same segment and layer-3 (routing) handles
|
||||
|
|
|
@ -118,6 +118,9 @@ Routed provider networks offer performance at scale that is difficult to
|
|||
achieve with a plain provider network at the expense of guaranteed layer-2
|
||||
connectivity.
|
||||
|
||||
Neutron port could be associated with only one network segment,
|
||||
but there is an exception for OVN distributed services like OVN Metadata.
|
||||
|
||||
See :ref:`config-routed-provider-networks` for more information.
|
||||
|
||||
.. _intro-os-networking-selfservice:
|
||||
|
|
|
@ -77,6 +77,11 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
|
|||
raise exc.InvalidAllocationPool(pool=ip_pool)
|
||||
return ip_range_pools
|
||||
|
||||
@staticmethod
|
||||
def _is_distributed_service(port):
|
||||
return (port.get('device_owner') == const.DEVICE_OWNER_DHCP and
|
||||
port.get('device_id').startswith('ovn'))
|
||||
|
||||
def delete_subnet(self, context, subnet_id):
|
||||
pass
|
||||
|
||||
|
@ -642,7 +647,8 @@ 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_ips=None):
|
||||
fixed_configured=False, fixed_ips=None,
|
||||
distributed_service=False):
|
||||
"""Return eligible subnets
|
||||
|
||||
If no eligible subnets are found, determine why and potentially raise
|
||||
|
@ -650,7 +656,7 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
|
|||
"""
|
||||
subnets = subnet_obj.Subnet.find_candidate_subnets(
|
||||
context, network_id, host, service_type, fixed_configured,
|
||||
fixed_ips)
|
||||
fixed_ips, distributed_service=distributed_service)
|
||||
if subnets:
|
||||
subnet_dicts = [self._make_subnet_dict(subnet, context=context)
|
||||
for subnet in subnets]
|
||||
|
@ -712,7 +718,8 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
|
|||
if old_ips and new_host_requested and not fixed_ips_requested:
|
||||
valid_subnets = self._ipam_get_subnets(
|
||||
context, old_port['network_id'], host,
|
||||
service_type=old_port.get('device_owner'))
|
||||
service_type=old_port.get('device_owner'),
|
||||
distributed_service=self._is_distributed_service(old_port))
|
||||
valid_subnet_ids = {s['id'] for s in valid_subnets}
|
||||
for fixed_ip in old_ips:
|
||||
if fixed_ip['subnet_id'] not in valid_subnet_ids:
|
||||
|
|
|
@ -231,12 +231,14 @@ 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_ips=fixed_ips)
|
||||
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_ips=fixed_ips,
|
||||
distributed_service=self._is_distributed_service(p))
|
||||
|
||||
v4, v6_stateful, v6_stateless = self._classify_subnets(
|
||||
context, subnets)
|
||||
|
@ -348,7 +350,8 @@ class IpamPluggableBackend(ipam_backend_mixin.IpamBackendMixin):
|
|||
subnets = self._ipam_get_subnets(
|
||||
context, network_id=port['network_id'], host=host,
|
||||
service_type=port.get('device_owner'), fixed_configured=True,
|
||||
fixed_ips=changes.add + changes.original)
|
||||
fixed_ips=changes.add + changes.original,
|
||||
distributed_service=self._is_distributed_service(port))
|
||||
except ipam_exc.DeferIpam:
|
||||
subnets = []
|
||||
|
||||
|
|
|
@ -298,7 +298,8 @@ class Subnet(base.NeutronDbObject):
|
|||
|
||||
@classmethod
|
||||
def find_candidate_subnets(cls, context, network_id, host, service_type,
|
||||
fixed_configured, fixed_ips):
|
||||
fixed_configured, fixed_ips,
|
||||
distributed_service=False):
|
||||
"""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(
|
||||
|
@ -312,7 +313,8 @@ class Subnet(base.NeutronDbObject):
|
|||
# on port update with binding:host_id set. Allocation _cannot_
|
||||
# be deferred as requested fixed_ips would then be lost.
|
||||
return cls._query_filter_by_fixed_ips_segment(
|
||||
query, fixed_ips).all()
|
||||
query, fixed_ips,
|
||||
allow_multiple_segments=distributed_service).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()
|
||||
|
@ -334,7 +336,8 @@ class Subnet(base.NeutronDbObject):
|
|||
return [subnet for subnet, _mapping in results]
|
||||
|
||||
@classmethod
|
||||
def _query_filter_by_fixed_ips_segment(cls, query, fixed_ips):
|
||||
def _query_filter_by_fixed_ips_segment(cls, query, fixed_ips,
|
||||
allow_multiple_segments=False):
|
||||
"""Excludes subnets not on the same segment as fixed_ips
|
||||
|
||||
:raises: FixedIpsSubnetsNotOnSameSegment
|
||||
|
@ -367,9 +370,12 @@ class Subnet(base.NeutronDbObject):
|
|||
if subnet and subnet.segment_id not in segment_ids:
|
||||
segment_ids.append(subnet.segment_id)
|
||||
|
||||
if 1 < len(segment_ids):
|
||||
if 1 < len(segment_ids) and not allow_multiple_segments:
|
||||
raise segment_exc.FixedIpsSubnetsNotOnSameSegment()
|
||||
|
||||
if allow_multiple_segments:
|
||||
return query
|
||||
|
||||
segment_id = None if not segment_ids else segment_ids[0]
|
||||
return query.filter(cls.db_model.segment_id == segment_id)
|
||||
|
||||
|
|
|
@ -86,6 +86,18 @@ class TestIpamBackendMixin(base.BaseTestCase):
|
|||
self.mixin._get_subnet_object = mock.Mock(
|
||||
side_effect=_get_subnet_object)
|
||||
|
||||
def test__is_distributed_service(self):
|
||||
port = {'device_owner':
|
||||
'%snova' % constants.DEVICE_OWNER_COMPUTE_PREFIX,
|
||||
'device_id': uuidutils.generate_uuid()}
|
||||
self.assertFalse(self.mixin._is_distributed_service(port))
|
||||
port = {'device_owner': constants.DEVICE_OWNER_DHCP,
|
||||
'device_id': uuidutils.generate_uuid()}
|
||||
self.assertFalse(self.mixin._is_distributed_service(port))
|
||||
port = {'device_owner': constants.DEVICE_OWNER_DHCP,
|
||||
'device_id': 'ovnmeta-%s' % uuidutils.generate_uuid()}
|
||||
self.assertTrue(self.mixin._is_distributed_service(port))
|
||||
|
||||
def _test_get_changed_ips_for_port(self, expected, original_ips,
|
||||
new_ips, owner):
|
||||
change = self.mixin._get_changed_ips_for_port(self.ctx,
|
||||
|
|
|
@ -690,12 +690,48 @@ class TestDbBasePluginIpam(test_db_base.NeutronDbPluginV2TestCase):
|
|||
mocks['ipam']._ipam_get_subnets.assert_called_once_with(
|
||||
context, network_id=port_dict['network_id'], fixed_configured=True,
|
||||
fixed_ips=[ip_dict], host=None,
|
||||
service_type=port_dict['device_owner'])
|
||||
service_type=port_dict['device_owner'],
|
||||
distributed_service=False)
|
||||
# Validate port_dict is passed into address_factory
|
||||
address_factory.get_request.assert_called_once_with(context,
|
||||
port_dict,
|
||||
ip_dict)
|
||||
|
||||
@mock.patch('neutron.ipam.driver.Pool')
|
||||
def test_update_ips_for_port_ovn_distributed_svc(self, pool_mock):
|
||||
address_factory = mock.Mock()
|
||||
mocks = self._prepare_mocks_with_pool_mock(
|
||||
pool_mock, address_factory=address_factory)
|
||||
context = mock.Mock()
|
||||
new_ips = mock.Mock()
|
||||
original_ips = mock.Mock()
|
||||
mac = mock.Mock()
|
||||
|
||||
ip_dict = {'ip_address': '192.1.1.10',
|
||||
'subnet_id': uuidutils.generate_uuid()}
|
||||
changes = ipam_pluggable_backend.IpamPluggableBackend.Changes(
|
||||
add=[ip_dict], original=[], remove=[])
|
||||
changes_mock = mock.Mock(return_value=changes)
|
||||
fixed_ips_mock = mock.Mock(return_value=changes.add)
|
||||
mocks['ipam'] = ipam_pluggable_backend.IpamPluggableBackend()
|
||||
mocks['ipam']._get_changed_ips_for_port = changes_mock
|
||||
mocks['ipam']._ipam_get_subnets = mock.Mock(return_value=[])
|
||||
mocks['ipam']._test_fixed_ips_for_port = fixed_ips_mock
|
||||
mocks['ipam']._update_ips_for_pd_subnet = mock.Mock(return_value=[])
|
||||
|
||||
port_dict = {
|
||||
'device_owner': constants.DEVICE_OWNER_DHCP,
|
||||
'device_id': 'ovnmeta-%s' % uuidutils.generate_uuid(),
|
||||
'network_id': uuidutils.generate_uuid()}
|
||||
|
||||
mocks['ipam']._update_ips_for_port(context, port_dict, None,
|
||||
original_ips, new_ips, mac)
|
||||
mocks['ipam']._ipam_get_subnets.assert_called_once_with(
|
||||
context, network_id=port_dict['network_id'], fixed_configured=True,
|
||||
fixed_ips=[ip_dict], host=None,
|
||||
service_type=port_dict['device_owner'],
|
||||
distributed_service=True)
|
||||
|
||||
@mock.patch('neutron.ipam.driver.Pool')
|
||||
def test_update_ips_for_port_passes_port_id_to_factory(self, pool_mock):
|
||||
port_id = uuidutils.generate_uuid()
|
||||
|
|
Loading…
Reference in New Issue