Browse Source

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
tags/20.0.0.0rc1
Balazs Gibizer 7 months ago
parent
commit
307999c581

+ 13
- 12
nova/cmd/manage.py View File

@@ -1674,10 +1674,16 @@ class PlacementCommands(object):
1674 1674
                    {'instance': instance.uuid, 'node_uuid': node_uuid,
1675 1675
                     'resources': resources})
1676 1676
         else:
1677
-            if placement.put_allocations(
1678
-                    ctxt, node_uuid, instance.uuid, resources,
1679
-                    instance.project_id, instance.user_id,
1680
-                    consumer_generation=None):
1677
+            payload = {
1678
+                'allocations': {
1679
+                    node_uuid: {'resources': resources},
1680
+                },
1681
+                'project_id': instance.project_id,
1682
+                'user_id': instance.user_id,
1683
+                'consumer_generation': None
1684
+            }
1685
+            resp = placement.put_allocations(ctxt, instance.uuid, payload)
1686
+            if resp:
1681 1687
                 output(_('Successfully created allocations for '
1682 1688
                          'instance %(instance)s against resource '
1683 1689
                          'provider %(provider)s.') %
@@ -1688,21 +1694,16 @@ class PlacementCommands(object):
1688 1694
                     instance=instance.uuid, provider=node_uuid)
1689 1695
 
1690 1696
     def _heal_missing_project_and_user_id(
1691
-            self, allocations, instance, dry_run, output, placement):
1697
+            self, ctxt, allocations, instance, dry_run, output, placement):
1692 1698
 
1693 1699
         allocations['project_id'] = instance.project_id
1694 1700
         allocations['user_id'] = instance.user_id
1695
-        # We use CONSUMER_GENERATION_VERSION for PUT
1696
-        # /allocations/{consumer_id} to mirror the body structure from
1697
-        # get_allocs_for_consumer.
1698 1701
         if dry_run:
1699 1702
             output(_('[dry-run] Update allocations for instance '
1700 1703
                      '%(instance)s: %(allocations)s') %
1701 1704
                    {'instance': instance.uuid, 'allocations': allocations})
1702 1705
         else:
1703
-            resp = placement.put(
1704
-                '/allocations/%s' % instance.uuid,
1705
-                allocations, version=report.CONSUMER_GENERATION_VERSION)
1706
+            resp = placement.put_allocations(ctxt, instance.uuid, allocations)
1706 1707
             if resp:
1707 1708
                 output(_('Successfully updated allocations for '
1708 1709
                          'instance %s.') % instance.uuid)
@@ -1772,7 +1773,7 @@ class PlacementCommands(object):
1772 1773
             # because we don't want to mess up shared or nested
1773 1774
             # provider allocations.
1774 1775
             return self._heal_missing_project_and_user_id(
1775
-                allocations, instance, dry_run, output, placement)
1776
+                ctxt, allocations, instance, dry_run, output, placement)
1776 1777
 
1777 1778
         output(_('Instance %s already has allocations with '
1778 1779
                  'matching consumer project/user.') % instance.uuid)

+ 6
- 24
nova/scheduler/client/report.py View File

@@ -1930,27 +1930,17 @@ class SchedulerReportClient(object):
1930 1930
                      'text': r.text})
1931 1931
         return r.status_code == 204
1932 1932
 
1933
+    # TODO(gibi): kill safe_connect
1933 1934
     @safe_connect
1934 1935
     @retries
1935
-    def put_allocations(self, context, rp_uuid, consumer_uuid, alloc_data,
1936
-                        project_id, user_id, consumer_generation):
1937
-        """Creates allocation records for the supplied instance UUID against
1938
-        the supplied resource provider.
1939
-
1940
-        :note Currently we only allocate against a single resource provider.
1941
-              Once shared storage and things like NUMA allocations are a
1942
-              reality, this will change to allocate against multiple providers.
1936
+    def put_allocations(self, context, consumer_uuid, payload):
1937
+        """Creates allocation records for the supplied consumer UUID based on
1938
+        the provided allocation dict
1943 1939
 
1944 1940
         :param context: The security context
1945
-        :param rp_uuid: The UUID of the resource provider to allocate against.
1946 1941
         :param consumer_uuid: The instance's UUID.
1947
-        :param alloc_data: Dict, keyed by resource class, of amounts to
1948
-                           consume.
1949
-        :param project_id: The project_id associated with the allocations.
1950
-        :param user_id: The user_id associated with the allocations.
1951
-        :param consumer_generation: The current generation of the consumer or
1952
-                                    None if this the initial allocation of the
1953
-                                    consumer
1942
+        :param payload: Dict in the format expected by the placement
1943
+            PUT /allocations/{consumer_uuid} API
1954 1944
         :returns: True if the allocations were created, False otherwise.
1955 1945
         :raises: Retry if the operation should be retried due to a concurrent
1956 1946
                  resource provider update.
@@ -1958,14 +1948,6 @@ class SchedulerReportClient(object):
1958 1948
                                         generation conflict
1959 1949
         """
1960 1950
 
1961
-        payload = {
1962
-            'allocations': {
1963
-                rp_uuid: {'resources': alloc_data},
1964
-            },
1965
-            'project_id': project_id,
1966
-            'user_id': user_id,
1967
-            'consumer_generation': consumer_generation
1968
-        }
1969 1951
         r = self._put_allocations(context, consumer_uuid, payload)
1970 1952
         if r.status_code != 204:
1971 1953
             err = r.json()['errors'][0]

+ 18
- 7
nova/tests/functional/test_report_client.py View File

@@ -243,10 +243,16 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
243 243
             # Update allocations with our instance
244 244
             alloc_dict = utils.resources_from_flavor(self.instance,
245 245
                                                      self.instance.flavor)
246
+            payload = {
247
+                "allocations": {
248
+                    self.compute_uuid: {"resources": alloc_dict}
249
+                },
250
+                "project_id": self.instance.project_id,
251
+                "user_id": self.instance.user_id,
252
+                "consumer_generation": None
253
+            }
246 254
             self.client.put_allocations(
247
-                self.context, self.compute_uuid, self.instance_uuid,
248
-                alloc_dict, self.instance.project_id, self.instance.user_id,
249
-                None)
255
+                self.context, self.instance_uuid, payload)
250 256
 
251 257
             # Check that allocations were made
252 258
             resp = self.client.get('/allocations/%s' % self.instance_uuid)
@@ -685,13 +691,18 @@ class SchedulerReportClientTests(SchedulerReportClientTestBase):
685 691
                 inv,
686 692
                 self.client._get_inventory(
687 693
                     self.context, uuids.cn)['inventories'])
688
-
694
+            payload = {
695
+                "allocations": {
696
+                    uuids.cn: {"resources": {orc.SRIOV_NET_VF: 1}}
697
+                },
698
+                "project_id": uuids.proj,
699
+                "user_id": uuids.user,
700
+                "consumer_generation": None
701
+            }
689 702
             # Now set up an InventoryInUse case by creating a VF allocation...
690 703
             self.assertTrue(
691 704
                 self.client.put_allocations(
692
-                    self.context, uuids.cn, uuids.consumer,
693
-                    {orc.SRIOV_NET_VF: 1},
694
-                    uuids.proj, uuids.user, None))
705
+                    self.context, uuids.consumer, payload))
695 706
             # ...and trying to delete the provider's VF inventory
696 707
             bad_inv = {
697 708
                 'CUSTOM_BANDWIDTH': {

+ 54
- 29
nova/tests/unit/scheduler/client/test_report.py View File

@@ -269,14 +269,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
269 269
         consumer_uuid = mock.sentinel.consumer
270 270
         data = {"MEMORY_MB": 1024}
271 271
         expected_url = "/allocations/%s" % consumer_uuid
272
-        resp = self.client.put_allocations(self.context, rp_uuid,
273
-                                           consumer_uuid, data,
274
-                                           mock.sentinel.project_id,
275
-                                           mock.sentinel.user_id,
276
-                                           mock.sentinel.consumer_generation)
272
+        payload = {
273
+            "allocations": {
274
+                rp_uuid: {"resources": data}
275
+            },
276
+            "project_id": mock.sentinel.project_id,
277
+            "user_id": mock.sentinel.user_id,
278
+            "consumer_generation": mock.sentinel.consumer_generation
279
+        }
280
+        resp = self.client.put_allocations(
281
+            self.context, consumer_uuid, payload)
277 282
         self.assertTrue(resp)
278 283
         mock_put.assert_called_once_with(
279
-            expected_url, mock.ANY, version='1.28',
284
+            expected_url, payload, version='1.28',
280 285
             global_request_id=self.context.global_id)
281 286
 
282 287
     @mock.patch.object(report.LOG, 'warning')
@@ -288,14 +293,20 @@ class TestPutAllocations(SchedulerReportClientTestCase):
288 293
         consumer_uuid = mock.sentinel.consumer
289 294
         data = {"MEMORY_MB": 1024}
290 295
         expected_url = "/allocations/%s" % consumer_uuid
291
-        resp = self.client.put_allocations(self.context, rp_uuid,
292
-                                           consumer_uuid, data,
293
-                                           mock.sentinel.project_id,
294
-                                           mock.sentinel.user_id,
295
-                                           mock.sentinel.consumer_generation)
296
+        payload = {
297
+            "allocations": {
298
+                rp_uuid: {"resources": data}
299
+            },
300
+            "project_id": mock.sentinel.project_id,
301
+            "user_id": mock.sentinel.user_id,
302
+            "consumer_generation": mock.sentinel.consumer_generation
303
+        }
304
+        resp = self.client.put_allocations(
305
+            self.context, consumer_uuid, payload)
306
+
296 307
         self.assertFalse(resp)
297 308
         mock_put.assert_called_once_with(
298
-            expected_url, mock.ANY, version='1.28',
309
+            expected_url, payload, version='1.28',
299 310
             global_request_id=self.context.global_id)
300 311
         log_msg = mock_warn.call_args[0][0]
301 312
         self.assertIn("Failed to save allocation for", log_msg)
@@ -313,13 +324,17 @@ class TestPutAllocations(SchedulerReportClientTestCase):
313 324
         consumer_uuid = mock.sentinel.consumer
314 325
         data = {"MEMORY_MB": 1024}
315 326
         expected_url = "/allocations/%s" % consumer_uuid
327
+        payload = {
328
+            "allocations": {
329
+                rp_uuid: {"resources": data}
330
+            },
331
+            "project_id": mock.sentinel.project_id,
332
+            "user_id": mock.sentinel.user_id,
333
+            "consumer_generation": mock.sentinel.consumer_generation
334
+        }
316 335
         self.assertRaises(exception.AllocationUpdateFailed,
317 336
                           self.client.put_allocations,
318
-                          self.context, rp_uuid,
319
-                          consumer_uuid, data,
320
-                          mock.sentinel.project_id,
321
-                          mock.sentinel.user_id,
322
-                          mock.sentinel.consumer_generation)
337
+                          self.context, consumer_uuid, payload)
323 338
 
324 339
         mock_put.assert_called_once_with(
325 340
             expected_url, mock.ANY, version='1.28',
@@ -343,14 +358,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
343 358
         consumer_uuid = mock.sentinel.consumer
344 359
         data = {"MEMORY_MB": 1024}
345 360
         expected_url = "/allocations/%s" % consumer_uuid
346
-        resp = self.client.put_allocations(self.context, rp_uuid,
347
-                                           consumer_uuid, data,
348
-                                           mock.sentinel.project_id,
349
-                                           mock.sentinel.user_id,
350
-                                           mock.sentinel.consumer_generation)
361
+        payload = {
362
+            "allocations": {
363
+                rp_uuid: {"resources": data}
364
+            },
365
+            "project_id": mock.sentinel.project_id,
366
+            "user_id": mock.sentinel.user_id,
367
+            "consumer_generation": mock.sentinel.consumer_generation
368
+        }
369
+        resp = self.client.put_allocations(
370
+            self.context, consumer_uuid, payload)
351 371
         self.assertTrue(resp)
352 372
         mock_put.assert_has_calls([
353
-            mock.call(expected_url, mock.ANY, version='1.28',
373
+            mock.call(expected_url, payload, version='1.28',
354 374
                       global_request_id=self.context.global_id)] * 2)
355 375
 
356 376
     @mock.patch('time.sleep', new=mock.Mock())
@@ -369,14 +389,19 @@ class TestPutAllocations(SchedulerReportClientTestCase):
369 389
         consumer_uuid = mock.sentinel.consumer
370 390
         data = {"MEMORY_MB": 1024}
371 391
         expected_url = "/allocations/%s" % consumer_uuid
372
-        resp = self.client.put_allocations(self.context, rp_uuid,
373
-                                           consumer_uuid, data,
374
-                                           mock.sentinel.project_id,
375
-                                           mock.sentinel.user_id,
376
-                                           mock.sentinel.consumer_generation)
392
+        payload = {
393
+            "allocations": {
394
+                rp_uuid: {"resources": data}
395
+            },
396
+            "project_id": mock.sentinel.project_id,
397
+            "user_id": mock.sentinel.user_id,
398
+            "consumer_generation": mock.sentinel.consumer_generation
399
+        }
400
+        resp = self.client.put_allocations(
401
+            self.context, consumer_uuid, payload)
377 402
         self.assertFalse(resp)
378 403
         mock_put.assert_has_calls([
379
-            mock.call(expected_url, mock.ANY, version='1.28',
404
+            mock.call(expected_url, payload, version='1.28',
380 405
             global_request_id=self.context.global_id)] * 3)
381 406
 
382 407
     def test_claim_resources_success(self):

+ 28
- 48
nova/tests/unit/test_nova_manage.py View File

@@ -21,6 +21,7 @@ import ddt
21 21
 import fixtures
22 22
 import mock
23 23
 from oslo_db import exception as db_exc
24
+from oslo_serialization import jsonutils
24 25
 from oslo_utils.fixture import uuidsentinel
25 26
 from oslo_utils import uuidutils
26 27
 from six.moves import StringIO
@@ -2469,8 +2470,9 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2469 2470
                 return_value=objects.ComputeNode(uuid=uuidsentinel.node))
2470 2471
     @mock.patch('nova.scheduler.utils.resources_from_flavor',
2471 2472
                 return_value=mock.sentinel.resources)
2472
-    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
2473
-                'put_allocations', return_value=False)
2473
+    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
2474
+                return_value=fake_requests.FakeResponse(
2475
+                    500, content=jsonutils.dumps({"errors": [{"code": ""}]})))
2474 2476
     def test_heal_allocations_put_allocations_fails(
2475 2477
             self, mock_put_allocations, mock_res_from_flavor,
2476 2478
             mock_get_compute_node, mock_get_allocs, mock_get_instances,
@@ -2481,46 +2483,20 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2481 2483
         instance = mock_get_instances.return_value[0]
2482 2484
         mock_res_from_flavor.assert_called_once_with(
2483 2485
             instance, instance.flavor)
2484
-        mock_put_allocations.assert_called_once_with(
2485
-            test.MatchType(context.RequestContext), uuidsentinel.node,
2486
-            uuidsentinel.instance, mock.sentinel.resources, 'fake-project',
2487
-            'fake-user', consumer_generation=None)
2488 2486
 
2489
-    @mock.patch('nova.objects.CellMappingList.get_all',
2490
-                return_value=objects.CellMappingList(objects=[
2491
-                    objects.CellMapping(name='cell1',
2492
-                                        uuid=uuidsentinel.cell1)]))
2493
-    @mock.patch('nova.objects.InstanceList.get_by_filters',
2494
-                return_value=objects.InstanceList(objects=[
2495
-                    objects.Instance(
2496
-                        uuid=uuidsentinel.instance, host='fake', node='fake',
2497
-                        task_state=None, flavor=objects.Flavor(),
2498
-                        project_id='fake-project', user_id='fake-user')]))
2499
-    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
2500
-                'get_allocs_for_consumer', return_value={})
2501
-    @mock.patch('nova.objects.ComputeNode.get_by_host_and_nodename',
2502
-                return_value=objects.ComputeNode(uuid=uuidsentinel.node))
2503
-    @mock.patch('nova.scheduler.utils.resources_from_flavor',
2504
-                return_value=mock.sentinel.resources)
2505
-    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
2506
-                'put_allocations',
2507
-                side_effect=exception.AllocationUpdateFailed(
2508
-                    consumer_uuid=uuidsentinel.instance,
2509
-                    error="consumer generation conflict"))
2510
-    def test_heal_allocations_put_allocations_fails_with_consumer_conflict(
2511
-            self, mock_put_allocations, mock_res_from_flavor,
2512
-            mock_get_compute_node, mock_get_allocs, mock_get_instances,
2513
-            mock_get_all_cells):
2514
-        self.assertEqual(3, self.cli.heal_allocations())
2515
-        self.assertIn('Failed to update allocations for consumer',
2516
-                      self.output.getvalue())
2517
-        instance = mock_get_instances.return_value[0]
2518
-        mock_res_from_flavor.assert_called_once_with(
2519
-            instance, instance.flavor)
2487
+        expected_payload = {
2488
+            'allocations': {
2489
+                uuidsentinel.node: {
2490
+                    'resources': mock.sentinel.resources
2491
+                }
2492
+            },
2493
+            'user_id': 'fake-user',
2494
+            'project_id': 'fake-project',
2495
+            'consumer_generation': None
2496
+        }
2520 2497
         mock_put_allocations.assert_called_once_with(
2521
-            test.MatchType(context.RequestContext), uuidsentinel.node,
2522
-            uuidsentinel.instance, mock.sentinel.resources, 'fake-project',
2523
-            'fake-user', consumer_generation=None)
2498
+            '/allocations/%s' % instance.uuid, expected_payload,
2499
+            global_request_id=mock.ANY, version='1.28')
2524 2500
 
2525 2501
     @mock.patch('nova.objects.CellMappingList.get_all',
2526 2502
                 new=mock.Mock(return_value=objects.CellMappingList(objects=[
@@ -2560,8 +2536,8 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2560 2536
                     return_value=objects.ComputeNode(uuid=uuidsentinel.node)))
2561 2537
     @mock.patch('nova.scheduler.utils.resources_from_flavor',
2562 2538
                 new=mock.Mock(return_value=mock.sentinel.resources))
2563
-    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.'
2564
-                'put_allocations', return_value=True)
2539
+    @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
2540
+                return_value=fake_requests.FakeResponse(204))
2565 2541
     def test_heal_allocations_get_allocs_retrieval_fails(self, mock_put,
2566 2542
                                                          mock_getinst):
2567 2543
         # This "succeeds"
@@ -2612,7 +2588,8 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2612 2588
                 }
2613 2589
             },
2614 2590
             "project_id": uuidsentinel.project_id,
2615
-            "user_id": uuidsentinel.user_id
2591
+            "user_id": uuidsentinel.user_id,
2592
+            "consumer_generation": 12,
2616 2593
         }
2617 2594
         self.assertEqual(0, self.cli.heal_allocations(verbose=True))
2618 2595
         self.assertIn('Processed 1 instances.', self.output.getvalue())
@@ -2623,7 +2600,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2623 2600
         expected_put_data['user_id'] = 'fake-user'
2624 2601
         mock_put.assert_called_once_with(
2625 2602
             '/allocations/%s' % uuidsentinel.instance, expected_put_data,
2626
-            version='1.28')
2603
+            global_request_id=mock.ANY, version='1.28')
2627 2604
 
2628 2605
     @mock.patch('nova.objects.CellMappingList.get_all',
2629 2606
                 return_value=objects.CellMappingList(objects=[
@@ -2639,8 +2616,11 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2639 2616
                 'get_allocs_for_consumer')
2640 2617
     @mock.patch('nova.scheduler.client.report.SchedulerReportClient.put',
2641 2618
                 return_value=fake_requests.FakeResponse(
2642
-                    409, content='Inventory and/or allocations changed while '
2643
-                                 'attempting to allocate'))
2619
+                    409,
2620
+                    content=jsonutils.dumps(
2621
+                        {"errors": [
2622
+                            {"code": "placement.concurrent_update",
2623
+                             "detail": "consumer generation conflict"}]})))
2644 2624
     def test_heal_allocations_put_fails(
2645 2625
             self, mock_put, mock_get_allocs, mock_get_instances,
2646 2626
             mock_get_all_cells):
@@ -2666,7 +2646,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2666 2646
         }
2667 2647
         self.assertEqual(3, self.cli.heal_allocations(verbose=True))
2668 2648
         self.assertIn(
2669
-            'Inventory and/or allocations changed', self.output.getvalue())
2649
+            'consumer generation conflict', self.output.getvalue())
2670 2650
         mock_get_allocs.assert_called_once_with(
2671 2651
             test.MatchType(context.RequestContext), uuidsentinel.instance)
2672 2652
         expected_put_data = mock_get_allocs.return_value
@@ -2674,7 +2654,7 @@ class TestNovaManagePlacement(test.NoDBTestCase):
2674 2654
         expected_put_data['user_id'] = 'fake-user'
2675 2655
         mock_put.assert_called_once_with(
2676 2656
             '/allocations/%s' % uuidsentinel.instance, expected_put_data,
2677
-            version='1.28')
2657
+            global_request_id=mock.ANY, version='1.28')
2678 2658
 
2679 2659
     @mock.patch('nova.compute.api.AggregateAPI.get_aggregate_list',
2680 2660
                 return_value=objects.AggregateList(objects=[

Loading…
Cancel
Save