Add net & utils methods for routed nets & segments
Before adding a prefilter, we need to add new methods for asking Neutron about the related aggregates we need to ask Placement if the user creates or moves an instance with a requested network or a port. NOTE(sbauza): Looks like mypy doesn't like the current methods in nova.network.neutron, so we need to fix the issues before adding this module to mypy-files.txt. Partially-Implements : blueprint routed-networks-scheduling Change-Id: Ie166f3b51fddeaf916cda7c5ac34bbcdda0fd17a
This commit is contained in:
@@ -1195,6 +1195,11 @@ class RequestFilterFailed(NovaException):
|
|||||||
msg_fmt = _("Scheduling failed: %(reason)s")
|
msg_fmt = _("Scheduling failed: %(reason)s")
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidRoutedNetworkConfiguration(RequestFilterFailed):
|
||||||
|
msg_fmt = _("Neutron routed networks configuration is invalid: "
|
||||||
|
"%(reason)s.")
|
||||||
|
|
||||||
|
|
||||||
class MaxRetriesExceeded(NoValidHost):
|
class MaxRetriesExceeded(NoValidHost):
|
||||||
msg_fmt = _("Exceeded maximum number of retries. %(reason)s")
|
msg_fmt = _("Exceeded maximum number of retries. %(reason)s")
|
||||||
|
|
||||||
|
|||||||
@@ -29,3 +29,4 @@ MIGRATING_ATTR = 'migrating_to'
|
|||||||
L3_NETWORK_TYPES = ['vxlan', 'gre', 'geneve']
|
L3_NETWORK_TYPES = ['vxlan', 'gre', 'geneve']
|
||||||
ALLOCATION = 'allocation'
|
ALLOCATION = 'allocation'
|
||||||
RESOURCE_REQUEST = 'resource_request'
|
RESOURCE_REQUEST = 'resource_request'
|
||||||
|
SEGMENT = 'Segment'
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ API and utilities for nova-network interactions.
|
|||||||
import copy
|
import copy
|
||||||
import functools
|
import functools
|
||||||
import time
|
import time
|
||||||
|
import typing as ty
|
||||||
|
|
||||||
from keystoneauth1 import loading as ks_loading
|
from keystoneauth1 import loading as ks_loading
|
||||||
from neutronclient.common import exceptions as neutron_client_exc
|
from neutronclient.common import exceptions as neutron_client_exc
|
||||||
@@ -1249,6 +1250,10 @@ class API(base.Base):
|
|||||||
self._refresh_neutron_extensions_cache(context)
|
self._refresh_neutron_extensions_cache(context)
|
||||||
return constants.SUBSTR_PORT_FILTERING in self.extensions
|
return constants.SUBSTR_PORT_FILTERING in self.extensions
|
||||||
|
|
||||||
|
def _has_segment_extension(self, context, neutron=None):
|
||||||
|
self._refresh_neutron_extensions_cache(context, neutron=neutron)
|
||||||
|
return constants.SEGMENT in self.extensions
|
||||||
|
|
||||||
def supports_port_binding_extension(self, context):
|
def supports_port_binding_extension(self, context):
|
||||||
"""This is a simple check to see if the neutron "binding-extended"
|
"""This is a simple check to see if the neutron "binding-extended"
|
||||||
extension exists and is enabled.
|
extension exists and is enabled.
|
||||||
@@ -3484,6 +3489,60 @@ class API(base.Base):
|
|||||||
'for port %s.',
|
'for port %s.',
|
||||||
vif['id'], instance=instance)
|
vif['id'], instance=instance)
|
||||||
|
|
||||||
|
def get_segment_ids_for_network(
|
||||||
|
self,
|
||||||
|
context: nova.context.RequestContext,
|
||||||
|
network_id: str,
|
||||||
|
) -> ty.List[str]:
|
||||||
|
"""Query the segmentation ids for the given network.
|
||||||
|
|
||||||
|
:param context: The request context.
|
||||||
|
:param network_id: The UUID of the network to be queried.
|
||||||
|
:returns: The list of segment UUIDs of the network or an empty list if
|
||||||
|
either Segment extension isn't enabled in Neutron or if the network
|
||||||
|
isn't configured for routing.
|
||||||
|
"""
|
||||||
|
if not self._has_segment_extension(context):
|
||||||
|
return []
|
||||||
|
|
||||||
|
client = get_client(context)
|
||||||
|
try:
|
||||||
|
# NOTE(sbauza): We can't use list_segments() directly because the
|
||||||
|
# API is borked and returns both segments but also segmentation IDs
|
||||||
|
# of a provider network if any.
|
||||||
|
subnets = client.list_subnets(network_id=network_id,
|
||||||
|
fields='segment_id')['subnets']
|
||||||
|
except neutron_client_exc.NeutronClientException as e:
|
||||||
|
raise exception.InvalidRoutedNetworkConfiguration(
|
||||||
|
'Failed to get segment IDs for network %s' % network_id) from e
|
||||||
|
# The segment field of an unconfigured subnet could be None
|
||||||
|
return [subnet['segment_id'] for subnet in subnets
|
||||||
|
if subnet['segment_id'] is not None]
|
||||||
|
|
||||||
|
def get_segment_id_for_subnet(
|
||||||
|
self,
|
||||||
|
context: nova.context.RequestContext,
|
||||||
|
subnet_id: str,
|
||||||
|
) -> ty.Optional[str]:
|
||||||
|
"""Query the segmentation id for the given subnet.
|
||||||
|
|
||||||
|
:param context: The request context.
|
||||||
|
:param subnet_id: The UUID of the subnet to be queried.
|
||||||
|
:returns: The segment UUID of the subnet or None if either Segment
|
||||||
|
extension isn't enabled in Neutron or the provided subnet doesn't
|
||||||
|
have segments (if the related network isn't configured for routing)
|
||||||
|
"""
|
||||||
|
if not self._has_segment_extension(context):
|
||||||
|
return None
|
||||||
|
|
||||||
|
client = get_client(context)
|
||||||
|
try:
|
||||||
|
subnet = client.show_subnet(subnet_id)['subnet']
|
||||||
|
except neutron_client_exc.NeutronClientException as e:
|
||||||
|
raise exception.InvalidRoutedNetworkConfiguration(
|
||||||
|
'Subnet %s not found' % subnet_id) from e
|
||||||
|
return subnet.get('segment_id')
|
||||||
|
|
||||||
|
|
||||||
def _ensure_requested_network_ordering(accessor, unordered, preferred):
|
def _ensure_requested_network_ordering(accessor, unordered, preferred):
|
||||||
"""Sort a list with respect to the preferred network ordering."""
|
"""Sort a list with respect to the preferred network ordering."""
|
||||||
|
|||||||
@@ -1328,3 +1328,82 @@ def fill_provider_mapping_based_on_allocation(
|
|||||||
# allocation_request_version key of the Selection object.
|
# allocation_request_version key of the Selection object.
|
||||||
request_spec.map_requested_resources_to_providers(
|
request_spec.map_requested_resources_to_providers(
|
||||||
allocation, provider_traits)
|
allocation, provider_traits)
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME(sbauza) : Move this method closer to the prefilter once split.
|
||||||
|
def get_aggregates_for_routed_network(
|
||||||
|
context, network_api, report_client, network_uuid):
|
||||||
|
"""Collects the aggregate UUIDs describing the segmentation of a routed
|
||||||
|
network from Nova perspective.
|
||||||
|
|
||||||
|
A routed network consists of multiple network segments. Each segment is
|
||||||
|
available on a given set of compute hosts. Such segmentation is modelled as
|
||||||
|
host aggregates from Nova perspective.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param network_api: nova.network.neutron.API instance to be used to
|
||||||
|
communicate with Neutron
|
||||||
|
:param report_client: SchedulerReportClient instance to be used to
|
||||||
|
communicate with Placement
|
||||||
|
:param network_uuid: The UUID of the Neutron network to be translated to
|
||||||
|
aggregates
|
||||||
|
:returns: A list of aggregate UUIDs
|
||||||
|
:raises InvalidRoutedNetworkConfiguration: if something goes wrong when
|
||||||
|
try to find related aggregates
|
||||||
|
"""
|
||||||
|
aggregates = []
|
||||||
|
|
||||||
|
segment_ids = network_api.get_segment_ids_for_network(
|
||||||
|
context, network_uuid)
|
||||||
|
# Each segment is a resource provider in placement and is in an
|
||||||
|
# aggregate for the routed network, so we have to get the
|
||||||
|
# aggregates for each segment provider - and those aggregates are
|
||||||
|
# mirrored as nova host aggregates.
|
||||||
|
# NOTE(sbauza): In case of a network with non-configured routed segments,
|
||||||
|
# we will get an empty list of segment UUIDs, so we won't enter the loop.
|
||||||
|
for segment_id in segment_ids:
|
||||||
|
# TODO(sbauza): Don't use a private method.
|
||||||
|
agg_info = report_client._get_provider_aggregates(context, segment_id)
|
||||||
|
# @safe_connect can return None but we also want to hard-stop here if
|
||||||
|
# we can't find the aggregate that Neutron created for the segment.
|
||||||
|
if agg_info is None or not agg_info.aggregates:
|
||||||
|
raise exception.InvalidRoutedNetworkConfiguration(
|
||||||
|
'Failed to find aggregate related to segment %s' % segment_id)
|
||||||
|
aggregates.extend(agg_info.aggregates)
|
||||||
|
return aggregates
|
||||||
|
|
||||||
|
|
||||||
|
# FIXME(sbauza) : Move this method closer to the prefilter once split.
|
||||||
|
def get_aggregates_for_routed_subnet(
|
||||||
|
context, network_api, report_client, subnet_id):
|
||||||
|
"""Collects the aggregate UUIDs matching the segment that relates to a
|
||||||
|
particular subnet from a routed network.
|
||||||
|
|
||||||
|
A routed network consists of multiple network segments. Each segment is
|
||||||
|
available on a given set of compute hosts. Such segmentation is modelled as
|
||||||
|
host aggregates from Nova perspective.
|
||||||
|
|
||||||
|
:param context: The security context
|
||||||
|
:param network_api: nova.network.neutron.API instance to be used to
|
||||||
|
communicate with Neutron
|
||||||
|
:param report_client: SchedulerReportClient instance to be used to
|
||||||
|
communicate with Placement
|
||||||
|
:param subnet_id: The UUID of the Neutron subnet to be translated to
|
||||||
|
aggregate
|
||||||
|
:returns: A list of aggregate UUIDs
|
||||||
|
:raises InvalidRoutedNetworkConfiguration: if something goes wrong when
|
||||||
|
try to find related aggregates
|
||||||
|
"""
|
||||||
|
|
||||||
|
segment_id = network_api.get_segment_id_for_subnet(
|
||||||
|
context, subnet_id)
|
||||||
|
if segment_id:
|
||||||
|
# TODO(sbauza): Don't use a private method.
|
||||||
|
agg_info = report_client._get_provider_aggregates(context, segment_id)
|
||||||
|
# @safe_connect can return None but we also want to hard-stop here if
|
||||||
|
# we can't find the aggregate that Neutron created for the segment.
|
||||||
|
if agg_info is None or not agg_info.aggregates:
|
||||||
|
raise exception.InvalidRoutedNetworkConfiguration(
|
||||||
|
'Failed to find aggregate related to segment %s' % segment_id)
|
||||||
|
return agg_info.aggregates
|
||||||
|
return []
|
||||||
|
|||||||
@@ -6268,6 +6268,105 @@ class TestAPI(TestAPIBase):
|
|||||||
|
|
||||||
mock_get_client.assert_called_once_with(self.context, admin=True)
|
mock_get_client.assert_called_once_with(self.context, admin=True)
|
||||||
|
|
||||||
|
def test_get_segment_ids_for_network_no_segment_ext(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=False
|
||||||
|
):
|
||||||
|
self.assertEqual(
|
||||||
|
[], self.api.get_segment_ids_for_network(self.context,
|
||||||
|
uuids.network_id))
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_ids_for_network_passes(self, mock_client):
|
||||||
|
subnets = {'subnets': [{'segment_id': uuids.segment_id}]}
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.list_subnets.return_value = subnets
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
res = self.api.get_segment_ids_for_network(
|
||||||
|
self.context, uuids.network_id)
|
||||||
|
self.assertEqual([uuids.segment_id], res)
|
||||||
|
mocked_client.list_subnets.assert_called_once_with(
|
||||||
|
network_id=uuids.network_id, fields='segment_id')
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_ids_for_network_with_no_segments(self, mock_client):
|
||||||
|
subnets = {'subnets': [{'segment_id': None}]}
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.list_subnets.return_value = subnets
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
res = self.api.get_segment_ids_for_network(
|
||||||
|
self.context, uuids.network_id)
|
||||||
|
self.assertEqual([], res)
|
||||||
|
mocked_client.list_subnets.assert_called_once_with(
|
||||||
|
network_id=uuids.network_id, fields='segment_id')
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_ids_for_network_fails(self, mock_client):
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.list_subnets.side_effect = (
|
||||||
|
exceptions.NeutronClientException(status_code=404))
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
self.assertRaises(exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
self.api.get_segment_ids_for_network,
|
||||||
|
self.context, uuids.network_id)
|
||||||
|
|
||||||
|
def test_get_segment_id_for_subnet_no_segment_ext(self):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=False
|
||||||
|
):
|
||||||
|
self.assertIsNone(
|
||||||
|
self.api.get_segment_id_for_subnet(self.context,
|
||||||
|
uuids.subnet_id))
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_id_for_subnet_passes(self, mock_client):
|
||||||
|
subnet = {'subnet': {'segment_id': uuids.segment_id}}
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.show_subnet.return_value = subnet
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
res = self.api.get_segment_id_for_subnet(
|
||||||
|
self.context, uuids.subnet_id)
|
||||||
|
self.assertEqual(uuids.segment_id, res)
|
||||||
|
mocked_client.show_subnet.assert_called_once_with(uuids.subnet_id)
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_id_for_subnet_with_no_segment(self, mock_client):
|
||||||
|
subnet = {'subnet': {}}
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.show_subnet.return_value = subnet
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
self.assertIsNone(
|
||||||
|
self.api.get_segment_id_for_subnet(self.context,
|
||||||
|
uuids.subnet_id))
|
||||||
|
|
||||||
|
@mock.patch.object(neutronapi, 'get_client')
|
||||||
|
def test_get_segment_id_for_subnet_fails(self, mock_client):
|
||||||
|
mocked_client = mock.create_autospec(client.Client)
|
||||||
|
mock_client.return_value = mocked_client
|
||||||
|
mocked_client.show_subnet.side_effect = (
|
||||||
|
exceptions.NeutronClientException(status_code=404))
|
||||||
|
with mock.patch.object(
|
||||||
|
self.api, '_has_segment_extension', return_value=True
|
||||||
|
):
|
||||||
|
self.assertRaises(exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
self.api.get_segment_id_for_subnet,
|
||||||
|
self.context, uuids.subnet_id)
|
||||||
|
|
||||||
|
|
||||||
class TestAPIModuleMethods(test.NoDBTestCase):
|
class TestAPIModuleMethods(test.NoDBTestCase):
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,9 @@ from nova.compute import flavors
|
|||||||
from nova.compute import utils as compute_utils
|
from nova.compute import utils as compute_utils
|
||||||
from nova import context as nova_context
|
from nova import context as nova_context
|
||||||
from nova import exception
|
from nova import exception
|
||||||
|
from nova.network import neutron
|
||||||
from nova import objects
|
from nova import objects
|
||||||
|
from nova.scheduler.client import report
|
||||||
from nova.scheduler import utils as scheduler_utils
|
from nova.scheduler import utils as scheduler_utils
|
||||||
from nova import test
|
from nova import test
|
||||||
from nova.tests.unit import fake_instance
|
from nova.tests.unit import fake_instance
|
||||||
@@ -407,3 +409,111 @@ class SchedulerUtilsTestCase(test.NoDBTestCase):
|
|||||||
self.assertRaises(exception.NoValidHost,
|
self.assertRaises(exception.NoValidHost,
|
||||||
scheduler_utils.setup_instance_group,
|
scheduler_utils.setup_instance_group,
|
||||||
self.context, spec)
|
self.context, spec)
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_ids_for_network')
|
||||||
|
def test_get_aggregates_for_routed_network(self, mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = [uuids.segment1, uuids.segment2]
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
|
||||||
|
def fake_get_provider_aggregates(context, segment_id):
|
||||||
|
agg = uuids.agg1 if segment_id == uuids.segment1 else uuids.agg2
|
||||||
|
agg_info = report.AggInfo(aggregates=[agg], generation=1)
|
||||||
|
return agg_info
|
||||||
|
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
side_effect=fake_get_provider_aggregates) as mock_get_aggs:
|
||||||
|
res = scheduler_utils.get_aggregates_for_routed_network(
|
||||||
|
self.context, network_api, report_client, uuids.network1)
|
||||||
|
self.assertEqual([uuids.agg1, uuids.agg2], res)
|
||||||
|
mock_get_segment_ids.assert_called_once_with(
|
||||||
|
self.context, uuids.network1)
|
||||||
|
mock_get_aggs.assert_has_calls(
|
||||||
|
[mock.call(self.context, uuids.segment1),
|
||||||
|
mock.call(self.context, uuids.segment2)])
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_ids_for_network')
|
||||||
|
def test_get_aggregates_for_routed_network_none(self,
|
||||||
|
mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = []
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
self.assertEqual(
|
||||||
|
[],
|
||||||
|
scheduler_utils.get_aggregates_for_routed_network(
|
||||||
|
self.context, network_api, report_client, uuids.network1))
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_ids_for_network')
|
||||||
|
def test_get_aggregates_for_routed_network_fails(self,
|
||||||
|
mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = [uuids.segment1]
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
|
||||||
|
# We could fail on some placement issue...
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
return_value=None):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
scheduler_utils.get_aggregates_for_routed_network,
|
||||||
|
self.context, network_api, report_client, uuids.network1)
|
||||||
|
|
||||||
|
# ... but we also want to fail if we can't find the related aggregate
|
||||||
|
agg_info = report.AggInfo(aggregates=set(), generation=1)
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
return_value=agg_info):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
scheduler_utils.get_aggregates_for_routed_network,
|
||||||
|
self.context, network_api, report_client, uuids.network1)
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_id_for_subnet')
|
||||||
|
def test_get_aggregates_for_routed_subnet(self, mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = uuids.segment1
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
agg_info = report.AggInfo(aggregates=[uuids.agg1], generation=1)
|
||||||
|
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
return_value=agg_info) as mock_get_aggs:
|
||||||
|
res = scheduler_utils.get_aggregates_for_routed_subnet(
|
||||||
|
self.context, network_api, report_client,
|
||||||
|
uuids.subnet1)
|
||||||
|
self.assertEqual([uuids.agg1], res)
|
||||||
|
mock_get_segment_ids.assert_called_once_with(
|
||||||
|
self.context, uuids.subnet1)
|
||||||
|
mock_get_aggs.assert_called_once_with(self.context, uuids.segment1)
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_id_for_subnet')
|
||||||
|
def test_get_aggregates_for_routed_subnet_none(self, mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = None
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
self.assertEqual(
|
||||||
|
[],
|
||||||
|
scheduler_utils.get_aggregates_for_routed_subnet(
|
||||||
|
self.context, network_api, report_client, uuids.subnet1))
|
||||||
|
|
||||||
|
@mock.patch('nova.network.neutron.API.get_segment_id_for_subnet')
|
||||||
|
def test_get_aggregates_for_routed_subnet_fails(self,
|
||||||
|
mock_get_segment_ids):
|
||||||
|
mock_get_segment_ids.return_value = uuids.segment1
|
||||||
|
report_client = report.SchedulerReportClient()
|
||||||
|
network_api = neutron.API()
|
||||||
|
|
||||||
|
# We could fail on some placement issue...
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
return_value=None):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
scheduler_utils.get_aggregates_for_routed_subnet,
|
||||||
|
self.context, network_api, report_client, uuids.subnet1)
|
||||||
|
|
||||||
|
# ... but we also want to fail if we can't find the related aggregate
|
||||||
|
agg_info = report.AggInfo(aggregates=set(), generation=1)
|
||||||
|
with mock.patch.object(report_client, '_get_provider_aggregates',
|
||||||
|
return_value=agg_info):
|
||||||
|
self.assertRaises(
|
||||||
|
exception.InvalidRoutedNetworkConfiguration,
|
||||||
|
scheduler_utils.get_aggregates_for_routed_subnet,
|
||||||
|
self.context, network_api, report_client, uuids.subnet1)
|
||||||
|
|||||||
Reference in New Issue
Block a user