#!/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 = '''
---
module: hwc_ecs_instance
description:
    - instance management.
short_description: Creates a resource of Ecs/Instance 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 name of the AZ where the ECS is located.
        type: str
        required: true
    flavor_name:
        description:
            - Specifies the name of the system flavor.
        type: str
        required: true
    image_id:
        description:
            - Specifies the ID of the system image.
        type: str
        required: true
    name:
        description:
            - Specifies the ECS name. Value requirements consists of 1 to 64
              characters, including letters, digits, underscores (V(_)), hyphens
              (V(-)), periods (V(.)).
        type: str
        required: true
    nics:
        description:
            - Specifies the NIC information of the ECS. Constraints the
              network of the NIC must belong to the VPC specified by vpc_id. A
              maximum of 12 NICs can be attached to an ECS.
        type: list
        elements: dict
        required: true
        suboptions:
            ip_address:
                description:
                    - Specifies the IP address of the NIC. The value is an IPv4
                      address. Its value must be an unused IP
                      address in the network segment of the subnet.
                type: str
                required: true
            subnet_id:
                description:
                    - Specifies the ID of subnet.
                type: str
                required: true
    root_volume:
        description:
            - Specifies the configuration of the ECS's system disks.
        type: dict
        required: true
        suboptions:
            volume_type:
                description:
                    - Specifies the ECS system disk type.
                    - SATA is common I/O disk type.
                    - SAS is high I/O disk type.
                    - SSD is ultra-high I/O disk type.
                    - co-p1 is high I/O (performance-optimized I) disk type.
                    - uh-l1 is ultra-high I/O (latency-optimized) disk type.
                    - NOTE is For HANA, HL1, and HL2 ECSs, use co-p1 and uh-l1
                      disks. For other ECSs, do not use co-p1 or uh-l1 disks.
                type: str
                required: true
            size:
                description:
                    - Specifies the system disk size, in GB. The value range is
                      1 to 1024. The system disk size must be
                      greater than or equal to the minimum system disk size
                      supported by the image (min_disk attribute of the image).
                      If this parameter is not specified or is set to 0, the
                      default system disk size is the minimum value of the
                      system disk in the image (min_disk attribute of the
                      image).
                type: int
                required: false
            snapshot_id:
                description:
                    - Specifies the snapshot ID or ID of the original data disk
                      contained in the full-ECS image.
                type: str
                required: false
    vpc_id:
        description:
            - Specifies the ID of the VPC to which the ECS belongs.
        type: str
        required: true
    admin_pass:
        description:
            - Specifies the initial login password of the administrator account
              for logging in to an ECS using password authentication. The Linux
              administrator is root, and the Windows administrator is
              Administrator. Password complexity requirements, consists of 8 to
              26 characters. The password must contain at least three of the
              following character types 'uppercase letters, lowercase letters,
              digits, and special characters (!@$%^-_=+[{}]:,./?)'. The password
              cannot contain the username or the username in reverse. The
              Windows ECS password cannot contain the username, the username in
              reverse, or more than two consecutive characters in the username.
        type: str
        required: false
    data_volumes:
        description:
            - Specifies the data disks of ECS instance.
        type: list
        elements: dict
        required: false
        suboptions:
            volume_id:
                description:
                    - Specifies the disk ID.
                type: str
                required: true
            device:
                description:
                    - Specifies the disk device name.
                type: str
                required: false
    description:
        description:
            - Specifies the description of an ECS, which is a null string by
              default. Can contain a maximum of 85 characters. Cannot contain
              special characters, such as < and >.
        type: str
        required: false
    eip_id:
        description:
            - Specifies the ID of the elastic IP address assigned to the ECS.
              Only elastic IP addresses in the DOWN state can be
              assigned.
        type: str
        required: false
    enable_auto_recovery:
        description:
            - Specifies whether automatic recovery is enabled on the ECS.
        type: bool
        required: false
    enterprise_project_id:
        description:
            - Specifies the ID of the enterprise project to which the ECS
              belongs.
        type: str
        required: false
    security_groups:
        description:
            - Specifies the security groups of the ECS. If this
              parameter is left blank, the default security group is bound to
              the ECS by default.
        type: list
        elements: str
        required: false
    server_metadata:
        description:
            - Specifies the metadata of ECS to be created.
        type: dict
        required: false
    server_tags:
        description:
            - Specifies the tags of an ECS. When you create ECSs, one ECS
              supports up to 10 tags.
        type: dict
        required: false
    ssh_key_name:
        description:
            - Specifies the name of the SSH key used for logging in to the ECS.
        type: str
        required: false
    user_data:
        description:
            - Specifies the user data to be injected during the ECS creation
              process. Text, text files, and gzip files can be injected.
              The content to be injected must be encoded with
              base64. The maximum size of the content to be injected (before
              encoding) is 32 KB. For Linux ECSs, this parameter does not take
              effect when adminPass is used.
        type: str
        required: false
extends_documentation_fragment:
  - community.general.hwc
  - community.general.attributes

'''

EXAMPLES = '''
# create an ecs instance
- name: Create a vpc
  hwc_network_vpc:
    cidr: "192.168.100.0/24"
    name: "ansible_network_vpc_test"
  register: vpc
- name: Create a subnet
  hwc_vpc_subnet:
    gateway_ip: "192.168.100.32"
    name: "ansible_network_subnet_test"
    dhcp_enable: true
    vpc_id: "{{ vpc.id }}"
    cidr: "192.168.100.0/26"
  register: subnet
- name: Create a eip
  hwc_vpc_eip:
    dedicated_bandwidth:
      charge_mode: "traffic"
      name: "ansible_test_dedicated_bandwidth"
      size: 1
    type: "5_bgp"
  register: eip
- name: Create a disk
  hwc_evs_disk:
    availability_zone: "cn-north-1a"
    name: "ansible_evs_disk_test"
    volume_type: "SATA"
    size: 10
  register: disk
- name: Create an instance
  community.general.hwc_ecs_instance:
    data_volumes:
      - volume_id: "{{ disk.id }}"
    enable_auto_recovery: false
    eip_id: "{{ eip.id }}"
    name: "ansible_ecs_instance_test"
    availability_zone: "cn-north-1a"
    nics:
      - subnet_id: "{{ subnet.id }}"
        ip_address: "192.168.100.33"
      - subnet_id: "{{ subnet.id }}"
        ip_address: "192.168.100.34"
    server_tags:
      my_server: "my_server"
    image_id: "8da46d6d-6079-4e31-ad6d-a7167efff892"
    flavor_name: "s3.small.1"
    vpc_id: "{{ vpc.id }}"
    root_volume:
      volume_type: "SAS"
'''

RETURN = '''
    availability_zone:
        description:
            - Specifies the name of the AZ where the ECS is located.
        type: str
        returned: success
    flavor_name:
        description:
            - Specifies the name of the system flavor.
        type: str
        returned: success
    image_id:
        description:
            - Specifies the ID of the system image.
        type: str
        returned: success
    name:
        description:
            - Specifies the ECS name. Value requirements "Consists of 1 to 64
              characters, including letters, digits, underscores (V(_)), hyphens
              (V(-)), periods (V(.)).".
        type: str
        returned: success
    nics:
        description:
            - Specifies the NIC information of the ECS. The
              network of the NIC must belong to the VPC specified by vpc_id. A
              maximum of 12 NICs can be attached to an ECS.
        type: list
        returned: success
        contains:
            ip_address:
                description:
                    - Specifies the IP address of the NIC. The value is an IPv4
                      address. Its value must be an unused IP
                      address in the network segment of the subnet.
                type: str
                returned: success
            subnet_id:
                description:
                    - Specifies the ID of subnet.
                type: str
                returned: success
            port_id:
                description:
                    - Specifies the port ID corresponding to the IP address.
                type: str
                returned: success
    root_volume:
        description:
            - Specifies the configuration of the ECS's system disks.
        type: dict
        returned: success
        contains:
            volume_type:
                description:
                    - Specifies the ECS system disk type.
                    - SATA is common I/O disk type.
                    - SAS is high I/O disk type.
                    - SSD is ultra-high I/O disk type.
                    - co-p1 is high I/O (performance-optimized I) disk type.
                    - uh-l1 is ultra-high I/O (latency-optimized) disk type.
                    - NOTE is For HANA, HL1, and HL2 ECSs, use co-p1 and uh-l1
                      disks. For other ECSs, do not use co-p1 or uh-l1 disks.
                type: str
                returned: success
            size:
                description:
                    - Specifies the system disk size, in GB. The value range is
                      1 to 1024. The system disk size must be
                      greater than or equal to the minimum system disk size
                      supported by the image (min_disk attribute of the image).
                      If this parameter is not specified or is set to 0, the
                      default system disk size is the minimum value of the
                      system disk in the image (min_disk attribute of the
                      image).
                type: int
                returned: success
            snapshot_id:
                description:
                    - Specifies the snapshot ID or ID of the original data disk
                      contained in the full-ECS image.
                type: str
                returned: success
            device:
                description:
                    - Specifies the disk device name.
                type: str
                returned: success
            volume_id:
                description:
                    - Specifies the disk ID.
                type: str
                returned: success
    vpc_id:
        description:
            - Specifies the ID of the VPC to which the ECS belongs.
        type: str
        returned: success
    admin_pass:
        description:
            - Specifies the initial login password of the administrator account
              for logging in to an ECS using password authentication. The Linux
              administrator is root, and the Windows administrator is
              Administrator. Password complexity requirements consists of 8 to
              26 characters. The password must contain at least three of the
              following character types "uppercase letters, lowercase letters,
              digits, and special characters (!@$%^-_=+[{}]:,./?)". The password
              cannot contain the username or the username in reverse. The
              Windows ECS password cannot contain the username, the username in
              reverse, or more than two consecutive characters in the username.
        type: str
        returned: success
    data_volumes:
        description:
            - Specifies the data disks of ECS instance.
        type: list
        returned: success
        contains:
            volume_id:
                description:
                    - Specifies the disk ID.
                type: str
                returned: success
            device:
                description:
                    - Specifies the disk device name.
                type: str
                returned: success
    description:
        description:
            - Specifies the description of an ECS, which is a null string by
              default. Can contain a maximum of 85 characters. Cannot contain
              special characters, such as < and >.
        type: str
        returned: success
    eip_id:
        description:
            - Specifies the ID of the elastic IP address assigned to the ECS.
              Only elastic IP addresses in the DOWN state can be assigned.
        type: str
        returned: success
    enable_auto_recovery:
        description:
            - Specifies whether automatic recovery is enabled on the ECS.
        type: bool
        returned: success
    enterprise_project_id:
        description:
            - Specifies the ID of the enterprise project to which the ECS
              belongs.
        type: str
        returned: success
    security_groups:
        description:
            - Specifies the security groups of the ECS. If this parameter is left
              blank, the default security group is bound to the ECS by default.
        type: list
        returned: success
    server_metadata:
        description:
            - Specifies the metadata of ECS to be created.
        type: dict
        returned: success
    server_tags:
        description:
            - Specifies the tags of an ECS. When you create ECSs, one ECS
              supports up to 10 tags.
        type: dict
        returned: success
    ssh_key_name:
        description:
            - Specifies the name of the SSH key used for logging in to the ECS.
        type: str
        returned: success
    user_data:
        description:
            - Specifies the user data to be injected during the ECS creation
              process. Text, text files, and gzip files can be injected.
              The content to be injected must be encoded with base64. The maximum
              size of the content to be injected (before encoding) is 32 KB. For
              Linux ECSs, this parameter does not take effect when adminPass is
              used.
        type: str
        returned: success
    config_drive:
        description:
            - Specifies the configuration driver.
        type: str
        returned: success
    created:
        description:
            - Specifies the time when an ECS was created.
        type: str
        returned: success
    disk_config_type:
        description:
            - Specifies the disk configuration type. MANUAL is The image
              space is not expanded. AUTO is the image space of the system disk
              will be expanded to be as same as the flavor.
        type: str
        returned: success
    host_name:
        description:
            - Specifies the host name of the ECS.
        type: str
        returned: success
    image_name:
        description:
            - Specifies the image name of the ECS.
        type: str
        returned: success
    power_state:
        description:
            - Specifies the power status of the ECS.
        type: int
        returned: success
    server_alias:
        description:
            - Specifies the ECS alias.
        type: str
        returned: success
    status:
        description:
            - Specifies the ECS status. Options are ACTIVE, REBOOT, HARD_REBOOT,
              REBUILD, MIGRATING, BUILD, SHUTOFF, RESIZE, VERIFY_RESIZE, ERROR,
              and DELETED.
        type: str
        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),
            flavor_name=dict(type='str', required=True),
            image_id=dict(type='str', required=True),
            name=dict(type='str', required=True),
            nics=dict(
                type='list', required=True, elements='dict',
                options=dict(
                    ip_address=dict(type='str', required=True),
                    subnet_id=dict(type='str', required=True)
                ),
            ),
            root_volume=dict(type='dict', required=True, options=dict(
                volume_type=dict(type='str', required=True),
                size=dict(type='int'),
                snapshot_id=dict(type='str')
            )),
            vpc_id=dict(type='str', required=True),
            admin_pass=dict(type='str', no_log=True),
            data_volumes=dict(type='list', elements='dict', options=dict(
                volume_id=dict(type='str', required=True),
                device=dict(type='str')
            )),
            description=dict(type='str'),
            eip_id=dict(type='str'),
            enable_auto_recovery=dict(type='bool'),
            enterprise_project_id=dict(type='str'),
            security_groups=dict(type='list', elements='str'),
            server_metadata=dict(type='dict'),
            server_tags=dict(type='dict'),
            ssh_key_name=dict(type='str'),
            user_data=dict(type='str')
        ),
        supports_check_mode=True,
    )


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

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

    try:
        _init(config)
        is_exist = module.params['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['id']:
        return

    v = search_resource(config)
    n = len(v)
    if n > 1:
        raise Exception("Found more than one resource(%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 {
        "admin_pass": module.params.get("admin_pass"),
        "availability_zone": module.params.get("availability_zone"),
        "data_volumes": module.params.get("data_volumes"),
        "description": module.params.get("description"),
        "eip_id": module.params.get("eip_id"),
        "enable_auto_recovery": module.params.get("enable_auto_recovery"),
        "enterprise_project_id": module.params.get("enterprise_project_id"),
        "flavor_name": module.params.get("flavor_name"),
        "image_id": module.params.get("image_id"),
        "name": module.params.get("name"),
        "nics": module.params.get("nics"),
        "root_volume": module.params.get("root_volume"),
        "security_groups": module.params.get("security_groups"),
        "server_metadata": module.params.get("server_metadata"),
        "server_tags": module.params.get("server_tags"),
        "ssh_key_name": module.params.get("ssh_key_name"),
        "user_data": module.params.get("user_data"),
        "vpc_id": module.params.get("vpc_id"),
    }


def create(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "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)
    obj = async_wait(config, r, client, timeout)

    sub_job_identity = {
        "job_type": "createSingleServer",
    }
    for item in navigate_value(obj, ["entities", "sub_jobs"]):
        for k, v in sub_job_identity.items():
            if item[k] != v:
                break
        else:
            obj = item
            break
    else:
        raise Exception("Can't find the sub job")
    module.params['id'] = navigate_value(obj, ["entities", "server_id"])


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

    params = build_delete_nics_parameters(expect_state)
    params1 = build_delete_nics_parameters(current_state)
    if params and are_different_dicts(params, params1):
        r = send_delete_nics_request(module, params, client)
        async_wait(config, r, client, timeout)

    params = build_set_auto_recovery_parameters(expect_state)
    params1 = build_set_auto_recovery_parameters(current_state)
    if params and are_different_dicts(params, params1):
        send_set_auto_recovery_request(module, params, client)

    params = build_attach_nics_parameters(expect_state)
    params1 = build_attach_nics_parameters(current_state)
    if params and are_different_dicts(params, params1):
        r = send_attach_nics_request(module, params, client)
        async_wait(config, r, client, timeout)

    multi_invoke_delete_volume(config, expect_state, client, timeout)

    multi_invoke_attach_data_disk(config, expect_state, client, timeout)


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

    opts = user_input_parameters(module)
    opts["ansible_module"] = module

    params = build_delete_parameters(opts)
    if params:
        r = send_delete_request(module, params, client)
        async_wait(config, r, client, timeout)


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

    res = {}

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

    r = send_read_auto_recovery_request(module, client)
    res["read_auto_recovery"] = fill_read_auto_recovery_resp_body(r)

    return res, None


def preprocess_read_response(resp):
    v = resp.get("os-extended-volumes:volumes_attached")
    if v and isinstance(v, list):
        for i in range(len(v)):
            if v[i].get("bootIndex") == "0":
                root_volume = v[i]

                if (i + 1) != len(v):
                    v[i] = v[-1]

                v.pop()

                resp["root_volume"] = root_volume
                break

    v = resp.get("addresses")
    if v:
        rv = {}
        eips = []
        for val in v.values():
            for item in val:
                if item["OS-EXT-IPS:type"] == "floating":
                    eips.append(item)
                else:
                    rv[item["OS-EXT-IPS:port_id"]] = item

        for item in eips:
            k = item["OS-EXT-IPS:port_id"]
            if k in rv:
                rv[k]["eip_address"] = item.get("addr", "")
            else:
                rv[k] = item
                item["eip_address"] = item.get("addr", "")
                item["addr"] = ""

        resp["address"] = rv.values()


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


def _build_query_link(opts):
    query_params = []

    v = navigate_value(opts, ["enterprise_project_id"])
    if v or v in [False, 0]:
        query_params.append(
            "enterprise_project_id=" + (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()))

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

    return query_link


def search_resource(config):
    module = config.module
    client = config.client(get_region(module), "ecs", "project")
    opts = user_input_parameters(module)
    identity_obj = _build_identity_object(opts)
    query_link = _build_query_link(opts)
    link = "cloudservers/detail" + query_link

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

        for item in r:
            item = fill_list_resp_body(item)
            adjust_list_resp(identity_obj, item)
            if not are_different_dicts(identity_obj, item):
                result.append(item)

        if len(result) > 1:
            break

        p['offset'] += 1

    return result


def build_delete_nics_parameters(opts):
    params = dict()

    v = expand_delete_nics_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    return params


def expand_delete_nics_nics(d, array_index):
    cv = d["current_state"].get("nics")
    if not cv:
        return None

    val = cv

    ev = d.get("nics")
    if ev:
        m = [item.get("ip_address") for item in ev]
        val = [item for item in cv if item.get("ip_address") not in m]

    r = []
    for item in val:
        transformed = dict()

        v = item.get("port_id")
        if not is_empty_value(v):
            transformed["id"] = v

        if transformed:
            r.append(transformed)

    return r


def send_delete_nics_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/nics/delete")

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

    return r


def build_set_auto_recovery_parameters(opts):
    params = dict()

    v = expand_set_auto_recovery_support_auto_recovery(opts, None)
    if v is not None:
        params["support_auto_recovery"] = v

    return params


def expand_set_auto_recovery_support_auto_recovery(d, array_index):
    v = navigate_value(d, ["enable_auto_recovery"], None)
    return None if v is None else str(v).lower()


def send_set_auto_recovery_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/autorecovery")

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

    return r


def build_create_parameters(opts):
    params = dict()

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

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

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

    v = expand_create_extendparam(opts, None)
    if not is_empty_value(v):
        params["extendparam"] = v

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

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

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

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

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

    v = expand_create_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    v = expand_create_publicip(opts, None)
    if not is_empty_value(v):
        params["publicip"] = v

    v = expand_create_root_volume(opts, None)
    if not is_empty_value(v):
        params["root_volume"] = v

    v = expand_create_security_groups(opts, None)
    if not is_empty_value(v):
        params["security_groups"] = v

    v = expand_create_server_tags(opts, None)
    if not is_empty_value(v):
        params["server_tags"] = v

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

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

    if not params:
        return params

    params = {"server": params}

    return params


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

    r["chargingMode"] = 0

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

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

    return r


def expand_create_nics(d, array_index):
    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    req = []

    v = navigate_value(
        d, ["nics"], new_ai)

    if not v:
        return req
    n = len(v)
    for i in range(n):
        new_ai["nics"] = i
        transformed = dict()

        v = navigate_value(d, ["nics", "ip_address"], new_ai)
        if not is_empty_value(v):
            transformed["ip_address"] = v

        v = navigate_value(d, ["nics", "subnet_id"], new_ai)
        if not is_empty_value(v):
            transformed["subnet_id"] = v

        if transformed:
            req.append(transformed)

    return req


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

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

    return r


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

    v = expand_create_root_volume_extendparam(d, array_index)
    if not is_empty_value(v):
        r["extendparam"] = v

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

    v = navigate_value(d, ["root_volume", "volume_type"], array_index)
    if not is_empty_value(v):
        r["volumetype"] = v

    return r


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

    v = navigate_value(d, ["root_volume", "snapshot_id"], array_index)
    if not is_empty_value(v):
        r["snapshotId"] = v

    return r


def expand_create_security_groups(d, array_index):
    v = d.get("security_groups")
    if not v:
        return None

    return [{"id": i} for i in v]


def expand_create_server_tags(d, array_index):
    v = d.get("server_tags")
    if not v:
        return None

    return [{"key": k, "value": v1} for k, v1 in v.items()]


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

    return r


def build_attach_nics_parameters(opts):
    params = dict()

    v = expand_attach_nics_nics(opts, None)
    if not is_empty_value(v):
        params["nics"] = v

    return params


def expand_attach_nics_nics(d, array_index):
    ev = d.get("nics")
    if not ev:
        return None

    val = ev

    cv = d["current_state"].get("nics")
    if cv:
        m = [item.get("ip_address") for item in cv]
        val = [item for item in ev if item.get("ip_address") not in m]

    r = []
    for item in val:
        transformed = dict()

        v = item.get("ip_address")
        if not is_empty_value(v):
            transformed["ip_address"] = v

        v = item.get("subnet_id")
        if not is_empty_value(v):
            transformed["subnet_id"] = v

        if transformed:
            r.append(transformed)

    return r


def send_attach_nics_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/nics")

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

    return r


def send_delete_volume_request(module, params, client, info):
    path_parameters = {
        "volume_id": ["volume_id"],
    }
    data = dict((key, navigate_value(info, path))
                for key, path in path_parameters.items())

    url = build_path(module, "cloudservers/{id}/detachvolume/{volume_id}", data)

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

    return r


def build_attach_data_disk_parameters(opts, array_index):
    params = dict()

    v = expand_attach_data_disk_volume_attachment(opts, array_index)
    if not is_empty_value(v):
        params["volumeAttachment"] = v

    return params


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

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

    v = navigate_value(d, ["data_volumes", "volume_id"], array_index)
    if not is_empty_value(v):
        r["volumeId"] = v

    return r


def send_attach_data_disk_request(module, params, client):
    url = build_path(module, "cloudservers/{id}/attachvolume")

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

    return r


def build_delete_parameters(opts):
    params = dict()

    params["delete_publicip"] = False

    params["delete_volume"] = False

    v = expand_delete_servers(opts, None)
    if not is_empty_value(v):
        params["servers"] = v

    return params


def expand_delete_servers(d, array_index):
    new_ai = dict()
    if array_index:
        new_ai.update(array_index)

    req = []

    n = 1
    for i in range(n):
        transformed = dict()

        v = expand_delete_servers_id(d, new_ai)
        if not is_empty_value(v):
            transformed["id"] = v

        if transformed:
            req.append(transformed)

    return req


def expand_delete_servers_id(d, array_index):
    return d["ansible_module"].params.get("id")


def send_delete_request(module, params, client):
    url = "cloudservers/delete"
    try:
        r = client.post(url, params)
    except HwcClientException as ex:
        msg = ("module(hwc_ecs_instance): error running "
               "api(delete), error: %s" % str(ex))
        module.fail_json(msg=msg)

    return r


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

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

    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_ecs_instance): error "
                             "waiting to be done, error= %s" % str(ex))


def multi_invoke_delete_volume(config, opts, client, timeout):
    module = config.module

    opts1 = None
    expect = opts["data_volumes"]
    current = opts["current_state"]["data_volumes"]
    if expect and current:
        v = [i["volume_id"] for i in expect]
        opts1 = {
            "data_volumes": [
                i for i in current if i["volume_id"] not in v
            ]
        }

    loop_val = navigate_value(opts1, ["data_volumes"])
    if not loop_val:
        return

    for i in range(len(loop_val)):
        r = send_delete_volume_request(module, None, client, loop_val[i])
        async_wait(config, r, client, timeout)


def multi_invoke_attach_data_disk(config, opts, client, timeout):
    module = config.module

    opts1 = opts
    expect = opts["data_volumes"]
    current = opts["current_state"]["data_volumes"]
    if expect and current:
        v = [i["volume_id"] for i in current]
        opts1 = {
            "data_volumes": [
                i for i in expect if i["volume_id"] not in v
            ]
        }

    loop_val = navigate_value(opts1, ["data_volumes"])
    if not loop_val:
        return

    for i in range(len(loop_val)):
        params = build_attach_data_disk_parameters(opts1, {"data_volumes": i})
        r = send_attach_data_disk_request(module, params, client)
        async_wait(config, r, client, timeout)


def send_read_request(module, client):
    url = build_path(module, "cloudservers/{id}")

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

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


def fill_read_resp_body(body):
    result = dict()

    result["OS-DCF:diskConfig"] = body.get("OS-DCF:diskConfig")

    result["OS-EXT-AZ:availability_zone"] = body.get(
        "OS-EXT-AZ:availability_zone")

    result["OS-EXT-SRV-ATTR:hostname"] = body.get("OS-EXT-SRV-ATTR:hostname")

    result["OS-EXT-SRV-ATTR:instance_name"] = body.get(
        "OS-EXT-SRV-ATTR:instance_name")

    result["OS-EXT-SRV-ATTR:user_data"] = body.get("OS-EXT-SRV-ATTR:user_data")

    result["OS-EXT-STS:power_state"] = body.get("OS-EXT-STS:power_state")

    v = fill_read_resp_address(body.get("address"))
    result["address"] = v

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

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

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

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

    v = fill_read_resp_flavor(body.get("flavor"))
    result["flavor"] = v

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

    v = fill_read_resp_image(body.get("image"))
    result["image"] = v

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

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

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

    v = fill_read_resp_os_extended_volumes_volumes_attached(
        body.get("os-extended-volumes:volumes_attached"))
    result["os-extended-volumes:volumes_attached"] = v

    v = fill_read_resp_root_volume(body.get("root_volume"))
    result["root_volume"] = v

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

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

    return result


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

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

        val["OS-EXT-IPS:port_id"] = item.get("OS-EXT-IPS:port_id")

        val["OS-EXT-IPS:type"] = item.get("OS-EXT-IPS:type")

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

        result.append(val)

    return result


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

    result = dict()

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

    return result


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

    result = dict()

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

    return result


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

    result = dict()

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

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

    return result


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

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

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

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

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

        result.append(val)

    return result


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

    result = dict()

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

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

    return result


def send_read_auto_recovery_request(module, client):
    url = build_path(module, "cloudservers/{id}/autorecovery")

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

    return r


def fill_read_auto_recovery_resp_body(body):
    result = dict()

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

    return result


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

    v = navigate_value(
        response, ["read", "OS-EXT-AZ:availability_zone"], array_index)
    r["availability_zone"] = v

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

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

    v = flatten_data_volumes(response, array_index)
    r["data_volumes"] = v

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

    v = navigate_value(response, ["read", "OS-DCF:diskConfig"], array_index)
    r["disk_config_type"] = v

    v = flatten_enable_auto_recovery(response, array_index)
    r["enable_auto_recovery"] = v

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

    v = navigate_value(response, ["read", "flavor", "id"], array_index)
    r["flavor_name"] = v

    v = navigate_value(
        response, ["read", "OS-EXT-SRV-ATTR:hostname"], array_index)
    r["host_name"] = v

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

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

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

    v = flatten_nics(response, array_index)
    r["nics"] = v

    v = navigate_value(
        response, ["read", "OS-EXT-STS:power_state"], array_index)
    r["power_state"] = v

    v = flatten_root_volume(response, array_index)
    r["root_volume"] = v

    v = navigate_value(
        response, ["read", "OS-EXT-SRV-ATTR:instance_name"], array_index)
    r["server_alias"] = v

    v = flatten_server_tags(response, array_index)
    r["server_tags"] = v

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

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

    v = navigate_value(
        response, ["read", "OS-EXT-SRV-ATTR:user_data"], array_index)
    r["user_data"] = v

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

    return r


def flatten_data_volumes(d, array_index):
    v = navigate_value(d, ["read", "os-extended-volumes:volumes_attached"],
                       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.os-extended-volumes:volumes_attached"] = i

        val = dict()

        v = navigate_value(
            d, ["read", "os-extended-volumes:volumes_attached", "device"], new_ai)
        val["device"] = v

        v = navigate_value(
            d, ["read", "os-extended-volumes:volumes_attached", "id"], new_ai)
        val["volume_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_auto_recovery(d, array_index):
    v = navigate_value(d, ["read_auto_recovery", "support_auto_recovery"],
                       array_index)
    return v == "true"


def flatten_nics(d, array_index):
    v = navigate_value(d, ["read", "address"],
                       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.address"] = i

        val = dict()

        v = navigate_value(d, ["read", "address", "addr"], new_ai)
        val["ip_address"] = v

        v = navigate_value(
            d, ["read", "address", "OS-EXT-IPS:port_id"], new_ai)
        val["port_id"] = v

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

    return result if result else None


def flatten_root_volume(d, array_index):
    result = dict()

    v = navigate_value(d, ["read", "root_volume", "device"], array_index)
    result["device"] = v

    v = navigate_value(d, ["read", "root_volume", "id"], array_index)
    result["volume_id"] = v

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


def flatten_server_tags(d, array_index):
    v = navigate_value(d, ["read", "tags"], array_index)
    if not v:
        return None

    r = dict()
    for item in v:
        v1 = item.split("=")
        if v1:
            r[v1[0]] = v1[1]
    return r


def adjust_options(opts, states):
    adjust_data_volumes(opts, states)

    adjust_nics(opts, states)


def adjust_data_volumes(parent_input, parent_cur):
    iv = parent_input.get("data_volumes")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("data_volumes")
    if not (cv and isinstance(cv, list)):
        return

    lcv = len(cv)
    result = []
    q = []
    for iiv in iv:
        if len(q) == lcv:
            break

        icv = None
        for j in range(lcv):
            if j in q:
                continue

            icv = cv[j]

            if iiv["volume_id"] != icv["volume_id"]:
                continue

            result.append(icv)
            q.append(j)
            break
        else:
            break

    if len(q) != lcv:
        for i in range(lcv):
            if i not in q:
                result.append(cv[i])

    if len(result) != lcv:
        raise Exception("adjust property(data_volumes) failed, "
                        "the array number is not equal")

    parent_cur["data_volumes"] = result


def adjust_nics(parent_input, parent_cur):
    iv = parent_input.get("nics")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("nics")
    if not (cv and isinstance(cv, list)):
        return

    lcv = len(cv)
    result = []
    q = []
    for iiv in iv:
        if len(q) == lcv:
            break

        icv = None
        for j in range(lcv):
            if j in q:
                continue

            icv = cv[j]

            if iiv["ip_address"] != icv["ip_address"]:
                continue

            result.append(icv)
            q.append(j)
            break
        else:
            break

    if len(q) != lcv:
        for i in range(lcv):
            if i not in q:
                result.append(cv[i])

    if len(result) != lcv:
        raise Exception("adjust property(nics) failed, "
                        "the array number is not equal")

    parent_cur["nics"] = result


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

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

    set_unread_nics(
        opts.get("nics"), states.get("nics"))

    set_unread_root_volume(
        opts.get("root_volume"), states.get("root_volume"))

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

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


def set_unread_nics(inputv, curv):
    if not (inputv and isinstance(inputv, list)):
        return

    if not (curv and isinstance(curv, list)):
        return

    lcv = len(curv)
    q = []
    for iv in inputv:
        if len(q) == lcv:
            break

        cv = None
        for j in range(lcv):
            if j in q:
                continue

            cv = curv[j]

            if iv["ip_address"] != cv["ip_address"]:
                continue

            q.append(j)
            break
        else:
            continue

        cv["subnet_id"] = iv.get("subnet_id")


def set_unread_root_volume(inputv, curv):
    if not (inputv and isinstance(inputv, dict)):
        return

    if not (curv and isinstance(curv, dict)):
        return

    curv["size"] = inputv.get("size")

    curv["snapshot_id"] = inputv.get("snapshot_id")

    curv["volume_type"] = inputv.get("volume_type")


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

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

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

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

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

    set_readonly_nics(
        opts.get("nics"), states.get("nics"))

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

    set_readonly_root_volume(
        opts.get("root_volume"), states.get("root_volume"))

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

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


def set_readonly_nics(inputv, curv):
    if not (curv and isinstance(curv, list)):
        return

    if not (inputv and isinstance(inputv, list)):
        return

    lcv = len(curv)
    q = []
    for iv in inputv:
        if len(q) == lcv:
            break

        cv = None
        for j in range(lcv):
            if j in q:
                continue

            cv = curv[j]

            if iv["ip_address"] != cv["ip_address"]:
                continue

            q.append(j)
            break
        else:
            continue

        iv["port_id"] = cv.get("port_id")


def set_readonly_root_volume(inputv, curv):
    if not (inputv and isinstance(inputv, dict)):
        return

    if not (curv and isinstance(curv, dict)):
        return

    inputv["device"] = curv.get("device")

    inputv["volume_id"] = curv.get("volume_id")


def send_list_request(module, client, url):

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

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


def _build_identity_object(all_opts):
    result = dict()

    result["OS-DCF:diskConfig"] = None

    v = navigate_value(all_opts, ["availability_zone"], None)
    result["OS-EXT-AZ:availability_zone"] = v

    result["OS-EXT-SRV-ATTR:hostname"] = None

    result["OS-EXT-SRV-ATTR:instance_name"] = None

    v = navigate_value(all_opts, ["user_data"], None)
    result["OS-EXT-SRV-ATTR:user_data"] = v

    result["OS-EXT-STS:power_state"] = None

    result["config_drive"] = None

    result["created"] = None

    v = navigate_value(all_opts, ["description"], None)
    result["description"] = v

    v = navigate_value(all_opts, ["enterprise_project_id"], None)
    result["enterprise_project_id"] = v

    v = expand_list_flavor(all_opts, None)
    result["flavor"] = v

    result["id"] = None

    v = expand_list_image(all_opts, None)
    result["image"] = v

    v = navigate_value(all_opts, ["ssh_key_name"], None)
    result["key_name"] = v

    v = expand_list_metadata(all_opts, None)
    result["metadata"] = v

    v = navigate_value(all_opts, ["name"], None)
    result["name"] = v

    result["status"] = None

    v = expand_list_tags(all_opts, None)
    result["tags"] = v

    return result


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

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

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


def expand_list_image(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 expand_list_metadata(d, array_index):
    r = dict()

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

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


def expand_list_tags(d, array_index):
    v = d.get("server_tags")
    if not v:
        return None

    return [k + "=" + v1 for k, v1 in v.items()]


def fill_list_resp_body(body):
    result = dict()

    result["OS-DCF:diskConfig"] = body.get("OS-DCF:diskConfig")

    result["OS-EXT-AZ:availability_zone"] = body.get(
        "OS-EXT-AZ:availability_zone")

    result["OS-EXT-SRV-ATTR:hostname"] = body.get("OS-EXT-SRV-ATTR:hostname")

    result["OS-EXT-SRV-ATTR:instance_name"] = body.get(
        "OS-EXT-SRV-ATTR:instance_name")

    result["OS-EXT-SRV-ATTR:user_data"] = body.get("OS-EXT-SRV-ATTR:user_data")

    result["OS-EXT-STS:power_state"] = body.get("OS-EXT-STS:power_state")

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

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

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

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

    v = fill_list_resp_flavor(body.get("flavor"))
    result["flavor"] = v

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

    v = fill_list_resp_image(body.get("image"))
    result["image"] = v

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

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

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

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

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

    return result


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

    result = dict()

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

    return result


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

    result = dict()

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

    return result


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

    result = dict()

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

    return result


def adjust_list_resp(opts, resp):
    adjust_list_api_tags(opts, resp)


def adjust_list_api_tags(parent_input, parent_cur):
    iv = parent_input.get("tags")
    if not (iv and isinstance(iv, list)):
        return

    cv = parent_cur.get("tags")
    if not (cv and isinstance(cv, list)):
        return

    result = []
    for iiv in iv:
        if iiv not in cv:
            break

        result.append(iiv)

        j = cv.index(iiv)
        cv[j] = cv[-1]
        cv.pop()

    if cv:
        result.extend(cv)
    parent_cur["tags"] = result


if __name__ == '__main__':
    main()
