576 lines
22 KiB
Python
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()
|