diff --git a/releasenotes/notes/overcloud-failures-0e98b37251fb2be2.yaml b/releasenotes/notes/overcloud-failures-0e98b37251fb2be2.yaml new file mode 100644 index 000000000..9bc0c39eb --- /dev/null +++ b/releasenotes/notes/overcloud-failures-0e98b37251fb2be2.yaml @@ -0,0 +1,4 @@ +--- +features: + - A new command, openstack overcloud failures, is added to show + the failures from a deployment plan when using config-download. diff --git a/setup.cfg b/setup.cfg index 7f8555ce9..ffcff1a02 100644 --- a/setup.cfg +++ b/setup.cfg @@ -66,6 +66,7 @@ openstack.tripleoclient.v1 = overcloud_delete = tripleoclient.v1.overcloud_delete:DeleteOvercloud overcloud_credentials = tripleoclient.v1.overcloud_credentials:OvercloudCredentials overcloud_deploy = tripleoclient.v1.overcloud_deploy:DeployOvercloud + overcloud_failures = tripleoclient.v1.overcloud_deploy:GetDeploymentFailures overcloud_status = tripleoclient.v1.overcloud_deploy:GetDeploymentStatus overcloud_image_build = tripleoclient.v1.overcloud_image:BuildOvercloudImage overcloud_image_upload = tripleoclient.v1.overcloud_image:UploadOvercloudImage diff --git a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py index 873d1a942..e5f25aed2 100644 --- a/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py +++ b/tripleoclient/tests/v1/overcloud_deploy/test_overcloud_deploy.py @@ -1965,3 +1965,56 @@ class TestGetDeploymentStatus(utils.TestCommand): '+-----------+-----------+---------+-------------------+\n') self.assertEqual(expected, self.cmd.app.stdout.getvalue()) + + +class TestGetDeploymentFailures(utils.TestCommand): + + def setUp(self): + super(TestGetDeploymentFailures, self).setUp() + self.cmd = overcloud_deploy.GetDeploymentFailures(self.app, None) + self.app.client_manager = mock.Mock() + self.clients = self.app.client_manager + + @mock.patch( + 'tripleoclient.workflows.deployment.get_deployment_failures', + autospec=True) + def test_plan_get_deployment_status(self, mock_get_deployment_failures): + parsed_args = self.check_parser(self.cmd, [], []) + self.cmd.app.stdout = six.StringIO() + + failures = { + 'host0': [ + ['Task1', dict(key1=1, key2=2, key3=3)], + ['Task2', dict(key4=4, key5=5, key3=5)] + ], + 'host1': [ + ['Task1', dict(key1=1, key2=2, key3=['a', 'b', 'c'])] + ], + } + + mock_get_deployment_failures.return_value = failures + + self.cmd.take_action(parsed_args) + + expected = ( + '|-> Failures for host: host0\n' + '|--> Task: Task1\n' + '|---> key1: 1\n' + '|---> key2: 2\n' + '|---> key3: 3\n' + '|--> Task: Task2\n' + '|---> key3: 5\n' + '|---> key4: 4\n' + '|---> key5: 5\n' + '\n' + '|-> Failures for host: host1\n' + '|--> Task: Task1\n' + '|---> key1: 1\n' + '|---> key2: 2\n' + '|---> key3: [\n' + ' "a",\n' + ' "b",\n' + ' "c"\n' + ']\n\n') + + self.assertEqual(expected, self.cmd.app.stdout.getvalue()) diff --git a/tripleoclient/v1/overcloud_deploy.py b/tripleoclient/v1/overcloud_deploy.py index 6b4a91e56..bc0f32657 100644 --- a/tripleoclient/v1/overcloud_deploy.py +++ b/tripleoclient/v1/overcloud_deploy.py @@ -15,6 +15,7 @@ from __future__ import print_function import argparse +import json import logging import os import os.path @@ -1033,7 +1034,7 @@ class DeployOvercloud(command.Command): class GetDeploymentStatus(command.Command): - """Check status of a deployment plan""" + """Get deployment status""" log = logging.getLogger(__name__ + ".GetDeploymentStatus") @@ -1063,3 +1064,51 @@ class GetDeploymentStatus(command.Command): execution['updated_at'], payload['deployment_status']]) print(table, file=self.app.stdout) + + +class GetDeploymentFailures(command.Command): + """Get deployment failures""" + + log = logging.getLogger(__name__ + ".GetDeploymentFailures") + + def get_parser(self, prog_name): + parser = super(GetDeploymentFailures, self).get_parser(prog_name) + parser.add_argument('--plan', '--stack', + help=_('Name of the stack/plan. ' + '(default: overcloud)'), + default='overcloud') + return parser + + def take_action(self, parsed_args): + self.log.debug("take_action(%s)" % parsed_args) + plan = parsed_args.plan + + failures = deployment.get_deployment_failures( + self.app.client_manager, + plan=plan + ) + + out = self.app.stdout + + hosts = list(failures.keys()) + hosts.sort() + + for host in hosts: + host_failures = failures[host] + host_failures = sorted(host_failures, key=lambda k: k[0]) + out.write("|-> Failures for host: %s\n" % host) + for task_name, task in host_failures: + out.write('|--> Task: %s\n' % task_name) + task_keys = sorted(task.keys()) + for task_key in task_keys: + task_value = task[task_key] + out.write('|---> %s: ' % task_key) + try: + value = json.dumps(task_value, + sort_keys=True, + separators=(',', ': '), + indent=4) + except ValueError: + value = task_value + out.write('%s\n' % value) + out.write('\n') diff --git a/tripleoclient/workflows/deployment.py b/tripleoclient/workflows/deployment.py index 13b52fc05..7f6b28271 100644 --- a/tripleoclient/workflows/deployment.py +++ b/tripleoclient/workflows/deployment.py @@ -299,14 +299,23 @@ def get_deployment_status(clients, **workflow_input): def get_deployment_failures(clients, **workflow_input): workflow_client = clients.workflow_engine + tripleoclients = clients.tripleoclient - result = base.call_action( + execution = base.start_workflow( workflow_client, - 'tripleo.deployment.get_deployment_failures', - **workflow_input + 'tripleo.deployment.v1.get_deployment_failures', + workflow_input=workflow_input ) - if result.get('message', ''): - print(result['message']) + with tripleoclients.messaging_websocket() as ws: + for payload in base.wait_for_messages(workflow_client, ws, execution, + _WORKFLOW_TIMEOUT): + if 'message' in payload: + print(payload['message']) - return result['failures'] + if payload['status'] == 'SUCCESS': + return payload['deployment_failures']['failures'] + else: + raise exceptions.WorkflowServiceError( + 'Exception getting deployment failures: {}'.format( + payload.get('message', '')))