Prepare _heal_allocations_for_instance for nested allocations

When no allocations exist for an instance the current heal code uses a
report client call that can only handle allocations from a single RP.
This call is now replaced with a more generic one so in a later patch
port allocations can be added to this code path too.

Related-Bug: #1819923
Change-Id: Ide343c1c922dac576b1944827dc24caefab59b74
This commit is contained in:
Balazs Gibizer 2019-03-18 16:49:57 +01:00 committed by Balazs Gibizer
parent 9adcf53210
commit 307999c581
5 changed files with 119 additions and 120 deletions

View File

@ -1674,10 +1674,16 @@ class PlacementCommands(object):
{'instance': instance.uuid, 'node_uuid': node_uuid,
'resources': resources})
else:
if placement.put_allocations(
ctxt, node_uuid, instance.uuid, resources,
instance.project_id, instance.user_id,
consumer_generation=None):
payload = {
'allocations': {
node_uuid: {'resources': resources},
},
'project_id': instance.project_id,
'user_id': instance.user_id,
'consumer_generation': None
}
resp = placement.put_allocations(ctxt, instance.uuid, payload)
if resp:
output(_('Successfully created allocations for '
'instance %(instance)s against resource '
'provider %(provider)s.') %
@ -1688,21 +1694,16 @@ class PlacementCommands(object):
instance=instance.uuid, provider=node_uuid)
def _heal_missing_project_and_user_id(
self, allocations, instance, dry_run, output, placement):
self, ctxt, allocations, instance, dry_run, output, placement):
allocations['project_id'] = instance.project_id
allocations['user_id'] = instance.user_id
# We use CONSUMER_GENERATION_VERSION for PUT
# /allocations/{consumer_id} to mirror the body structure from
# get_allocs_for_consumer.
if dry_run:
output(_('[dry-run] Update allocations for instance '
'%(instance)s: %(allocations)s') %
{'instance': instance.uuid, 'allocations': allocations})
else:
resp = placement.put(
'/allocations/%s' % instance.uuid,
allocations, version=report.CONSUMER_GENERATION_VERSION)
resp = placement.put_allocations(ctxt, instance.uuid, allocations)
if resp:
output(_('Successfully updated allocations for '
'instance %s.') % instance.uuid)
@ -1772,7 +1773,7 @@ class PlacementCommands(object):
# because we don't want to mess up shared or nested
# provider allocations.
return self._heal_missing_project_and_user_id(
allocations, instance, dry_run, output, placement)
ctxt, allocations, instance, dry_run, output, placement)
output(_('Instance %s already has allocations with '
'matching consumer project/user.') % instance.uuid)

View File

@ -1930,27 +1930,17 @@ class SchedulerReportClient(object):
'text': r.text})
return r.status_code == 204
# TODO(gibi): kill safe_connect
@safe_connect
@retries
def put_allocations(self, context, rp_uuid, consumer_uuid, alloc_data,
project_id, user_id, consumer_generation):
"""Creates allocation records for the supplied instance UUID against
the supplied resource provider.
:note Currently we only allocate against a single resource provider.
Once shared storage and things like NUMA allocations are a
reality, this will change to allocate against multiple providers.
def put_allocations(self, context, consumer_uuid, payload):
"""Creates allocation records for the supplied consumer UUID based on
the provided allocation dict
:param context: The security context
:param rp_uuid: The UUID of the resource provider to allocate against.
:param consumer_uuid: The instance's UUID.
:param alloc_data: Dict, keyed by resource class, of amounts to
consume.
:param project_id: The project_id associated with the allocations.
:param user_id: The user_id associated with the allocations.
:param consumer_generation: The current generation of the consumer or
None if this the initial allocation of the
consumer
:param payload: Dict in the format expected by the placement
PUT /allocations/{consumer_uuid} API
:returns: True if the allocations were created, False otherwise.
:raises: Retry if the operation should be retried due to a concurrent
resource provider update.
@ -1958,14 +1948,6 @@ class SchedulerReportClient(object):
generation conflict
"""
payload = {
'allocations': {
rp_uuid: {'resources': alloc_data},
},
'project_id': project_id,
'user_id': user_id,
'consumer_generation': consumer_generation
}
r = self._put_allocations(context, consumer_uuid, payload)
if r.status_code != 204:
err = r.json()['errors'][0]

View File

@ -243,10 +243,16 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
# Update allocations with our instance
alloc_dict = utils.resources_from_flavor(self.instance,
self.instance.flavor)
payload = {
"allocations": {
self.compute_uuid: {"resources": alloc_dict}
},
"project_id": self.instance.project_id,
"user_id": self.instance.user_id,
"consumer_generation": None
}
self.client.put_allocations(
self.context, self.compute_uuid, self.instance_uuid,
alloc_dict, self.instance.project_id, self.instance.user_id,
None)
self.context, self.instance_uuid, payload)
# Check that allocations were made
resp = self.client.get('/allocations/%s' % self.instance_uuid)
@ -685,13 +691,18 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
inv,
self.client._get_inventory(
self.context, uuids.cn)['inventories'])
payload = {
"allocations": {
uuids.cn: {"resources": {orc.SRIOV_NET_VF: 1}}
},
"project_id": uuids.proj,
"user_id": uuids.user,
"consumer_generation": None
}
# Now set up an InventoryInUse case by creating a VF allocation...
self.assertTrue(
self.client.put_allocations(
self.context, uuids.cn, uuids.consumer,
{orc.SRIOV_NET_VF: 1},
uuids.proj, uuids.user, None))
self.context, uuids.consumer, payload))
# ...and trying to delete the provider's VF inventory
bad_inv = {
'CUSTOM_BANDWIDTH': {

View File

@ -269,14 +269,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
consumer_uuid = mock.sentinel.consumer
data = {"MEMORY_MB": 1024}
expected_url = "/allocations/%s" % consumer_uuid
resp = self.client.put_allocations(self.context, rp_uuid,
consumer_uuid, data,
mock.sentinel.project_id,
mock.sentinel.user_id,
mock.sentinel.consumer_generation)
payload = {
"allocations": {
rp_uuid: {"resources": data}
},
"project_id": mock.sentinel.project_id,
"user_id": mock.sentinel.user_id,
"consumer_generation": mock.sentinel.consumer_generation
}
resp = self.client.put_allocations(
self.context, consumer_uuid, payload)
self.assertTrue(resp)
mock_put.assert_called_once_with(
expected_url, mock.ANY, version='1.28',
expected_url, payload, version='1.28',
global_request_id=self.context.global_id)
@mock.patch.object(report.LOG, 'warning')
@ -288,14 +293,20 @@ class TestPutAllocations(SchedulerReportClientTestCase):
consumer_uuid = mock.sentinel.consumer
data = {"MEMORY_MB": 1024}
expected_url = "/allocations/%s" % consumer_uuid
resp = self.client.put_allocations(self.context, rp_uuid,
consumer_uuid, data,
mock.sentinel.project_id,
mock.sentinel.user_id,
mock.sentinel.consumer_generation)
payload = {
"allocations": {
rp_uuid: {"resources": data}
},
"project_id": mock.sentinel.project_id,
"user_id": mock.sentinel.user_id,
"consumer_generation": mock.sentinel.consumer_generation
}
resp = self.client.put_allocations(
self.context, consumer_uuid, payload)
self.assertFalse(resp)
mock_put.assert_called_once_with(
expected_url, mock.ANY, version='1.28',
expected_url, payload, version='1.28',
global_request_id=self.context.global_id)
log_msg = mock_warn.call_args[0][0]
self.assertIn("Failed to save allocation for", log_msg)
@ -313,13 +324,17 @@ class TestPutAllocations(SchedulerReportClientTestCase):
consumer_uuid = mock.sentinel.consumer
data = {"MEMORY_MB": 1024}
expected_url = "/allocations/%s" % consumer_uuid
payload = {
"allocations": {
rp_uuid: {"resources": data}
},
"project_id": mock.sentinel.project_id,
"user_id": mock.sentinel.user_id,
"consumer_generation": mock.sentinel.consumer_generation
}
self.assertRaises(exception.AllocationUpdateFailed,
self.client.put_allocations,
self.context, rp_uuid,
consumer_uuid, data,
mock.sentinel.project_id,
mock.sentinel.user_id,
mock.sentinel.consumer_generation)
self.context, consumer_uuid, payload)
mock_put.assert_called_once_with(
expected_url, mock.ANY, version='1.28',
@ -343,14 +358,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
consumer_uuid = mock.sentinel.consumer
data = {"MEMORY_MB": 1024}
expected_url = "/allocations/%s" % consumer_uuid
resp = self.client.put_allocations(self.context, rp_uuid,
consumer_uuid, data,
mock.sentinel.project_id,
mock.sentinel.user_id,
mock.sentinel.consumer_generation)
payload = {
"allocations": {
rp_uuid: {"resources": data}
},
"project_id": mock.sentinel.project_id,
"user_id": mock.sentinel.user_id,
"consumer_generation": mock.sentinel.consumer_generation
}
resp = self.client.put_allocations(
self.context, consumer_uuid, payload)
self.assertTrue(resp)
mock_put.assert_has_calls([
mock.call(expected_url, mock.ANY, version='1.28',
mock.call(expected_url, payload, version='1.28',
global_request_id=self.context.global_id)] * 2)
@mock.patch('time.sleep', new=mock.Mock())
@ -369,14 +389,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
consumer_uuid = mock.sentinel.consumer
data = {"MEMORY_MB": 1024}
expected_url = "/allocations/%s" % consumer_uuid
resp = self.client.put_allocations(self.context, rp_uuid,
consumer_uuid, data,
mock.sentinel.project_id,
mock.sentinel.user_id,
mock.sentinel.consumer_generation)
payload = {
"allocations": {
rp_uuid: {"resources": data}
},
"project_id": mock.sentinel.project_id,
"user_id": mock.sentinel.user_id,
"consumer_generation": mock.sentinel.consumer_generation
}
resp = self.client.put_allocations(
self.context, consumer_uuid, payload)
self.assertFalse(resp)
mock_put.assert_has_calls([
mock.call(expected_url, mock.ANY, version='1.28',
mock.call(expected_url, payload, version='1.28',
global_request_id=self.context.global_id)] * 3)
def test_claim_resources_success(self):

View File

@ -21,6 +21,7 @@ import ddt
import fixtures
import mock
from oslo_db import exception as db_exc
from oslo_serialization import jsonutils
from oslo_utils.fixture import uuidsentinel
from oslo_utils import uuidutils
from six.moves import StringIO
@ -2469,8 +2470,9 @@ class TestNovaManagePlacement(test.NoDBTestCase):
return_value=objects.ComputeNode(uuid=uuidsentinel.node))
@mock.patch('nova.scheduler.utils.resources_from_flavor',
return_value=mock.sentinel.resources)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put_allocations', return_value=False)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
return_value=fake_requests.FakeResponse(
500, content=jsonutils.dumps({"errors": [{"code": ""}]})))
def test_heal_allocations_put_allocations_fails(
self, mock_put_allocations, mock_res_from_flavor,
mock_get_compute_node, mock_get_allocs, mock_get_instances,
@ -2481,46 +2483,20 @@ class TestNovaManagePlacement(test.NoDBTestCase):
instance = mock_get_instances.return_value[0]
mock_res_from_flavor.assert_called_once_with(
instance, instance.flavor)
mock_put_allocations.assert_called_once_with(
test.MatchType(context.RequestContext), uuidsentinel.node,
uuidsentinel.instance, mock.sentinel.resources, 'fake-project',
'fake-user', consumer_generation=None)
@mock.patch('nova.objects.CellMappingList.get_all',
return_value=objects.CellMappingList(objects=[
objects.CellMapping(name='cell1',
uuid=uuidsentinel.cell1)]))
@mock.patch('nova.objects.InstanceList.get_by_filters',
return_value=objects.InstanceList(objects=[
objects.Instance(
uuid=uuidsentinel.instance, host='fake', node='fake',
task_state=None, flavor=objects.Flavor(),
project_id='fake-project', user_id='fake-user')]))
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'get_allocs_for_consumer', return_value={})
@mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
return_value=objects.ComputeNode(uuid=uuidsentinel.node))
@mock.patch('nova.scheduler.utils.resources_from_flavor',
return_value=mock.sentinel.resources)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put_allocations',
side_effect=exception.AllocationUpdateFailed(
consumer_uuid=uuidsentinel.instance,
error="consumer generation conflict"))
def test_heal_allocations_put_allocations_fails_with_consumer_conflict(
self, mock_put_allocations, mock_res_from_flavor,
mock_get_compute_node, mock_get_allocs, mock_get_instances,
mock_get_all_cells):
self.assertEqual(3, self.cli.heal_allocations())
self.assertIn('Failed to update allocations for consumer',
self.output.getvalue())
instance = mock_get_instances.return_value[0]
mock_res_from_flavor.assert_called_once_with(
instance, instance.flavor)
expected_payload = {
'allocations': {
uuidsentinel.node: {
'resources': mock.sentinel.resources
}
},
'user_id': 'fake-user',
'project_id': 'fake-project',
'consumer_generation': None
}
mock_put_allocations.assert_called_once_with(
test.MatchType(context.RequestContext), uuidsentinel.node,
uuidsentinel.instance, mock.sentinel.resources, 'fake-project',
'fake-user', consumer_generation=None)
'/allocations/%s' % instance.uuid, expected_payload,
global_request_id=mock.ANY, version='1.28')
@mock.patch('nova.objects.CellMappingList.get_all',
new=mock.Mock(return_value=objects.CellMappingList(objects=[
@ -2560,8 +2536,8 @@ class TestNovaManagePlacement(test.NoDBTestCase):
return_value=objects.ComputeNode(uuid=uuidsentinel.node)))
@mock.patch('nova.scheduler.utils.resources_from_flavor',
new=mock.Mock(return_value=mock.sentinel.resources))
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
'put_allocations', return_value=True)
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
return_value=fake_requests.FakeResponse(204))
def test_heal_allocations_get_allocs_retrieval_fails(self, mock_put,
mock_getinst):
# This "succeeds"
@ -2612,7 +2588,8 @@ class TestNovaManagePlacement(test.NoDBTestCase):
}
},
"project_id": uuidsentinel.project_id,
"user_id": uuidsentinel.user_id
"user_id": uuidsentinel.user_id,
"consumer_generation": 12,
}
self.assertEqual(0, self.cli.heal_allocations(verbose=True))
self.assertIn('Processed 1 instances.', self.output.getvalue())
@ -2623,7 +2600,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
expected_put_data['user_id'] = 'fake-user'
mock_put.assert_called_once_with(
'/allocations/%s' % uuidsentinel.instance, expected_put_data,
version='1.28')
global_request_id=mock.ANY, version='1.28')
@mock.patch('nova.objects.CellMappingList.get_all',
return_value=objects.CellMappingList(objects=[
@ -2639,8 +2616,11 @@ class TestNovaManagePlacement(test.NoDBTestCase):
'get_allocs_for_consumer')
@mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
return_value=fake_requests.FakeResponse(
409, content='Inventory and/or allocations changed while '
'attempting to allocate'))
409,
content=jsonutils.dumps(
{"errors": [
{"code": "placement.concurrent_update",
"detail": "consumer generation conflict"}]})))
def test_heal_allocations_put_fails(
self, mock_put, mock_get_allocs, mock_get_instances,
mock_get_all_cells):
@ -2666,7 +2646,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
}
self.assertEqual(3, self.cli.heal_allocations(verbose=True))
self.assertIn(
'Inventory and/or allocations changed', self.output.getvalue())
'consumer generation conflict', self.output.getvalue())
mock_get_allocs.assert_called_once_with(
test.MatchType(context.RequestContext), uuidsentinel.instance)
expected_put_data = mock_get_allocs.return_value
@ -2674,7 +2654,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
expected_put_data['user_id'] = 'fake-user'
mock_put.assert_called_once_with(
'/allocations/%s' % uuidsentinel.instance, expected_put_data,
version='1.28')
global_request_id=mock.ANY, version='1.28')
@mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
return_value=objects.AggregateList(objects=[