Use consumer generation in _heal_allocations_for_instance

If we're updating existing allocations for an instance due
to the project_id/user_id not matching the instance, we should
use the consumer_generation parameter, new in placement 1.28,
to ensure we don't overwrite the allocations while another
process is updating them.

As a result, the include_project_user kwarg to method
get_allocations_for_consumer is removed since nothing else
is using it now, and the minimum required version of placement
checked by nova-status is updated to 1.28.

Change-Id: I4d5f26061594fa9863c1110e6152069e44168cc3
This commit is contained in:
Matt Riedemann 2018-06-25 14:56:48 -04:00
parent 838f6dfb03
commit 660e328a25
9 changed files with 37 additions and 27 deletions

View File

@ -309,7 +309,8 @@ Placement
Specify ``--verbose`` to get detailed progress output during execution.
This command requires that the ``[api_database]/connection`` and
``[placement]`` configuration options are set.
``[placement]`` configuration options are set. Placement API >= 1.28 is
required.
Return codes:

View File

@ -108,7 +108,7 @@ Upgrade
**18.0.0 (Rocky)**
* Checks for the Placement API are modified to require version 1.25.
* Checks for the Placement API are modified to require version 1.28.
* Checks that ironic instances have had their embedded flavors migrated to
use custom resource classes.
* Checks for ``nova-osapi_compute`` service versions that are less than 15

View File

@ -1797,7 +1797,7 @@ class PlacementCommands(object):
return
allocations = placement.get_allocations_for_consumer(
ctxt, instance.uuid, include_project_user=True)
ctxt, instance.uuid, include_generation=True)
# get_allocations_for_consumer uses safe_connect which will
# return None if we can't communicate with Placement, and the
# response can have an empty {'allocations': {}} response if
@ -1823,11 +1823,10 @@ class PlacementCommands(object):
# provider allocations.
allocations['project_id'] = instance.project_id
allocations['user_id'] = instance.user_id
# We use 1.12 for PUT /allocations/{consumer_id} to mirror
# We use 1.28 for PUT /allocations/{consumer_id} to mirror
# the body structure from get_allocations_for_consumer.
# TODO(mriedem): Pass a consumer generation using 1.28.
resp = placement.put('/allocations/%s' % instance.uuid,
allocations, version='1.12')
allocations, version='1.28')
if resp:
output(_('Successfully updated allocations for '
'instance %s.') % instance.uuid)

View File

@ -52,11 +52,12 @@ CONF = nova.conf.CONF
PLACEMENT_DOCS_LINK = 'https://docs.openstack.org/nova/latest' \
'/user/placement.html'
# NOTE(efried): 1.25 is required by nova-scheduler to make granular
# resource requests to GET /allocation_candidates.
# NOTE(efried): 1.28 is required by "nova-manage placement heal_allocations"
# to get the consumer generation when updating incomplete allocations with
# instance consumer project_id and user_id values.
# NOTE: If you bump this version, remember to update the history
# section in the nova-status man page (doc/source/cli/nova-status).
MIN_PLACEMENT_MICROVERSION = "1.25"
MIN_PLACEMENT_MICROVERSION = "1.28"
class UpgradeCheckCode(enum.IntEnum):

View File

@ -47,12 +47,12 @@ _RE_INV_IN_USE = re.compile("Inventory for (.+) on resource provider "
"(.+) in use")
WARN_EVERY = 10
PLACEMENT_CLIENT_SEMAPHORE = 'placement_client'
CONSUMER_GENERATION_VERSION = '1.28'
GRANULAR_AC_VERSION = '1.25'
POST_RPS_RETURNS_PAYLOAD_API_VERSION = '1.20'
AGGREGATE_GENERATION_VERSION = '1.19'
NESTED_PROVIDER_API_VERSION = '1.14'
POST_ALLOCATIONS_API_VERSION = '1.13'
ALLOCATION_PROJECT_USER = '1.12'
AggInfo = collections.namedtuple('AggInfo', ['aggregates', 'generation'])
TraitInfo = collections.namedtuple('TraitInfo', ['traits', 'generation'])
@ -1527,25 +1527,28 @@ class SchedulerReportClient(object):
@safe_connect
def get_allocations_for_consumer(self, context, consumer,
include_project_user=False):
include_generation=False):
"""Makes a GET /allocations/{consumer} call to Placement.
:param context: The nova.context.RequestContext auth context
:param consumer: UUID of the consumer resource
:param include_project_user: True if the response should be the
full allocations response including project_id and user_id (new
in microversion 1.12), False if only the "allocations" dict from
:param include_generation: True if the response should be the
full allocations response including ``consumer_generation`` (new
in microversion 1.28), False if only the "allocations" dict from
the response body should be returned.
:returns: dict, see ``include_project_user`` for details on format;
:returns: dict, see ``include_generation`` for details on format;
returns None if unable to connect to Placement (see safe_connect)
"""
url = '/allocations/%s' % consumer
resp = self.get(url, version=ALLOCATION_PROJECT_USER,
global_request_id=context.global_id)
resp = self.get(
url, version=CONSUMER_GENERATION_VERSION,
global_request_id=context.global_id)
if not resp:
return {}
else:
if include_project_user:
# TODO(efried): refactor all callers to accept the whole response
# so we can get rid of this condition
if include_generation:
return resp.json()
return resp.json()['allocations']

View File

@ -229,6 +229,12 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
vcpu_data = usage_data[res_class]
self.assertEqual(2, vcpu_data)
# Check that we can get allocations for the consumer with
# the generation.
allocations = self.client.get_allocations_for_consumer(
self.context, self.instance.uuid, include_generation=True)
self.assertIn('consumer_generation', allocations)
# Delete allocations with our instance
self.client.update_instance_allocation(
self.context, self.compute_node, self.instance, -1)

View File

@ -209,7 +209,7 @@ class TestPlacementCheck(test.NoDBTestCase):
"versions": [
{
"min_version": "1.0",
"max_version": "1.25",
"max_version": status.MIN_PLACEMENT_MICROVERSION,
"id": "v1.0"
}
]
@ -229,7 +229,7 @@ class TestPlacementCheck(test.NoDBTestCase):
"versions": [
{
"min_version": "1.0",
"max_version": "1.25",
"max_version": status.MIN_PLACEMENT_MICROVERSION,
"id": "v1.0"
}
]
@ -250,8 +250,8 @@ class TestPlacementCheck(test.NoDBTestCase):
}
res = self.cmd._check_placement()
self.assertEqual(status.UpgradeCheckCode.FAILURE, res.code)
self.assertIn('Placement API version 1.25 needed, you have 0.9',
res.details)
self.assertIn('Placement API version %s needed, you have 0.9' %
status.MIN_PLACEMENT_MICROVERSION, res.details)
class TestUpgradeCheckBasic(test.NoDBTestCase):

View File

@ -3271,7 +3271,7 @@ class TestAllocations(SchedulerReportClientTestCase):
self.client.update_instance_allocation(self.context, cn, inst, 1)
self.assertFalse(mock_put.called)
mock_get.assert_called_once_with(
'/allocations/%s' % inst.uuid, version='1.12',
'/allocations/%s' % inst.uuid, version='1.28',
global_request_id=self.context.global_id)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'

View File

@ -2571,13 +2571,13 @@ class TestNovaManagePlacement(test.NoDBTestCase):
self.assertIn('Processed 1 instances.', self.output.getvalue())
mock_get_allocs.assert_called_once_with(
test.MatchType(context.RequestContext), uuidsentinel.instance,
include_project_user=True)
include_generation=True)
expected_put_data = mock_get_allocs.return_value
expected_put_data['project_id'] = 'fake-project'
expected_put_data['user_id'] = 'fake-user'
mock_put.assert_called_once_with(
'/allocations/%s' % uuidsentinel.instance, expected_put_data,
version='1.12')
version='1.28')
@mock.patch('nova.objects.CellMappingList.get_all',
return_value=objects.CellMappingList(objects=[
@ -2623,13 +2623,13 @@ class TestNovaManagePlacement(test.NoDBTestCase):
'Inventory and/or allocations changed', self.output.getvalue())
mock_get_allocs.assert_called_once_with(
test.MatchType(context.RequestContext), uuidsentinel.instance,
include_project_user=True)
include_generation=True)
expected_put_data = mock_get_allocs.return_value
expected_put_data['project_id'] = 'fake-project'
expected_put_data['user_id'] = 'fake-user'
mock_put.assert_called_once_with(
'/allocations/%s' % uuidsentinel.instance, expected_put_data,
version='1.12')
version='1.28')
class TestNovaManageMain(test.NoDBTestCase):