From 3c255e6f8f96c7f4e5573ba2d2507aecb984c699 Mon Sep 17 00:00:00 2001 From: root Date: Sun, 23 Jan 2022 11:15:00 +0000 Subject: [PATCH] Fix Sanity Test --- .../ncp/plugins/module_utils/base_module.py | 2 +- nutanix/ncp/plugins/module_utils/entity.py | 24 +- nutanix/ncp/plugins/module_utils/prism/vms.py | 27 +- nutanix/ncp/plugins/modules/nutanix_images.py | 57 ++-- .../ncp/plugins/modules/nutanix_subnets.py | 59 ++-- nutanix/ncp/plugins/modules/nutanix_vms.py | 300 +++++++++++++++--- .../targets/nutanix_vms/vars/main.yml | 10 +- .../unit/plugins/module_utils/test_entity.py | 2 +- 8 files changed, 367 insertions(+), 114 deletions(-) diff --git a/nutanix/ncp/plugins/module_utils/base_module.py b/nutanix/ncp/plugins/module_utils/base_module.py index 5b6d19a40..d215d5d8c 100644 --- a/nutanix/ncp/plugins/module_utils/base_module.py +++ b/nutanix/ncp/plugins/module_utils/base_module.py @@ -14,7 +14,7 @@ class BaseModule(AnsibleModule): action=dict(type="str", required=True, aliases=["state"]), auth=dict(type="dict", required=True), data=dict(type="dict", required=False), - operations=dict(type="list", required=False), + operations=dict(type="list", elements="str", required=False), wait=dict(type="bool", required=False, default=True), wait_timeout=dict(type="int", required=False, default=300), validate_certs=dict(type="bool", required=False, default=False), diff --git a/nutanix/ncp/plugins/module_utils/entity.py b/nutanix/ncp/plugins/module_utils/entity.py index 21354b55c..8cb4d4cd9 100644 --- a/nutanix/ncp/plugins/module_utils/entity.py +++ b/nutanix/ncp/plugins/module_utils/entity.py @@ -90,7 +90,8 @@ def check_response(self): self.result["task_information"] = task self.result["changed"] = True - status = self.response.get("state") or self.response.get("status").get("state") + status = self.response.get( + "state") or self.response.get("status").get("state") if status and status.lower() != "succeeded" or self.action == "list": self.result["changed"] = False if status.lower() != "complete": @@ -156,13 +157,10 @@ def send_request(module, method, req_url, req_data, username, password, timeout= ) if not 300 > info["status"] > 199: module.fail_json( - msg="Fail: %s" - % ( - "Status: " - + str(info["msg"]) - + ", Message: " - + str(info.get("body")) - ) + msg="Fail: " + "Status: " + + f'{str(info["msg"])}' + ", Message: " + + f'{str(info.get("body"))}' + ) body = resp.read() if resp else info.get("body") @@ -203,7 +201,7 @@ def generate_url_from_operations(self, name, netloc=None, ops=None): elif type(each) is dict: key = list(each.keys())[0] val = each[key] - path += "/{0}/{1}".format(key, val) + path += f"/{key}/{val}" url += path return self.validate_url(url, netloc, path) @@ -222,7 +220,8 @@ def validate_url(url, netloc, path=""): def get_action(self): if self.action == "present": - self.action = "update" if self.data["metadata"].get("uuid") else "create" + self.action = "update" if self.data["metadata"].get( + "uuid") else "create" elif self.action == "absent": self.action = self.methods_of_actions[self.action] elif self.action not in self.methods_of_actions.keys(): @@ -241,7 +240,7 @@ def get_spec(self): ) file_path = join(ncp_dir, self.spec_file) - with open(file_path) as f: + with open(file_path, encoding='utf_8') as f: # spec = json.loads(str(f.read())) spec = yaml.safe_load(f.read()) return spec @@ -326,7 +325,8 @@ def run_module(self, module): if not self.url: self.url = ( - str(self.auth.get("ip_address")) + ":" + str(self.auth.get("port")) + str(self.auth.get("ip_address")) + + ":" + str(self.auth.get("port")) ) self.netloc = self.url diff --git a/nutanix/ncp/plugins/module_utils/prism/vms.py b/nutanix/ncp/plugins/module_utils/prism/vms.py index 5f5454b11..18c0320cf 100644 --- a/nutanix/ncp/plugins/module_utils/prism/vms.py +++ b/nutanix/ncp/plugins/module_utils/prism/vms.py @@ -31,8 +31,9 @@ def _get_api_spec(self, param_spec, **kwargs): pass def get_entity_by_name(self, name="", kind=""): - url = self.generate_url_from_operations(kind, netloc=self.url, ops=["list"]) - data = {"filter": "name==%s" % name, "length": 1} + url = self.generate_url_from_operations( + kind, netloc=self.url, ops=["list"]) + data = {"filter": f"name=={name}", "length": 1} resp = self.send_request( self.module, self.methods_of_actions["list"], @@ -41,24 +42,32 @@ def get_entity_by_name(self, name="", kind=""): self.credentials["username"], self.credentials["password"], ) + try: return resp["entities"][0]["metadata"] except IndexError: - self.result["message"] = 'Entity with name "%s" does not exist.' % name + + self.result["message"] = f'Entity with name {name} does not exist.' self.result["failed"] = True + self.module.exit_json(**self.result) class VMSpec: def get_default_spec(self): raise NotImplementedError( - "Get Default Spec helper not implemented for {0}".format(self.entity_type) + + f"Get Default Spec helper not implemented for {self.entity_type}" + + ) def _get_api_spec(self, param_spec, **kwargs): raise NotImplementedError( - "Get Api Spec helper not implemented for {0}".format(self.entity_type) + + f"Get Api Spec helper not implemented for {self.entity_type}" + ) def remove_null_references(self, spec, parent_spec=None, spec_key=None): @@ -207,9 +216,11 @@ def _get_api_spec(self, param_spec, **kwargs): # nic_final['subnet_reference'][k.split('_')[-1]] = v elif k == "subnet_uuid" and v: - nic_final["subnet_reference"] = {"kind": "subnet", "uuid": v} + nic_final["subnet_reference"] = { + "kind": "subnet", "uuid": v} elif k == "subnet_name" and not nic_param.get("subnet_uuid"): - nic_final["subnet_reference"] = self.__get_subnet_ref(v, **kwargs) + nic_final["subnet_reference"] = self.__get_subnet_ref( + v, **kwargs) elif k == "ip_endpoint_list" and bool(v): nic_final[k] = [{"ip": v[0]}] @@ -246,7 +257,7 @@ def _get_api_spec(self, param_spec, **kwargs): gc_spec = self.get_default_spec() script_file_path = param_spec["script_path"] - with open(script_file_path, "r") as f: + with open(script_file_path, "r", encoding='utf_8') as f: content = f.read() content = b64encode(content) type = param_spec["type"] diff --git a/nutanix/ncp/plugins/modules/nutanix_images.py b/nutanix/ncp/plugins/modules/nutanix_images.py index 859a98a7e..d3d94fad9 100644 --- a/nutanix/ncp/plugins/modules/nutanix_images.py +++ b/nutanix/ncp/plugins/modules/nutanix_images.py @@ -2,15 +2,11 @@ # Copyright: (c) 2021 # 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 - -from ..module_utils.base_module import BaseModule -from ..module_utils.prism.images import Image - +from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = r""" +DOCUMENTATION = r''' --- module: nutanix_images @@ -21,36 +17,45 @@ description: This module allows to perform the following tasks on /images options: - action: + state: description: This is the action used to indicate the type of request + aliases: ['action'] required: true type: str - credentials: + auth: description: Credentials needed for authenticating to the subnet required: true - type: dict (Variable from file) + type: dict #(Variable from file) data: description: This acts as either the params or the body payload depending on the HTTP action required: false type: dict - operation: + operations: description: This acts as the sub_url in the requested url required: false - type: str - ip_address: - description: This acts as the ip_address of the subnet. It can be passed as a list in ansible using with_items - required: True - type: str (Variable from file) - port: - description: This acts as the port of the subnet. It can be passed as a list in ansible using with_items - required: True - type: str (Variable from file) + type: list + elements: str + wait_timeout: ### + description: This is the wait_timeout description + required: False + type: int + default: 300 + wait: ### + description: This is the wait description + required: False + type: bool + default: true + validate_certs: ### + description: This is the validate_certs description + required: False + type: bool + default: False author: - Gevorg Khachatryan (@gevorg_khachatryan) -""" +''' -EXAMPLES = r""" +EXAMPLES = r''' #CREATE action, request to /images - hosts: [hosts_group] @@ -115,9 +120,9 @@ data: metadata: uuid: string -""" +''' -RETURN = r""" +RETURN = r''' CREATE: description: CREATE /images Response for nutanix imagese returned: (for CREATE /images operation) @@ -148,7 +153,9 @@ - default Internal Error - 404 Invalid UUID provided - 202 Request Accepted -""" +''' +from ..module_utils.prism.images import Image +from ..module_utils.base_module import BaseModule def run_module(): @@ -160,5 +167,5 @@ def main(): run_module() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/nutanix/ncp/plugins/modules/nutanix_subnets.py b/nutanix/ncp/plugins/modules/nutanix_subnets.py index 1d736717a..70ad8e25d 100644 --- a/nutanix/ncp/plugins/modules/nutanix_subnets.py +++ b/nutanix/ncp/plugins/modules/nutanix_subnets.py @@ -2,15 +2,11 @@ # Copyright: (c) 2021 # 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 - -from ..module_utils.base_module import BaseModule -from ..module_utils.prism.subnets import Subnet - +from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = r""" +DOCUMENTATION = r''' --- module: nutanix_subnets @@ -21,36 +17,45 @@ description: This module allows to perform the following tasks on /subnets options: - action: - description: This is the HTTP action used to indicate the type of request + state: + description: This is the action used to indicate the type of request + aliases: ['action'] required: true type: str - credentials: + auth: description: Credentials needed for authenticating to the subnet required: true - type: dict (Variable from file) + type: dict #(Variable from file) data: description: This acts as either the params or the body payload depending on the HTTP action required: false type: dict - operation: + operations: description: This acts as the sub_url in the requested url required: false - type: str - ip_address: - description: This acts as the ip_address of the subnet. It can be passed as a list in ansible using with_items - required: True - type: str (Variable from file) - port: - description: This acts as the port of the subnet. It can be passed as a list in ansible using with_items - required: True - type: str (Variable from file) + type: list + elements: str + wait_timeout: ### + description: This is the wait_timeout description + required: False + type: int + default: 300 + wait: ### + description: This is the wait description + required: False + type: bool + default: true + validate_certs: ### + description: This is the validate_certs description + required: False + type: bool + default: False author: - Gevorg Khachatryan (@gevorg_khachatryan-97) -""" +''' -EXAMPLES = r""" +EXAMPLES = r''' #CREATE action, request to /subnets - hosts: [hosts_group] @@ -99,9 +104,9 @@ metadata: uuid: string -""" +''' -RETURN = r""" +RETURN = r''' CREATE: description: CREATE /subnets Response for nutanix subnets returned: (for CREATE /subnets operation) @@ -132,7 +137,9 @@ - default Internal Error - 404 Invalid UUID provided - 202 Request Accepted -""" +''' +from ..module_utils.prism.subnets import Subnet +from ..module_utils.base_module import BaseModule def run_module(): @@ -144,5 +151,5 @@ def main(): run_module() -if __name__ == "__main__": +if __name__ == '__main__': main() diff --git a/nutanix/ncp/plugins/modules/nutanix_vms.py b/nutanix/ncp/plugins/modules/nutanix_vms.py index ddc4a34b0..858ca067f 100755 --- a/nutanix/ncp/plugins/modules/nutanix_vms.py +++ b/nutanix/ncp/plugins/modules/nutanix_vms.py @@ -2,54 +2,280 @@ # Copyright: (c) 2021 # 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 - -from ..module_utils.base_module import BaseModule -from ..module_utils.prism.vms import VM +from __future__ import (absolute_import, division, print_function) __metaclass__ = type + DOCUMENTATION = r""" + --- module: nutanix_vms - short_description: This module allows to communicate with the resource /vms - -version_added: "1.0.0" - +version_added: 1.0.0 description: This module allows to perform the following tasks on /vms - options: - action: - description: This is the HTTP action used to indicate the type of request - required: true + state: + description: This is the action used to indicate the type of request + aliases: + - action + required: true + type: str + auth: + description: Credentials needed for authenticating to the subnet + required: true + type: dict + data: + description: >- + This acts as either the params or the body payload depending on the HTTP + action + required: false + type: dict + operations: + description: This acts as the sub_url in the requested url + required: false + type: list + elements: str + wait_timeout: + description: This is the wait_timeout description + required: false + type: int + default: 300 + wait: + description: This is the wait description + required: false + type: bool + default: true + validate_certs: + description: This is the validate_certs description + required: false + type: bool + default: false + vcpus: + description: This is the vcpus description + aliases: + - spec__resources__num_sockets + required: false + type: int + default: 1 + name: + description: vm Name + aliases: + - spec__name + required: false + type: str + desc: + description: A description for vm. + aliases: + - description + - spec__description + required: false + type: str + cores_per_vcpu: + description: This is the num_vcpus_per_socket description + aliases: + - spec__resources__num_vcpus_per_socket + type: int + default: 1 + timezone: + description: VM's hardware clock timezone in IANA TZDB format (America/Los_Angeles). + aliases: + - spec__resources__hardware_clock_timezone + type: str + default: UTC + boot_type: + description: >- + Indicates whether the VM should use Secure boot, UEFI boot or Legacy + boot.If UEFI or Secure boot is enabled then other legacy boot options + (like boot_device and boot_device_order_list) are ignored. Secure boot + depends on UEFI boot, i.e. enabling Secure boot means that UEFI boot is + also enabled. + aliases: + - spec__resources__boot_config__boot_type + type: str + default: LEGACY + memory_overcommit_enabled: + description: This is the memory_overcommit_enabled description + aliases: + - spec__resources__memory_overcommit_enabled + type: bool + default: false + memory_size_mib: + description: Memory size in MiB + aliases: + - memory_gb + - spec__resources__memory_size_mib + type: int + default: 1 + cluster: + description: The reference to a cluster + type: dict + default: {} + suboptions: + cluster_name: + description: Cluster Name + aliases: + - name + - spec__cluster_reference__name type: str - credentials: - description: Credentials needed for authenticating to the subnet - required: true - type: dict (Variable from file) - data: - description: This acts as either the params or the body payload depending on the HTTP action required: false - type: dict - operation: - description: This acts as the sub_url in the requested url + cluster_uuid: + description: Cluster UUID + aliases: + - uuid + - spec__cluster_reference__uuid + type: str required: false + cluster_kind: + description: The Kind Name + aliases: + - spec__cluster_reference__kind type: str - ip_address: - description: This acts as the ip_address of the subnet. It can be passed as a list in ansible using with_items - required: True + required: false + default: cluster + categories: + description: >- + Categories for the vm. This allows assigning one value of a key to any + entity. Changes done in this will be reflected in the categories_mapping + field. + aliases: + - metadata__categories_mapping + type: dict + use_categories_mapping: + description: >- + Client need to specify this field as true if user want to use the newer + way of assigning the categories. Without this things should work as it was + earlier. + aliases: + - metadata__use_categories_mapping + type: bool + default: false + uuid: + description: VM UUID + aliases: + - metadata__uuid + type: str + required: false + networks: + description: NICs attached to the VM. + aliases: + - spec__resources__nic_list + type: list + elements: dict + default: [] + suboptions: + nic_uuid: + description: >- + The NIC's UUID, which is used to uniquely identify this particular + NIC. This UUID may be used to refer to the NIC outside the context + of the particular VM it is attached to. + aliases: + - uuid type: str - + subnet_name: + description: This is the subnet_uuid description + type: str + subnet_uuid: + description: The subnet uuid + type: str + subnet_kind: + description: This is the subnet_kind description + type: str + default: subnet + is_connected: + description: Whether or not the NIC is connected. True by default. + default: False + aliases: + - connected + type: bool + private_ip: + description: 'IP endpoints for the adapter. Currently, IPv4 addresses are supported.' + aliases: + - ip_endpoint_list + elements: str + default: [] + type: list + nic_type: + description: The type of this Network function NIC. + default: NORMAL_NIC + type: str + disks: + description: Disks attached to the VM + aliases: + - spec__resources__disk_list + type: list + elements: dict + default: [] + suboptions: + type: + description: 'Disk Type , CDROM or Disk' + type: str + size_gb: + description: The Disk Size in Giga + type: int + bus: + description: 'The Disk bus , like sata or pcie' + type: str + storage_container: + description: >- + This preference specifies the storage configuration parameters for VM + disks. + aliases: + - storage_config + type: dict + suboptions: + storage_container_name: + description: storage container name + type: str + storage_container_uuid: + description: storage container uuid + aliases: + - uuid + type: str + boot_device_order_list: + description: >- + Indicates the order of device types in which VM should try to boot from. + If boot device order is not provided the system will decide appropriate + boot device order. + aliases: + - spec__resources__boot_config__boot_device_order_list + type: list + elements: str + default: + - CDROM + - DISK + - NETWORK + guest_customization: + description: >- + test desc + aliases: + - spec__resources__guest_customization + type: dict + suboptions: + type: + type: str + choices: [ sysprep, cloud_init ] + default: sysprep + description: Test description + script_path: + type: str + required: True + description: Test description + is_overridable: + type: bool + default: False + description: Flag to allow override of customization by deployer. author: - Gevorg Khachatryan (@gevorg_khachatryan) """ - EXAMPLES = r""" """ + RETURN = r""" """ +from ..module_utils.prism.vms import VM +from ..module_utils.base_module import BaseModule def run_module(): @@ -61,15 +287,13 @@ def run_module(): ), metadata__uuid=dict(type="str", aliases=["uuid"], required=False), spec__resources__num_sockets=dict( - type="int", default=1, aliases=["core_count", "vcpus"] - ), - spec__resources__num_threads_per_core=dict( - type="int", aliases=["threads_per_core"] # default=1,#will not provide + type="int", default=1, aliases=["vcpus"] ), + spec__resources__num_vcpus_per_socket=dict( type="int", default=1, - aliases=["num_vcpus_per_socket", "cores_per_vcpu"], + aliases=["cores_per_vcpu"], ), cluster=dict( type="dict", @@ -91,8 +315,8 @@ def run_module(): ), spec__resources__nic_list=dict( type="list", - aliases=["networks"], elements="dict", + aliases=["networks"], options=dict( uuid=dict(type="str", aliases=["nic_uuid"]), subnet_uuid=dict(type="str"), @@ -102,7 +326,7 @@ def run_module(): type="bool", aliases=["connected"], default=False ), ip_endpoint_list=dict( - type="list", aliases=["private_ip"], default=[] + type="list", aliases=["private_ip"], default=[], elements="str" ), nic_type=dict(type="str", default="NORMAL_NIC"), ), @@ -110,17 +334,19 @@ def run_module(): ), spec__resources__disk_list=dict( type="list", + elements="dict", aliases=["disks"], options=dict( type=dict(type="str"), size_gb=dict(type="int"), bus=dict(type="str"), storage_config=dict( - type=dict, + type="dict", aliases=["storage_container"], options=dict( storage_container_name=dict(type="str"), - storage_container_uuid=dict(type="str", aliases=["uuid"]), + storage_container_uuid=dict( + type="str", aliases=["uuid"]), ), ), ), @@ -133,6 +359,7 @@ def run_module(): type="str", default="LEGACY", aliases=["boot_type"] ), spec__resources__boot_config__boot_device_order_list=dict( + elements="str", type="list", default=["CDROM", "DISK", "NETWORK"], aliases=["boot_device_order_list"], @@ -143,7 +370,8 @@ def run_module(): spec__resources__memory_size_mib=dict( type="int", default=1, aliases=["memory_size_mib", "memory_gb"] ), - metadata__categories_mapping=dict(type="dict", aliases=["categories"]), + metadata__categories_mapping=dict( + type="dict", aliases=["categories"]), metadata__use_categories_mapping=dict( type="bool", aliases=["use_categories_mapping"], default=False ), diff --git a/nutanix/ncp/tests/integration/targets/nutanix_vms/vars/main.yml b/nutanix/ncp/tests/integration/targets/nutanix_vms/vars/main.yml index c9904abe0..668dbda58 100644 --- a/nutanix/ncp/tests/integration/targets/nutanix_vms/vars/main.yml +++ b/nutanix/ncp/tests/integration/targets/nutanix_vms/vars/main.yml @@ -3,17 +3,17 @@ credentials: username: admin password: Nutanix.123 config: - ip_address: 10.37.161.185 + ip_address: 10.44.76.242 port: 9440 cluster: - uuid: "0005d5aa-3876-9503-0bb4-204ea767711e" + uuid: "0005d54c-b572-b290-0000-000000029016" networks: mannaged: name: "test_managed_subnet" - uuid: "ca7fab15-83be-4cca-bc28-88b0b8db293d" + uuid: "548d29f3-d98d-44cf-893b-4e17e0066161" ip: "10.30.30.75" unmannaged: - uuid: "50d81e3f-fb84-468e-8ff5-b4ce54f6c7cc" + uuid: "55d9fbba-71ea-4ef4-b776-1c8f3513b733" storage_config: - uuid: "6e78251f-9cf9-45d7-ad16-59274e6f2ea2" + uuid: "985d6049-82a4-4a93-9707-892360040865" diff --git a/nutanix/ncp/tests/unit/plugins/module_utils/test_entity.py b/nutanix/ncp/tests/unit/plugins/module_utils/test_entity.py index 2c0184fcc..eecd7deb9 100644 --- a/nutanix/ncp/tests/unit/plugins/module_utils/test_entity.py +++ b/nutanix/ncp/tests/unit/plugins/module_utils/test_entity.py @@ -211,7 +211,7 @@ def test_generate_url(self): elif isinstance(each, dict): key = list(each.keys())[0] val = each[key] - path += "/{0}/{1}".format(key, val) + path += f"/{key}/{val}" self.assertTrue("http" in actual.scheme) self.assertEqual(netloc, actual.netloc) self.assertEqual(path, actual.path)