tripleo-ansible/tripleo_ansible/ansible_plugins/modules/os_baremetal_clean_node.py

576 lines
22 KiB
Python

#!/usr/bin/python
# Copyright (c) 2019 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
# flake8: noqa: E501
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: os_baremetal_clean_node
short_description: Clean baremetal nodes of Ironic
extends_documentation_fragment: openstack
author:
- "Sagi Shnaidman (@sshnaidm)"
version_added: "2.10"
description:
- Clean Ironic nodes.
options:
node_uuid:
description:
- globally unique identifier (UUID) to identify the host.
type: list
required: False
default: []
node_name:
description:
- unique name identifier to identify the host in Ironic.
type: list
required: False
default: []
clean_steps:
description:
- The clean steps as a list of clean-step
dictionaries; each dictionary should have keys 'interface' and
'step', and optional key 'args'. This must be specified for node
cleaning.
type: list
required: True
timeout:
description:
- Timeout to wait for each node to clean in seconds.
type: int
required: False
default: 1800
quiet:
description:
- Don't provide cleaned nodes info in output of the module
type: bool
default: False
max_retries:
description:
- Number of attempts before failing.
type: int
required: False
default: 0
concurrency:
description:
- Max level of concurrency.
type: int
required: False
default: 20
raid_config:
description:
- Sets the raid configuration for a given node.
type: dict
required: False
requirements: ["openstacksdk"]
'''
RETURN = '''
baremetal_nodes:
description: Dictionary of new facts representing discovered properties of
the node.
returned: changed
type: dict
sample: {
"baremetal_data": [
{
"af7e758c-d5d0-4cd6-9f60-efbabf5a7788": {
"allocation_id": null,
"bios_interface": null,
"boot_interface": null,
"chassis_id": null,
"clean_step": null,
"conductor": null,
"conductor_group": null,
"console_interface": null,
"created_at": null,
"deploy_interface": null,
"deploy_step": null,
"driver": null,
"driver_info": null,
"driver_internal_info": null,
"extra": null,
"fault": null,
"id": "af7e758c-d5d0-4cd6-9f60-efbabf5a7788",
"inspect_interface": null,
"instance_id": null,
"instance_info": null,
"is_automated_clean_enabled": null,
"is_console_enabled": null,
"is_maintenance": null,
"is_protected": null,
"last_error": null,
"links": [
{
"href": "https://192.168.24.2:13385/v1/nodes/af7e758c-d5d0-4cd6-9f60-efbabf5a7788",
"rel": "self"
},
{
"href": "https://192.168.24.2:13385/nodes/af7e758c-d5d0-4cd6-9f60-efbabf5a7788",
"rel": "bookmark"
}
],
"location": {
"cloud": "undercloud",
"project": {
"domain_id": null,
"domain_name": "Default",
"id": "09c0706606d04ca5a57b3894ad6e915a",
"name": "admin"
},
"region_name": "regionOne",
"zone": null
},
"maintenance_reason": null,
"management_interface": null,
"name": null,
"network_interface": null,
"owner": null,
"port_groups": null,
"ports": null,
"power_interface": null,
"power_state": null,
"properties": null,
"protected_reason": null,
"provision_state": "manageable",
"raid_config": null,
"raid_interface": null,
"rescue_interface": null,
"reservation": null,
"resource_class": null,
"states": null,
"storage_interface": null,
"target_power_state": null,
"target_provision_state": null,
"target_raid_config": null,
"traits": null,
"updated_at": null,
"vendor_interface": null
}
},
{
"c0a4aa96-742d-40be-b594-f940856dfae7": {
"allocation_id": null,
"bios_interface": null,
"boot_interface": null,
"chassis_id": null,
"clean_step": null,
"conductor": null,
"conductor_group": null,
"console_interface": null,
"created_at": null,
"deploy_interface": null,
"deploy_step": null,
"driver": null,
"driver_info": null,
"driver_internal_info": null,
"extra": null,
"fault": null,
"id": "c0a4aa96-742d-40be-b594-f940856dfae7",
"inspect_interface": null,
"instance_id": null,
"instance_info": null,
"is_automated_clean_enabled": null,
"is_console_enabled": null,
"is_maintenance": null,
"is_protected": null,
"last_error": null,
"links": [
{
"href": "https://192.168.24.2:13385/v1/nodes/c0a4aa96-742d-40be-b594-f940856dfae7",
"rel": "self"
},
{
"href": "https://192.168.24.2:13385/nodes/c0a4aa96-742d-40be-b594-f940856dfae7",
"rel": "bookmark"
}
],
"location": {
"cloud": "undercloud",
"project": {
"domain_id": null,
"domain_name": "Default",
"id": "09c0706606d04ca5a57b3894ad6e915a",
"name": "admin"
},
"region_name": "regionOne",
"zone": null
},
"maintenance_reason": null,
"management_interface": null,
"name": null,
"network_interface": null,
"owner": null,
"port_groups": null,
"ports": null,
"power_interface": null,
"power_state": null,
"properties": null,
"protected_reason": null,
"provision_state": "manageable",
"raid_config": null,
"raid_interface": null,
"rescue_interface": null,
"reservation": null,
"resource_class": null,
"states": null,
"storage_interface": null,
"target_power_state": null,
"target_provision_state": null,
"target_raid_config": null,
"traits": null,
"updated_at": null,
"vendor_interface": null
}
},
{
"72176c3a-cfcb-4d82-927d-92b1d3f46716": {
"allocation_id": null,
"bios_interface": null,
"boot_interface": null,
"chassis_id": null,
"clean_step": null,
"conductor": null,
"conductor_group": null,
"console_interface": null,
"created_at": null,
"deploy_interface": null,
"deploy_step": null,
"driver": null,
"driver_info": null,
"driver_internal_info": null,
"extra": null,
"fault": null,
"id": "72176c3a-cfcb-4d82-927d-92b1d3f46716",
"inspect_interface": null,
"instance_id": null,
"instance_info": null,
"is_automated_clean_enabled": null,
"is_console_enabled": null,
"is_maintenance": null,
"is_protected": null,
"last_error": null,
"links": [
{
"href": "https://192.168.24.2:13385/v1/nodes/72176c3a-cfcb-4d82-927d-92b1d3f46716",
"rel": "self"
},
{
"href": "https://192.168.24.2:13385/nodes/72176c3a-cfcb-4d82-927d-92b1d3f46716",
"rel": "bookmark"
}
],
"location": {
"cloud": "undercloud",
"project": {
"domain_id": null,
"domain_name": "Default",
"id": "09c0706606d04ca5a57b3894ad6e915a",
"name": "admin"
},
"region_name": "regionOne",
"zone": null
},
"maintenance_reason": null,
"management_interface": null,
"name": null,
"network_interface": null,
"owner": null,
"port_groups": null,
"ports": null,
"power_interface": null,
"power_state": null,
"properties": null,
"protected_reason": null,
"provision_state": "manageable",
"raid_config": null,
"raid_interface": null,
"rescue_interface": null,
"reservation": null,
"resource_class": null,
"states": null,
"storage_interface": null,
"target_power_state": null,
"target_provision_state": null,
"target_raid_config": null,
"traits": null,
"updated_at": null,
"vendor_interface": null
}
}
],
"changed": true,
"failed_nodes": [],
"passed_nodes": [
"af7e758c-d5d0-4cd6-9f60-efbabf5a7788",
"c0a4aa96-742d-40be-b594-f940856dfae7",
"72176c3a-cfcb-4d82-927d-92b1d3f46716"
]
}
''' # noqa
EXAMPLES = '''
# Invoke node inspection
- os_baremetal_clean_node:
cloud: undercloud
node_uuid:
- 0593c323-ad62-4ce9-b431-3c322827a428
clean_steps:
- interface: deploy
step: erase_devices_metadata
- os_baremetal_clean_node:
cloud: undercloud
node_uuid:
- 0593c323-ad62-4ce9-b431-3c322827a428
raid_config:
logical_disks:
- "size_gb": 100
"raid_level": "1"
"controller": "software"
clean_steps:
- interface: raid
step: delete_configuration
- interface: raid
step: create_configuration
- os_baremetal_clean_node:
cloud: undercloud
node_uuid:
- 0593c323-ad62-4ce9-b431-3c322827a428
clean_steps:
- interface: bios
step: apply_configuration
priority: 150
args:
settings:
- name: "LogicalProc"
value: "Disabled"
- os_baremetal_clean_node:
cloud: undercloud
node_name:
- baremetal-85-3
clean_steps:
- interface: management
step: activate_license
args:
ilo_license_key: "ABC12-XXXXX-XXXXX-XXXXX-YZ345"
- interface: management
step: update_firmware
args:
firmware_update_mode: "ilo"
firmware_images:
- url: "file:///firmware_images/ilo/1.5/CP024444.scexe"
checksum: "a94e683ea16d9ae44768f0a65942234d"
component: "ilo"
'''
import yaml
from concurrent import futures
from openstack import exceptions
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
def parallel_nodes_cleaning(conn, module):
client = conn.baremetal
node_timeout = module.params['timeout']
nodes = module.params['node_uuid'] + module.params['node_name']
clean_steps = module.params['clean_steps']
result = {}
if module.params['raid_config']:
for node in nodes:
try:
node_info = client.update_node(
node,
target_raid_config=module.params['raid_config']
)
result.update({node: {
'msg': 'Setting the raid configuration'
' for node {} succeeded.'.format(node),
'failed': False,
'info': node_info,
}})
except exceptions.BadRequestException as e:
result.update({node: {
'msg': 'Setting raid configuration'
' for node {} failed. Error: {}'.format(
node,
str(e)
),
'failed': True,
'error': str(e),
'info': {},
}})
nodes.pop(nodes.index(node))
workers = min(len(nodes), module.params['concurrency']) or 1
with futures.ThreadPoolExecutor(max_workers=workers) as executor:
future_to_build = {
executor.submit(
client.set_node_provision_state,
node,
"clean",
clean_steps=clean_steps,
wait=True
): node for node in nodes
}
done, not_done = futures.wait(
future_to_build,
timeout=node_timeout,
return_when=futures.ALL_COMPLETED
)
nodes_wait = list()
for job in done:
if job._exception:
result.update(
{
future_to_build[job]: {
'msg': 'Cleaning failed for node {}: {}'.format(
future_to_build[job],
str(job._exception)
),
'failed': True,
'info': {}
}
}
)
else:
nodes_wait.append(future_to_build[job])
else:
if not_done:
for job in not_done:
result.update(
{
future_to_build[job]: {
'msg': 'Cleaning incomplete for node {}'.format(
future_to_build[job],
),
'failed': True,
'info': {}
}
}
)
nodes_to_delete = []
for node in nodes_wait:
node_info = client.get_node(
node,
fields=['provision_state', 'last_error']
).to_dict()
state = node_info['provision_state']
if state == 'manageable':
nodes_to_delete.append(node)
result.update({node: {
'msg': 'Successful cleaning for node %s' % node,
'failed': False,
'error': '',
'info': node_info,
}})
elif state not in [
'manageable', 'cleaning', 'clean wait', 'available']:
nodes_to_delete.append(node)
result.update({node: {
'msg': 'Failed cleaning for node %s: %s' % (
node,
node_info['last_error'] or 'state %s' % state),
'failed': True,
'info': node_info,
}})
for node in nodes_to_delete:
nodes_wait.remove(node)
if nodes_wait:
for node in nodes_wait:
node_info = client.get_node(
node,
fields=['provision_state', 'last_error']
).to_dict()
state = node_info['provision_state']
result.update({node: {
'msg': 'Timeout exceeded for node %s: '
'node is in state %s' % (node, state),
'failed': True,
'info': node_info,
}})
return result
def main():
argument_spec = openstack_full_argument_spec(
**yaml.safe_load(DOCUMENTATION)['options']
)
module_kwargs = openstack_module_kwargs()
module = AnsibleModule(
argument_spec,
supports_check_mode=False,
**module_kwargs
)
if not module.params['node_uuid'] and not module.params['node_name']:
module.fail_json(msg="Provide either UUID or names of nodes!")
sdk, cloud = openstack_cloud_from_module(module)
try:
result = parallel_nodes_cleaning(cloud, module)
module_results = {"changed": True}
failed_nodes = [k for k, v in result.items() if v['failed']]
passed_nodes = [k for k, v in result.items() if not v['failed']]
infos = [{k: v['info']} for k, v in result.items()]
all_errors = "\n".join(
[v['msg'] for k, v in result.items() if v['failed']])
failed = len(failed_nodes)
if failed > 0:
message = ("Cleaning completed with failures. %s node(s) failed."
"Errors: %s"
% (failed, all_errors))
module_results.update({'failed': True})
else:
message = "Cleaning completed successfully: %s nodes" % len(
module.params["node_uuid"])
module_results.update({
"baremetal_data": infos if not module.params['quiet'] else {},
"failed_nodes": failed_nodes,
"passed_nodes": passed_nodes,
"msg": message
})
module.exit_json(**module_results)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == "__main__":
main()