From bd0588e3efbd29412ca20867da50d19d44b8d2ce Mon Sep 17 00:00:00 2001 From: Steve Baker Date: Wed, 11 Mar 2020 13:55:30 +1300 Subject: [PATCH] overcloud node delete, use ansible for unprovision Change-Id: I1340345702a506fe195429b6e4900eb9ce95e827 Story: 2007212 Task: 38457 --- .../v1/overcloud_node/test_overcloud_node.py | 285 ++++++++---------- tripleoclient/v1/overcloud_node.py | 107 +++---- 2 files changed, 171 insertions(+), 221 deletions(-) diff --git a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py index 72b7d4a5f..2f2884d7c 100644 --- a/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py +++ b/tripleoclient/tests/v1/overcloud_node/test_overcloud_node.py @@ -41,6 +41,7 @@ class TestDeleteNode(fakes.TestDeleteNode): # Get the command object to test self.cmd = overcloud_node.DeleteNode(self.app, None) + self.cmd.app_args = mock.Mock(verbose_level=1) self.app.client_manager.workflow_engine = mock.Mock() self.tripleoclient = mock.Mock() @@ -134,13 +135,11 @@ class TestDeleteNode(fakes.TestDeleteNode): @mock.patch('tripleoclient.utils.run_ansible_playbook', autospec=True) - @mock.patch('tripleoclient.workflows.baremetal.expand_roles', - autospec=True) - @mock.patch('tripleoclient.workflows.baremetal.undeploy_roles', - autospec=True) - def test_node_delete_baremetal_deployment(self, mock_undeploy_roles, - mock_expand_roles, + @mock.patch('tripleoclient.utils.tempfile') + def test_node_delete_baremetal_deployment(self, + mock_tempfile, mock_playbook): + bm_yaml = [{ 'name': 'Compute', 'count': 5, @@ -159,60 +158,27 @@ class TestDeleteNode(fakes.TestDeleteNode): }] }] - expand_to_delete = { - 'instances': [{ - 'name': 'baremetal-1', - 'hostname': 'overcast-controller-1' - }, { - 'name': 'baremetal-2', - 'hostname': 'overcast-compute-0' - }] - } - expand_to_translate = { - 'environment': { - 'parameter_defaults': { - 'ComputeRemovalPolicies': [{ - 'resource_list': [0] - }], - 'ControllerRemovalPolicies': [{ - 'resource_list': [1] - }] - } - } - } - mock_expand_roles.side_effect = [ - expand_to_delete, - expand_to_translate + tmp = tempfile.mkdtemp() + mock_tempfile.mkdtemp.side_effect = [ + tmp, + tempfile.mkdtemp(), + tempfile.mkdtemp(), + tempfile.mkdtemp() ] - res_list = self.app.client_manager.orchestration.resources.list - res_list.return_value = [ - mock.Mock( - resource_type='OS::TripleO::ComputeServer', - parent_resource='0', - physical_resource_id='aaaa' - ), - mock.Mock( - resource_type='OS::TripleO::ComputeServer', - parent_resource='1', - physical_resource_id='bbbb' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='0', - physical_resource_id='cccc' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='1', - physical_resource_id='dddd' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='2', - physical_resource_id='eeee' - ) - ] + unprovision_confirm = os.path.join(tmp, 'unprovision_confirm.json') + with open(unprovision_confirm, 'w') as confirm: + confirm.write(json.dumps([ + { + 'hostname': 'overcast-controller-1', + 'name': 'baremetal-1', + 'id': 'aaaa' + }, { + 'hostname': 'overcast-compute-0', + 'name': 'baremetal-2', + 'id': 'bbbb' + } + ])) with tempfile.NamedTemporaryFile(mode='w') as inp: yaml.dump(bm_yaml, inp, encoding='utf-8') @@ -229,71 +195,94 @@ class TestDeleteNode(fakes.TestDeleteNode): self.cmd.take_action(parsed_args) # Verify - res_list.assert_called_once_with('overcast', nested_depth=5) - mock_expand_roles.assert_has_calls([ + mock_playbook.assert_has_calls([ mock.call( - self.app.client_manager, - provisioned=False, - roles=bm_yaml, - stackname='overcast' + playbook='cli-overcloud-node-unprovision.yaml', + inventory='localhost,', + verbosity=0, + workdir=mock.ANY, + playbook_dir='/usr/share/ansible/tripleo-playbooks', + extra_vars={ + 'stack_name': 'overcast', + 'baremetal_deployment': [{ + 'count': 5, + 'instances': [{ + 'hostname': 'overcast-compute-0', + 'name': 'baremetal-2', + 'provisioned': False + }], + 'name': 'Compute' + }, { + 'count': 2, + 'instances': [{ + 'hostname': 'overcast-controller-1', + 'name': 'baremetal-1', + 'provisioned': False + }], 'name': 'Controller' + }], + 'prompt': True, + 'unprovision_confirm': unprovision_confirm, + }, ), mock.call( - self.app.client_manager, - provisioned=True, - roles=bm_yaml, - stackname='overcast' + playbook='cli-grant-local-access.yaml', + inventory='localhost,', + workdir=mock.ANY, + playbook_dir='/usr/share/ansible/tripleo-playbooks', + extra_vars={ + 'access_path': '/var/lib/mistral', + 'execution_user': mock.ANY}, + ), + mock.call( + playbook=mock.ANY, + inventory=mock.ANY, + workdir=mock.ANY, + playbook_dir=mock.ANY, + skip_tags='opendev-validation', + ansible_cfg=None, + verbosity=1, + ssh_user='tripleo-admin', + key=mock.ANY, + limit_hosts='overcast-controller-1:overcast-compute-0', + ansible_timeout=90, + reproduce_command=True, + extra_env_variables={'ANSIBLE_BECOME': True}, + extra_vars=None, + tags=None + ), + mock.call( + inventory='localhost,', + playbook='cli-overcloud-node-unprovision.yaml', + verbosity=0, + workdir=mock.ANY, + playbook_dir='/usr/share/ansible/tripleo-playbooks', + extra_vars={ + 'stack_name': 'overcast', + 'baremetal_deployment': [{ + 'count': 5, + 'instances': [{ + 'hostname': 'overcast-compute-0', + 'name': 'baremetal-2', + 'provisioned': False + }], 'name': 'Compute' + }, { + 'count': 2, + 'instances': [{ + 'hostname': 'overcast-controller-1', + 'name': 'baremetal-1', + 'provisioned': False + }], + 'name': 'Controller' + }], + 'prompt': False + }, ) ]) @mock.patch('tripleoclient.utils.run_ansible_playbook', autospec=True) - @mock.patch('tripleoclient.workflows.baremetal.expand_roles', - autospec=True) - def test_nodes_to_delete(self, mock_expand_roles, mock_playbook): - bm_yaml = [{ - 'name': 'Compute', - 'count': 5, - 'instances': [{ - 'name': 'baremetal-2', - 'hostname': 'overcast-compute-0', - 'provisioned': False - }], - }, { - 'name': 'Controller', - 'count': 2, - 'instances': [{ - 'name': 'baremetal-1', - 'hostname': 'overcast-controller-1', - 'provisioned': False - }] - }] - mock_expand_roles.return_value = { - 'instances': [{ - 'name': 'baremetal-1', - 'hostname': 'overcast-controller-1' - }, { - 'name': 'baremetal-2', - 'hostname': 'overcast-compute-0' - }] - } - argslist = ['--baremetal-deployment', '/foo/bm_deploy.yaml'] - verifylist = [ - ('baremetal_deployment', '/foo/bm_deploy.yaml') - ] - parsed_args = self.check_parser(self.cmd, argslist, verifylist) - result = self.cmd._nodes_to_delete(parsed_args, bm_yaml) - expected = '''+-----------------------+-------------+ -| hostname | name | -+-----------------------+-------------+ -| overcast-controller-1 | baremetal-1 | -| overcast-compute-0 | baremetal-2 | -+-----------------------+-------------+ -''' - self.assertEqual(expected, result) - - @mock.patch('tripleoclient.workflows.baremetal.expand_roles', - autospec=True) - def test_translate_nodes_to_resources(self, mock_expand_roles): + @mock.patch('tripleoclient.utils.tempfile') + def test_nodes_to_delete(self, mock_tempfile, mock_playbook): bm_yaml = [{ 'name': 'Compute', 'count': 5, @@ -312,56 +301,38 @@ class TestDeleteNode(fakes.TestDeleteNode): }] }] - res_list = self.app.client_manager.orchestration.resources.list - res_list.return_value = [ - mock.Mock( - resource_type='OS::TripleO::ComputeServer', - parent_resource='0', - physical_resource_id='aaaa' - ), - mock.Mock( - resource_type='OS::TripleO::ComputeServer', - parent_resource='1', - physical_resource_id='bbbb' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='0', - physical_resource_id='cccc' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='1', - physical_resource_id='dddd' - ), - mock.Mock( - resource_type='OS::TripleO::ControllerServer', - parent_resource='2', - physical_resource_id='eeee' - ) - ] + tmp = tempfile.mkdtemp() + mock_tempfile.mkdtemp.return_value = tmp - mock_expand_roles.return_value = { - 'environment': { - 'parameter_defaults': { - 'ComputeRemovalPolicies': [{ - 'resource_list': [0] - }], - 'ControllerRemovalPolicies': [{ - 'resource_list': [1] - }] + unprovision_confirm = os.path.join(tmp, 'unprovision_confirm.json') + with open(unprovision_confirm, 'w') as confirm: + confirm.write(json.dumps([ + { + 'hostname': 'compute-0', + 'name': 'baremetal-1', + 'id': 'aaaa' + }, { + 'hostname': 'controller-0', + 'name': 'baremetal-2', + 'id': 'bbbb' } - } - } + ])) argslist = ['--baremetal-deployment', '/foo/bm_deploy.yaml'] verifylist = [ ('baremetal_deployment', '/foo/bm_deploy.yaml') ] parsed_args = self.check_parser(self.cmd, argslist, verifylist) - result = self.cmd._translate_nodes_to_resources( - parsed_args, bm_yaml) - self.assertEqual(['aaaa', 'dddd'], result) + nodes_text, nodes = self.cmd._nodes_to_delete(parsed_args, bm_yaml) + expected = '''+--------------+-------------+------+ +| hostname | name | id | ++--------------+-------------+------+ +| compute-0 | baremetal-1 | aaaa | +| controller-0 | baremetal-2 | bbbb | ++--------------+-------------+------+ +''' + self.assertEqual(expected, nodes_text) + self.assertEqual(['compute-0', 'controller-0'], nodes) class TestProvideNode(fakes.TestOvercloudNode): diff --git a/tripleoclient/v1/overcloud_node.py b/tripleoclient/v1/overcloud_node.py index ee2a53b02..96420769f 100644 --- a/tripleoclient/v1/overcloud_node.py +++ b/tripleoclient/v1/overcloud_node.py @@ -14,7 +14,9 @@ # import collections +import json import logging +import os import sys from cliff.formatters import table @@ -87,76 +89,46 @@ class DeleteNode(command.Command): return parser def _nodes_to_delete(self, parsed_args, roles): - # expand for provisioned:False to get a list of nodes - # to delete - expanded = baremetal.expand_roles( - self.app.client_manager, - roles=roles, - stackname=parsed_args.stack, - provisioned=False) - nodes = expanded.get('instances', []) + with oooutils.TempDirs() as tmp: + unprovision_confirm = os.path.join( + tmp, 'unprovision_confirm.json') + + oooutils.run_ansible_playbook( + playbook='cli-overcloud-node-unprovision.yaml', + inventory='localhost,', + verbosity=self.app_args.verbose_level - 1, + workdir=tmp, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + extra_vars={ + "stack_name": parsed_args.stack, + "baremetal_deployment": roles, + "prompt": True, + "unprovision_confirm": unprovision_confirm, + } + ) + with open(unprovision_confirm) as f: + nodes = json.load(f) if not nodes: print('No nodes to unprovision') - return + return None, None TableArgs = collections.namedtuple( 'TableArgs', 'print_empty max_width fit_width') - args = TableArgs(print_empty=True, max_width=80, fit_width=True) + args = TableArgs(print_empty=True, max_width=-1, fit_width=True) nodes_data = [(i.get('hostname', ''), - i.get('name', '')) for i in nodes] + i.get('name', ''), + i.get('id', '')) for i in nodes] + + node_hostnames = [i['hostname'] for i in nodes if 'hostname' in i] formatter = table.TableFormatter() output = six.StringIO() formatter.emit_list( - column_names=['hostname', 'name'], + column_names=['hostname', 'name', 'id'], data=nodes_data, stdout=output, parsed_args=args ) - return output.getvalue() - - def _translate_nodes_to_resources(self, parsed_args, roles): - # build a dict of resource type names to role name - role_types = dict( - ('OS::TripleO::%sServer' % r['name'], r['name']) - for r in roles - ) - expanded = baremetal.expand_roles( - self.app.client_manager, - roles=roles, - stackname=parsed_args.stack, - provisioned=True) - - parameters = expanded.get( - 'environment', {}).get('parameter_defaults', {}) - - # build a dict with the role and - # a list of indexes of nodes to delete for that role - removal_indexes = {} - for role in role_types.values(): - removal_indexes.setdefault(role, []) - param = '%sRemovalPolicies' % role - policies = parameters.get(param, []) - if policies: - removal_indexes[role] = policies[0].get('resource_list', []) - - nodes = [] - clients = self.app.client_manager - - # iterate every server resource and compare its index with - # the list of indexes to be deleted - resources = clients.orchestration.resources.list( - parsed_args.stack, nested_depth=5) - for res in resources: - if res.resource_type not in role_types: - continue - role = role_types[res.resource_type] - removal_list = removal_indexes.get(role, []) - - index = int(res.parent_resource) - if index in removal_list: - node = res.physical_resource_id - nodes.append(node) - return nodes + return output.getvalue(), node_hostnames def take_action(self, parsed_args): self.log.debug("take_action(%s)" % parsed_args) @@ -166,10 +138,8 @@ class DeleteNode(command.Command): with open(parsed_args.baremetal_deployment, 'r') as fp: roles = yaml.safe_load(fp) - nodes_text = self._nodes_to_delete(parsed_args, roles) + nodes_text, nodes = self._nodes_to_delete(parsed_args, roles) if nodes_text: - nodes = self._translate_nodes_to_resources( - parsed_args, roles) print(nodes_text) else: return @@ -204,10 +174,19 @@ class DeleteNode(command.Command): ) if parsed_args.baremetal_deployment: - baremetal.undeploy_roles( - self.app.client_manager, - roles=roles, - plan=parsed_args.stack) + with oooutils.TempDirs() as tmp: + oooutils.run_ansible_playbook( + playbook='cli-overcloud-node-unprovision.yaml', + inventory='localhost,', + verbosity=self.app_args.verbose_level - 1, + workdir=tmp, + playbook_dir=constants.ANSIBLE_TRIPLEO_PLAYBOOKS, + extra_vars={ + "stack_name": parsed_args.stack, + "baremetal_deployment": roles, + "prompt": False, + } + ) class ProvideNode(command.Command):