#!/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 = {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()