Merge "overcloud node delete, use ansible for unprovision"

This commit is contained in:
Zuul 2020-03-31 04:00:33 +00:00 committed by Gerrit Code Review
commit 1ec8fd29b3
2 changed files with 171 additions and 221 deletions

View File

@ -41,6 +41,7 @@ class TestDeleteNode(fakes.TestDeleteNode):
# Get the command object to test # Get the command object to test
self.cmd = overcloud_node.DeleteNode(self.app, None) 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.app.client_manager.workflow_engine = mock.Mock()
self.tripleoclient = mock.Mock() self.tripleoclient = mock.Mock()
@ -134,13 +135,11 @@ class TestDeleteNode(fakes.TestDeleteNode):
@mock.patch('tripleoclient.utils.run_ansible_playbook', @mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True) autospec=True)
@mock.patch('tripleoclient.workflows.baremetal.expand_roles', @mock.patch('tripleoclient.utils.tempfile')
autospec=True) def test_node_delete_baremetal_deployment(self,
@mock.patch('tripleoclient.workflows.baremetal.undeploy_roles', mock_tempfile,
autospec=True)
def test_node_delete_baremetal_deployment(self, mock_undeploy_roles,
mock_expand_roles,
mock_playbook): mock_playbook):
bm_yaml = [{ bm_yaml = [{
'name': 'Compute', 'name': 'Compute',
'count': 5, 'count': 5,
@ -159,60 +158,27 @@ class TestDeleteNode(fakes.TestDeleteNode):
}] }]
}] }]
expand_to_delete = { tmp = tempfile.mkdtemp()
'instances': [{ mock_tempfile.mkdtemp.side_effect = [
'name': 'baremetal-1', tmp,
'hostname': 'overcast-controller-1' tempfile.mkdtemp(),
}, { tempfile.mkdtemp(),
'name': 'baremetal-2', tempfile.mkdtemp()
'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
] ]
res_list = self.app.client_manager.orchestration.resources.list unprovision_confirm = os.path.join(tmp, 'unprovision_confirm.json')
res_list.return_value = [ with open(unprovision_confirm, 'w') as confirm:
mock.Mock( confirm.write(json.dumps([
resource_type='OS::TripleO::ComputeServer', {
parent_resource='0', 'hostname': 'overcast-controller-1',
physical_resource_id='aaaa' 'name': 'baremetal-1',
), 'id': 'aaaa'
mock.Mock( }, {
resource_type='OS::TripleO::ComputeServer', 'hostname': 'overcast-compute-0',
parent_resource='1', 'name': 'baremetal-2',
physical_resource_id='bbbb' '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'
)
]
with tempfile.NamedTemporaryFile(mode='w') as inp: with tempfile.NamedTemporaryFile(mode='w') as inp:
yaml.dump(bm_yaml, inp, encoding='utf-8') yaml.dump(bm_yaml, inp, encoding='utf-8')
@ -229,71 +195,94 @@ class TestDeleteNode(fakes.TestDeleteNode):
self.cmd.take_action(parsed_args) self.cmd.take_action(parsed_args)
# Verify # Verify
res_list.assert_called_once_with('overcast', nested_depth=5) mock_playbook.assert_has_calls([
mock_expand_roles.assert_has_calls([
mock.call( mock.call(
self.app.client_manager, playbook='cli-overcloud-node-unprovision.yaml',
provisioned=False, inventory='localhost,',
roles=bm_yaml, verbosity=0,
stackname='overcast' 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( mock.call(
self.app.client_manager, playbook='cli-grant-local-access.yaml',
provisioned=True, inventory='localhost,',
roles=bm_yaml, workdir=mock.ANY,
stackname='overcast' 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', @mock.patch('tripleoclient.utils.run_ansible_playbook',
autospec=True) autospec=True)
@mock.patch('tripleoclient.workflows.baremetal.expand_roles', @mock.patch('tripleoclient.utils.tempfile')
autospec=True) def test_nodes_to_delete(self, mock_tempfile, mock_playbook):
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):
bm_yaml = [{ bm_yaml = [{
'name': 'Compute', 'name': 'Compute',
'count': 5, 'count': 5,
@ -312,56 +301,38 @@ class TestDeleteNode(fakes.TestDeleteNode):
}] }]
}] }]
res_list = self.app.client_manager.orchestration.resources.list tmp = tempfile.mkdtemp()
res_list.return_value = [ mock_tempfile.mkdtemp.return_value = tmp
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'
)
]
mock_expand_roles.return_value = { unprovision_confirm = os.path.join(tmp, 'unprovision_confirm.json')
'environment': { with open(unprovision_confirm, 'w') as confirm:
'parameter_defaults': { confirm.write(json.dumps([
'ComputeRemovalPolicies': [{ {
'resource_list': [0] 'hostname': 'compute-0',
}], 'name': 'baremetal-1',
'ControllerRemovalPolicies': [{ 'id': 'aaaa'
'resource_list': [1] }, {
}] 'hostname': 'controller-0',
'name': 'baremetal-2',
'id': 'bbbb'
} }
} ]))
}
argslist = ['--baremetal-deployment', '/foo/bm_deploy.yaml'] argslist = ['--baremetal-deployment', '/foo/bm_deploy.yaml']
verifylist = [ verifylist = [
('baremetal_deployment', '/foo/bm_deploy.yaml') ('baremetal_deployment', '/foo/bm_deploy.yaml')
] ]
parsed_args = self.check_parser(self.cmd, argslist, verifylist) parsed_args = self.check_parser(self.cmd, argslist, verifylist)
result = self.cmd._translate_nodes_to_resources( nodes_text, nodes = self.cmd._nodes_to_delete(parsed_args, bm_yaml)
parsed_args, bm_yaml) expected = '''+--------------+-------------+------+
self.assertEqual(['aaaa', 'dddd'], result) | 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): class TestProvideNode(fakes.TestOvercloudNode):

View File

@ -14,7 +14,9 @@
# #
import collections import collections
import json
import logging import logging
import os
import sys import sys
from cliff.formatters import table from cliff.formatters import table
@ -87,76 +89,46 @@ class DeleteNode(command.Command):
return parser return parser
def _nodes_to_delete(self, parsed_args, roles): def _nodes_to_delete(self, parsed_args, roles):
# expand for provisioned:False to get a list of nodes with oooutils.TempDirs() as tmp:
# to delete unprovision_confirm = os.path.join(
expanded = baremetal.expand_roles( tmp, 'unprovision_confirm.json')
self.app.client_manager,
roles=roles, oooutils.run_ansible_playbook(
stackname=parsed_args.stack, playbook='cli-overcloud-node-unprovision.yaml',
provisioned=False) inventory='localhost,',
nodes = expanded.get('instances', []) 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: if not nodes:
print('No nodes to unprovision') print('No nodes to unprovision')
return return None, None
TableArgs = collections.namedtuple( TableArgs = collections.namedtuple(
'TableArgs', 'print_empty max_width fit_width') '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', ''), 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() formatter = table.TableFormatter()
output = six.StringIO() output = six.StringIO()
formatter.emit_list( formatter.emit_list(
column_names=['hostname', 'name'], column_names=['hostname', 'name', 'id'],
data=nodes_data, data=nodes_data,
stdout=output, stdout=output,
parsed_args=args parsed_args=args
) )
return output.getvalue() return output.getvalue(), node_hostnames
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
def take_action(self, parsed_args): def take_action(self, parsed_args):
self.log.debug("take_action(%s)" % 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: with open(parsed_args.baremetal_deployment, 'r') as fp:
roles = yaml.safe_load(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: if nodes_text:
nodes = self._translate_nodes_to_resources(
parsed_args, roles)
print(nodes_text) print(nodes_text)
else: else:
return return
@ -204,10 +174,19 @@ class DeleteNode(command.Command):
) )
if parsed_args.baremetal_deployment: if parsed_args.baremetal_deployment:
baremetal.undeploy_roles( with oooutils.TempDirs() as tmp:
self.app.client_manager, oooutils.run_ansible_playbook(
roles=roles, playbook='cli-overcloud-node-unprovision.yaml',
plan=parsed_args.stack) 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): class ProvideNode(command.Command):