Ejemplo n.º 1
0
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)
Ejemplo n.º 2
0
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)