Move _fill_provider_mapping to the scheduler_utils

The _fill_provider_mapping is called during boot and reschedule so far
but to support move operations with ports having resource request we
need to call it during various coductor tasks. So this patch move the
function to the utils and adjust the name and the signature accordingly.

blueprint: support-move-ops-with-qos-ports

Change-Id: I76f777e4f354b92c55dbd52a20039e504434b3a1
This commit is contained in:
Balazs Gibizer 2019-04-15 15:27:16 +02:00
parent 5e5e5daa90
commit 276001914d
4 changed files with 77 additions and 66 deletions

View File

@ -691,8 +691,9 @@ class ComputeTaskManager(base.Base):
# provider mapping as the above claim call
# moves the allocation of the instance to
# another host
self._fill_provider_mapping(
context, request_spec, host)
scheduler_utils.fill_provider_mapping(
context, self.report_client, request_spec,
host)
except Exception as exc:
self._cleanup_when_reschedule_fails(
context, instance, exc, legacy_request_spec,
@ -1282,55 +1283,6 @@ class ComputeTaskManager(base.Base):
with obj_target_cell(inst, cell0):
inst.destroy()
def _fill_provider_mapping(self, context, request_spec, host_selection):
"""Fills out the request group - resource provider mapping in the
request spec.
This is a workaround as placement does not return which RP
fulfills which granular request group in the allocation candidate
request. There is a spec proposing a solution in placement:
https://review.opendev.org/#/c/597601/
When that spec is implemented then this function can be
replaced with a simpler code that copies the group - RP
mapping out from the Selection object returned by the scheduler's
select_destinations call.
:param context: The security context
:param request_spec: The RequestSpec object associated with the
operation
:param host_selection: The Selection object returned by the scheduler
for this operation
"""
# Exit early if this request spec does not require mappings.
if not request_spec.maps_requested_resources:
return
# Technically out-of-tree scheduler drivers can still not create
# allocations in placement but if request_spec.maps_requested_resources
# is not empty and the scheduling succeeded then placement has to be
# involved
ar = jsonutils.loads(host_selection.allocation_request)
allocs = ar['allocations']
# NOTE(gibi): Getting traits from placement for each instance in a
# instance multi-create scenario is unnecessarily expensive. But
# instance multi-create cannot be used with pre-created neutron ports
# and this code can only be triggered with such pre-created ports so
# instance multi-create is not an issue. If this ever become an issue
# in the future then we could stash the RP->traits mapping on the
# Selection object since we can pull the traits for each provider from
# the GET /allocation_candidates response in the scheduler (or leverage
# the change from the spec mentioned in the docstring above).
provider_traits = {
rp_uuid: self.report_client.get_provider_traits(
context, rp_uuid).traits
for rp_uuid in allocs}
# NOTE(gibi): The allocs dict is in the format of the PUT /allocations
# and that format can change. The current format can be detected from
# host_selection.allocation_request_version
request_spec.map_requested_resources_to_providers(
allocs, provider_traits)
def schedule_and_build_instances(self, context, build_requests,
request_specs, image,
admin_password, injected_files,
@ -1450,7 +1402,8 @@ class ComputeTaskManager(base.Base):
# allocations in the scheduler) for this instance, we may need to
# map allocations to resource providers in the request spec.
try:
self._fill_provider_mapping(context, request_spec, host)
scheduler_utils.fill_provider_mapping(
context, self.report_client, request_spec, host)
except Exception as exc:
# If anything failed here we need to cleanup and bail out.
with excutils.save_and_reraise_exception():

View File

@ -1100,3 +1100,56 @@ def get_weight_multiplier(host_state, multiplier_name, multiplier_config):
value = multiplier_config
return value
def fill_provider_mapping(
context, report_client, request_spec, host_selection):
"""Fills out the request group - resource provider mapping in the
request spec.
This is a workaround as placement does not return which RP
fulfills which granular request group in the allocation candidate
request. There is a spec proposing a solution in placement:
https://review.opendev.org/#/c/597601/
When that spec is implemented then this function can be
replaced with a simpler code that copies the group - RP
mapping out from the Selection object returned by the scheduler's
select_destinations call.
:param context: The security context
:param report_client: SchedulerReportClient instance to be used to
communicate with placement
:param request_spec: The RequestSpec object associated with the
operation
:param host_selection: The Selection object returned by the scheduler
for this operation
"""
# Exit early if this request spec does not require mappings.
if not request_spec.maps_requested_resources:
return
# Technically out-of-tree scheduler drivers can still not create
# allocations in placement but if request_spec.maps_requested_resources
# is not empty and the scheduling succeeded then placement has to be
# involved
ar = jsonutils.loads(host_selection.allocation_request)
allocs = ar['allocations']
# NOTE(gibi): Getting traits from placement for each instance in a
# instance multi-create scenario is unnecessarily expensive. But
# instance multi-create cannot be used with pre-created neutron ports
# and this code can only be triggered with such pre-created ports so
# instance multi-create is not an issue. If this ever become an issue
# in the future then we could stash the RP->traits mapping on the
# Selection object since we can pull the traits for each provider from
# the GET /allocation_candidates response in the scheduler (or leverage
# the change from the spec mentioned in the docstring above).
provider_traits = {
rp_uuid: report_client.get_provider_traits(
context, rp_uuid).traits
for rp_uuid in allocs}
# NOTE(gibi): The allocs dict is in the format of the PUT /allocations
# and that format can change. The current format can be detected from
# host_selection.allocation_request_version
request_spec.map_requested_resources_to_providers(
allocs, provider_traits)

View File

@ -36,7 +36,6 @@ from nova.compute import api as compute_api
from nova.compute import instance_actions
from nova.compute import manager as compute_manager
from nova.compute import rpcapi
from nova.conductor import manager
from nova import context
from nova import exception
from nova import objects
@ -6933,11 +6932,10 @@ class PortResourceRequestReSchedulingTest(
# First call is during boot, we want that to succeed normally. Then the
# fake virt driver triggers a re-schedule. During that re-schedule the
# fill is called again, and we simulate that call raises.
fill = manager.ComputeTaskManager._fill_provider_mapping
fill = nova.scheduler.utils.fill_provider_mapping
with mock.patch(
'nova.conductor.manager.ComputeTaskManager.'
'_fill_provider_mapping',
'nova.scheduler.utils.fill_provider_mapping',
side_effect=[
fill,
exception.ResourceProviderTraitRetrievalFailed(

View File

@ -47,6 +47,7 @@ from nova.objects import base as obj_base
from nova.objects import block_device as block_device_obj
from nova.objects import fields
from nova.scheduler.client import query
from nova.scheduler.client import report
from nova.scheduler import utils as scheduler_utils
from nova import test
from nova.tests import fixtures
@ -968,8 +969,8 @@ class _BaseTaskTestCase(object):
# build_instances() is a cast, we need to wait for it to complete
self.useFixture(cast_as_call.CastAsCall(self))
@mock.patch('nova.conductor.manager.ComputeTaskManager.'
'_fill_provider_mapping')
@mock.patch('nova.scheduler.utils.'
'fill_provider_mapping')
@mock.patch('nova.scheduler.utils.claim_resources')
@mock.patch('nova.objects.request_spec.RequestSpec.from_primitives',
return_value=request_spec)
@ -1018,8 +1019,11 @@ class _BaseTaskTestCase(object):
host_list=expected_build_run_host_list)
mock_rp_mapping.assert_called_once_with(
self.context, mock.ANY, test.MatchType(objects.Selection))
actual_request_spec = mock_rp_mapping.mock_calls[0][1][1]
self.context,
test.MatchType(report.SchedulerReportClient),
test.MatchType(objects.RequestSpec),
test.MatchType(objects.Selection))
actual_request_spec = mock_rp_mapping.mock_calls[0][1][2]
self.assertEqual(
rg1.resources,
actual_request_spec.requested_resources[0].resources)
@ -1043,8 +1047,8 @@ class _BaseTaskTestCase(object):
# build_instances() is a cast, we need to wait for it to complete
self.useFixture(cast_as_call.CastAsCall(self))
@mock.patch('nova.conductor.manager.ComputeTaskManager.'
'_fill_provider_mapping')
@mock.patch('nova.scheduler.utils.'
'fill_provider_mapping')
@mock.patch('nova.scheduler.utils.claim_resources',
# simulate that the first claim fails during re-schedule
side_effect=[False, True])
@ -1100,8 +1104,11 @@ class _BaseTaskTestCase(object):
# called only once when the claim succeeded
mock_rp_mapping.assert_called_once_with(
self.context, mock.ANY, test.MatchType(objects.Selection))
actual_request_spec = mock_rp_mapping.mock_calls[0][1][1]
self.context,
test.MatchType(report.SchedulerReportClient),
test.MatchType(objects.RequestSpec),
test.MatchType(objects.Selection))
actual_request_spec = mock_rp_mapping.mock_calls[0][1][2]
self.assertEqual(
rg1.resources,
actual_request_spec.requested_resources[0].resources)
@ -2233,8 +2240,8 @@ class ConductorTaskTestCase(_BaseTaskTestCase, test_compute.BaseTestCase):
@mock.patch('nova.conductor.manager.ComputeTaskManager.'
'_cleanup_build_artifacts')
@mock.patch('nova.conductor.manager.ComputeTaskManager.'
'_fill_provider_mapping', side_effect=test.TestingException)
@mock.patch('nova.scheduler.utils.'
'fill_provider_mapping', side_effect=test.TestingException)
def test_schedule_and_build_instances_fill_request_spec_error(
self, mock_fill, mock_cleanup):
self.assertRaises(