diff --git a/galaxy.yml b/galaxy.yml index d43b4ea70..24f3eb21d 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -6,6 +6,7 @@ authors: - "Balu George (@balugeorge)" - "Sarath Kumar K (@kumarsarath588)" - "Prem Karat (@premkarat)" + - "Gevorg Khachatryan (@Gevorg-Khachatryan-97)" description: Ansible collection for v3 Nutanix APIs https://www.nutanix.dev/api-reference-v3/ license_file: 'LICENSE' tags: [nutanix, prism, ahv] diff --git a/nutanix-ncp-1.0.0.tar.gz b/nutanix-ncp-1.0.0.tar.gz new file mode 100644 index 000000000..8118564a6 Binary files /dev/null and b/nutanix-ncp-1.0.0.tar.gz differ diff --git a/plugins/inventory/ntnx_prism_vm_inventory.py b/plugins/inventory/ntnx_prism_vm_inventory.py index 11a0a57b1..5b67d261e 100644 --- a/plugins/inventory/ntnx_prism_vm_inventory.py +++ b/plugins/inventory/ntnx_prism_vm_inventory.py @@ -1,4 +1,3 @@ -#!/usr/bin/python # -*- coding: utf-8 -*- # Copyright: (c) 2021 [Balu George, Prem Karat] @@ -66,12 +65,12 @@ requirements: "null" """ -import json -import tempfile -from ansible.errors import AnsibleError -from ansible.plugins.inventory import BaseInventoryPlugin +import json # noqa: E402 +import tempfile # noqa: E402 -from ..module_utils.prism import vms +from ansible.plugins.inventory import BaseInventoryPlugin # noqa: E402 + +from ..module_utils.prism import vms # noqa: E402 class Mock_Module: diff --git a/plugins/module_utils/base_module.py b/plugins/module_utils/base_module.py index e0bae4b8b..bfd85bf9b 100644 --- a/plugins/module_utils/base_module.py +++ b/plugins/module_utils/base_module.py @@ -2,8 +2,7 @@ # Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause ) from __future__ import absolute_import, division, print_function -from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.basic import env_fallback +from ansible.module_utils.basic import AnsibleModule, env_fallback __metaclass__ = type diff --git a/plugins/module_utils/entity.py b/plugins/module_utils/entity.py index 3f850bd15..3b6fe298d 100644 --- a/plugins/module_utils/entity.py +++ b/plugins/module_utils/entity.py @@ -1,15 +1,19 @@ # This file is part of Ansible # 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 import json -from ansible.module_utils.urls import fetch_url -from ansible.module_utils._text import to_text from base64 import b64encode -from urllib.parse import urlparse, parse_qsl, urlencode, urlunparse + +from ansible.module_utils._text import to_text +from ansible.module_utils.urls import fetch_url + +try: + from urllib.parse import parse_qsl, urlencode, urlparse, urlunparse +except ImportError: + from urlparse import urlparse # python2 class Entity(object): @@ -35,7 +39,7 @@ def create(self, data=None, endpoint=None, query=None, timeout=30): def read(self, uuid=None, endpoint=None, query=None, timeout=30): url = self.base_url + "/{0}".format(uuid) if uuid else self.base_url if endpoint: - url = url + "/{}".format(endpoint) + url = url + "/{0}".format(endpoint) if query: url = self._build_url_with_query(url, query) return self._fetch_url(url, method="GET", timeout=timeout) @@ -63,8 +67,8 @@ def list(self, data=None, endpoint=None, use_base_url=False, timeout=30): return self._fetch_url(url, method="POST", data=data, timeout=timeout) def get_uuid(self, name): - data = {"filter": f"name=={name}", "length": 1} - resp, _ = self.list(data) + data = {"filter": "name=={0}".format(name), "length": 1} + resp, status = self.list(data) if resp and resp.get("entities"): return resp["entities"][0]["metadata"]["uuid"] return None @@ -74,11 +78,11 @@ def _build_url(self, module, scheme, resource_type): url = "{proto}://{host}".format(proto=scheme, host=host) port = module.params.get("nutanix_port") if port: - url += ":{port}".format(port=port) + url += ":{0}".format(port) if resource_type.startswith("/"): url += resource_type else: - url += "/{resource_type}".format(resource_type=resource_type) + url += "/{0}".format(resource_type) return url def _build_headers(self, module, additional_headers): @@ -88,8 +92,11 @@ def _build_headers(self, module, additional_headers): usr = module.params.get("nutanix_username") pas = module.params.get("nutanix_password") if usr and pas: - cred = f"{usr}:{pas}".format(usr=usr, pas=pas) - encoded_cred = b64encode(bytes(cred, encoding="ascii")).decode("ascii") + cred = "{0}:{1}".format(usr, pas) + try: + encoded_cred = b64encode(bytes(cred, encoding="ascii")).decode("ascii") + except BaseException: + encoded_cred = b64encode(bytes(cred).encode("ascii")).decode("ascii") auth_header = "Basic " + encoded_cred headers.update({"Authorization": auth_header}) return headers @@ -126,7 +133,7 @@ def _fetch_url(self, url, method, data=None, timeout=30): else: err = info.get("msg", "Status code != 2xx") self.module.fail_json( - msg="Failed fetching URL: {}".format(url), + msg="Failed fetching URL: {0}".format(url), status_code=status_code, error=err, response=resp_json, diff --git a/plugins/module_utils/prism/clusters.py b/plugins/module_utils/prism/clusters.py index 4ad573e14..c95d82bb6 100644 --- a/plugins/module_utils/prism/clusters.py +++ b/plugins/module_utils/prism/clusters.py @@ -1,5 +1,8 @@ # This file is part of Ansible # 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 from .prism import Prism @@ -7,4 +10,4 @@ class Cluster(Prism): def __init__(self, module): resource_type = "/clusters" - super().__init__(module, resource_type=resource_type) + super(Cluster, self).__init__(module, resource_type=resource_type) diff --git a/plugins/module_utils/prism/groups.py b/plugins/module_utils/prism/groups.py index 382f93440..5b876db59 100644 --- a/plugins/module_utils/prism/groups.py +++ b/plugins/module_utils/prism/groups.py @@ -1,5 +1,8 @@ # This file is part of Ansible # 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 from .prism import Prism @@ -7,11 +10,11 @@ class Groups(Prism): def __init__(self, module): resource_type = "/groups" - super().__init__(module, resource_type=resource_type) + super(Groups, self).__init__(module, resource_type=resource_type) def get_uuid(self, entity_type, filter): data = {"entity_type": entity_type, "filter_criteria": filter} - resp, _ = self.list(data, use_base_url=True) + resp, status = self.list(data, use_base_url=True) if resp.get("group_results"): return resp["group_results"][0]["entity_results"][0]["entity_id"] return None diff --git a/plugins/module_utils/prism/images.py b/plugins/module_utils/prism/images.py index b616fa3e5..f3d7236d8 100644 --- a/plugins/module_utils/prism/images.py +++ b/plugins/module_utils/prism/images.py @@ -1,5 +1,8 @@ # This file is part of Ansible # 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 from .prism import Prism @@ -7,4 +10,4 @@ class Image(Prism): def __init__(self, module): resource_type = "/images" - super().__init__(module, resource_type=resource_type) + super(Image, self).__init__(module, resource_type=resource_type) diff --git a/plugins/module_utils/prism/prism.py b/plugins/module_utils/prism/prism.py index 177dff054..fc1892bf5 100644 --- a/plugins/module_utils/prism/prism.py +++ b/plugins/module_utils/prism/prism.py @@ -10,4 +10,4 @@ class Prism(Entity): def __init__(self, module, resource_type): resource_type = self.__BASEURL__ + resource_type - super().__init__(module, resource_type) + super(Prism, self).__init__(module, resource_type) diff --git a/plugins/module_utils/prism/projects.py b/plugins/module_utils/prism/projects.py index 546c8c040..eec9f92bc 100644 --- a/plugins/module_utils/prism/projects.py +++ b/plugins/module_utils/prism/projects.py @@ -1,5 +1,8 @@ # This file is part of Ansible # 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 from .prism import Prism @@ -7,4 +10,4 @@ class Project(Prism): def __init__(self, module): resource_type = "/projects" - super().__init__(module, resource_type=resource_type) + super(Project, self).__init__(module, resource_type=resource_type) diff --git a/plugins/module_utils/prism/subnets.py b/plugins/module_utils/prism/subnets.py index 25e8da896..06a08731e 100644 --- a/plugins/module_utils/prism/subnets.py +++ b/plugins/module_utils/prism/subnets.py @@ -1,5 +1,8 @@ # This file is part of Ansible # 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 from .prism import Prism @@ -7,4 +10,4 @@ class Subnet(Prism): def __init__(self, module): resource_type = "/subnets" - super().__init__(module, resource_type=resource_type) + super(Subnet, self).__init__(module, resource_type=resource_type) diff --git a/plugins/module_utils/prism/tasks.py b/plugins/module_utils/prism/tasks.py index ddcc8a0ad..18f87b4fd 100644 --- a/plugins/module_utils/prism/tasks.py +++ b/plugins/module_utils/prism/tasks.py @@ -1,7 +1,9 @@ # This file is part of Ansible # 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 -from os import stat import time from .prism import Prism @@ -10,7 +12,7 @@ class Task(Prism): def __init__(self, module): resource_type = "/tasks" - super().__init__(module, resource_type=resource_type) + super(Task, self).__init__(module, resource_type=resource_type) def create(self, data=None, endpoint=None, query=None, timeout=30): raise NotImplementedError("Create not permitted") diff --git a/plugins/module_utils/prism/vms.py b/plugins/module_utils/prism/vms.py index 1f0686475..e3f84881a 100644 --- a/plugins/module_utils/prism/vms.py +++ b/plugins/module_utils/prism/vms.py @@ -1,25 +1,25 @@ # This file is part of Ansible # 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 copy import deepcopy +__metaclass__ = type import base64 import os +from copy import deepcopy from .clusters import Cluster +from .groups import Groups +from .images import Image from .prism import Prism from .projects import Project from .subnets import Subnet -from .groups import Groups -from .images import Image class VM(Prism): def __init__(self, module): resource_type = "/vms" - super().__init__(module, resource_type=resource_type) + super(VM, self).__init__(module, resource_type=resource_type) self.build_spec_methods = { "name": self._build_spec_name, "desc": self._build_spec_desc, @@ -41,7 +41,7 @@ def get_spec(self): for ansible_param, ansible_value in self.module.params.items(): build_spec_method = self.build_spec_methods.get(ansible_param) if build_spec_method and ansible_value: - _, error = build_spec_method(spec, ansible_value) + spec, error = build_spec_method(spec, ansible_value) if error: return None, error return spec, None @@ -113,7 +113,8 @@ def _build_spec_project(self, payload, param): name = param["name"] uuid = project.get_uuid(name) if not uuid: - error = "Project {} not found.".format(name) + + error = "Project {0} not found.".format(name) return None, error elif "uuid" in param: @@ -130,7 +131,8 @@ def _build_spec_cluster(self, payload, param): name = param["name"] uuid = cluster.get_uuid(name) if not uuid: - error = "Cluster {} not found.".format(name) + + error = "Cluster {0} not found.".format(name) return None, error elif "uuid" in param: @@ -165,7 +167,7 @@ def _build_spec_networks(self, payload, networks): name = network["subnet"]["name"] uuid = subnet.get_uuid(name) if not uuid: - error = "Subnet {} not found.".format(name) + error = "Subnet {0} not found.".format(name) return None, error elif network.get("subnet", {}).get("uuid"): @@ -180,30 +182,24 @@ def _build_spec_networks(self, payload, networks): def _build_spec_disks(self, payload, vdisks): disks = [] - scsi_index = sata_index = pci_index = ide_index = 0 + device_indexes = {} for vdisk in vdisks: disk = self._get_default_disk_spec() - if "type" in vdisk: + if vdisk.get("type"): disk["device_properties"]["device_type"] = vdisk["type"] - if "bus" in vdisk: - if vdisk["bus"] == "SCSI": - device_index = scsi_index - scsi_index += 1 - elif vdisk["bus"] == "SATA": - device_index = sata_index - sata_index += 1 - elif vdisk["bus"] == "PCI": - device_index = pci_index - pci_index += 1 - elif vdisk["bus"] == "IDE": - device_index = ide_index - ide_index += 1 + if vdisk.get("bus"): + if device_indexes.get(vdisk["bus"]): + device_indexes[vdisk["bus"]] += 1 + else: + device_indexes[vdisk["bus"]] = 0 disk["device_properties"]["disk_address"]["adapter_type"] = vdisk["bus"] - disk["device_properties"]["disk_address"]["device_index"] = device_index + disk["device_properties"]["disk_address"][ + "device_index" + ] = device_indexes[vdisk["bus"]] if vdisk.get("empty_cdrom"): disk.pop("disk_size_bytes") @@ -220,12 +216,10 @@ def _build_spec_disks(self, payload, vdisks): name = vdisk["storage_container"]["name"] uuid = groups.get_uuid( entity_type="storage_container", - filter=f"container_name=={name}", + filter="container_name=={0}".format(name), ) if not uuid: - error = "Storage container {} not found.".format( - name - ) + error = "Storage container {0} not found.".format(name) return None, error elif "uuid" in vdisk["storage_container"]: @@ -239,7 +233,7 @@ def _build_spec_disks(self, payload, vdisks): name = vdisk["clone_image"]["name"] uuid = image.get_uuid(name) if not uuid: - error = "Image {} not found.".format(name) + error = "Image {0} not found.".format(name) return None, error elif "uuid" in vdisk["clone_image"]: @@ -281,7 +275,7 @@ def _build_spec_gc(self, payload, param): fpath = param["script_path"] if not os.path.exists(fpath): - error = "File not found: {}".format(fpath) + error = "File not found: {0}".format(fpath) return None, error with open(fpath, "rb") as f: diff --git a/plugins/modules/ntnx_vms.py b/plugins/modules/ntnx_vms.py index 36a701312..91010b028 100644 --- a/plugins/modules/ntnx_vms.py +++ b/plugins/modules/ntnx_vms.py @@ -3,274 +3,280 @@ # Copyright: (c) 2021, Prem Karat # 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 -import re -from urllib import response __metaclass__ = type DOCUMENTATION = r""" --- -module: ntnx_vm - +module: ntnx_vms short_description: VM module which suports VM CRUD operations - -version_added: "1.0.0" - -description: Create, Update, Delete, Power-on, Power-off Nutanix VM's - +version_added: 1.0.0 +description: 'Create, Update, Delete, Power-on, Power-off Nutanix VM''s' options: - nutanix_host: + nutanix_host: + description: + - PC hostname or IP address + type: str + required: true + nutanix_port: + description: + - PC port + type: str + default: 9440 + required: false + nutanix_username: + description: + - PC username + type: str + required: true + nutanix_password: + description: + - PC password; + required: true + type: str + validate_certs: + description: + - Set value to C(False) to skip validation for self signed certificates + - This is not recommended for production setup + type: bool + default: true + state: + description: + - Specify state of Virtual Machine + - If C(state) is set to C(present) the VM is created. + - >- + If C(state) is set to C(absent) and the VM exists in the cluster, VM + with specified name is removed. + choices: + - present + - absent + type: str + default: present + wait: + description: This is the wait description. + type: bool + required: false + default: True + name: + description: VM Name + required: False + type: str + vm_uuid: + description: VM UUID + type: str + desc: + description: A description for VM. + required: false + type: str + project: + description: Name or UUID of the project. + required: false + type: dict + suboptions: + name: description: - - PC hostname or IP address + - Project Name + - Mutually exclusive with C(uuid) type: str - required: true - nutanix_port: + uuid: description: - - PC port + - Project UUID + - Mutually exclusive with C(name) type: str - default: 9440 - required: false - nutanix_username: + cluster: + description: + - Name or UUID of the cluster on which the VM will be placed. + type: dict + required: false + suboptions: + name: description: - - PC username + - Cluster Name + - Mutually exclusive with C(uuid) type: str - required: true - nutanix_password: + uuid: description: - - PC password; - required: true + - Cluster UUID + - Mutually exclusive with C(name) type: str - validate_certs: - description: - - Set value to C(False) to skip validation for self signed certificates - - This is not recommended for production setup - type: bool - default: true - state: + vcpus: + description: + - Number number of sockets. + required: false + type: int + default: 1 + cores_per_vcpu: + description: + - This is the number of vcpus per socket. + required: false + type: int + default: 1 + memory_gb: + description: + - Memory size in GB + required: false + type: int + default: 1 + networks: + description: + - list of subnets to which the VM needs to connect to. + type: list + elements: dict + required: false + suboptions: + subnet: description: - - Specify state of Virtual Machine - - If C(state) is set to C(present) the VM is created. - - If C(state) is set to C(absent) and the VM exists in the cluster, VM with specified name is removed. - choices: - - present - - absent - type: str - default: present - wait: - description: This is the wait description. - required: false - default: false - name: - description: VM Name - required: true - type: str - vm_uuid: - description: - - VM UUID - - Required for VM deletion - required: false - desc: - description: A description for VM. - required: false - type: str - project: - description: Name or UUID of the project. - required: false + - Name or UUID of the subnet to which the VM should be connnected. type: dict suboptions: - name: - description: - - Project Name - - Mutually exclusive with C(uuid) - type: str - required: true - uuid: - description: - - Project UUID - - Mutually exclusive with C(name) - type: str - required: true - cluster: + name: + description: + - Subnet Name + - Mutually exclusive with C(uuid) + type: str + uuid: + description: + - Subnet UUID + - Mutually exclusive with C(name) + type: str + private_ip: description: - - Name or UUID of the cluster on which the VM will be placed. + - Optionally assign static IP to the VM. + type: str required: false - suboptions: - name: - description: - - Cluster Name - - Mutually exclusive with C(uuid) - type: str - required: true - uuid: - description: - - Cluster UUID - - Mutually exclusive with C(name) - type: str - required: true - vcpus: + is_connected: description: - - Number number of sockets. + - connect or disconnect the VM to the subnet. + type: bool required: false - type: int - default: 1 - cores_per_vcpu: + default: true + disks: + description: + - List of disks attached to the VM + type: list + elements: dict + suboptions: + type: description: - - This is the number of vcpus per socket. - required: false - type: int - default: 1 - memory_gb: + - CDROM or DISK + choices: + - CDROM + - DISK + default: DISK + type: str + size_gb: description: - - Memory size in GB - required: false + - The Disk Size in GB. + - This option is applicable for only DISK type above. type: int - default: 1 - networks: + bus: + description: Bus type of the device + default: SCSI + choices: + - SCSI + - PCI + - SATA + - IDE + type: str + storage_container: description: - - list of subnets to which the VM needs to connect to. - type: list - elements: dict - required: false + - Mutually exclusive with C(clone_image) and C(empty_cdrom) + type: dict suboptions: - subnet: - description: - - Name or UUID of the subnet to which the VM should be connnected. - suboptions: - name: - description: - - Subnet Name - - Mutually exclusive with C(uuid) - type: str - required: true - uuid: - description: - - Subnet UUID - - Mutually exclusive with C(name) - type: str - required: true - private_ip: - description: - - Optionally assign static IP to the VM. - type: str - required: False - is_connected: - description: - - connect or disconnect the VM to the subnet. - type: bool - required: False - default: True - disks: + name: + description: + - Storage containter Name + - Mutually exclusive with C(uuid) + type: str + uuid: + description: + - Storage container UUID + - Mutually exclusive with C(name) + type: str + clone_image: description: - - List of disks attached to the VM - type: list - elements: dict + - Mutually exclusive with C(storage_container) and C(empty_cdrom) + type: dict suboptions: - type: - description: - - 'CDROM or DISK' - choices: [ 'CDROM', 'DISK' ] - default: DISK - type: str - size_gb: - description: - - The Disk Size in GB. - - This option is applicable for only DISK type above. - type: int - bus: - description: 'Bus type of the device' - choices: [ 'SCSI', 'PCI', 'SATA', 'IDE' ] for DISK type. - choices: [ 'SATA', 'IDE' ] for CDROM type. - type: str - storage_container: - description: - - Mutually exclusive with C(clone_image) and C(empty_cdrom) - suboptions: - name: - description: - - Storage containter Name - - Mutually exclusive with C(uuid) - type: str - required: true - uuid: - description: - - Storage container UUID - - Mutually exclusive with C(name) - type: str - required: true - clone_image: - description: - - Mutually exclusive with C(storage_container) and C(empty_cdrom) - suboptions: - name: - description: - - Image Name - - Mutually exclusive with C(uuid) - type: str - required: true - uuid: - description: - - Image UUID - - Mutually exclusive with C(name) - type: str - required: true - empty_cdrom: - type: bool - description: - - Mutually exclusive with C(clone_image) and C(storage_container) - boot_config: + name: + description: + - Image Name + - Mutually exclusive with C(uuid) + type: str + uuid: + description: + - Image UUID + - Mutually exclusive with C(name) + type: str + empty_cdrom: + type: bool description: - - Indicates whether the VM should use Secure boot, UEFI boot or Legacy boot. - required: False - suboptions: - boot_type: - description: - - Boot type of VM. - choices: [ "LEGACY", "UEFI", "SECURE_BOOT" ] - default: "LEGACY" - type: str - boot_order: - description: - - Applicable only for LEGACY boot_type - - Boot device order list - type: list - default: ["CDROM", "DISK", "NETWORK"] - guest_customization: + - Mutually exclusive with C(clone_image) and C(storage_container) + boot_config: + description: + - >- + Indicates whether the VM should use Secure boot, UEFI boot or Legacy + boot. + type: dict + required: false + suboptions: + boot_type: description: - - cloud_init or sysprep guest customization - type: dict - required: false - suboptions: - type: - description: - - cloud_init or sysprep type - type: str - choices: [sysprep, cloud_init] - default: sysprep - required: true - script_path: - description: - - Absolute file path to the script. - type: path - required: true - is_overridable: - description: - - Flag to allow override of customization during deployment. - type: bool - default: false - required: false - timezone: + - Boot type of VM. + choices: + - LEGACY + - UEFI + - SECURE_BOOT + default: LEGACY + type: str + boot_order: description: - - VM's hardware clock timezone in IANA TZDB format (America/Los_Angeles). + - Applicable only for LEGACY boot_type + - Boot device order list + type: list + elements: str + default: + - CDROM + - DISK + - NETWORK + guest_customization: + description: + - cloud_init or sysprep guest customization + type: dict + suboptions: + type: + description: + - cloud_init or sysprep type type: str - default: UTC - required: false - categories: + required: True + choices: + - cloud_init + - sysprep + script_path: description: - - categories to be attached to the VM. - type: dict + - Absolute file path to the script. + type: path + required: true + is_overridable: + description: + - Flag to allow override of customization during deployment. + type: bool + default: false required: false + timezone: + description: + - VM's hardware clock timezone in IANA TZDB format (America/Los_Angeles). + type: str + default: UTC + required: false + categories: + description: + - categories to be attached to the VM. + type: dict + required: false +author: + - Alaa Bishtawi (@alaa-bish) """ EXAMPLES = r""" @@ -281,10 +287,10 @@ # TODO """ -from ..module_utils.base_module import BaseModule -from ..module_utils.prism.vms import VM -from ..module_utils.prism.tasks import Task -from ..module_utils.utils import remove_param_with_none_value +from ..module_utils.base_module import BaseModule # noqa: E402 +from ..module_utils.prism.tasks import Task # noqa: E402 +from ..module_utils.prism.vms import VM # noqa: E402 +from ..module_utils.utils import remove_param_with_none_value # noqa: E402 def get_module_spec(): @@ -314,9 +320,11 @@ def get_module_spec(): ) boot_config_spec = dict( - boot_type=dict(type="str", choices=["LEGACY", "UEFI", "SECURE_BOOT"]), + boot_type=dict( + type="str", choices=["LEGACY", "UEFI", "SECURE_BOOT"], default="LEGACY" + ), boot_order=dict( - type="list", elements=str, default=["CDROM", "DISK", "NETWORK"] + type="list", elements="str", default=["CDROM", "DISK", "NETWORK"] ), ) @@ -383,7 +391,7 @@ def create_vm(module, result): if module.params.get("wait"): wait_for_task_completion(module, result) - resp, _ = vm.read(vm_uuid) + resp, tmp = vm.read(vm_uuid) result["response"] = resp @@ -421,9 +429,14 @@ def wait_for_task_completion(module, result): def run_module(): - module = BaseModule(argument_spec=get_module_spec(), supports_check_mode=True, - required_if=[("state", "present", ("name",)), - ("state", "absent", ("vm_uuid",))]) + module = BaseModule( + argument_spec=get_module_spec(), + supports_check_mode=True, + required_if=[ + ("state", "present", ("name",)), + ("state", "absent", ("vm_uuid",)), + ], + ) remove_param_with_none_value(module.params) result = { "changed": False, diff --git a/tests/integration/targets/nutanix_vms/tasks/create.yml b/tests/integration/targets/nutanix_vms/tasks/create.yml index 185846f67..c08214a1f 100644 --- a/tests/integration/targets/nutanix_vms/tasks/create.yml +++ b/tests/integration/targets/nutanix_vms/tasks/create.yml @@ -1,195 +1,499 @@ -- name: create Vm With Mannaged subnet and subnet_name - nutanix_vms: - state: present - name: Vm_Net_Name - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - cluster: - cluster_uuid: '{{cluster.uuid}}' - networks: - - connected: true - subnet_name: '{{networks.static.name}}' - private_ip: '{{networks.static.ip}}' - register: result - ignore_errors: true -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: 'Unable to Create Vm With Mannaged subnet and subnet_name ' - success_msg: 'VM with With Mannaged subnet and subnet_name Created successfully ' -- set_fact: - todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' - when: result.response.status.state == 'SUCCEEDED' -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: Unable to delete Vm - success_msg: VM Deleted successfully -- name: create VM with Minimum Requiremnts - nutanix_vms: - state: present - name: MinVm - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - cluster: - cluster_uuid: '{{cluster.uuid}}' - register: result - ignore_errors: true -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: Unable to Create Vm with Minimum Requiremnts - success_msg: VM with Minimum Requiremnts Created successfully -- set_fact: - todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' - when: result.response.status.state == 'SUCCEEDED' -- name: Create Vm With Unamannaged vlan - nutanix_vms: - desc: This is vm test description - state: present - name: Vm-Nics - timezone: UTC - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - cluster: - cluster_uuid: '{{cluster.uuid}}' - networks: - - connected: false - subnet_uuid: '{{networks.dhcp.uuid}}' - boot_type: LEGACY - boot_device_order_list: - - DISK - - CDROM - - NETWORK - vcpus: 1 - cores_per_vcpu: 1 - memory_gb: 1 - register: result - ignore_errors: true -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: Unable to Create Vm With Unamannaged vlan - success_msg: VM With Unamannaged vlan Created successfully -- set_fact: - todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' - when: result.response.status.state == 'SUCCEEDED' -- name: 'Create Vm with diffrent disk types and diffrent sizes ,UEFI' - nutanix_vms: - state: present - name: Vm_disks - timezone: GMT - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - cluster: - cluster_uuid: '{{cluster.uuid}}' - categories: - AppType: - - Apache_Spark - disks: - - type: "DISK" - clone_image: "CentOS-7-cloudinit" - bus: "SCSI" - - type: DISK - size_gb: 1 - bus: SCSI - - type: DISK - size_gb: 2 - bus: PCI - storage_container: - uuid: '{{storage_config.uuid}}' - - type: DISK - size_gb: 3 - bus: SATA - boot_type: UEFI - boot_device_order_list: - - DISK - - CDROM - - NETWORK - vcpus: 2 - cores_per_vcpu: 1 - memory_gb: 1 - register: result - ignore_errors: true -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: 'Unable to Create Vm with diffrent disk types and diffrent sizes ,UEFI ' - success_msg: >- - VM with with diffrent disk types and diffrent sizes ,UEFI Created - successfully -- set_fact: - todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' - when: result.response.status.state == 'SUCCEEDED' -- name: create Vm With different disks and both Mannaged and UnMannaged network - nutanix_vms: - state: present - name: Vm_Disks_NIC - timezone: UTC - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - cluster: - cluster_uuid: '{{cluster.uuid}}' - networks: - - connected: true - subnet_uuid: '{{networks.dhcp.uuid}}' - - connected: true - subnet_uuid: '{{networks.static.uuid}}' - disks: - - type: DISK - size_gb: 1 - bus: SCSI - - type: DISK - size_gb: 3 - bus: PCI - - type: CDROM - bus: SATA - - type: CDROM - bus: IDE - boot_type: UEFI - boot_device_order_list: - - DISK - - CDROM - - NETWORK - vcpus: 2 - cores_per_vcpu: 2 - memory_gb: 2 - memory_overcommit_enabled: true - register: result - ignore_errors: true -- name: assert when status not complete - assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: Unable to Create Vm with both Mannaged and UnMannaged network - success_msg: >- - VM with With different disks and both Mannaged and UnMannaged network - Created successfully -- set_fact: - todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' - when: result.response.status.state == 'SUCCEEDED' -- name: Delete all Created VM - nutanix_vms: - uuid: '{{ item }}' - state: absent - auth: - credentials: '{{credentials}}' - url: '{{config.ip_address}}:{{config.port}}' - register: result - loop: '{{ todelete }}' \ No newline at end of file + + - name: Create Cloud init Script file + copy: + dest: "cloud_init.yml" + content: | + #cloud-config + chpasswd: + list: | + root:Nutanix.123 + expire: False + fqdn: myNutanixVM + + + + ################################################################ + + - name: VM with ubuntu image and different specifications + ntnx_vms: + state: present + nutanix_host: "{{ IP }}" + validate_certs: False + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + name: "VM with Ubuntu image" + desc: "VM with cluster, network, category, disk with Ubuntu image, guest customization " + categories: + AppType: + - "Apache_Spark" + cluster: + name: "{{ClusterName}}" + networks: + - is_connected: True + subnet: + name: "{{ NetworkName }}" + disks: + - type: "DISK" + size_gb: 30 + bus: "SATA" + clone_image: + name: "{{ Ubuntu }}" + vcpus: 1 + cores_per_vcpu: 1 + memory_gb: 1 + guest_customization: + type: "cloud_init" + script_path: "./cloud_init.yml" + is_overridable: True + register: result + + + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: 'Unable to Create VM with Ubuntu image and different specifications ' + success_msg: 'VM with Ubuntu image and different specifications created successfully ' + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + + ######################################################################################### + + + - name: VM with CentOS-7-cloud-init image + ntnx_vms: + state: present + name: VM with CentOS-7-cloud-init image + timezone: "UTC" + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + cluster: + name: "{{ ClusterName }}" + disks: + - type: "DISK" + size_gb: 10 + clone_image: + name: "{{ CentOS }}" + bus: "SCSI" + guest_customization: + type: "cloud_init" + script_path: "./cloud_init.yml" + is_overridable: True + register: result + ignore_errors: True + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: 'Unable to create VM with CentOS-7-cloud-init image' + success_msg: 'VM with CentOS-7-cloud-init image created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ################################################################################# + + + - name: VM with Cluster, Network, Universal time zone, one Disk + ntnx_vms: + state: present + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + name: "VM with Cluster Network and Disk" + timezone: "Universal" + cluster: + name: "{{ ClusterName }}" + networks: + - is_connected: True + subnet: + name: "{{ NetworkName }}" + disks: + - type: "DISK" + size_gb: 10 + bus: "PCI" + register: result + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: 'Unable to create VM with Cluster , Network, Universal time zone, one Disk' + success_msg: 'VM with Cluster , Network, Universal time zone, one Disk created successfully ' + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ######################################################################################## + + - name: VM with Cluster, different Disks, Memory size + ntnx_vms: + state: present + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + name: "VM with different disks" + timezone: "UTC" + cluster: + name: "{{ ClusterName }}" + disks: + - type: "DISK" + size_gb: 10 + bus: "SATA" + - type: "DISK" + size_gb: 30 + bus: "SCSI" + memory_gb: 20 + register: result + ignore_errors: True + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: 'Unable to create VM with Cluster, different Disks, Memory size' + success_msg: 'VM with Cluster, different Disks, Memory size created successfully ' + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + + + ##################################################################################### + + - name: VM with Cluster, different CDROMs + ntnx_vms: + state: present + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + name: "VM with multiple CDROMs" + cluster: + name: "{{ ClusterName }}" + disks: + - type: "CDROM" + size_gb: 10 + bus: "SATA" + - type: "CDROM" + size_gb: 10 + bus: "IDE" + cores_per_vcpu: 1 + register: result + ignore_errors: True + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to Create VM with Cluster, different CDROMS ' + success_msg: ' VM with Cluster, different CDROMS created successfully ' + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + + #################################################################################### + + - name: VM with all specification + ntnx_vms: + state: present + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + name: "All specification" + timezone: "GMT" + cluster: + name: "{{ ClusterName }}" + disks: + - type: "DISK" + size_gb: 2 + bus: "SCSI" + - type: "DISK" + size_gb: 10 + bus: "PCI" + - type: "DISK" + size_gb: 2 + bus: "SATA" + - type: "DISK" + size_gb: 10 + bus: "SCSI" + - type: "CDROM" + size_gb: 10 + bus: "IDE" + boot_config: + boot_type: "LEGACY" + boot_order: + - "DISK" + - "CDROM" + - "NETWORK" + vcpus: 2 + cores_per_vcpu: 2 + memory_gb: 2 + register: result + ignore_errors: True + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with all specification ' + success_msg: ' VM with all specification created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ################################################################################################## + + + - name: VM with managed subnet + ntnx_vms: + state: present + name: VM with managed subnet + nutanix_host: "{{ IP }}" + validate_certs: False + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + cluster: + name: "{{ ClusterName }}" + networks: + - is_connected: true + subnet: + name: "{{ StaticNetworkName }}" + private_ip: "{{ StaticNetworkIP }}" + register: result + ignore_errors: true + + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with managed subnet ' + success_msg: ' VM with with managed subnet created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + + + ################################################################################################### + + - name: VM with minimum requiremnts + ntnx_vms: + state: present + name: MinReqVM + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + cluster: + name: "{{ ClusterName }}" + register: result + ignore_errors: true + + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with minimum requiremnts ' + success_msg: ' VM with minimum requiremnts created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ################################################################################################## + + - name: VM with unmanaged vlan + ntnx_vms: + desc: "VM with unmanaged vlan" + state: present + name: VM with unmanaged vlan + timezone: UTC + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + cluster: + name: "{{ ClusterName }}" + networks: + - is_connected: false + subnet: + uuid: "{{ dhcpUUID }}" + boot_config: + boot_type: LEGACY + boot_order: + - DISK + - CDROM + - NETWORK + vcpus: 1 + cores_per_vcpu: 1 + memory_gb: 1 + register: result + ignore_errors: true + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with unmanaged vlan ' + success_msg: ' VM with unmanaged vlan created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ################################################################################################# + + - name: VM with diffrent disk types and diffrent sizes with UEFI boot type + ntnx_vms: + state: present + name: VM with UEFI boot type + timezone: GMT + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + cluster: + name: "{{ ClusterName }}" + categories: + AppType: + - Apache_Spark + disks: + - type: "DISK" + clone_image: + name: "{{ Ubuntu }}" + bus: "SCSI" + size_gb: 20 + - type: DISK + size_gb: 1 + bus: SCSI + - type: DISK + size_gb: 2 + bus: PCI + storage_container: + name: "{{ StorageContainerName }}" + - type: DISK + size_gb: 3 + bus: SATA + boot_config: + boot_type: UEFI + boot_order: + - DISK + - CDROM + - NETWORK + vcpus: 2 + cores_per_vcpu: 1 + memory_gb: 1 + register: result + + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with diffrent disk types and diffrent sizes with UEFI boot type ' + success_msg: ' VM with diffrent disk types and diffrent sizes with UEFI boot type created successfully ' + + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + ###################################################################################### + + - name: VM with managed and unmanaged network + ntnx_vms: + state: present + name: VM_NIC + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + timezone: UTC + cluster: + name: "{{ ClusterName }}" + networks: + - is_connected: true + subnet: + uuid: "{{ dhcpUUID }}" + - is_connected: true + subnet: + uuid: "{{ StaticNetworkUUID }}" + disks: + - type: DISK + size_gb: 1 + bus: SCSI + - type: DISK + size_gb: 3 + bus: PCI + - type: CDROM + bus: SATA + size_gb: 1 + - type: CDROM + bus: IDE + size_gb: 1 + boot_config: + boot_type: UEFI + boot_order: + - DISK + - CDROM + - NETWORK + vcpus: 2 + cores_per_vcpu: 2 + memory_gb: 2 + register: result + ignore_errors: true + + - name: Creation Status + assert: + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with managed and unmanaged network ' + success_msg: ' VM with managed and unmanaged network created successfully ' + + - set_fact: + todelete: '{{ todelete + [ result["response"]["metadata"]["uuid"] ] }}' + when: result.response.status.state == 'COMPLETE' + + + ######################################################################################### + - name: Delete all Created VM + + ntnx_vms: + state: absent + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + vm_uuid: '{{ item }}' + register: result + loop: '{{ todelete }}' + + + + \ No newline at end of file diff --git a/tests/integration/targets/nutanix_vms/tasks/delete.yml b/tests/integration/targets/nutanix_vms/tasks/delete.yml index 9d79f99c8..a91d30e35 100644 --- a/tests/integration/targets/nutanix_vms/tasks/delete.yml +++ b/tests/integration/targets/nutanix_vms/tasks/delete.yml @@ -1,36 +1,40 @@ --- -- name: create VM with Minimum Requirment - nutanix_vms: - state: present - name: "MinVm" - auth: - credentials: "{{credentials}}" - url: "{{config.ip_address}}:{{config.port}}" - cluster: - cluster_uuid: "{{cluster.uuid}}" +- name: VM with minimum requiremnts + ntnx_vms: + state: present + name: MinReqVM + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False + cluster: + name: "{{ ClusterName }}" register: result - ignore_errors: True - -- name: assert when status not complete + ignore_errors: true + +- name: Creation Status assert: - that: - - result.response is defined - - result.response.status.state == 'SUCCEEDED' - fail_msg: "Unable to delete Vm" - success_msg: "VM Created successfully" + that: + - result.response is defined + - result.response.status.state == 'COMPLETE' + fail_msg: ' Unable to create VM with minimum requiremnts ' + success_msg: ' VM with minimum requiremnts created successfully ' + - name: delete vm - nutanix_vms: - uuid: '{{ result["response"]["metadata"]["uuid"] }}' + ntnx_vms: + vm_uuid: '{{ result["response"]["metadata"]["uuid"] }}' state: absent - auth: - credentials: "{{credentials}}" - url: "{{config.ip_address}}:{{config.port}}" + nutanix_host: "{{ IP }}" + nutanix_username: "{{ username }}" + nutanix_password: "{{ password }}" + validate_certs: False register: result + - name: assert when status not complete assert: that: - result.response is defined - - result.response.status.state == 'SUCCEEDED' + - result.response.status == 'SUCCEEDED' fail_msg: "Unable to delete Vm" success_msg: "VM Deleted successfully" diff --git a/tests/integration/targets/nutanix_vms/vars/main.yml b/tests/integration/targets/nutanix_vms/vars/main.yml index 089ac36b5..ae12a0060 100644 --- a/tests/integration/targets/nutanix_vms/vars/main.yml +++ b/tests/integration/targets/nutanix_vms/vars/main.yml @@ -1,20 +1,16 @@ --- -credentials: - username: admin - password: Nutanix.123 -config: - ip_address: 10.44.76.190 - port: 9440 - -cluster: - uuid: "0005d578-2faf-9fb6-3c07-ac1f6b6f9780" -networks: - static: - name: "static_subnet" - uuid: "72c5057d-93f7-4389-a01a-2c2f42eae3ef" - ip: "10.30.30.75" - dhcp: - uuid: "9343da22-886f-4055-88d2-3a8278c68700" -storage_config: - uuid: "4446ca0b-7846-4a6f-b00a-386736432121" +IP: 10.44.76.190 +username: "admin" +password: "Nutanix.123" +CentOS: "CentOS-7-cloudinit" +Ubuntu: "Ubuntu1404" +ClusterName: "auto_cluster_prod_1aa888141361" +ClusterUUID: "0005d578-2faf-9fb6-3c07-ac1f6b6f9780" +NetworkName: "vlan.800" +dhcpUUID: "9343da22-886f-4055-88d2-3a8278c68700" +StaticNetworkName: "static_subnet" +StaticNetworkUUID: "72c5057d-93f7-4389-a01a-2c2f42eae3ef" +StaticNetworkIP: 10.30.30.75 +StorageConfigUUID: "98b0fa80-a74b-4dac-b1b9-0fe90b2f5526" +StorageContainerName: "SelfServiceContainer" todelete: [] \ No newline at end of file diff --git a/tests/test_entity.py b/tests/test_entity.py index 1535ad0fd..82cb518ac 100644 --- a/tests/test_entity.py +++ b/tests/test_entity.py @@ -1,8 +1,10 @@ -from plugins.module_utils.prism.vms import VM -from plugins.module_utils.prism.images import Image +from __future__ import absolute_import, division, print_function + +__metaclass__ = type import json -from ansible.module_utils.basic import AnsibleModule + +from plugins.module_utils.prism.vms import VM class Module: