From 982c60dd0268232927cbed682fabe933923b9e8e Mon Sep 17 00:00:00 2001 From: Matt Riedemann Date: Thu, 11 Apr 2019 20:19:34 -0400 Subject: [PATCH] Add --instance option to heal_allocations This resolves one of the TODOs in the heal_allocations CLI by adding an --instance option to the command which, when specified, will process just the single instance given. Change-Id: Icf57f217f03ac52b1443addc34aa5128661a8554 (cherry picked from commit c92b297896161e11b6e423a3ae50f7d6c21cb5bd) (cherry picked from commit 92645be4bb2f580b7499b1d7d162abf8caf2cdd6) --- doc/source/cli/nova-manage.rst | 5 +- nova/cmd/manage.py | 48 +++++++++++++---- nova/tests/functional/test_nova_manage.py | 53 +++++++++++++++++++ ...ations-instance-uuid-9aa93fdef5015c64.yaml | 6 +++ 4 files changed, 100 insertions(+), 12 deletions(-) create mode 100644 releasenotes/notes/heal-allocations-instance-uuid-9aa93fdef5015c64.yaml diff --git a/doc/source/cli/nova-manage.rst b/doc/source/cli/nova-manage.rst index abd9b27af29d..bcaa16001431 100644 --- a/doc/source/cli/nova-manage.rst +++ b/doc/source/cli/nova-manage.rst @@ -299,7 +299,7 @@ Nova Cells v2 Placement ~~~~~~~~~ -``nova-manage placement heal_allocations [--max-count ] [--verbose] [--dry-run]`` +``nova-manage placement heal_allocations [--max-count ] [--verbose] [--dry-run] [--instance ]`` Iterates over non-cell0 cells looking for instances which do not have allocations in the Placement service and which are not undergoing a task state transition. For each instance found, allocations are created against @@ -321,6 +321,9 @@ Placement Specify ``--dry-run`` to print output but not commit any changes. The return code should be 4. + Specify ``--instance`` to process a specific instance given its UUID. If + specified the ``--max-count`` option has no effect. + This command requires that the ``[api_database]/connection`` and ``[placement]`` configuration options are set. Placement API >= 1.28 is required. diff --git a/nova/cmd/manage.py b/nova/cmd/manage.py index 806aeccdadd1..7a4d86c155a6 100644 --- a/nova/cmd/manage.py +++ b/nova/cmd/manage.py @@ -1913,7 +1913,7 @@ class PlacementCommands(object): instance=instance.uuid, provider=node_uuid) def _heal_instances_in_cell(self, ctxt, max_count, unlimited, output, - placement, dry_run): + placement, dry_run, instance_uuid): """Checks for instances to heal in a given cell. :param ctxt: cell-targeted nova.context.RequestContext @@ -1925,6 +1925,7 @@ class PlacementCommands(object): to communicate with the Placement service API. :param dry_run: Process instances and print output but do not commit any changes. + :param instance_uuid: UUID of a specific instance to process. :return: Number of instances that had allocations created. :raises: nova.exception.ComputeHostNotFound if a compute node for a given instance cannot be found @@ -1950,6 +1951,8 @@ class PlacementCommands(object): # automatically pick up where we left off without the user having # to pass it in (if unlimited is False). filters = {'deleted': False} + if instance_uuid: + filters['uuid'] = instance_uuid instances = objects.InstanceList.get_by_filters( ctxt, filters=filters, sort_key='created_at', sort_dir='asc', limit=max_count, expected_attrs=['flavor']) @@ -1968,7 +1971,8 @@ class PlacementCommands(object): # don't include instances that already have allocations in the # max_count number, only the number of instances that have # successfully created allocations. - if not unlimited and num_processed == max_count: + # If a specific instance was requested we return here as well. + if (not unlimited and num_processed == max_count) or instance_uuid: return num_processed # Use a marker to get the next page of instances in this cell. @@ -2001,7 +2005,11 @@ class PlacementCommands(object): @args('--dry-run', action='store_true', dest='dry_run', default=False, help='Runs the command and prints output but does not commit any ' 'changes. The return code should be 4.') - def heal_allocations(self, max_count=None, verbose=False, dry_run=False): + @args('--instance', metavar='', dest='instance_uuid', + help='UUID of a specific instance to process. If specified ' + '--max-count has no effect.') + def heal_allocations(self, max_count=None, verbose=False, dry_run=False, + instance_uuid=None): """Heals instance allocations in the Placement service Return codes: @@ -2017,7 +2025,6 @@ class PlacementCommands(object): # NOTE(mriedem): Thoughts on ways to expand this: # - allow passing a specific cell to heal # - allow filtering on enabled/disabled cells - # - allow passing a specific instance to heal # - add a force option to force allocations for instances which have # task_state is not None (would get complicated during a migration); # for example, this could cleanup ironic instances that have @@ -2036,7 +2043,10 @@ class PlacementCommands(object): # count, should we have separate options to be specific, i.e. --total # and --batch-size? Then --batch-size defaults to 50 and --total # defaults to None to mean unlimited. - if max_count is not None: + if instance_uuid: + max_count = 1 + unlimited = False + elif max_count is not None: try: max_count = int(max_count) except ValueError: @@ -2051,10 +2061,24 @@ class PlacementCommands(object): output(_('Running batches of %i until complete') % max_count) ctxt = context.get_admin_context() - cells = objects.CellMappingList.get_all(ctxt) - if not cells: - output(_('No cells to process.')) - return 4 + # If we are going to process a specific instance, just get the cell + # it is in up front. + if instance_uuid: + try: + im = objects.InstanceMapping.get_by_instance_uuid( + ctxt, instance_uuid) + cells = objects.CellMappingList(objects=[im.cell_mapping]) + except exception.InstanceMappingNotFound: + print('Unable to find cell for instance %s, is it mapped? Try ' + 'running "nova-manage cell_v2 verify_instance" or ' + '"nova-manage cell_v2 map_instances".' % + instance_uuid) + return 127 + else: + cells = objects.CellMappingList.get_all(ctxt) + if not cells: + output(_('No cells to process.')) + return 4 placement = report.SchedulerReportClient() num_processed = 0 @@ -2077,7 +2101,7 @@ class PlacementCommands(object): try: num_processed += self._heal_instances_in_cell( cctxt, limit_per_cell, unlimited, output, placement, - dry_run) + dry_run, instance_uuid) except exception.ComputeHostNotFound as e: print(e.format_message()) return 2 @@ -2090,7 +2114,9 @@ class PlacementCommands(object): # don't include instances that already have allocations in the # max_count number, only the number of instances that have # successfully created allocations. - if num_processed == max_count: + # If a specific instance was provided then we'll just exit + # the loop and process it below (either return 4 or 0). + if num_processed == max_count and not instance_uuid: output(_('Max count reached. Processed %s instances.') % num_processed) return 1 diff --git a/nova/tests/functional/test_nova_manage.py b/nova/tests/functional/test_nova_manage.py index 3b7a6c531f6c..7eeb72214705 100644 --- a/nova/tests/functional/test_nova_manage.py +++ b/nova/tests/functional/test_nova_manage.py @@ -674,6 +674,59 @@ class TestNovaManagePlacementHealAllocations( self.assertIn('[dry-run] Create allocations for instance %s on ' 'provider %s' % (server['id'], rp_uuid), output) + def test_heal_allocations_specific_instance(self): + """Tests the case that a specific instance is processed and only that + instance even though there are two which require processing. + """ + # Create one that we won't process. + self._boot_and_assert_no_allocations( + self.flavor, 'cell1') + # Create another that we will process specifically. + server, _ = self._boot_and_assert_no_allocations( + self.flavor, 'cell1') + # First do a dry run to make sure two instances need processing. + result = self.cli.heal_allocations( + max_count=2, verbose=True, dry_run=True) + # Nothing changed so the return code should be 4. + self.assertEqual(4, result, self.output.getvalue()) + output = self.output.getvalue() + self.assertIn('Found 2 candidate instances', output) + + # Now run with our specific instance and it should be the only one + # processed. Also run with max_count specified to show it's ignored. + result = self.cli.heal_allocations( + max_count=10, verbose=True, instance_uuid=server['id']) + output = self.output.getvalue() + self.assertEqual(0, result, self.output.getvalue()) + self.assertIn('Found 1 candidate instances', output) + self.assertIn('Processed 1 instances.', output) + # There shouldn't be any messages about running in batches. + self.assertNotIn('Running batches', output) + # There shouldn't be any message about max count reached. + self.assertNotIn('Max count reached.', output) + + # Now run it again on the specific instance and it should be done. + result = self.cli.heal_allocations( + verbose=True, instance_uuid=server['id']) + output = self.output.getvalue() + self.assertEqual(4, result, self.output.getvalue()) + self.assertIn('Found 1 candidate instances', output) + self.assertIn('Processed 0 instances.', output) + # There shouldn't be any message about max count reached. + self.assertNotIn('Max count reached.', output) + + # Delete the instance mapping and make sure that results in an error + # when we run the command. + ctxt = context.get_admin_context() + im = objects.InstanceMapping.get_by_instance_uuid(ctxt, server['id']) + im.destroy() + result = self.cli.heal_allocations( + verbose=True, instance_uuid=server['id']) + output = self.output.getvalue() + self.assertEqual(127, result, self.output.getvalue()) + self.assertIn('Unable to find cell for instance %s, is it mapped?' % + server['id'], output) + class TestNovaManagePlacementSyncAggregates( integrated_helpers.ProviderUsageBaseTestCase): diff --git a/releasenotes/notes/heal-allocations-instance-uuid-9aa93fdef5015c64.yaml b/releasenotes/notes/heal-allocations-instance-uuid-9aa93fdef5015c64.yaml new file mode 100644 index 000000000000..4517ea057b3a --- /dev/null +++ b/releasenotes/notes/heal-allocations-instance-uuid-9aa93fdef5015c64.yaml @@ -0,0 +1,6 @@ +--- +other: + - | + An ``--instance`` option has been added to the + ``nova-manage placement heal_allocations`` CLI which allows running the + command on a specific instance given its UUID.