diff --git a/releasenotes/notes/overcloud-node-clean-981790791a0d0246.yaml b/releasenotes/notes/overcloud-node-clean-981790791a0d0246.yaml new file mode 100644 index 000000000..c48e9e0ae --- /dev/null +++ b/releasenotes/notes/overcloud-node-clean-981790791a0d0246.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Adds new command to run metadata cleaning on nodes:: + + openstack overcloud node clean [--all-manageable|uuid1,uuid2,..] diff --git a/setup.cfg b/setup.cfg index de2aa63b4..ecd30a996 100644 --- a/setup.cfg +++ b/setup.cfg @@ -64,6 +64,7 @@ openstack.tripleoclient.v1 = overcloud_node_introspect = tripleoclient.v1.overcloud_node:IntrospectNode overcloud_node_provide = tripleoclient.v1.overcloud_node:ProvideNode overcloud_node_discover = tripleoclient.v1.overcloud_node:DiscoverNode + overcloud_node_clean = tripleoclient.v1.overcloud_node:CleanNode overcloud_parameters_set = tripleoclient.v1.overcloud_parameters:SetParameters overcloud_plan_create = tripleoclient.v1.overcloud_plan:CreatePlan overcloud_plan_delete = tripleoclient.v1.overcloud_plan:DeletePlan diff --git a/tripleoclient/tests/workflows/test_baremetal.py b/tripleoclient/tests/workflows/test_baremetal.py index 13a8a08bf..7b0858b70 100644 --- a/tripleoclient/tests/workflows/test_baremetal.py +++ b/tripleoclient/tests/workflows/test_baremetal.py @@ -114,11 +114,11 @@ class TestBaremetalWorkflows(utils.TestCommand): 'node_uuids': [], }) - def test_format_provide_errors(self): + def test_format_errors(self): payload = {'message': [{'result': 'Error1a\nError1b'}, {'result': 'Error2a\nError2b\n'}]} - error_string = baremetal._format_provide_errors(payload) + error_string = baremetal._format_errors(payload) self.assertEqual(error_string, "Error1b\nError2b") def test_provide_error_with_format_message(self): @@ -306,3 +306,63 @@ class TestBaremetalWorkflows(utils.TestCommand): 'tripleo.baremetal.v1.configure_manageable_nodes', workflow_input={} ) + + def test_clean_nodes_success(self): + + self.websocket.wait_for_messages.return_value = self.message_success + + baremetal.clean_nodes(self.app.client_manager, node_uuids=[]) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.clean_nodes', + workflow_input={ + 'node_uuids': [], + }) + + def test_clean_nodes_error(self): + + self.websocket.wait_for_messages.return_value = self.message_failed + + self.assertRaises( + exceptions.NodeConfigurationError, + baremetal.clean_nodes, + self.app.client_manager, + node_uuids=[] + ) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.clean_nodes', + workflow_input={ + 'node_uuids': [], + }) + + def test_clean_manageable_nodes_success(self): + + self.websocket.wait_for_messages.return_value = iter([{ + "execution": {"id": "IDID"}, + "status": "SUCCESS", + "cleaned_nodes": [], + }]) + + baremetal.clean_manageable_nodes( + self.app.client_manager + ) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.clean_manageable_nodes', + workflow_input={} + ) + + def test_clean_manageable_nodes_error(self): + + self.websocket.wait_for_messages.return_value = self.message_failed + + self.assertRaises( + exceptions.NodeConfigurationError, + baremetal.clean_manageable_nodes, + self.app.client_manager) + + self.workflow.executions.create.assert_called_once_with( + 'tripleo.baremetal.v1.clean_manageable_nodes', + workflow_input={} + ) diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py index f6df211a1..d292e8bee 100644 --- a/tripleoclient/v1/overcloud_node.py +++ b/tripleoclient/v1/overcloud_node.py @@ -122,6 +122,36 @@ class ProvideNode(command.Command): baremetal.provide_manageable_nodes(self.app.client_manager) +class CleanNode(command.Command): + """Run node(s) through cleaning.""" + + log = logging.getLogger(__name__ + ".CleanNode") + + def get_parser(self, prog_name): + parser = super(CleanNode, self).get_parser(prog_name) + group = parser.add_mutually_exclusive_group(required=True) + group.add_argument('node_uuids', + nargs="*", + metavar="", + default=[], + help=_('Baremetal Node UUIDs for the node(s) to be ' + 'cleaned')) + group.add_argument("--all-manageable", + action='store_true', + help=_("Clean all nodes currently in 'manageable'" + " state")) + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + + if parsed_args.node_uuids: + baremetal.clean_nodes(self.app.client_manager, + node_uuids=parsed_args.node_uuids) + else: + baremetal.clean_manageable_nodes(self.app.client_manager) + + class IntrospectNode(command.Command): """Introspect specified nodes or all nodes in 'manageable' state.""" diff --git a/tripleoclient/workflows/baremetal.py b/tripleoclient/workflows/baremetal.py index 18f3aa732..a47784c30 100644 --- a/tripleoclient/workflows/baremetal.py +++ b/tripleoclient/workflows/baremetal.py @@ -14,6 +14,8 @@ from __future__ import print_function +import six + from tripleoclient import exceptions from tripleoclient.workflows import base @@ -78,18 +80,23 @@ def register_or_update(clients, **workflow_input): 'Exception registering nodes: {}'.format(payload['message'])) -def _format_provide_errors(payload): +def _format_errors(payload): errors = [] messages = payload.get('message', []) for msg in messages: + # Adapt for different formats + if isinstance(msg, six.string_types): + text = msg + else: + text = msg.get('result') or msg.get('message', '') try: # With multiple workflows, the error message can become # quite large and unreadable as it gets passed from task to # task. This attempts to keep only the last, and hopefully # useful part. - errors.append(msg.get('result', '').rstrip('\n').split('\n')[-1]) + errors.append(text.rstrip('\n').split('\n')[-1]) except Exception: - errors.append(msg.get('result', '')) + errors.append(text) return '\n'.join(errors) @@ -115,7 +122,7 @@ def provide(clients, **workflow_input): if payload['status'] != 'SUCCESS': try: - message = _format_provide_errors(payload) + message = _format_errors(payload) except Exception: message = 'Failed.' raise exceptions.NodeProvideError( @@ -330,3 +337,58 @@ def discover_and_enroll(clients, **workflow_input): else: raise exceptions.RegisterOrUpdateError( 'Exception discovering nodes: {}'.format(payload['message'])) + + +def clean_nodes(clients, **workflow_input): + """Clean Baremetal Nodes + + Run the tripleo.baremetal.v1.clean_nodes Mistral workflow. + """ + + workflow_client = clients.workflow_engine + tripleoclients = clients.tripleoclient + + with tripleoclients.messaging_websocket() as ws: + execution = base.start_workflow( + workflow_client, + 'tripleo.baremetal.v1.clean_nodes', + workflow_input={'node_uuids': workflow_input['node_uuids']} + ) + + for payload in base.wait_for_messages(workflow_client, ws, execution): + if payload.get('message'): + print(payload['message']) + + if payload['status'] != 'SUCCESS': + message = _format_errors(payload) + raise exceptions.NodeConfigurationError( + 'Error(s) cleaning nodes:\n{}'.format(message)) + + print('Successfully cleaned nodes') + + +def clean_manageable_nodes(clients, **workflow_input): + """Clean all manageable Nodes + + Run the tripleo.baremetal.v1.clean_manageable_nodes Mistral workflow. + """ + + workflow_client = clients.workflow_engine + tripleoclients = clients.tripleoclient + + with tripleoclients.messaging_websocket() as ws: + execution = base.start_workflow( + workflow_client, + 'tripleo.baremetal.v1.clean_manageable_nodes', + workflow_input=workflow_input + ) + + for payload in base.wait_for_messages(workflow_client, ws, execution): + if payload.get('message'): + print(payload['message']) + + if payload['status'] != 'SUCCESS': + raise exceptions.NodeConfigurationError( + 'Error cleaning nodes: {}'.format(payload['message'])) + + print('Cleaned %d node(s)' % len(payload['cleaned_nodes']))