#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Huawei
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
# SPDX-License-Identifier: GPL-3.0-or-later

from __future__ import absolute_import, division, print_function
__metaclass__ = type

###############################################################################
# Documentation
###############################################################################

DOCUMENTATION = r"""
module: hwc_evs_disk
description:
  - Block storage management.
short_description: Creates a resource of Evs/Disk in Huawei Cloud
version_added: '0.2.0'
author: Huawei Inc. (@huaweicloud)
requirements:
  - keystoneauth1 >= 3.6.0
attributes:
  check_mode:
    support: full
  diff_mode:
    support: none
options:
  state:
    description:
      - Whether the given object should exist in Huawei Cloud.
    type: str
    choices: ['present', 'absent']
    default: 'present'
  timeouts:
    description:
      - The timeouts for each operations.
    type: dict
    default: {}
    suboptions:
      create:
        description:
          - The timeouts for create operation.
        type: str
        default: '30m'
      update:
        description:
          - The timeouts for update operation.
        type: str
        default: '30m'
      delete:
        description:
          - The timeouts for delete operation.
        type: str
        default: '30m'
  availability_zone:
    description:
      - Specifies the AZ where you want to create the disk.
    type: str
    required: true
  name:
    description:
      - Specifies the disk name. The value can contain a maximum of 255 bytes.
    type: str
    required: true
  volume_type:
    description:
      - Specifies the disk type. Currently, the value can be SSD, SAS, or SATA.
      - SSD specifies the ultra-high I/O disk type.
      - SAS specifies the high I/O disk type.
      - SATA specifies the common I/O disk type.
      - If the specified disk type is not available in the AZ, the disk will fail to create. If the EVS disk is created from
        a snapshot, the volume_type field must be the same as that of the snapshot's source disk.
    type: str
    required: true
  backup_id:
    description:
      - Specifies the ID of the backup that can be used to create a disk. This parameter is mandatory when you use a backup
        to create the disk.
    type: str
    required: false
  description:
    description:
      - Specifies the disk description. The value can contain a maximum of 255 bytes.
    type: str
    required: false
  enable_full_clone:
    description:
      - If the disk is created from a snapshot and linked cloning needs to be used, set this parameter to True.
    type: bool
    required: false
  enable_scsi:
    description:
      - If this parameter is set to True, the disk device type will be SCSI, which allows ECS OSs to directly access underlying
        storage media. SCSI reservation command is supported. If this parameter is set to False, the disk device type will
        be VBD, which supports only simple SCSI read/write commands.
      - If parameter enable_share is set to True and this parameter is not specified, shared SCSI disks are created. SCSI
        EVS disks cannot be created from backups, which means that this parameter cannot be True if backup_id has been specified.
    type: bool
    required: false
  enable_share:
    description:
      - Specifies whether the disk is shareable. The default value is False.
    type: bool
    required: false
  encryption_id:
    description:
      - Specifies the encryption ID. The length of it fixes at 36 bytes.
    type: str
    required: false
  enterprise_project_id:
    description:
      - Specifies the enterprise project ID. This ID is associated with the disk during the disk creation. If it is not specified,
        the disk is bound to the default enterprise project.
    type: str
    required: false
  image_id:
    description:
      - Specifies the image ID. If this parameter is specified, the disk is created from an image. BMS system disks cannot
        be created from BMS images.
    type: str
    required: false
  size:
    description:
      - Specifies the disk size, in GB. Its values are as follows, System disk 1 GB to 1024 GB, Data disk 10 GB to 32768 GB.
        This parameter is mandatory when you create an empty disk or use an image or a snapshot to create a disk. If you use
        an image or a snapshot to create a disk, the disk size must be greater than or equal to the image or snapshot size.
        This parameter is optional when you use a backup to create a disk. If this parameter is not specified, the disk size
        is equal to the backup size.
    type: int
    required: false
  snapshot_id:
    description:
      - Specifies the snapshot ID. If this parameter is specified, the disk is created from a snapshot.
    type: str
    required: false
extends_documentation_fragment:
  - community.general.hwc
  - community.general.attributes
"""

EXAMPLES = r"""
# test create disk
- name: Create a disk
  community.general.hwc_evs_disk:
    availability_zone: "cn-north-1a"
    name: "ansible_evs_disk_test"
    volume_type: "SATA"
    size: 10
"""

RETURN = r"""
availability_zone:
  description:
    - Specifies the AZ where you want to create the disk.
  type: str
  returned: success
name:
  description:
    - Specifies the disk name. The value can contain a maximum of 255 bytes.
  type: str
  returned: success
volume_type:
  description:
    - Specifies the disk type. Currently, the value can be SSD, SAS, or SATA.
    - SSD specifies the ultra-high I/O disk type.
    - SAS specifies the high I/O disk type.
    - SATA specifies the common I/O disk type.
    - If the specified disk type is not available in the AZ, the disk will fail to create. If the EVS disk is created from
      a snapshot, the volume_type field must be the same as that of the snapshot's source disk.
  type: str
  returned: success
backup_id:
  description:
    - Specifies the ID of the backup that can be used to create a disk. This parameter is mandatory when you use a backup
      to create the disk.
  type: str
  returned: success
description:
  description:
    - Specifies the disk description. The value can contain a maximum of 255 bytes.
  type: str
  returned: success
enable_full_clone:
  description:
    - If the disk is created from a snapshot and linked cloning needs to be used, set this parameter to True.
  type: bool
  returned: success
enable_scsi:
  description:
    - If this parameter is set to True, the disk device type will be SCSI, which allows ECS OSs to directly access underlying
      storage media. SCSI reservation command is supported. If this parameter is set to False, the disk device type will be
      VBD, which supports only simple SCSI read/write commands.
    - If parameter enable_share is set to True and this parameter is not specified, shared SCSI disks are created. SCSI EVS
      disks cannot be created from backups, which means that this parameter cannot be True if backup_id has been specified.
  type: bool
  returned: success
enable_share:
  description:
    - Specifies whether the disk is shareable. The default value is False.
  type: bool
  returned: success
encryption_id:
  description:
    - Specifies the encryption ID. The length of it fixes at 36 bytes.
  type: str
  returned: success
enterprise_project_id:
  description:
    - Specifies the enterprise project ID. This ID is associated with the disk during the disk creation. If it is not specified,
      the disk is bound to the default enterprise project.
  type: str
  returned: success
image_id:
  description:
    - Specifies the image ID. If this parameter is specified, the disk is created from an image. BMS system disks cannot be
      created from BMS images.
  type: str
  returned: success
size:
  description:
    - Specifies the disk size, in GB. Its values are as follows, System disk 1 GB to 1024 GB, Data disk 10 GB to 32768 GB.
      This parameter is mandatory when you create an empty disk or use an image or a snapshot to create a disk. If you use
      an image or a snapshot to create a disk, the disk size must be greater than or equal to the image or snapshot size.
      This parameter is optional when you use a backup to create a disk. If this parameter is not specified, the disk size
      is equal to the backup size.
  type: int
  returned: success
snapshot_id:
  description:
    - Specifies the snapshot ID. If this parameter is specified, the disk is created from a snapshot.
  type: str
  returned: success
attachments:
  description:
    - Specifies the disk attachment information.
  type: complex
  returned: success
  contains:
    attached_at:
      description:
        - Specifies the time when the disk was attached. Time format is 'UTC YYYY-MM-DDTHH:MM:SS'.
      type: str
      returned: success
    attachment_id:
      description:
        - Specifies the ID of the attachment information.
      type: str
      returned: success
    device:
      description:
        - Specifies the device name.
      type: str
      returned: success
    server_id:
      description:
        - Specifies the ID of the server to which the disk is attached.
      type: str
      returned: success
backup_policy_id:
  description:
    - Specifies the backup policy ID.
  type: str
  returned: success
created_at:
  description:
    - Specifies the time when the disk was created. Time format is 'UTC YYYY-MM-DDTHH:MM:SS'.
  type: str
  returned: success
is_bootable:
  description:
    - Specifies whether the disk is bootable.
  type: bool
  returned: success
is_readonly:
  description:
    - Specifies whether the disk is read-only or read/write. True indicates that the disk is read-only. False indicates that
      the disk is read/write.
  type: bool
  returned: success
source_volume_id:
  description:
    - Specifies the source disk ID. This parameter has a value if the disk is created from a source disk.
  type: str
  returned: success
status:
  description:
    - Specifies the disk status.
  type: str
  returned: success
tags:
  description:
    - Specifies the disk tags.
  type: dict
  returned: success
"""

from ansible_collections.community.general.plugins.module_utils.hwc_utils import (
    Config, HwcClientException, HwcModule, are_different_dicts, build_path,
    get_region, is_empty_value, navigate_value, wait_to_finish)


def build_module():
    return HwcModule(
        argument_spec=dict(
            state=dict(default='present', choices=['present', 'absent'],
                       type='str'),
            timeouts=dict(type='dict', options=dict(
                create=dict(default='30m', type='str'),
                update=dict(default='30m', type='str'),
                delete=dict(default='30m', type='str'),
            ), default=dict()),
            availability_zone=dict(type='str', required=True),
            name=dict(type='str', required=True),
            volume_type=dict(type='str', required=True),
            backup_id=dict(type='str'),
            description=dict(type='str'),
            enable_full_clone=dict(type='bool'),
            enable_scsi=dict(type='bool'),
            enable_share=dict(type='bool'),
            encryption_id=dict(type='str'),
            enterprise_project_id=dict(type='str'),
            image_id=dict(type='str'),
            size=dict(type='int'),
            snapshot_id=dict(type='str')
        ),
        supports_check_mode=True,
    )


def main():
    """Main function"""

    module = build_module()
    config = Config(module, "evs")

    try:
        _init(config)
        is_exist = module.params.get('id')

        result = None
        changed = False
        if module.params['state'] == 'present':
            if not is_exist:
                if not module.check_mode:
                    create(config)
                changed = True

            inputv = user_input_parameters(module)
            resp, array_index = read_resource(config)
            result = build_state(inputv, resp, array_index)
            set_readonly_options(inputv, result)
            if are_different_dicts(inputv, result):
                if not module.check_mode:
                    update(config, inputv, result)

                    inputv = user_input_parameters(module)
                    resp, array_index = read_resource(config)
                    result = build_state(inputv, resp, array_index)
                    set_readonly_options(inputv, result)
                    if are_different_dicts(inputv, result):
                        raise Exception("Update resource failed, "
                                        "some attributes are not updated")

                changed = True

            result['id'] = module.params.get('id')
        else:
            result = dict()
            if is_exist:
                if not module.check_mode:
                    delete(config)
                changed = True

    except Exception as ex:
        module.fail_json(msg=str(ex))

    else:
        result['changed'] = changed
        module.exit_json(**result)


def _init(config):
    module = config.module
    if module.params.get('id'):
        return

    v = search_resource(config)
    n = len(v)
    if n > 1:
        raise Exception("find more than one resources(%s)" % ", ".join([
            navigate_value(i, ["id"])
            for i in v
        ]))

    if n == 1:
        module.params['id'] = navigate_value(v[0], ["id"])


def user_input_parameters(module):
    return {
        "availability_zone": module.params.get("availability_zone"),
        "backup_id": module.params.get("backup_id"),
        "description": module.params.get("description"),
        "enable_full_clone": module.params.get("enable_full_clone"),
        "enable_scsi": module.params.get("enable_scsi"),
        "enable_share": module.params.get("enable_share"),
        "encryption_id": module.params.get("encryption_id"),
        "enterprise_project_id": module.params.get("enterprise_project_id"),
        "image_id": module.params.get("image_id"),
        "name": module.params.get("name"),
        "size": module.params.get("size"),
        "snapshot_id": module.params.get("snapshot_id"),
        "volume_type": module.params.get("volume_type"),
    }


def create(config):
    module = config.module
    client = config.client(get_region(module), "volumev3", "project")
    timeout = 60 * int(module.params['timeouts']['create'].rstrip('m'))
    opts = user_input_parameters(module)
    opts["ansible_module"] = module

    params = build_create_parameters(opts)
    r = send_create_request(module, params, client)

    client1 = config.client(get_region(module), "volume", "project")
    client1.endpoint = client1.endpoint.replace("/v2/", "/v1/")
    obj = async_wait(config, r, client1, timeout)
    module.params['id'] = navigate_value(obj, ["entities", "volume_id"])


def update(config, expect_state, current_state):
    module = config.module
    expect_state["current_state"] = current_state
    current_state["current_state"] = current_state
    client = config.client(get_region(module), "evs", "project")
    timeout = 60 * int(module.params['timeouts']['update'].rstrip('m'))

    params = build_update_parameters(expect_state)
    params1 = build_update_parameters(current_state)
    if params and are_different_dicts(params, params1):
        send_update_request(module, params, client)

    params = build_extend_disk_parameters(expect_state)
    params1 = build_extend_disk_parameters(current_state)
    if params and are_different_dicts(params, params1):
        client1 = config.client(get_region(module), "evsv2.1", "project")
        r = send_extend_disk_request(module, params, client1)

        client1 = config.client(get_region(module), "volume", "project")
        client1.endpoint = client1.endpoint.replace("/v2/", "/v1/")
        async_wait(config, r, client1, timeout)


def delete(config):
    module = config.module
    client = config.client(get_region(module), "evs", "project")
    timeout = 60 * int(module.params['timeouts']['delete'].rstrip('m'))

    r = send_delete_request(module, None, client)

    client = config.client(get_region(module), "volume", "project")
    client.endpoint = client.endpoint.replace("/v2/", "/v1/")
    async_wait(config, r, client, timeout)


def read_resource(config):
    module = config.module
    client = config.client(get_region(module), "volumev3", "project")

    res = {}

    r = send_read_request(module, client)
    res["read"] = fill_read_resp_body(r)

    return res, None


def build_state(opts, response, array_index):
    states = flatten_options(response, array_index)
    set_unreadable_options(opts, states)
    return states


def _build_query_link(opts):
    query_params = []

    v = navigate_value(opts, ["enable_share"])
    if v or v in [False, 0]:
        query_params.append(
            "multiattach=" + (str(v) if v else str(v).lower()))

    v = navigate_value(opts, ["name"])
    if v or v in [False, 0]:
        query_params.append(
            "name=" + (str(v) if v else str(v).lower()))

    v = navigate_value(opts, ["availability_zone"])
    if v or v in [False, 0]:
        query_params.append(
            "availability_zone=" + (str(v) if v else str(v).lower()))

    query_link = "?limit=10&offset={start}"
    if query_params:
        query_link += "&" + "&".join(query_params)

    return query_link


def search_resource(config):
    module = config.module
    client = config.client(get_region(module), "volumev3", "project")
    opts = user_input_parameters(module)
    name = module.params.get("name")
    query_link = _build_query_link(opts)
    link = "os-vendor-volumes/detail" + query_link

    result = []
    p = {'start': 0}
    while True:
        url = link.format(**p)
        r = send_list_request(module, client, url)
        if not r:
            break

        for item in r:
            if name == item.get("name"):
                result.append(item)

        if len(result) > 1:
            break

        p['start'] += len(r)

    return result


def build_create_parameters(opts):
    params = dict()

    v = navigate_value(opts, ["availability_zone"], None)
    if not is_empty_value(v):
        params["availability_zone"] = v

    v = navigate_value(opts, ["backup_id"], None)
    if not is_empty_value(v):
        params["backup_id"] = v

    v = navigate_value(opts, ["description"], None)
    if not is_empty_value(v):
        params["description"] = v

    v = navigate_value(opts, ["enterprise_project_id"], None)
    if not is_empty_value(v):
        params["enterprise_project_id"] = v

    v = navigate_value(opts, ["image_id"], None)
    if not is_empty_value(v):
        params["imageRef"] = v

    v = expand_create_metadata(opts, None)
    if not is_empty_value(v):
        params["metadata"] = v

    v = navigate_value(opts, ["enable_share"], None)
    if not is_empty_value(v):
        params["multiattach"] = v

    v = navigate_value(opts, ["name"], None)
    if not is_empty_value(v):
        params["name"] = v

    v = navigate_value(opts, ["size"], None)
    if not is_empty_value(v):
        params["size"] = v

    v = navigate_value(opts, ["snapshot_id"], None)
    if not is_empty_value(v):
        params["snapshot_id"] = v

    v = navigate_value(opts, ["volume_type"], None)
    if not is_empty_value(v):
        params["volume_type"] = v

    if not params:
        return params

    params = {"volume": params}

    return params


def expand_create_metadata(d, array_index):
    r = dict()

    v = navigate_value(d, ["encryption_id"], array_index)
    if not is_empty_value(v):
        r["__system__cmkid"] = v

    v = expand_create_metadata_system_encrypted(d, array_index)
    if not is_empty_value(v):
        r["__system__encrypted"] = v

    v = expand_create_metadata_full_clone(d, array_index)
    if not is_empty_value(v):
        r["full_clone"] = v

    v = expand_create_metadata_hw_passthrough(d, array_index)
    if not is_empty_value(v):
        r["hw:passthrough"] = v

    return r


def expand_create_metadata_system_encrypted(d, array_index):
    v = navigate_value(d, ["encryption_id"], array_index)
    return "1" if v else ""


def expand_create_metadata_full_clone(d, array_index):
    v = navigate_value(d, ["enable_full_clone"], array_index)
    return "0" if v else ""


def expand_create_metadata_hw_passthrough(d, array_index):
    v = navigate_value(d, ["enable_scsi"], array_index)
    if v is None:
        return v
    return "true" if v else "false"


def send_create_request(module, params, client):
    url = "cloudvolumes"
    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(create), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return r


def build_update_parameters(opts):
    params = dict()

    v = navigate_value(opts, ["description"], None)
    if v is not None:
        params["description"] = v

    v = navigate_value(opts, ["name"], None)
    if not is_empty_value(v):
        params["name"] = v

    if not params:
        return params

    params = {"volume": params}

    return params


def send_update_request(module, params, client):
    url = build_path(module, "cloudvolumes/{id}")

    try:
        r = client.put(url, params)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(update), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return r


def send_delete_request(module, params, client):
    url = build_path(module, "cloudvolumes/{id}")

    try:
        r = client.delete(url, params)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(delete), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return r


def build_extend_disk_parameters(opts):
    params = dict()

    v = expand_extend_disk_os_extend(opts, None)
    if not is_empty_value(v):
        params["os-extend"] = v

    return params


def expand_extend_disk_os_extend(d, array_index):
    r = dict()

    v = navigate_value(d, ["size"], array_index)
    if not is_empty_value(v):
        r["new_size"] = v

    return r


def send_extend_disk_request(module, params, client):
    url = build_path(module, "cloudvolumes/{id}/action")

    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(extend_disk), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return r


def async_wait(config, result, client, timeout):
    module = config.module

    path_parameters = {
        "job_id": ["job_id"],
    }
    data = {key: navigate_value(result, path) for key, path in path_parameters.items()}

    url = build_path(module, "jobs/{job_id}", data)

    def _query_status():
        r = None
        try:
            r = client.get(url, timeout=timeout)
        except HwcClientException:
            return None, ""

        try:
            s = navigate_value(r, ["status"])
            return r, s
        except Exception:
            return None, ""

    try:
        return wait_to_finish(
            ["SUCCESS"],
            ["RUNNING", "INIT"],
            _query_status, timeout)
    except Exception as ex:
        module.fail_json(msg="module(hwc_evs_disk): error "
                             "waiting to be done, error= %s" % str(ex))


def send_read_request(module, client):
    url = build_path(module, "os-vendor-volumes/{id}")

    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(read), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return navigate_value(r, ["volume"], None)


def fill_read_resp_body(body):
    result = dict()

    v = fill_read_resp_attachments(body.get("attachments"))
    result["attachments"] = v

    result["availability_zone"] = body.get("availability_zone")

    result["bootable"] = body.get("bootable")

    result["created_at"] = body.get("created_at")

    result["description"] = body.get("description")

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    result["id"] = body.get("id")

    v = fill_read_resp_metadata(body.get("metadata"))
    result["metadata"] = v

    result["multiattach"] = body.get("multiattach")

    result["name"] = body.get("name")

    result["size"] = body.get("size")

    result["snapshot_id"] = body.get("snapshot_id")

    result["source_volid"] = body.get("source_volid")

    result["status"] = body.get("status")

    result["tags"] = body.get("tags")

    v = fill_read_resp_volume_image_metadata(body.get("volume_image_metadata"))
    result["volume_image_metadata"] = v

    result["volume_type"] = body.get("volume_type")

    return result


def fill_read_resp_attachments(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["attached_at"] = item.get("attached_at")

        val["attachment_id"] = item.get("attachment_id")

        val["device"] = item.get("device")

        val["server_id"] = item.get("server_id")

        result.append(val)

    return result


def fill_read_resp_metadata(value):
    if not value:
        return None

    result = dict()

    result["__system__cmkid"] = value.get("__system__cmkid")

    result["attached_mode"] = value.get("attached_mode")

    result["full_clone"] = value.get("full_clone")

    result["hw:passthrough"] = value.get("hw:passthrough")

    result["policy"] = value.get("policy")

    result["readonly"] = value.get("readonly")

    return result


def fill_read_resp_volume_image_metadata(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


def flatten_options(response, array_index):
    r = dict()

    v = flatten_attachments(response, array_index)
    r["attachments"] = v

    v = navigate_value(response, ["read", "availability_zone"], array_index)
    r["availability_zone"] = v

    v = navigate_value(response, ["read", "metadata", "policy"], array_index)
    r["backup_policy_id"] = v

    v = navigate_value(response, ["read", "created_at"], array_index)
    r["created_at"] = v

    v = navigate_value(response, ["read", "description"], array_index)
    r["description"] = v

    v = flatten_enable_full_clone(response, array_index)
    r["enable_full_clone"] = v

    v = flatten_enable_scsi(response, array_index)
    r["enable_scsi"] = v

    v = navigate_value(response, ["read", "multiattach"], array_index)
    r["enable_share"] = v

    v = navigate_value(
        response, ["read", "metadata", "__system__cmkid"], array_index)
    r["encryption_id"] = v

    v = navigate_value(
        response, ["read", "enterprise_project_id"], array_index)
    r["enterprise_project_id"] = v

    v = navigate_value(
        response, ["read", "volume_image_metadata", "id"], array_index)
    r["image_id"] = v

    v = flatten_is_bootable(response, array_index)
    r["is_bootable"] = v

    v = flatten_is_readonly(response, array_index)
    r["is_readonly"] = v

    v = navigate_value(response, ["read", "name"], array_index)
    r["name"] = v

    v = navigate_value(response, ["read", "size"], array_index)
    r["size"] = v

    v = navigate_value(response, ["read", "snapshot_id"], array_index)
    r["snapshot_id"] = v

    v = navigate_value(response, ["read", "source_volid"], array_index)
    r["source_volume_id"] = v

    v = navigate_value(response, ["read", "status"], array_index)
    r["status"] = v

    v = navigate_value(response, ["read", "tags"], array_index)
    r["tags"] = v

    v = navigate_value(response, ["read", "volume_type"], array_index)
    r["volume_type"] = v

    return r


def flatten_attachments(d, array_index):
    v = navigate_value(d, ["read", "attachments"],
                       array_index)
    if not v:
        return None
    n = len(v)
    result = []

    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    for i in range(n):
        new_ai["read.attachments"] = i

        val = dict()

        v = navigate_value(d, ["read", "attachments", "attached_at"], new_ai)
        val["attached_at"] = v

        v = navigate_value(d, ["read", "attachments", "attachment_id"], new_ai)
        val["attachment_id"] = v

        v = navigate_value(d, ["read", "attachments", "device"], new_ai)
        val["device"] = v

        v = navigate_value(d, ["read", "attachments", "server_id"], new_ai)
        val["server_id"] = v

        for v in val.values():
            if v is not None:
                result.append(val)
                break

    return result if result else None


def flatten_enable_full_clone(d, array_index):
    v = navigate_value(d, ["read", "metadata", "full_clone"],
                       array_index)
    if v is None:
        return v
    return True if v == "0" else False


def flatten_enable_scsi(d, array_index):
    v = navigate_value(d, ["read", "metadata", "hw:passthrough"],
                       array_index)
    if v is None:
        return v
    return True if v in ["true", "True"] else False


def flatten_is_bootable(d, array_index):
    v = navigate_value(d, ["read", "bootable"], array_index)
    if v is None:
        return v
    return True if v in ["true", "True"] else False


def flatten_is_readonly(d, array_index):
    v = navigate_value(d, ["read", "metadata", "readonly"],
                       array_index)
    if v is None:
        return v
    return True if v in ["true", "True"] else False


def set_unreadable_options(opts, states):
    states["backup_id"] = opts.get("backup_id")


def set_readonly_options(opts, states):
    opts["attachments"] = states.get("attachments")

    opts["backup_policy_id"] = states.get("backup_policy_id")

    opts["created_at"] = states.get("created_at")

    opts["is_bootable"] = states.get("is_bootable")

    opts["is_readonly"] = states.get("is_readonly")

    opts["source_volume_id"] = states.get("source_volume_id")

    opts["status"] = states.get("status")

    opts["tags"] = states.get("tags")


def send_list_request(module, client, url):

    r = None
    try:
        r = client.get(url)
    except HwcClientException as ex:
        msg = ("module(hwc_evs_disk): error running "
               "api(list), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return navigate_value(r, ["volumes"], None)


def expand_list_metadata(d, array_index):
    r = dict()

    v = navigate_value(d, ["encryption_id"], array_index)
    r["__system__cmkid"] = v

    r["attached_mode"] = None

    v = navigate_value(d, ["enable_full_clone"], array_index)
    r["full_clone"] = v

    v = navigate_value(d, ["enable_scsi"], array_index)
    r["hw:passthrough"] = v

    r["policy"] = None

    r["readonly"] = None

    for v in r.values():
        if v is not None:
            return r
    return None


def expand_list_volume_image_metadata(d, array_index):
    r = dict()

    v = navigate_value(d, ["image_id"], array_index)
    r["id"] = v

    for v in r.values():
        if v is not None:
            return r
    return None


def fill_list_resp_body(body):
    result = dict()

    v = fill_list_resp_attachments(body.get("attachments"))
    result["attachments"] = v

    result["availability_zone"] = body.get("availability_zone")

    result["bootable"] = body.get("bootable")

    result["created_at"] = body.get("created_at")

    result["description"] = body.get("description")

    result["enterprise_project_id"] = body.get("enterprise_project_id")

    result["id"] = body.get("id")

    v = fill_list_resp_metadata(body.get("metadata"))
    result["metadata"] = v

    result["multiattach"] = body.get("multiattach")

    result["name"] = body.get("name")

    result["size"] = body.get("size")

    result["snapshot_id"] = body.get("snapshot_id")

    result["source_volid"] = body.get("source_volid")

    result["status"] = body.get("status")

    result["tags"] = body.get("tags")

    v = fill_list_resp_volume_image_metadata(body.get("volume_image_metadata"))
    result["volume_image_metadata"] = v

    result["volume_type"] = body.get("volume_type")

    return result


def fill_list_resp_attachments(value):
    if not value:
        return None

    result = []
    for item in value:
        val = dict()

        val["attached_at"] = item.get("attached_at")

        val["attachment_id"] = item.get("attachment_id")

        val["device"] = item.get("device")

        val["server_id"] = item.get("server_id")

        result.append(val)

    return result


def fill_list_resp_metadata(value):
    if not value:
        return None

    result = dict()

    result["__system__cmkid"] = value.get("__system__cmkid")

    result["attached_mode"] = value.get("attached_mode")

    result["full_clone"] = value.get("full_clone")

    result["hw:passthrough"] = value.get("hw:passthrough")

    result["policy"] = value.get("policy")

    result["readonly"] = value.get("readonly")

    return result


def fill_list_resp_volume_image_metadata(value):
    if not value:
        return None

    result = dict()

    result["id"] = value.get("id")

    return result


if __name__ == '__main__':
    main()