def run_module(): module = AnsibleModule(argument_spec=dict( state=dict(default="present", choices=['present', 'absent']), name=dict(required=True), resource_class=dict(default="ocf", choices=['ocf', 'systemd', 'stonith']), resource_type=dict(required=False), options=dict(default="", required=False), force_resource_update=dict(default=False, type='bool', required=False), cib_file=dict(required=False), ), supports_check_mode=True) state = module.params['state'] resource_name = module.params['name'] resource_class = module.params['resource_class'] cib_file = module.params['cib_file'] if state == 'present' and (not module.params['resource_type']): module.fail_json( msg= 'When creating cluster resource you must specify the resource_type' ) result = {} if find_executable('pcs') is None: module.fail_json(msg="'pcs' executable not found. Install 'pcs'.") module.params['cib_file_param'] = '' if cib_file is not None: # use cib_file if specified if os.path.isfile(cib_file): try: current_cib = ET.parse(cib_file) except Exception as e: module.fail_json( msg="Error encountered parsing the cib_file - %s" % (e)) current_cib_root = current_cib.getroot() module.params['cib_file_param'] = '-f ' + cib_file else: module.fail_json( msg="%(cib_file)s is not a file or doesn't exists" % module.params) else: # get running cluster configuration rc, out, err = module.run_command('pcs cluster cib') if rc == 0: current_cib_root = ET.fromstring(out) else: module.fail_json(msg='Failed to load cluster configuration', out=out, error=err) # try to find the resource that we seek resource = None cib_resources = current_cib_root.find('./configuration/resources') resource = find_resource(cib_resources, resource_name) if state == 'present' and resource is None: # resource should be present, but we don't see it in configuration - lets create it result['changed'] = True if not module.check_mode: if resource_class == 'stonith': cmd = 'pcs %(cib_file_param)s stonith create %(name)s %(resource_type)s %(options)s' % module.params else: cmd = 'pcs %(cib_file_param)s resource create %(name)s %(resource_type)s %(options)s' % module.params rc, out, err = module.run_command(cmd) if rc != 0 and "Call cib_replace failed (-62): Timer expired" in err: # EL6: special retry when we failed to create resource because of timer waiting on cib expired rc, out, err = module.run_command(cmd) if rc == 0: module.exit_json(changed=True) else: module.fail_json( msg="Failed to create resource using command '" + cmd + "'", output=out, error=err) elif state == 'present' and resource is not None: # resource should be present and we have find resource with such ID - lets compare it with definition if it needs a change # lets simulate how the resource would look like if it was created using command we have clean_cib_fd, clean_cib_path = tempfile.mkstemp() module.add_cleanup_file(clean_cib_path) module.do_cleanup_files() # we must be sure that clean_cib_path is empty if resource_class == 'stonith': cmd = 'pcs -f ' + clean_cib_path + ' stonith create %(name)s %(resource_type)s %(options)s' % module.params else: cmd = 'pcs -f ' + clean_cib_path + ' resource create %(name)s %(resource_type)s %(options)s' % module.params rc, out, err = module.run_command(cmd) if rc == 0: # we have a comparable resource created in clean cluster, so lets select it and compare it clean_cib = ET.parse(clean_cib_path) clean_cib_root = clean_cib.getroot() clean_resource = None cib_clean_resources = clean_cib_root.find( './configuration/resources') clean_resource = find_resource(cib_clean_resources, resource_name) if clean_resource is not None: # remove the meta_attribute element from original cluster cib when empty to make comparison clean - Issue #10 for elem in list(resource): if elem.tag == 'meta_attributes' and len(list(elem)) == 0: resource.remove(elem) rc, diff = compare_resources(module, resource, clean_resource) if rc == 0: # if no differnces were find there is no need to update the resource module.exit_json(changed=False) else: # otherwise lets replace the resource with new one result['changed'] = True result['diff'] = diff if not module.check_mode: replace_element(resource, clean_resource) # when we use cib_file then we can dump the changed CIB directly into file if cib_file is not None: try: current_cib.write( cib_file ) # FIXME add try/catch for writing into file except Exception as e: module.fail_json( msg= "Error encountered writing result to cib_file - %s" % (e)) module.exit_json(changed=True) # when not using cib_file then we continue preparing changes for cib-push into running cluster new_cib = ET.ElementTree(current_cib_root) new_cib_fd, new_cib_path = tempfile.mkstemp() module.add_cleanup_file(new_cib_path) new_cib.write(new_cib_path) push_scope = 'scope=resources' if module.params[ 'force_resource_update'] else '' push_cmd = 'pcs cluster cib-push ' + push_scope + ' ' + new_cib_path rc, out, err = module.run_command(push_cmd) if rc == 0: module.exit_json(changed=True) else: module.fail_json( msg= "Failed to push updated configuration to cluster using command '" + push_cmd + "'", output=out, error=err) else: module.fail_json( msg= "Unable to find simulated resource, This is most probably a bug." ) else: module.fail_json( msg= "Unable to simulate resource with given definition using command '" + cmd + "'", output=out, error=err) elif state == 'absent' and resource is not None: # resource should not be present but we have found something - lets remove that result['changed'] = True if not module.check_mode: if resource_class == 'stonith': cmd = 'pcs %(cib_file_param)s stonith delete %(name)s' % module.params else: cmd = 'pcs %(cib_file_param)s resource delete %(name)s' % module.params rc, out, err = module.run_command(cmd) if rc == 0: module.exit_json(changed=True) else: module.fail_json( msg="Failed to delete resource using command '" + cmd + "'", output=out, error=err) else: # resource should not be present and is nto there, nothing to do result['changed'] = False # END of module module.exit_json(**result)
def run_module(): module = AnsibleModule(argument_spec=dict( state=dict(default="present", choices=['present', 'absent']), name=dict(required=True), resource_class=dict( default="ocf", choices=['ocf', 'systemd', 'stonith', 'master', 'promotable']), resource_type=dict(required=False), options=dict(default="", required=False), force_resource_update=dict(default=False, type='bool', required=False), cib_file=dict(required=False), child_name=dict(required=False), ignored_meta_attributes=dict(required=False, type='list', elements='str', default=[]), ), supports_check_mode=True) state = module.params['state'] resource_name = module.params['name'] resource_class = module.params['resource_class'] cib_file = module.params['cib_file'] if 'child_name' in module.params and module.params['child_name'] is None: module.params['child_name'] = resource_name + '-child' child_name = module.params['child_name'] resource_options = module.params['options'] ignored_meta_attributes = module.params['ignored_meta_attributes'] if state == 'present' and (not module.params['resource_type']): module.fail_json( msg= 'When creating cluster resource you must specify the resource_type' ) result = {} if find_executable('pcs') is None: module.fail_json(msg="'pcs' executable not found. Install 'pcs'.") # get the pcs major.minor version rc, out, err = module.run_command('pcs --version') if rc == 0: pcs_version = out.split('.')[0] + '.' + out.split('.')[1] else: module.fail_json(msg="pcs --version exited with non-zero exit code (" + rc + "): " + out + err) # check if 'master' and 'promotable' classes have the needed keyword in options if resource_class == 'master' and not ('--master' in resource_options or 'master' in resource_options): module.fail_json( msg= 'When creating Master/Slave resource you must specify keyword "master" or "--master" in "options"' ) if resource_class == 'promotable' and 'promotable' not in resource_options: module.fail_json( msg= 'When creating promotable resource you must specify keyword "promotable" in "options"' ) module.params['cib_file_param'] = '' if cib_file is not None: # use cib_file if specified if os.path.isfile(cib_file): try: current_cib = ET.parse(cib_file) except Exception as e: module.fail_json( msg="Error encountered parsing the cib_file - %s" % (e)) current_cib_root = current_cib.getroot() module.params['cib_file_param'] = '-f ' + cib_file else: module.fail_json( msg="%(cib_file)s is not a file or doesn't exists" % module.params) else: # get running cluster configuration rc, out, err = module.run_command('pcs cluster cib') if rc == 0: current_cib_root = ET.fromstring(out) else: module.fail_json(msg='Failed to load cluster configuration', out=out, error=err) # try to find the resource that we seek resource = None cib_resources = current_cib_root.find('./configuration/resources') resource = find_resource(cib_resources, resource_name) if state == 'present' and resource is None: # resource should be present, but we don't see it in configuration - lets create it result['changed'] = True if not module.check_mode: if resource_class == 'stonith': cmd = 'pcs %(cib_file_param)s stonith create %(name)s %(resource_type)s %(options)s' % module.params elif resource_class == 'master' or resource_class == 'promotable': # we first create Master/Slave or Promotable resource with child_name and later rename it cmd = 'pcs %(cib_file_param)s resource create %(child_name)s %(resource_type)s %(options)s' % module.params else: cmd = 'pcs %(cib_file_param)s resource create %(name)s %(resource_type)s %(options)s' % module.params rc, out, err = module.run_command(cmd) if rc != 0 and "Call cib_replace failed (-62): Timer expired" in err: # EL6: special retry when we failed to create resource because of timer waiting on cib expired rc, out, err = module.run_command(cmd) if rc == 0: if resource_class == 'master' or resource_class == 'promotable': # rename the resource to desirable name rc, out, err = module.run_command('pcs cluster cib') if rc == 0: updated_cib_root = ET.fromstring(out) multistate_resource = None updated_cib_resources = updated_cib_root.find( './configuration/resources') resource_suffix = '-master' if pcs_version == '0.9' else '-clone' multistate_resource = find_resource( updated_cib_resources, child_name + resource_suffix) if multistate_resource is not None: rename_multistate_element(multistate_resource, resource_name, child_name, resource_suffix) ## # when not using cib_file then we continue preparing changes for cib-push into running cluster new_cib = ET.ElementTree(updated_cib_root) new_cib_fd, new_cib_path = tempfile.mkstemp() module.add_cleanup_file(new_cib_path) new_cib.write(new_cib_path) push_scope = 'scope=resources' if module.params[ 'force_resource_update'] else '' push_cmd = 'pcs cluster cib-push ' + push_scope + ' ' + new_cib_path rc, out, err = module.run_command(push_cmd) if rc == 0: module.exit_json(changed=True) else: # rollback the failed rename by deleting the multistate resource cmd = 'pcs %(cib_file_param)s resource delete %(child_name)s' % module.params rc2, out2, err2 = module.run_command(cmd) if rc2 == 0: module.fail_json( msg= "Failed to push updated configuration for multistate resource to cluster using command '" + push_cmd + "'. Creation of multistate resource was rolled back. You can retry this task with 'force_resource_update=true' to see if that helps.", output=out, error=err) else: module.fail_json( msg= "Failed to delete resource after unsuccessful multistate resource configuration update using command '" + cmd + "'", output=out2, error=err2) else: module.fail_json( msg= "Failed to detect multistate resource after creating it with cmd '" + cmd + "'!", output=out, error=err, previous_cib=current_cib) module.exit_json(changed=True) else: module.fail_json( msg="Failed to create resource using command '" + cmd + "'", output=out, error=err) elif state == 'present' and resource is not None: # resource should be present and we have find resource with such ID - lets compare it with definition if it needs a change # lets simulate how the resource would look like if it was created using command we have clean_cib_fd, clean_cib_path = tempfile.mkstemp() module.add_cleanup_file(clean_cib_path) module.do_cleanup_files() # we must be sure that clean_cib_path is empty if resource_class == 'stonith': cmd = 'pcs -f ' + clean_cib_path + ' stonith create %(name)s %(resource_type)s %(options)s' % module.params elif resource_class == 'master' or resource_class == 'promotable': # we first create Master/Slave or Promotable resource with child_name and later rename it cmd = 'pcs -f ' + clean_cib_path + ' resource create %(child_name)s %(resource_type)s %(options)s' % module.params else: cmd = 'pcs -f ' + clean_cib_path + ' resource create %(name)s %(resource_type)s %(options)s' % module.params rc, out, err = module.run_command(cmd) if rc == 0: if resource_class == 'master' or resource_class == 'promotable': # deal with multistate resources clean_cib = ET.parse(clean_cib_path) clean_cib_root = clean_cib.getroot() multistate_resource = None updated_cib_resources = clean_cib_root.find( './configuration/resources') resource_suffix = '-master' if pcs_version == '0.9' else '-clone' multistate_resource = find_resource( updated_cib_resources, child_name + resource_suffix) if multistate_resource is not None: rename_multistate_element(multistate_resource, resource_name, child_name, resource_suffix) # we try to write the changes into temporary cib_file try: clean_cib.write(clean_cib_path) except Exception as e: module.fail_json( msg= "Error encountered writing intermediate multistate result to clean_cib_path - %s" % (e)) else: module.fail_json( msg= "Failed to detect intermediate multistate resource after creating it with cmd '" + cmd + "'!", output=out, error=err, previous_cib=current_cib) # we have a comparable resource created in clean cluster, so lets select it and compare it clean_cib = ET.parse(clean_cib_path) clean_cib_root = clean_cib.getroot() clean_resource = None cib_clean_resources = clean_cib_root.find( './configuration/resources') clean_resource = find_resource(cib_clean_resources, resource_name) if clean_resource is not None: # cleanup the definition of resource and clean_resource before comparison remove_ignored_meta_attributes(resource, ignored_meta_attributes) remove_empty_meta_attributes_tag(resource) remove_ignored_meta_attributes(clean_resource, ignored_meta_attributes) remove_empty_meta_attributes_tag(clean_resource) # compare the existing resource in cluster and simulated clean_resource rc, diff = compare_resources(module, resource, clean_resource) if rc == 0: # if no differnces were find there is no need to update the resource module.exit_json(changed=False) else: # otherwise lets replace the resource with new one result['changed'] = True result['diff'] = diff if not module.check_mode: replace_element(resource, clean_resource) # when we use cib_file then we can dump the changed CIB directly into file if cib_file is not None: try: current_cib.write( cib_file ) # FIXME add try/catch for writing into file except Exception as e: module.fail_json( msg= "Error encountered writing result to cib_file - %s" % (e)) module.exit_json(changed=True) # when not using cib_file then we continue preparing changes for cib-push into running cluster new_cib = ET.ElementTree(current_cib_root) new_cib_fd, new_cib_path = tempfile.mkstemp() module.add_cleanup_file(new_cib_path) new_cib.write(new_cib_path) push_scope = 'scope=resources' if module.params[ 'force_resource_update'] else '' push_cmd = 'pcs cluster cib-push ' + push_scope + ' ' + new_cib_path rc, out, err = module.run_command(push_cmd) if rc == 0: module.exit_json(changed=True) else: module.fail_json( msg= "Failed to push updated configuration to cluster using command '" + push_cmd + "'", output=out, error=err) else: module.fail_json( msg= "Unable to find simulated resource, This is most probably a bug." ) else: module.fail_json( msg= "Unable to simulate resource with given definition using command '" + cmd + "'", output=out, error=err) elif state == 'absent' and resource is not None: # resource should not be present but we have found something - lets remove that result['changed'] = True if not module.check_mode: if resource_class == 'stonith': cmd = 'pcs %(cib_file_param)s stonith delete %(name)s' % module.params else: cmd = 'pcs %(cib_file_param)s resource delete %(name)s' % module.params rc, out, err = module.run_command(cmd) if rc == 0: module.exit_json(changed=True) else: module.fail_json( msg="Failed to delete resource using command '" + cmd + "'", output=out, error=err) else: # resource should not be present and is nto there, nothing to do result['changed'] = False # END of module module.exit_json(**result)