#!/usr/bin/python # coding: utf-8 -*- # (c) 2015, Hewlett-Packard Development Company, L.P. # # This module is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this software. If not, see . try: import shade HAS_SHADE = True except ImportError: HAS_SHADE = False from distutils.version import StrictVersion DOCUMENTATION = ''' --- module: os_ironic_node short_description: Activate/Deactivate Bare Metal Resources from OpenStack author: "Monty Taylor (@emonty)" extends_documentation_fragment: openstack version_added: "2.0" description: - Deploy to nodes controlled by Ironic. options: state: description: - Indicates desired state of the resource choices: ['present', 'absent'] default: present deploy: description: - Indicates if the resource should be deployed. Allows for deployment logic to be disengaged and control of the node power or maintenance state to be changed. choices: ['true', 'false'] default: true uuid: description: - globally unique identifier (UUID) to be given to the resource. required: false default: None ironic_url: description: - If noauth mode is utilized, this is required to be set to the endpoint URL for the Ironic API. Use with "auth" and "auth_type" settings set to None. required: false default: None config_drive: description: - A configdrive file or HTTP(S) URL that will be passed along to the node. required: false default: None instance_info: description: - Definition of the instance information which is used to deploy the node. This information is only required when an instance is set to present. suboptions: image_source: description: - An HTTP(S) URL where the image can be retrieved from. image_checksum: description: - The checksum of image_source. image_disk_format: description: - The type of image that has been requested to be deployed. power: description: - A setting to allow power state to be asserted allowing nodes that are not yet deployed to be powered on, and nodes that are deployed to be powered off. choices: ['present', 'absent'] default: present maintenance: description: - A setting to allow the direct control if a node is in maintenance mode. required: false default: false maintenance_reason: description: - A string expression regarding the reason a node is in a maintenance mode. required: false default: None wait: description: - A boolean value instructing the module to wait for node activation or deactivation to complete before returning. required: false default: False version_added: "2.1" timeout: description: - An integer value representing the number of seconds to wait for the node activation or deactivation to complete. version_added: "2.1" ''' EXAMPLES = ''' # Activate a node by booting an image with a configdrive attached os_ironic_node: cloud: "openstack" uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" state: present power: present deploy: True maintenance: False config_drive: "http://192.168.1.1/host-configdrive.iso" instance_info: image_source: "http://192.168.1.1/deploy_image.img" image_checksum: "356a6b55ecc511a20c33c946c4e678af" image_disk_format: "qcow" delegate_to: localhost ''' def _choose_id_value(module): if module.params['uuid']: return module.params['uuid'] if module.params['name']: return module.params['name'] return None # TODO(TheJulia): Change this over to use the machine patch method # in shade once it is available. def _prepare_instance_info_patch(instance_info): patch = [] patch.append({ 'op': 'replace', 'path': '/instance_info', 'value': instance_info }) return patch def _is_true(value): true_values = [True, 'yes', 'Yes', 'True', 'true', 'present', 'on'] if value in true_values: return True return False def _is_false(value): false_values = [False, None, 'no', 'No', 'False', 'false', 'absent', 'off'] if value in false_values: return True return False def _check_set_maintenance(module, cloud, node): if _is_true(module.params['maintenance']): if _is_false(node['maintenance']): cloud.set_machine_maintenance_state( node['uuid'], True, reason=module.params['maintenance_reason']) module.exit_json(changed=True, msg="Node has been set into " "maintenance mode") else: # User has requested maintenance state, node is already in the # desired state, checking to see if the reason has changed. if (str(node['maintenance_reason']) not in str(module.params['maintenance_reason'])): cloud.set_machine_maintenance_state( node['uuid'], True, reason=module.params['maintenance_reason']) module.exit_json(changed=True, msg="Node maintenance reason " "updated, cannot take any " "additional action.") elif _is_false(module.params['maintenance']): if node['maintenance'] is True: cloud.remove_machine_from_maintenance(node['uuid']) return True else: module.fail_json(msg="maintenance parameter was set but a valid " "the value was not recognized.") return False def _check_set_power_state(module, cloud, node): if 'power on' in str(node['power_state']): if _is_false(module.params['power']): # User has requested the node be powered off. cloud.set_machine_power_off(node['uuid']) module.exit_json(changed=True, msg="Power requested off") if 'power off' in str(node['power_state']): if (_is_false(module.params['power']) and _is_false(module.params['state'])): return False if (_is_false(module.params['power']) and _is_false(module.params['state'])): module.exit_json( changed=False, msg="Power for node is %s, node must be reactivated " "OR set to state absent" ) # In the event the power has been toggled on and # deployment has been requested, we need to skip this # step. if (_is_true(module.params['power']) and _is_false(module.params['deploy'])): # Node is powered down when it is not awaiting to be provisioned cloud.set_machine_power_on(node['uuid']) return True # Default False if no action has been taken. return False def main(): argument_spec = openstack_full_argument_spec( uuid=dict(required=False), name=dict(required=False), instance_info=dict(type='dict', required=False), config_drive=dict(required=False), ironic_url=dict(required=False), state=dict(required=False, default='present'), maintenance=dict(required=False), maintenance_reason=dict(required=False), power=dict(required=False, default='present'), deploy=dict(required=False, default=True), wait=dict(type='bool', required=False, default=False), timeout=dict(required=False, type='int', default=1800), ) module_kwargs = openstack_module_kwargs() module = AnsibleModule(argument_spec, **module_kwargs) if not HAS_SHADE: module.fail_json(msg='shade is required for this module') if (module.params['wait'] and StrictVersion(shade.__version__) < StrictVersion('1.4.0')): module.fail_json(msg="To utilize wait, the installed version of" "the shade library MUST be >=1.4.0") if (module.params['auth_type'] in [None, 'None'] and module.params['ironic_url'] is None): module.fail_json(msg="Authentication appears disabled, Please " "define an ironic_url parameter") if (module.params['ironic_url'] and module.params['auth_type'] in [None, 'None']): module.params['auth'] = dict( endpoint=module.params['ironic_url'] ) node_id = _choose_id_value(module) if not node_id: module.fail_json(msg="A uuid or name value must be defined " "to use this module.") try: cloud = shade.operator_cloud(**module.params) node = cloud.get_machine(node_id) if node is None: module.fail_json(msg="node not found") uuid = node['uuid'] instance_info = module.params['instance_info'] changed = False wait = module.params['wait'] timeout = module.params['timeout'] # User has reqeusted desired state to be in maintenance state. if module.params['state'] is 'maintenance': module.params['maintenance'] = True if node['provision_state'] in [ 'cleaning', 'deleting', 'wait call-back']: module.fail_json(msg="Node is in %s state, cannot act upon the " "request as the node is in a transition " "state" % node['provision_state']) # TODO(TheJulia) This is in-development code, that requires # code in the shade library that is still in development. if _check_set_maintenance(module, cloud, node): if node['provision_state'] in 'active': module.exit_json(changed=True, result="Maintenance state changed") changed = True node = cloud.get_machine(node_id) if _check_set_power_state(module, cloud, node): changed = True node = cloud.get_machine(node_id) if _is_true(module.params['state']): if _is_false(module.params['deploy']): module.exit_json( changed=changed, result="User request has explicitly disabled " "deployment logic" ) if 'active' in node['provision_state']: module.exit_json( changed=changed, result="Node already in an active state." ) if instance_info is None: module.fail_json( changed=changed, msg="When setting an instance to present, " "instance_info is a required variable.") # TODO(TheJulia): Update instance info, however info is # deployment specific. Perhaps consider adding rebuild # support, although there is a known desire to remove # rebuild support from Ironic at some point in the future. patch = _prepare_instance_info_patch(instance_info) cloud.set_node_instance_info(uuid, patch) cloud.validate_node(uuid) if not wait: cloud.activate_node(uuid, module.params['config_drive']) else: cloud.activate_node( uuid, configdrive=module.params['config_drive'], wait=wait, timeout=timeout) # TODO(TheJulia): Add more error checking.. module.exit_json(changed=changed, result="node activated") elif _is_false(module.params['state']): if node['provision_state'] not in "deleted": cloud.purge_node_instance_info(uuid) if not wait: cloud.deactivate_node(uuid) else: cloud.deactivate_node( uuid, wait=wait, timeout=timeout) module.exit_json(changed=True, result="deleted") else: module.exit_json(changed=False, result="node not found") else: module.fail_json(msg="State must be present, absent, " "maintenance, off") except shade.OpenStackCloudException as e: module.fail_json(msg=str(e)) # this is magic, see lib/ansible/module_common.py from ansible.module_utils.basic import * from ansible.module_utils.openstack import * if __name__ == "__main__": main()