Merge "ipam_backend_mixin: Segments model to OVO"

This commit is contained in:
Zuul
2018-08-24 01:54:25 +00:00
committed by Gerrit Code Review
2 changed files with 127 additions and 117 deletions
+6 -117
View File
@@ -26,17 +26,14 @@ from neutron_lib.db import utils as db_utils
from neutron_lib import exceptions as exc
from oslo_config import cfg
from oslo_log import log as logging
from sqlalchemy import and_, or_
from sqlalchemy.orm import exc as orm_exc
from neutron._i18n import _
from neutron.common import exceptions as n_exc
from neutron.common import ipv6_utils
from neutron.common import utils as common_utils
from neutron.db import _model_query as model_query
from neutron.db import api as db_api
from neutron.db import db_base_plugin_common
from neutron.db.models import segment as segment_model
from neutron.db import models_v2
from neutron.extensions import segment
from neutron.ipam import exceptions as ipam_exceptions
@@ -333,10 +330,8 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
segment_id not in associated_segments):
raise segment_exc.SubnetSegmentAssociationChangeNotAllowed()
query = context.session.query(segment_model.NetworkSegment.id)
query = query.filter(
segment_model.NetworkSegment.network_id == network_id)
if query.count() > 1:
if network_obj.NetworkSegment.count(
context, network_id=network_id) > 1:
raise segment_exc.NoUpdateSubnetWhenMultipleSegmentsOnNetwork(
network_id=network_id)
@@ -602,58 +597,6 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
fixed_ip_list.append({'subnet_id': subnet['id']})
return fixed_ip_list
def _query_subnets_on_network(self, context, network_id):
query = model_query.get_collection_query(context, models_v2.Subnet)
return query.filter(models_v2.Subnet.network_id == network_id)
@staticmethod
def _query_filter_by_segment_host_mapping(query, host):
"""Excludes subnets on segments not reachable by the host
The query gets two kinds of subnets: those that are on segments that
the host can reach and those that are not on segments at all (assumed
reachable by all hosts). Hence, subnets on segments that the host
*cannot* reach are excluded.
"""
Subnet = models_v2.Subnet
SegmentHostMapping = segment_model.SegmentHostMapping
# A host has been provided. Consider these two scenarios
# 1. Not a routed network: subnets are not on segments
# 2. Is a routed network: only subnets on segments mapped to host
# The following join query returns results for either. The two are
# guaranteed to be mutually exclusive when subnets are created.
query = query.add_entity(SegmentHostMapping)
query = query.outerjoin(
SegmentHostMapping,
and_(Subnet.segment_id == SegmentHostMapping.segment_id,
SegmentHostMapping.host == host))
# Essentially "segment_id IS NULL XNOR host IS NULL"
query = query.filter(or_(and_(Subnet.segment_id.isnot(None),
SegmentHostMapping.host.isnot(None)),
and_(Subnet.segment_id.is_(None),
SegmentHostMapping.host.is_(None))))
return query
@staticmethod
def _query_exclude_subnets_on_segments(query):
"""Excludes all subnets associated with segments
For the case where the host is not known, we don't consider any subnets
that are on segments. But, we still consider subnets that are not
associated with any segment (i.e. for non-routed networks).
"""
return query.filter(models_v2.Subnet.segment_id.is_(None))
@staticmethod
def is_host_set(host):
"""Utility to tell if the host is set in the port binding"""
# This seems redundant, but its not. Host is unset if its None, '',
# or ATTR_NOT_SPECIFIED due to differences in host binding
# implementations.
return host and validators.is_attr_set(host)
def _ipam_get_subnets(self, context, network_id, host, service_type=None,
fixed_configured=False):
"""Return eligible subnets
@@ -661,8 +604,8 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
If no eligible subnets are found, determine why and potentially raise
an appropriate error.
"""
subnets = self._find_candidate_subnets(context, network_id, host,
service_type, fixed_configured)
subnets = subnet_obj.Subnet.find_candidate_subnets(
context, network_id, host, service_type, fixed_configured)
if subnets:
subnet_dicts = [self._make_subnet_dict(subnet, context=context)
for subnet in subnets]
@@ -671,67 +614,13 @@ class IpamBackendMixin(db_base_plugin_common.DbBasePluginCommon):
subnet_dicts,
key=lambda subnet: not subnet.get('service_types'))
# Determine why we found no subnets to raise the right error
query = self._query_subnets_on_network(context, network_id)
if self.is_host_set(host):
# Empty because host isn't mapped to a segment with a subnet?
s_query = query.filter(models_v2.Subnet.segment_id.isnot(None))
if s_query.limit(1).count() != 0:
# It is a routed network but no subnets found for host
raise segment_exc.HostNotConnectedToAnySegment(
host=host, network_id=network_id)
if not query.limit(1).count():
# Network has *no* subnets of any kind. This isn't an error.
if subnet_obj.Subnet.network_has_no_subnet(
context, network_id, host, service_type):
return []
# Does filtering ineligible service subnets makes the list empty?
query = subnet_obj.SubnetServiceType.query_filter_service_subnets(
query, service_type)
if query.limit(1).count():
# No, must be a deferred IP port because there are matching
# subnets. Happens on routed networks when host isn't known.
raise ipam_exceptions.DeferIpam()
raise ipam_exceptions.IpAddressGenerationFailureNoMatchingSubnet(
network_id=network_id, service_type=service_type)
def _find_candidate_subnets(self, context, network_id, host, service_type,
fixed_configured):
"""Find canditate subnets for the network, host, and service_type"""
query = self._query_subnets_on_network(context, network_id)
query = subnet_obj.SubnetServiceType.query_filter_service_subnets(
query, service_type)
# Select candidate subnets and return them
if not self.is_host_set(host):
if fixed_configured:
# If fixed_ips in request and host is not known all subnets on
# 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()
# If the host isn't known, we can't allocate on a routed network.
# So, exclude any subnets attached to segments.
return self._query_exclude_subnets_on_segments(query).all()
# The host is known. Consider both routed and non-routed networks
results = self._query_filter_by_segment_host_mapping(query, host).all()
# For now, we're using a simplifying assumption that a host will only
# touch one segment in a given routed network. Raise exception
# otherwise. This restriction may be relaxed as use cases for multiple
# mappings are understood.
segment_ids = {subnet.segment_id
for subnet, mapping in results
if mapping}
if 1 < len(segment_ids):
raise segment_exc.HostConnectedToMultipleSegments(
host=host, network_id=network_id)
return [subnet for subnet, _mapping in results]
def _make_subnet_args(self, detail, subnet, subnetpool_id):
args = super(IpamBackendMixin, self)._make_subnet_args(
detail, subnet, subnetpool_id)
+121
View File
@@ -11,18 +11,23 @@
# under the License.
import netaddr
from neutron_lib.api import validators
from neutron_lib import constants as const
from oslo_versionedobjects import fields as obj_fields
from sqlalchemy import and_, or_
from neutron.common import utils
from neutron.db import _model_query as model_query
from neutron.db.models import segment as segment_model
from neutron.db.models import subnet_service_type
from neutron.db import models_v2
from neutron.ipam import exceptions as ipam_exceptions
from neutron.objects import base
from neutron.objects import common_types
from neutron.objects import network
from neutron.objects import rbac_db
from neutron.services.segments import exceptions as segment_exc
@base.NeutronObjectRegistry.register
@@ -287,3 +292,119 @@ class Subnet(base.NeutronDbObject):
if 'gateway_ip' in result and result['gateway_ip'] is not None:
result['gateway_ip'] = cls.filter_to_str(result['gateway_ip'])
return result
@classmethod
def find_candidate_subnets(cls, context, network_id, host, service_type,
fixed_configured):
"""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(
query, service_type)
# Select candidate subnets and return them
if not cls.is_host_set(host):
if fixed_configured:
# If fixed_ips in request and host is not known all subnets on
# 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()
# 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()
# The host is known. Consider both routed and non-routed networks
results = cls._query_filter_by_segment_host_mapping(query, host).all()
# For now, we're using a simplifying assumption that a host will only
# touch one segment in a given routed network. Raise exception
# otherwise. This restriction may be relaxed as use cases for multiple
# mappings are understood.
segment_ids = {subnet.segment_id
for subnet, mapping in results
if mapping}
if 1 < len(segment_ids):
raise segment_exc.HostConnectedToMultipleSegments(
host=host, network_id=network_id)
return [subnet for subnet, _mapping in results]
@classmethod
def _query_filter_by_segment_host_mapping(cls, query, host):
# TODO(tuanvu): find OVO-like solution for handling "join queries" and
# write unit test for this function
"""Excludes subnets on segments not reachable by the host
The query gets two kinds of subnets: those that are on segments that
the host can reach and those that are not on segments at all (assumed
reachable by all hosts). Hence, subnets on segments that the host
*cannot* reach are excluded.
"""
SegmentHostMapping = segment_model.SegmentHostMapping
# A host has been provided. Consider these two scenarios
# 1. Not a routed network: subnets are not on segments
# 2. Is a routed network: only subnets on segments mapped to host
# The following join query returns results for either. The two are
# guaranteed to be mutually exclusive when subnets are created.
query = query.add_entity(SegmentHostMapping)
query = query.outerjoin(
SegmentHostMapping,
and_(cls.db_model.segment_id == SegmentHostMapping.segment_id,
SegmentHostMapping.host == host))
# Essentially "segment_id IS NULL XNOR host IS NULL"
query = query.filter(or_(and_(cls.db_model.segment_id.isnot(None),
SegmentHostMapping.host.isnot(None)),
and_(cls.db_model.segment_id.is_(None),
SegmentHostMapping.host.is_(None))))
return query
@classmethod
def query_subnets_on_network(cls, context, network_id):
query = model_query.get_collection_query(context, cls.db_model)
return query.filter(cls.db_model.network_id == network_id)
@classmethod
def _query_exclude_subnets_on_segments(cls, query):
"""Excludes all subnets associated with segments
For the case where the host is not known, we don't consider any subnets
that are on segments. But, we still consider subnets that are not
associated with any segment (i.e. for non-routed networks).
"""
return query.filter(cls.db_model.segment_id.is_(None))
@classmethod
def is_host_set(cls, host):
"""Utility to tell if the host is set in the port binding"""
# This seems redundant, but its not. Host is unset if its None, '',
# or ATTR_NOT_SPECIFIED due to differences in host binding
# implementations.
return host and validators.is_attr_set(host)
@classmethod
def network_has_no_subnet(cls, context, network_id, host, service_type):
# Determine why we found no subnets to raise the right error
query = cls.query_subnets_on_network(context, network_id)
if cls.is_host_set(host):
# Empty because host isn't mapped to a segment with a subnet?
s_query = query.filter(cls.db_model.segment_id.isnot(None))
if s_query.limit(1).count() != 0:
# It is a routed network but no subnets found for host
raise segment_exc.HostNotConnectedToAnySegment(
host=host, network_id=network_id)
if not query.limit(1).count():
# Network has *no* subnets of any kind. This isn't an error.
return True
# Does filtering ineligible service subnets makes the list empty?
query = SubnetServiceType.query_filter_service_subnets(
query, service_type)
if query.limit(1).count():
# No, must be a deferred IP port because there are matching
# subnets. Happens on routed networks when host isn't known.
raise ipam_exceptions.DeferIpam()
return False