1
0
Fork 0
mirror of https://github.com/ansible-collections/community.general.git synced 2024-09-14 20:13:21 +02:00
community.general/plugins/modules/cloud/huawei/hwc_ecs_instance.py
Alexei Znamensky 3d1f9ed657
Tidy up validate-modules ignores for some cloud/ modules (#1354)
* fixed validation-modules for plugins/modules/cloud/atomic/atomic_container.py

* fixed validation-modules for plugins/modules/cloud/oracle/oci_vcn.py

* fixed validation-modules for plugins/modules/cloud/heroku/heroku_collaborator.py

* fixed validation-modules for plugins/modules/cloud/docker/docker_stack.py

* fixed validation-modules for plugins/modules/cloud/pubnub/pubnub_blocks.py

* fixed validation-modules for plugins/modules/cloud/huawei/hwc_ecs_instance.py

* fixed validation-modules for plugins/modules/cloud/huawei/hwc_vpc_port.py

* fixed validation-modules for plugins/modules/cloud/huawei/hwc_vpc_subnet.py

* fixed validation-modules for plugins/modules/cloud/ovh/ovh_ip_failover.py

* fixed validation-modules for plugins/modules/cloud/ovh/ovh_ip_loadbalancing_backend.py

* fixed validation-modules for plugins/modules/cloud/softlayer/sl_vm.py

* fixed validation-modules for plugins/modules/cloud/misc/proxmox_kvm.py

* fixed validation-modules for plugins/modules/cloud/misc/proxmox_template.py

* fixed validation-modules for plugins/modules/cloud/misc/terraform.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_dns_reload.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_memstore_info.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_server_info.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_zone_domain.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_zone.py

* fixed validation-modules for plugins/modules/cloud/memset/memset_zone_record.py

* fixed validation-modules for plugins/modules/cloud/linode/linode.py

* fixed validation-modules for plugins/modules/cloud/linode/linode_v4.py

* fixed validation-modules for plugins/doc_fragments/dimensiondata.py

* fixed validation-modules for plugins/modules/cloud/dimensiondata/dimensiondata_network.py

* fixed validation-modules for plugins/modules/cloud/dimensiondata/dimensiondata_vlan.py

* fixed validation-modules for plugins/modules/cloud/xenserver/xenserver_guest_info.py

* fixed validation-modules for plugins/modules/cloud/xenserver/xenserver_guest_powerstate.py

* Tidy up validate-modules ignores for varied cloud modules

- loking at the count of ignore lines, worked from the lower numbers up

* rolled back line for docker_container, removed two extraneous for xenserver

* rolled back lins for linode in 2.9
2020-11-23 09:22:30 +01:00

2135 lines
58 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019 Huawei
# GNU General Public License v3.0+ (see COPYING or
# https://www.gnu.org/licenses/gpl-3.0.txt)
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
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
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 C(_), hyphens
(-), periods (.).
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
'''
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 C(_), hyphens
(-), periods (.)".
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'),
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()