Ejemplo n.º 1
0
class OneViewModuleBase(object):
    MSG_CREATED = 'Resource created successfully.'
    MSG_UPDATED = 'Resource updated successfully.'
    MSG_DELETED = 'Resource deleted successfully.'
    MSG_ALREADY_PRESENT = 'Resource is already present.'
    MSG_ALREADY_ABSENT = 'Resource is already absent.'
    MSG_DIFF_AT_KEY = 'Difference found at key \'{0}\'. '

    ONEVIEW_COMMON_ARGS = dict(config=dict(type='path'),
                               hostname=dict(type='str'),
                               username=dict(type='str'),
                               password=dict(type='str', no_log=True),
                               api_version=dict(type='int'),
                               image_streamer_hostname=dict(type='str'))

    ONEVIEW_VALIDATE_ETAG_ARGS = dict(
        validate_etag=dict(type='bool', default=True))

    resource_client = None

    def __init__(self, additional_arg_spec=None, validate_etag_support=False):
        """
        OneViewModuleBase constructor.

        :arg dict additional_arg_spec: Additional argument spec definition.
        :arg bool validate_etag_support: Enables support to eTag validation.
        """
        argument_spec = self._build_argument_spec(additional_arg_spec,
                                                  validate_etag_support)

        self.module = AnsibleModule(argument_spec=argument_spec,
                                    supports_check_mode=False)

        self._check_hpe_oneview_sdk()
        self._create_oneview_client()

        self.state = self.module.params.get('state')
        self.data = self.module.params.get('data')

        # Preload params for get_all - used by facts
        self.facts_params = self.module.params.get('params') or {}

        # Preload options as dict - used by facts
        self.options = transform_list_to_dict(
            self.module.params.get('options'))

        self.validate_etag_support = validate_etag_support

    def _build_argument_spec(self, additional_arg_spec, validate_etag_support):

        merged_arg_spec = dict()
        merged_arg_spec.update(self.ONEVIEW_COMMON_ARGS)

        if validate_etag_support:
            merged_arg_spec.update(self.ONEVIEW_VALIDATE_ETAG_ARGS)

        if additional_arg_spec:
            merged_arg_spec.update(additional_arg_spec)

        return merged_arg_spec

    def _check_hpe_oneview_sdk(self):
        if not HAS_HPE_ONEVIEW:
            self.module.fail_json(msg=missing_required_lib('hpOneView'),
                                  exception=HPE_ONEVIEW_IMP_ERR)

    def _create_oneview_client(self):
        if self.module.params.get('hostname'):
            config = dict(ip=self.module.params['hostname'],
                          credentials=dict(
                              userName=self.module.params['username'],
                              password=self.module.params['password']),
                          api_version=self.module.params['api_version'],
                          image_streamer_ip=self.module.
                          params['image_streamer_hostname'])
            self.oneview_client = OneViewClient(config)
        elif not self.module.params['config']:
            self.oneview_client = OneViewClient.from_environment_variables()
        else:
            self.oneview_client = OneViewClient.from_json_file(
                self.module.params['config'])

    @abc.abstractmethod
    def execute_module(self):
        """
        Abstract method, must be implemented by the inheritor.

        This method is called from the run method. It should contains the module logic

        :return: dict: It must return a dictionary with the attributes for the module result,
            such as ansible_facts, msg and changed.
        """
        pass

    def run(self):
        """
        Common implementation of the OneView run modules.

        It calls the inheritor 'execute_module' function and sends the return to the Ansible.

        It handles any OneViewModuleException in order to signal a failure to Ansible, with a descriptive error message.

        """
        try:
            if self.validate_etag_support:
                if not self.module.params.get('validate_etag'):
                    self.oneview_client.connection.disable_etag_validation()

            result = self.execute_module()

            if "changed" not in result:
                result['changed'] = False

            self.module.exit_json(**result)

        except OneViewModuleException as exception:
            error_msg = '; '.join(to_native(e) for e in exception.args)
            self.module.fail_json(msg=error_msg,
                                  exception=traceback.format_exc())

    def resource_absent(self, resource, method='delete'):
        """
        Generic implementation of the absent state for the OneView resources.

        It checks if the resource needs to be removed.

        :arg dict resource: Resource to delete.
        :arg str method: Function of the OneView client that will be called for resource deletion.
            Usually delete or remove.
        :return: A dictionary with the expected arguments for the AnsibleModule.exit_json
        """
        if resource:
            getattr(self.resource_client, method)(resource)

            return {"changed": True, "msg": self.MSG_DELETED}
        else:
            return {"changed": False, "msg": self.MSG_ALREADY_ABSENT}

    def get_by_name(self, name):
        """
        Generic get by name implementation.

        :arg str name: Resource name to search for.

        :return: The resource found or None.
        """
        result = self.resource_client.get_by('name', name)
        return result[0] if result else None

    def resource_present(self, resource, fact_name, create_method='create'):
        """
        Generic implementation of the present state for the OneView resources.

        It checks if the resource needs to be created or updated.

        :arg dict resource: Resource to create or update.
        :arg str fact_name: Name of the fact returned to the Ansible.
        :arg str create_method: Function of the OneView client that will be called for resource creation.
            Usually create or add.
        :return: A dictionary with the expected arguments for the AnsibleModule.exit_json
        """

        changed = False
        if "newName" in self.data:
            self.data["name"] = self.data.pop("newName")

        if not resource:
            resource = getattr(self.resource_client, create_method)(self.data)
            msg = self.MSG_CREATED
            changed = True

        else:
            merged_data = resource.copy()
            merged_data.update(self.data)

            if self.compare(resource, merged_data):
                msg = self.MSG_ALREADY_PRESENT
            else:
                resource = self.resource_client.update(merged_data)
                changed = True
                msg = self.MSG_UPDATED

        return dict(msg=msg,
                    changed=changed,
                    ansible_facts={fact_name: resource})

    def resource_scopes_set(self, state, fact_name, scope_uris):
        """
        Generic implementation of the scopes update PATCH for the OneView resources.
        It checks if the resource needs to be updated with the current scopes.
        This method is meant to be run after ensuring the present state.
        :arg dict state: Dict containing the data from the last state results in the resource.
            It needs to have the 'msg', 'changed', and 'ansible_facts' entries.
        :arg str fact_name: Name of the fact returned to the Ansible.
        :arg list scope_uris: List with all the scope URIs to be added to the resource.
        :return: A dictionary with the expected arguments for the AnsibleModule.exit_json
        """
        if scope_uris is None:
            scope_uris = []
        resource = state['ansible_facts'][fact_name]
        operation_data = dict(operation='replace',
                              path='/scopeUris',
                              value=scope_uris)

        if resource['scopeUris'] is None or set(
                resource['scopeUris']) != set(scope_uris):
            state['ansible_facts'][fact_name] = self.resource_client.patch(
                resource['uri'], **operation_data)
            state['changed'] = True
            state['msg'] = self.MSG_UPDATED

        return state

    def compare(self, first_resource, second_resource):
        """
        Recursively compares dictionary contents equivalence, ignoring types and elements order.
        Particularities of the comparison:
            - Inexistent key = None
            - These values are considered equal: None, empty, False
            - Lists are compared value by value after a sort, if they have same size.
            - Each element is converted to str before the comparison.
        :arg dict first_resource: first dictionary
        :arg dict second_resource: second dictionary
        :return: bool: True when equal, False when different.
        """
        resource1 = first_resource
        resource2 = second_resource

        debug_resources = "resource1 = {0}, resource2 = {1}".format(
            resource1, resource2)

        # The first resource is True / Not Null and the second resource is False / Null
        if resource1 and not resource2:
            self.module.log("resource1 and not resource2. " + debug_resources)
            return False

        # Checks all keys in first dict against the second dict
        for key in resource1:
            if key not in resource2:
                if resource1[key] is not None:
                    # Inexistent key is equivalent to exist with value None
                    self.module.log(
                        self.MSG_DIFF_AT_KEY.format(key) + debug_resources)
                    return False
            # If both values are null, empty or False it will be considered equal.
            elif not resource1[key] and not resource2[key]:
                continue
            elif isinstance(resource1[key], Mapping):
                # recursive call
                if not self.compare(resource1[key], resource2[key]):
                    self.module.log(
                        self.MSG_DIFF_AT_KEY.format(key) + debug_resources)
                    return False
            elif isinstance(resource1[key], list):
                # change comparison function to compare_list
                if not self.compare_list(resource1[key], resource2[key]):
                    self.module.log(
                        self.MSG_DIFF_AT_KEY.format(key) + debug_resources)
                    return False
            elif _standardize_value(resource1[key]) != _standardize_value(
                    resource2[key]):
                self.module.log(
                    self.MSG_DIFF_AT_KEY.format(key) + debug_resources)
                return False

        # Checks all keys in the second dict, looking for missing elements
        for key in resource2.keys():
            if key not in resource1:
                if resource2[key] is not None:
                    # Inexistent key is equivalent to exist with value None
                    self.module.log(
                        self.MSG_DIFF_AT_KEY.format(key) + debug_resources)
                    return False

        return True

    def compare_list(self, first_resource, second_resource):
        """
        Recursively compares lists contents equivalence, ignoring types and element orders.
        Lists with same size are compared value by value after a sort,
        each element is converted to str before the comparison.
        :arg list first_resource: first list
        :arg list second_resource: second list
        :return: True when equal; False when different.
        """

        resource1 = first_resource
        resource2 = second_resource

        debug_resources = "resource1 = {0}, resource2 = {1}".format(
            resource1, resource2)

        # The second list is null / empty  / False
        if not resource2:
            self.module.log("resource 2 is null. " + debug_resources)
            return False

        if len(resource1) != len(resource2):
            self.module.log("resources have different length. " +
                            debug_resources)
            return False

        resource1 = sorted(resource1, key=_str_sorted)
        resource2 = sorted(resource2, key=_str_sorted)

        for i, val in enumerate(resource1):
            if isinstance(val, Mapping):
                # change comparison function to compare dictionaries
                if not self.compare(val, resource2[i]):
                    self.module.log("resources are different. " +
                                    debug_resources)
                    return False
            elif isinstance(val, list):
                # recursive call
                if not self.compare_list(val, resource2[i]):
                    self.module.log("lists are different. " + debug_resources)
                    return False
            elif _standardize_value(val) != _standardize_value(resource2[i]):
                self.module.log("values are different. " + debug_resources)
                return False

        # no differences found
        return True
Ejemplo n.º 2
0
class NetAppESeriesModule(object):
    """Base class for all NetApp E-Series modules.

    Provides a set of common methods for NetApp E-Series modules, including version checking, mode (proxy, embedded)
    verification, http requests, secure http redirection for embedded web services, and logging setup.

    Be sure to add the following lines in the module's documentation section:
    extends_documentation_fragment:
        - netapp.eseries

    :param dict(dict) ansible_options: dictionary of ansible option definitions
    :param str web_services_version: minimally required web services rest api version (default value: "02.00.0000.0000")
    :param bool supports_check_mode: whether the module will support the check_mode capabilities (default=False)
    :param list(list) mutually_exclusive: list containing list(s) of mutually exclusive options (optional)
    :param list(list) required_if: list containing list(s) containing the option, the option value, and then
    a list of required options. (optional)
    :param list(list) required_one_of: list containing list(s) of options for which at least one is required. (optional)
    :param list(list) required_together: list containing list(s) of options that are required together. (optional)
    :param bool log_requests: controls whether to log each request (default: True)
    """
    DEFAULT_TIMEOUT = 60
    DEFAULT_SECURE_PORT = "8443"
    DEFAULT_REST_API_PATH = "devmgr/v2/"
    DEFAULT_REST_API_ABOUT_PATH = "devmgr/utils/about"
    DEFAULT_HEADERS = {
        "Content-Type": "application/json",
        "Accept": "application/json",
        "netapp-client-type": "Ansible-%s" % ansible_version
    }
    HTTP_AGENT = "Ansible / %s" % ansible_version
    SIZE_UNIT_MAP = dict(bytes=1,
                         b=1,
                         kb=1024,
                         mb=1024**2,
                         gb=1024**3,
                         tb=1024**4,
                         pb=1024**5,
                         eb=1024**6,
                         zb=1024**7,
                         yb=1024**8)

    def __init__(self,
                 ansible_options,
                 web_services_version=None,
                 supports_check_mode=False,
                 mutually_exclusive=None,
                 required_if=None,
                 required_one_of=None,
                 required_together=None,
                 log_requests=True):
        argument_spec = eseries_host_argument_spec()
        argument_spec.update(ansible_options)

        self.module = AnsibleModule(argument_spec=argument_spec,
                                    supports_check_mode=supports_check_mode,
                                    mutually_exclusive=mutually_exclusive,
                                    required_if=required_if,
                                    required_one_of=required_one_of,
                                    required_together=required_together)

        args = self.module.params
        self.web_services_version = web_services_version if web_services_version else "02.00.0000.0000"
        self.ssid = args["ssid"]
        self.url = args["api_url"]
        self.log_requests = log_requests
        self.creds = dict(url_username=args["api_username"],
                          url_password=args["api_password"],
                          validate_certs=args["validate_certs"])

        if not self.url.endswith("/"):
            self.url += "/"

        self.is_embedded_mode = None
        self.is_web_services_valid_cache = None

    def _check_web_services_version(self):
        """Verify proxy or embedded web services meets minimum version required for module.

        The minimum required web services version is evaluated against version supplied through the web services rest
        api. AnsibleFailJson exception will be raised when the minimum is not met or exceeded.

        This helper function will update the supplied api url if secure http is not used for embedded web services

        :raise AnsibleFailJson: raised when the contacted api service does not meet the minimum required version.
        """
        if not self.is_web_services_valid_cache:

            url_parts = urlparse(self.url)
            if not url_parts.scheme or not url_parts.netloc:
                self.module.fail_json(
                    msg=
                    "Failed to provide valid API URL. Example: https://192.168.1.100:8443/devmgr/v2. URL [%s]."
                    % self.url)

            if url_parts.scheme not in ["http", "https"]:
                self.module.fail_json(
                    msg="Protocol must be http or https. URL [%s]." % self.url)

            self.url = "%s://%s/" % (url_parts.scheme, url_parts.netloc)
            about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
            rc, data = request(about_url,
                               timeout=self.DEFAULT_TIMEOUT,
                               headers=self.DEFAULT_HEADERS,
                               ignore_errors=True,
                               **self.creds)

            if rc != 200:
                self.module.warn(
                    "Failed to retrieve web services about information! Retrying with secure ports. Array Id [%s]."
                    % self.ssid)
                self.url = "https://%s:8443/" % url_parts.netloc.split(":")[0]
                about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
                try:
                    rc, data = request(about_url,
                                       timeout=self.DEFAULT_TIMEOUT,
                                       headers=self.DEFAULT_HEADERS,
                                       **self.creds)
                except Exception as error:
                    self.module.fail_json(
                        msg=
                        "Failed to retrieve the webservices about information! Array Id [%s]. Error [%s]."
                        % (self.ssid, to_native(error)))

            major, minor, other, revision = data["version"].split(".")
            minimum_major, minimum_minor, other, minimum_revision = self.web_services_version.split(
                ".")

            if not (major > minimum_major or
                    (major == minimum_major and minor > minimum_minor) or
                    (major == minimum_major and minor == minimum_minor
                     and revision >= minimum_revision)):
                self.module.fail_json(
                    msg=
                    "Web services version does not meet minimum version required. Current version: [%s]."
                    " Version required: [%s]." %
                    (data["version"], self.web_services_version))

            self.module.log(
                "Web services rest api version met the minimum required version."
            )
            self.is_web_services_valid_cache = True

    def is_embedded(self):
        """Determine whether web services server is the embedded web services.

        If web services about endpoint fails based on an URLError then the request will be attempted again using
        secure http.

        :raise AnsibleFailJson: raised when web services about endpoint failed to be contacted.
        :return bool: whether contacted web services is running from storage array (embedded) or from a proxy.
        """
        self._check_web_services_version()

        if self.is_embedded_mode is None:
            about_url = self.url + self.DEFAULT_REST_API_ABOUT_PATH
            try:
                rc, data = request(about_url,
                                   timeout=self.DEFAULT_TIMEOUT,
                                   headers=self.DEFAULT_HEADERS,
                                   **self.creds)
                self.is_embedded_mode = not data["runningAsProxy"]
            except Exception as error:
                self.module.fail_json(
                    msg=
                    "Failed to retrieve the webservices about information! Array Id [%s]. Error [%s]."
                    % (self.ssid, to_native(error)))

        return self.is_embedded_mode

    def request(self,
                path,
                data=None,
                method='GET',
                headers=None,
                ignore_errors=False):
        """Issue an HTTP request to a url, retrieving an optional JSON response.

        :param str path: web services rest api endpoint path (Example: storage-systems/1/graph). Note that when the
        full url path is specified then that will be used without supplying the protocol, hostname, port and rest path.
        :param data: data required for the request (data may be json or any python structured data)
        :param str method: request method such as GET, POST, DELETE.
        :param dict headers: dictionary containing request headers.
        :param bool ignore_errors: forces the request to ignore any raised exceptions.
        """
        self._check_web_services_version()

        if headers is None:
            headers = self.DEFAULT_HEADERS

        if not isinstance(
                data, str) and headers["Content-Type"] == "application/json":
            data = json.dumps(data)

        if path.startswith("/"):
            path = path[1:]
        request_url = self.url + self.DEFAULT_REST_API_PATH + path

        if self.log_requests or True:
            self.module.log(
                pformat(dict(url=request_url, data=data, method=method)))

        return request(url=request_url,
                       data=data,
                       method=method,
                       headers=headers,
                       use_proxy=True,
                       force=False,
                       last_mod_time=None,
                       timeout=self.DEFAULT_TIMEOUT,
                       http_agent=self.HTTP_AGENT,
                       force_basic_auth=True,
                       ignore_errors=ignore_errors,
                       **self.creds)
Ejemplo n.º 3
0
def run_module():

    # Define the available arguments/parameters that a user can pass to
    # the module.
    # Defaults for VDO parameters are None, in order to facilitate
    # the detection of parameters passed from the playbook.
    # Creation param defaults are determined by the creation section.

    module_args = dict(name=dict(type='str', required=True),
                       state=dict(type='str',
                                  default='present',
                                  choices=['absent', 'present']),
                       activated=dict(type='bool'),
                       running=dict(type='bool'),
                       growphysical=dict(type='bool', default=False),
                       device=dict(type='str'),
                       logicalsize=dict(type='str'),
                       deduplication=dict(type='str',
                                          choices=['disabled', 'enabled']),
                       compression=dict(type='str',
                                        choices=['disabled', 'enabled']),
                       blockmapcachesize=dict(type='str'),
                       readcache=dict(type='str',
                                      choices=['disabled', 'enabled']),
                       readcachesize=dict(type='str'),
                       emulate512=dict(type='bool', default=False),
                       slabsize=dict(type='str'),
                       writepolicy=dict(type='str',
                                        choices=['async', 'auto', 'sync']),
                       indexmem=dict(type='str'),
                       indexmode=dict(type='str', choices=['dense', 'sparse']),
                       ackthreads=dict(type='str'),
                       biothreads=dict(type='str'),
                       cputhreads=dict(type='str'),
                       logicalthreads=dict(type='str'),
                       physicalthreads=dict(type='str'))

    # Seed the result dictionary in the object.  There will be an
    # 'invocation' dictionary added with 'module_args' (arguments
    # given).
    result = dict(changed=False, )

    # the AnsibleModule object will be our abstraction working with Ansible
    # this includes instantiation, a couple of common attr would be the
    # args/params passed to the execution, as well as if the module
    # supports check mode
    module = AnsibleModule(
        argument_spec=module_args,
        supports_check_mode=False,
    )

    if not HAS_YAML:
        module.fail_json(msg=missing_required_lib('PyYAML'),
                         exception=YAML_IMP_ERR)

    vdocmd = module.get_bin_path("vdo", required=True)
    if not vdocmd:
        module.fail_json(msg='VDO is not installed.', **result)

    # Print a pre-run list of VDO volumes in the result object.
    vdolist = inventory_vdos(module, vdocmd)

    runningvdolist = list_running_vdos(module, vdocmd)

    # Collect the name of the desired VDO volume, and its state.  These will
    # determine what to do.
    desiredvdo = module.params['name']
    state = module.params['state']

    # Create a desired VDO volume that doesn't exist yet.
    if (desiredvdo not in vdolist) and (state == 'present'):
        device = module.params['device']
        if device is None:
            module.fail_json(msg="Creating a VDO volume requires specifying "
                             "a 'device' in the playbook.")

        # Create a dictionary of the options from the AnsibleModule
        # parameters, compile the vdo command options, and run "vdo create"
        # with those options.
        # Since this is a creation of a new VDO volume, it will contain all
        # all of the parameters given by the playbook; the rest will
        # assume default values.
        options = module.params
        vdocmdoptions = add_vdooptions(options)
        rc, out, err = module.run_command(
            "%s create --name=%s --device=%s %s" %
            (vdocmd, desiredvdo, device, vdocmdoptions))
        if rc == 0:
            result['changed'] = True
        else:
            module.fail_json(msg="Creating VDO %s failed." % desiredvdo,
                             rc=rc,
                             err=err)

        if (module.params['compression'] == 'disabled'):
            rc, out, err = module.run_command(
                "%s disableCompression --name=%s" % (vdocmd, desiredvdo))

        if ((module.params['deduplication'] is not None)
                and module.params['deduplication'] == 'disabled'):
            rc, out, err = module.run_command("%s disableDeduplication "
                                              "--name=%s" %
                                              (vdocmd, desiredvdo))

        if module.params['activated'] == 'no':
            deactivate_vdo(module, desiredvdo, vdocmd)

        if module.params['running'] == 'no':
            stop_vdo(module, desiredvdo, vdocmd)

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        module.log("created VDO volume %s" % desiredvdo)
        module.exit_json(**result)

    # Modify the current parameters of a VDO that exists.
    if (desiredvdo in vdolist) and (state == 'present'):
        rc, vdostatusoutput, err = module.run_command("%s status" % (vdocmd))
        vdostatusyaml = yaml.load(vdostatusoutput)

        # An empty dictionary to contain dictionaries of VDO statistics
        processedvdos = {}

        vdoyamls = vdostatusyaml['VDOs']
        if vdoyamls is not None:
            processedvdos = vdoyamls

        # The 'vdo status' keys that are currently modifiable.
        statusparamkeys = [
            'Acknowledgement threads', 'Bio submission threads',
            'Block map cache size', 'CPU-work threads', 'Logical threads',
            'Physical threads', 'Read cache', 'Read cache size',
            'Configured write policy', 'Compression', 'Deduplication'
        ]

        # A key translation table from 'vdo status' output to Ansible
        # module parameters.  This covers all of the 'vdo status'
        # parameter keys that could be modified with the 'vdo'
        # command.
        vdokeytrans = {
            'Logical size': 'logicalsize',
            'Compression': 'compression',
            'Deduplication': 'deduplication',
            'Block map cache size': 'blockmapcachesize',
            'Read cache': 'readcache',
            'Read cache size': 'readcachesize',
            'Configured write policy': 'writepolicy',
            'Acknowledgement threads': 'ackthreads',
            'Bio submission threads': 'biothreads',
            'CPU-work threads': 'cputhreads',
            'Logical threads': 'logicalthreads',
            'Physical threads': 'physicalthreads'
        }

        # Build a dictionary of the current VDO status parameters, with
        # the keys used by VDO.  (These keys will be converted later.)
        currentvdoparams = {}

        # Build a "lookup table" dictionary containing a translation table
        # of the parameters that can be modified
        modtrans = {}

        for statfield in statusparamkeys:
            if statfield in processedvdos[desiredvdo]:
                currentvdoparams[statfield] = processedvdos[desiredvdo][
                    statfield]

            modtrans[statfield] = vdokeytrans[statfield]

        # Build a dictionary of current parameters formatted with the
        # same keys as the AnsibleModule parameters.
        currentparams = {}
        for paramkey in modtrans.keys():
            currentparams[modtrans[paramkey]] = modtrans[paramkey]

        diffparams = {}

        # Check for differences between the playbook parameters and the
        # current parameters.  This will need a comparison function;
        # since AnsibleModule params are all strings, compare them as
        # strings (but if it's None; skip).
        for key in currentparams.keys():
            if module.params[key] is not None:
                if str(currentparams[key]) != module.params[key]:
                    diffparams[key] = module.params[key]

        if diffparams:
            vdocmdoptions = add_vdooptions(diffparams)
            if vdocmdoptions:
                rc, out, err = module.run_command(
                    "%s modify --name=%s %s" %
                    (vdocmd, desiredvdo, vdocmdoptions))
                if rc == 0:
                    result['changed'] = True
                else:
                    module.fail_json(msg="Modifying VDO %s failed." %
                                     desiredvdo,
                                     rc=rc,
                                     err=err)

            if 'deduplication' in diffparams.keys():
                dedupemod = diffparams['deduplication']
                if dedupemod == 'disabled':
                    rc, out, err = module.run_command("%s "
                                                      "disableDeduplication "
                                                      "--name=%s" %
                                                      (vdocmd, desiredvdo))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing deduplication on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

                if dedupemod == 'enabled':
                    rc, out, err = module.run_command("%s "
                                                      "enableDeduplication "
                                                      "--name=%s" %
                                                      (vdocmd, desiredvdo))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing deduplication on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

            if 'compression' in diffparams.keys():
                compressmod = diffparams['compression']
                if compressmod == 'disabled':
                    rc, out, err = module.run_command("%s disableCompression "
                                                      "--name=%s" %
                                                      (vdocmd, desiredvdo))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing compression on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

                if compressmod == 'enabled':
                    rc, out, err = module.run_command("%s enableCompression "
                                                      "--name=%s" %
                                                      (vdocmd, desiredvdo))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing compression on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

            if 'writepolicy' in diffparams.keys():
                writepolmod = diffparams['writepolicy']
                if writepolmod == 'auto':
                    rc, out, err = module.run_command(
                        "%s "
                        "changeWritePolicy "
                        "--name=%s "
                        "--writePolicy=%s" % (vdocmd, desiredvdo, writepolmod))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing write policy on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

                if writepolmod == 'sync':
                    rc, out, err = module.run_command(
                        "%s "
                        "changeWritePolicy "
                        "--name=%s "
                        "--writePolicy=%s" % (vdocmd, desiredvdo, writepolmod))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing write policy on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

                if writepolmod == 'async':
                    rc, out, err = module.run_command(
                        "%s "
                        "changeWritePolicy "
                        "--name=%s "
                        "--writePolicy=%s" % (vdocmd, desiredvdo, writepolmod))

                    if rc == 0:
                        result['changed'] = True
                    else:
                        module.fail_json(msg="Changing write policy on "
                                         "VDO volume %s failed." % desiredvdo,
                                         rc=rc,
                                         err=err)

        # Process the size parameters, to determine of a growPhysical or
        # growLogical operation needs to occur.
        sizeparamkeys = [
            'Logical size',
        ]

        currentsizeparams = {}
        sizetrans = {}
        for statfield in sizeparamkeys:
            currentsizeparams[statfield] = processedvdos[desiredvdo][statfield]
            sizetrans[statfield] = vdokeytrans[statfield]

        sizeparams = {}
        for paramkey in currentsizeparams.keys():
            sizeparams[sizetrans[paramkey]] = currentsizeparams[paramkey]

        diffsizeparams = {}
        for key in sizeparams.keys():
            if module.params[key] is not None:
                if str(sizeparams[key]) != module.params[key]:
                    diffsizeparams[key] = module.params[key]

        if module.params['growphysical']:
            physdevice = module.params['device']
            rc, devsectors, err = module.run_command("blockdev --getsz %s" %
                                                     (physdevice))
            devblocks = (int(devsectors) / 8)
            dmvdoname = ('/dev/mapper/' + desiredvdo)
            currentvdostats = (
                processedvdos[desiredvdo]['VDO statistics'][dmvdoname])
            currentphysblocks = currentvdostats['physical blocks']

            # Set a growPhysical threshold to grow only when there is
            # guaranteed to be more than 2 slabs worth of unallocated
            # space on the device to use.  For now, set to device
            # size + 64 GB, since 32 GB is the largest possible
            # slab size.
            growthresh = devblocks + 16777216

            if currentphysblocks > growthresh:
                result['changed'] = True
                rc, out, err = module.run_command("%s growPhysical --name=%s" %
                                                  (vdocmd, desiredvdo))

        if 'logicalsize' in diffsizeparams.keys():
            result['changed'] = True
            vdocmdoptions = ("--vdoLogicalSize=" +
                             diffsizeparams['logicalsize'])
            rc, out, err = module.run_command(
                "%s growLogical --name=%s %s" %
                (vdocmd, desiredvdo, vdocmdoptions))

        vdoactivatestatus = processedvdos[desiredvdo]['Activate']

        if ((module.params['activated'] == 'no')
                and (vdoactivatestatus == 'enabled')):
            deactivate_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        if ((module.params['activated'] == 'yes')
                and (vdoactivatestatus == 'disabled')):
            activate_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        if ((module.params['running'] == 'no')
                and (desiredvdo in runningvdolist)):
            stop_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        # Note that a disabled VDO volume cannot be started by the
        # 'vdo start' command, by design.  To accurately track changed
        # status, don't try to start a disabled VDO volume.
        # If the playbook contains 'activated: yes', assume that
        # the activate_vdo() operation succeeded, as 'vdoactivatestatus'
        # will have the activated status prior to the activate_vdo()
        # call.
        if (((vdoactivatestatus == 'enabled') or
             (module.params['activated'] == 'yes'))
                and (module.params['running'] == 'yes')
                and (desiredvdo not in runningvdolist)):
            start_vdo(module, desiredvdo, vdocmd)
            if not result['changed']:
                result['changed'] = True

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        if diffparams:
            module.log("modified parameters of VDO volume %s" % desiredvdo)

        module.exit_json(**result)

    # Remove a desired VDO that currently exists.
    if (desiredvdo in vdolist) and (state == 'absent'):
        rc, out, err = module.run_command("%s remove --name=%s" %
                                          (vdocmd, desiredvdo))
        if rc == 0:
            result['changed'] = True
        else:
            module.fail_json(msg="Removing VDO %s failed." % desiredvdo,
                             rc=rc,
                             err=err)

        # Print a post-run list of VDO volumes in the result object.
        vdolist = inventory_vdos(module, vdocmd)
        module.log("removed VDO volume %s" % desiredvdo)
        module.exit_json(**result)

    # fall through
    # The state for the desired VDO volume was absent, and it does
    # not exist. Print a post-run list of VDO volumes in the result
    # object.
    vdolist = inventory_vdos(module, vdocmd)
    module.log("received request to remove non-existent VDO volume %s" %
               desiredvdo)

    module.exit_json(**result)