diff --git a/test/units/module_utils/xenserver/FakeAnsibleModule.py b/test/units/module_utils/xenserver/FakeAnsibleModule.py new file mode 100644 index 00000000000000..b02ad4a1363c5c --- /dev/null +++ b/test/units/module_utils/xenserver/FakeAnsibleModule.py @@ -0,0 +1,30 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + + +class AnsibleModuleException(Exception): + def __init__(self, *args, **kwargs): + self.args = args + self.kwargs = kwargs + + +class ExitJsonException(AnsibleModuleException): + pass + + +class FailJsonException(AnsibleModuleException): + pass + + +class FakeAnsibleModule: + def __init__(self, params=None, check_mode=False): + self.params = params + self.check_mode = check_mode + + def exit_json(self, *args, **kwargs): + raise ExitJsonException(*args, **kwargs) + + def fail_json(self, *args, **kwargs): + raise FailJsonException(*args, **kwargs) diff --git a/test/units/module_utils/xenserver/FakeXenAPI.py b/test/units/module_utils/xenserver/FakeXenAPI.py new file mode 100644 index 00000000000000..dc657a6a090386 --- /dev/null +++ b/test/units/module_utils/xenserver/FakeXenAPI.py @@ -0,0 +1,66 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +FAKE_API_VERSION = "1.1" + + +class Failure(Exception): + def __init__(self, details): + self.details = details + + def __str__(self): + return str(self.details) + + +class Session(object): + def __init__(self, uri, transport=None, encoding=None, verbose=0, + allow_none=1, ignore_ssl=False): + + self.transport = transport + self._session = None + self.last_login_method = None + self.last_login_params = None + self.API_version = FAKE_API_VERSION + + def _get_api_version(self): + return FAKE_API_VERSION + + def _login(self, method, params): + self._session = "OpaqueRef:fake-xenapi-session-ref" + self.last_login_method = method + self.last_login_params = params + self.API_version = self._get_api_version() + + def _logout(self): + self._session = None + self.last_login_method = None + self.last_login_params = None + self.API_version = FAKE_API_VERSION + + def xenapi_request(self, methodname, params): + if methodname.startswith('login'): + self._login(methodname, params) + return None + elif methodname == 'logout' or methodname == 'session.logout': + self._logout() + return None + else: + # Should be patched with mocker.patch(). + return None + + def __getattr__(self, name): + if name == 'handle': + return self._session + elif name == 'xenapi': + # Should be patched with mocker.patch(). + return None + elif name.startswith('login') or name.startswith('slave_local'): + return lambda *params: self._login(name, params) + elif name == 'logout': + return self._logout + + +def xapi_local(): + return Session("http://_var_lib_xcp_xapi/") diff --git a/test/units/module_utils/xenserver/__init__.py b/test/units/module_utils/xenserver/__init__.py new file mode 100644 index 00000000000000..e69de29bb2d1d6 diff --git a/test/units/module_utils/xenserver/common.py b/test/units/module_utils/xenserver/common.py new file mode 100644 index 00000000000000..ea58c6455b42b4 --- /dev/null +++ b/test/units/module_utils/xenserver/common.py @@ -0,0 +1,25 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 + + +def fake_xenapi_ref(xenapi_class): + return "OpaqueRef:fake-xenapi-%s-ref" % xenapi_class + + +testcase_bad_xenapi_refs = { + "params": [ + None, + '', + 'OpaqueRef:NULL', + ], + "ids": [ + 'none', + 'empty', + 'ref-null', + ], +} diff --git a/test/units/module_utils/xenserver/conftest.py b/test/units/module_utils/xenserver/conftest.py new file mode 100644 index 00000000000000..60db1bfc5868dd --- /dev/null +++ b/test/units/module_utils/xenserver/conftest.py @@ -0,0 +1,118 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 sys +import importlib +import os +import json +import pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from ansible.module_utils import six +from mock import MagicMock + + +@pytest.fixture +def fake_ansible_module(request): + """Returns fake AnsibleModule with fake module params.""" + if hasattr(request, 'param'): + return FakeAnsibleModule(request.param) + else: + params = { + "hostname": "somehost", + "username": "someuser", + "password": "somepwd", + "validate_certs": True, + } + + return FakeAnsibleModule(params) + + +@pytest.fixture(autouse=True) +def XenAPI(): + """Imports and returns fake XenAPI module.""" + + # Import of fake XenAPI module is wrapped by fixture so that it does not + # affect other unit tests which could potentialy also use XenAPI module. + + # First we use importlib.import_module() to import the module and assign + # it to a local symbol. + fake_xenapi = importlib.import_module('units.module_utils.xenserver.FakeXenAPI') + + # Now we populate Python module cache with imported fake module using the + # original module name (XenAPI). That way, any 'import XenAPI' statement + # will just load already imported fake module from the cache. + sys.modules['XenAPI'] = fake_xenapi + + return fake_xenapi + + +@pytest.fixture(autouse=True) +def xenserver(XenAPI): + """Imports and returns xenserver module util.""" + + # Since we are wrapping fake XenAPI module inside a fixture, all modules + # that depend on it have to be imported inside a test function. To make + # this easier to handle and remove some code repetition, we wrap the import + # of xenserver module util with a fixture. + from ansible.module_utils import xenserver + + return xenserver + + +@pytest.fixture +def mock_xenapi_failure(XenAPI, mocker): + """ + Returns mock object that raises XenAPI.Failure on any XenAPI + method call. + """ + fake_error_msg = "Fake XAPI method call error!" + + # We need to use our MagicMock based class that passes side_effect to its + # children because calls to xenapi methods can generate an arbitrary + # hierarchy of mock objects. Any such object when called should use the + # same side_effect as its parent mock object. + class MagicMockSideEffect(MagicMock): + def _get_child_mock(self, **kw): + child_mock = super(MagicMockSideEffect, self)._get_child_mock(**kw) + child_mock.side_effect = self.side_effect + return child_mock + + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', new=MagicMockSideEffect(), create=True) + mocked_xenapi.side_effect = XenAPI.Failure(fake_error_msg) + + return mocked_xenapi, fake_error_msg + + +@pytest.fixture +def fixture_data_from_file(request): + """Loads fixture data from files.""" + if not hasattr(request, 'param'): + return {} + + fixture_path = os.path.join(os.path.dirname(__file__), 'fixtures') + fixture_data = {} + + if isinstance(request.param, six.string_types): + request.param = [request.param] + + for fixture_name in request.param: + path = os.path.join(fixture_path, fixture_name) + + with open(path) as f: + data = f.read() + + try: + data = json.loads(data) + except Exception: + pass + + fixture_data[fixture_name] = data + + return fixture_data diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-facts.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-facts.json new file mode 100644 index 00000000000000..add2dcf4b06b87 --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-facts.json @@ -0,0 +1,73 @@ +{ + "cdrom": { + "type": "none" + }, + "customization_agent": "native", + "disks": [ + { + "name": "ansible-test-vm-1-C", + "name_desc": "C:\\", + "os_device": "xvda", + "size": 42949672960, + "sr": "Ansible Test Storage 1", + "sr_uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "vbd_userdevice": "0" + } + ], + "domid": "143", + "folder": "/Ansible/Test", + "hardware": { + "memory_mb": 2048, + "num_cpu_cores_per_socket": 2, + "num_cpus": 2 + }, + "home_server": "", + "is_template": false, + "name": "ansible-test-vm-1", + "name_desc": "Created by Ansible", + "networks": [ + { + "gateway": "10.0.0.1", + "gateway6": "", + "ip": "10.0.0.2", + "ip6": [ + "fe80:0000:0000:0000:11e1:12c9:ef3b:75a0" + ], + "mac": "7a:a6:48:1e:31:46", + "mtu": "1500", + "name": "Host internal management network", + "netmask": "255.255.255.0", + "prefix": "24", + "prefix6": "", + "vif_device": "0" + } + ], + "other_config": { + "base_template_name": "Windows Server 2016 (64-bit)", + "folder": "/Ansible/Test", + "import_task": "OpaqueRef:e43eb71c-45d6-5351-09ff-96e4fb7d0fa5", + "install-methods": "cdrom", + "instant": "true", + "mac_seed": "366fe8e0-878b-4320-8731-90d1ed3c0b93" + }, + "platform": { + "acpi": "1", + "apic": "true", + "cores-per-socket": "2", + "device_id": "0002", + "hpet": "true", + "nx": "true", + "pae": "true", + "timeoffset": "-28800", + "vga": "std", + "videoram": "8", + "viridian": "true", + "viridian_reference_tsc": "true", + "viridian_time_ref_count": "true" + }, + "state": "poweredon", + "uuid": "81c373d7-a407-322f-911b-31386eb5215d", + "xenstore_data": { + "vm-data": "" + } +} diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-params.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-params.json new file mode 100644 index 00000000000000..7097696685672d --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-1-params.json @@ -0,0 +1,707 @@ +{ + "SR": { + "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f": { + "PBDs": [], + "VDIs": [], + "allowed_operations": [ + "unplug", + "plug", + "pbd_create", + "update", + "pbd_destroy", + "vdi_resize", + "vdi_clone", + "scan", + "vdi_snapshot", + "vdi_mirror", + "vdi_create", + "vdi_destroy" + ], + "blobs": {}, + "clustered": false, + "content_type": "", + "current_operations": {}, + "introduced_by": "OpaqueRef:NULL", + "is_tools_sr": false, + "local_cache_enabled": false, + "name_description": "", + "name_label": "Ansible Test Storage 1", + "other_config": { + "auto-scan": "false" + }, + "physical_size": "2521133219840", + "physical_utilisation": "1551485632512", + "shared": true, + "sm_config": { + "allocation": "thick", + "devserial": "scsi-3600a098038302d353624495242443848", + "multipathable": "true", + "use_vhd": "true" + }, + "tags": [], + "type": "lvmohba", + "uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "virtual_allocation": "1556925644800" + } + }, + "VBD": { + "OpaqueRef:1c0a7c6d-09e5-9b2c-bbe3-9a73aadcff9f": { + "VDI": "OpaqueRef:NULL", + "VM": "OpaqueRef:43a1b8d4-da96-cb08-10f5-fb368abed19c", + "allowed_operations": [ + "attach", + "unpause", + "insert", + "pause" + ], + "bootable": false, + "current_operations": {}, + "currently_attached": true, + "device": "xvdd", + "empty": true, + "metrics": "OpaqueRef:1a36eae4-87c8-0945-cee9-c85a71fd843f", + "mode": "RO", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "CD", + "unpluggable": true, + "userdevice": "3", + "uuid": "e6aacd53-a2c8-649f-b405-93fcb811411a" + }, + "OpaqueRef:ea4a4088-19c3-6db6-ebdf-c3c0ee4405a3": { + "VDI": "OpaqueRef:fd20510d-e9ca-b966-3b98-4ae547dacf9a", + "VM": "OpaqueRef:43a1b8d4-da96-cb08-10f5-fb368abed19c", + "allowed_operations": [ + "attach", + "unpause", + "unplug", + "unplug_force", + "pause" + ], + "bootable": true, + "current_operations": {}, + "currently_attached": true, + "device": "xvda", + "empty": false, + "metrics": "OpaqueRef:ddbd70d4-7dde-b51e-6208-eb434b300009", + "mode": "RW", + "other_config": { + "owner": "true" + }, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "Disk", + "unpluggable": true, + "userdevice": "0", + "uuid": "ffd6de9c-c416-1d52-3e9d-3bcbf567245e" + } + }, + "VDI": { + "OpaqueRef:fd20510d-e9ca-b966-3b98-4ae547dacf9a": { + "SR": "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f", + "VBDs": [ + "OpaqueRef:ea4a4088-19c3-6db6-ebdf-c3c0ee4405a3" + ], + "allow_caching": false, + "allowed_operations": [ + "clone", + "snapshot" + ], + "crash_dumps": [], + "current_operations": {}, + "is_a_snapshot": false, + "is_tools_iso": false, + "location": "b807f67b-3f37-4a6e-ad6c-033f812ab093", + "managed": true, + "metadata_latest": false, + "metadata_of_pool": "", + "missing": false, + "name_description": "C:\\", + "name_label": "ansible-test-vm-1-C", + "on_boot": "persist", + "other_config": {}, + "parent": "OpaqueRef:NULL", + "physical_utilisation": "43041947648", + "read_only": false, + "sharable": false, + "sm_config": { + "host_OpaqueRef:07a8da76-f1cf-f3b5-a531-6b751384f770": "RW", + "read-caching-enabled-on-92ac8132-276b-4d0f-9d3a-54db51e4a438": "false", + "read-caching-reason-92ac8132-276b-4d0f-9d3a-54db51e4a438": "LICENSE_RESTRICTION", + "vdi_type": "vhd" + }, + "snapshot_of": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "storage_lock": false, + "tags": [], + "type": "system", + "uuid": "b807f67b-3f37-4a6e-ad6c-033f812ab093", + "virtual_size": "42949672960", + "xenstore_data": {} + } + }, + "VIF": { + "OpaqueRef:38da2120-6086-5043-8383-ab0a53ede42a": { + "MAC": "7a:a6:48:1e:31:46", + "MAC_autogenerated": false, + "MTU": "1500", + "VM": "OpaqueRef:43a1b8d4-da96-cb08-10f5-fb368abed19c", + "allowed_operations": [ + "attach", + "unplug" + ], + "current_operations": {}, + "currently_attached": true, + "device": "0", + "ipv4_addresses": [ + "10.0.0.2/24" + ], + "ipv4_allowed": [], + "ipv4_configuration_mode": "Static", + "ipv4_gateway": "10.0.0.1", + "ipv6_addresses": [ + "" + ], + "ipv6_allowed": [], + "ipv6_configuration_mode": "None", + "ipv6_gateway": "", + "locking_mode": "network_default", + "metrics": "OpaqueRef:15502939-df0f-0095-1ce3-e51367199d27", + "network": "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "uuid": "bd108d25-488a-f9b5-4c7b-02d40f1e38a8" + } + }, + "VM": { + "OpaqueRef:43a1b8d4-da96-cb08-10f5-fb368abed19c": { + "HVM_boot_params": { + "order": "dc" + }, + "HVM_boot_policy": "BIOS order", + "HVM_shadow_multiplier": 1.0, + "PCI_bus": "", + "PV_args": "", + "PV_bootloader": "", + "PV_bootloader_args": "", + "PV_kernel": "", + "PV_legacy_args": "", + "PV_ramdisk": "", + "VBDs": [ + "OpaqueRef:1c0a7c6d-09e5-9b2c-bbe3-9a73aadcff9f", + "OpaqueRef:ea4a4088-19c3-6db6-ebdf-c3c0ee4405a3" + ], + "VCPUs_at_startup": "2", + "VCPUs_max": "2", + "VCPUs_params": {}, + "VGPUs": [], + "VIFs": [ + "OpaqueRef:38da2120-6086-5043-8383-ab0a53ede42a" + ], + "VTPMs": [], + "actions_after_crash": "restart", + "actions_after_reboot": "restart", + "actions_after_shutdown": "destroy", + "affinity": "OpaqueRef:NULL", + "allowed_operations": [ + "changing_dynamic_range", + "migrate_send", + "pool_migrate", + "changing_VCPUs_live", + "suspend", + "hard_reboot", + "hard_shutdown", + "clean_reboot", + "clean_shutdown", + "pause", + "checkpoint", + "snapshot" + ], + "appliance": "OpaqueRef:NULL", + "attached_PCIs": [], + "bios_strings": { + "bios-vendor": "Xen", + "bios-version": "", + "hp-rombios": "", + "oem-1": "Xen", + "oem-2": "MS_VM_CERT/SHA1/bdbeb6e0a816d43fa6d3fe8aaef04c2bad9d3e3d", + "system-manufacturer": "Xen", + "system-product-name": "HVM domU", + "system-serial-number": "", + "system-version": "" + }, + "blobs": {}, + "blocked_operations": {}, + "children": [], + "consoles": [ + "OpaqueRef:4fa7d34e-1fb6-9e88-1b21-41a3c6550d8b" + ], + "crash_dumps": [], + "current_operations": {}, + "domarch": "", + "domid": "143", + "generation_id": "3274224479562869847:6952848762503845513", + "guest_metrics": "OpaqueRef:453f21be-954d-2ca8-e38e-09741e91350c", + "ha_always_run": false, + "ha_restart_priority": "", + "hardware_platform_version": "0", + "has_vendor_device": false, + "is_a_snapshot": false, + "is_a_template": false, + "is_control_domain": false, + "is_default_template": false, + "is_snapshot_from_vmpp": false, + "is_vmss_snapshot": false, + "last_boot_CPU_flags": { + "features": "17cbfbff-f7fa3223-2d93fbff-00000023-00000001-000007ab-00000000-00000000-00001000-0c000000", + "vendor": "GenuineIntel" + }, + "last_booted_record": "", + "memory_dynamic_max": "2147483648", + "memory_dynamic_min": "2147483648", + "memory_overhead": "20971520", + "memory_static_max": "2147483648", + "memory_static_min": "1073741824", + "memory_target": "2147483648", + "metrics": "OpaqueRef:6eede779-4e55-7cfb-8b8a-e4b9becf770b", + "name_description": "Created by Ansible", + "name_label": "ansible-test-vm-1", + "order": "0", + "other_config": { + "base_template_name": "Windows Server 2016 (64-bit)", + "folder": "/Ansible/Test", + "import_task": "OpaqueRef:e43eb71c-45d6-5351-09ff-96e4fb7d0fa5", + "install-methods": "cdrom", + "instant": "true", + "mac_seed": "366fe8e0-878b-4320-8731-90d1ed3c0b93" + }, + "parent": "OpaqueRef:NULL", + "platform": { + "acpi": "1", + "apic": "true", + "cores-per-socket": "2", + "device_id": "0002", + "hpet": "true", + "nx": "true", + "pae": "true", + "timeoffset": "-28800", + "vga": "std", + "videoram": "8", + "viridian": "true", + "viridian_reference_tsc": "true", + "viridian_time_ref_count": "true" + }, + "power_state": "Running", + "protection_policy": "OpaqueRef:NULL", + "recommendations": "", + "reference_label": "windows-server-2016-64bit", + "requires_reboot": false, + "resident_on": "OpaqueRef:07a8da76-f1cf-f3b5-a531-6b751384f770", + "shutdown_delay": "0", + "snapshot_info": {}, + "snapshot_metadata": "", + "snapshot_of": "OpaqueRef:NULL", + "snapshot_schedule": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "start_delay": "0", + "suspend_SR": "OpaqueRef:NULL", + "suspend_VDI": "OpaqueRef:NULL", + "tags": [], + "transportable_snapshot_id": "", + "user_version": "1", + "uuid": "81c373d7-a407-322f-911b-31386eb5215d", + "version": "0", + "xenstore_data": { + "vm-data": "" + } + } + }, + "VM_guest_metrics": { + "OpaqueRef:453f21be-954d-2ca8-e38e-09741e91350c": { + "PV_drivers_detected": true, + "PV_drivers_up_to_date": true, + "PV_drivers_version": { + "build": "1020", + "major": "7", + "micro": "0", + "minor": "1" + }, + "can_use_hotplug_vbd": "yes", + "can_use_hotplug_vif": "yes", + "disks": {}, + "last_updated": "20190113T19:40:34Z", + "live": true, + "memory": {}, + "networks": { + "0/ip": "10.0.0.2", + "0/ipv6/0": "fe80:0000:0000:0000:11e1:12c9:ef3b:75a0" + }, + "os_version": { + "distro": "windows", + "major": "6", + "minor": "2", + "name": "Microsoft Windows Server 2016 Standard|C:\\Windows|\\Device\\Harddisk0\\Partition2", + "spmajor": "0", + "spminor": "0" + }, + "other": { + "data-ts": "1", + "error": "WTSQueryUserToken : 1008 failed.", + "feature-balloon": "1", + "feature-poweroff": "1", + "feature-reboot": "1", + "feature-s3": "1", + "feature-s4": "1", + "feature-setcomputername": "1", + "feature-static-ip-setting": "1", + "feature-suspend": "1", + "feature-ts": "1", + "feature-ts2": "1", + "feature-xs-batcmd": "1", + "has-vendor-device": "0", + "platform-feature-multiprocessor-suspend": "1" + }, + "other_config": {}, + "uuid": "9ea6803f-12ca-3d6a-47b7-c90a33b67b98" + } + }, + "VM_metrics": { + "OpaqueRef:6eede779-4e55-7cfb-8b8a-e4b9becf770b": { + "VCPUs_CPU": {}, + "VCPUs_flags": {}, + "VCPUs_number": "2", + "VCPUs_params": {}, + "VCPUs_utilisation": {}, + "hvm": true, + "install_time": "20190113T19:31:47Z", + "last_updated": "19700101T00:00:00Z", + "memory_actual": "2147475456", + "nested_virt": false, + "nomigrate": false, + "other_config": {}, + "start_time": "20190113T19:38:59Z", + "state": [], + "uuid": "c67fadf7-8143-0c92-c772-cd3901c18e70" + } + }, + "host": { + "OpaqueRef:07a8da76-f1cf-f3b5-a531-6b751384f770": { + "API_version_major": "2", + "API_version_minor": "7", + "API_version_vendor": "XenSource", + "API_version_vendor_implementation": {}, + "PBDs": [], + "PCIs": [], + "PGPUs": [], + "PIFs": [], + "address": "10.0.0.1", + "allowed_operations": [ + "vm_migrate", + "provision", + "vm_resume", + "evacuate", + "vm_start" + ], + "bios_strings": {}, + "blobs": {}, + "capabilities": [ + "xen-3.0-x86_64", + "xen-3.0-x86_32p", + "hvm-3.0-x86_32", + "hvm-3.0-x86_32p", + "hvm-3.0-x86_64", + "" + ], + "chipset_info": { + "iommu": "true" + }, + "control_domain": "OpaqueRef:a2a31555-f232-822b-8f36-10d75d44b79c", + "cpu_configuration": {}, + "cpu_info": { + "cpu_count": "40", + "family": "6", + "features": "7ffefbff-bfebfbff-00000021-2c100800", + "features_hvm": "17cbfbff-f7fa3223-2d93fbff-00000023-00000001-000007ab-00000000-00000000-00001000-0c000000", + "features_pv": "17c9cbf5-f6f83203-2191cbf5-00000023-00000001-00000329-00000000-00000000-00001000-0c000000", + "flags": "fpu de tsc msr pae mce cx8 apic sep mca cmov pat clflush acpi mmx fxsr sse sse2 ht syscall nx lm constant_tsc arch_perfmon rep_good nopl nonstop_tsc eagerfpu pni pclmulqdq monitor est ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb pln pts dtherm fsgsbase bmi1 avx2 bmi2 erms xsaveopt cqm_llc cqm_occup_llc", + "model": "63", + "modelname": "Intel(R) Xeon(R) CPU E5-2660 v3 @ 2.60GHz", + "socket_count": "2", + "speed": "2597.064", + "stepping": "2", + "vendor": "GenuineIntel" + }, + "crash_dump_sr": "OpaqueRef:ed72d7bf-4e53-67fc-17f5-e27b203042ba", + "crashdumps": [], + "current_operations": {}, + "display": "enabled", + "edition": "free", + "enabled": true, + "external_auth_configuration": {}, + "external_auth_service_name": "", + "external_auth_type": "", + "features": [], + "guest_VCPUs_params": {}, + "ha_network_peers": [], + "ha_statefiles": [], + "host_CPUs": [ + "OpaqueRef:f7e744f6-a6f9-c460-999a-c27e1395e2e0", + "OpaqueRef:f6e5dcf0-0453-8f3f-88c1-7ad6e2ef3dd1", + "OpaqueRef:f27a52fb-5feb-173d-1a07-d1735a83c2cc", + "OpaqueRef:ed65327a-508a-ccfc-dba6-2a0175cb2432", + "OpaqueRef:e41d2f2a-fe9e-72cb-8104-b22d6d314b13", + "OpaqueRef:e1988469-b814-5d10-17a6-bfd7c62d2b5f", + "OpaqueRef:d73967dc-b8d8-b47b-39f4-d599fdcabf55", + "OpaqueRef:cba9ebd9-40dc-0611-d1bb-aa661bd0bf70", + "OpaqueRef:c53d3110-4085-60af-8300-d879818789f7", + "OpaqueRef:bee0cf87-7df6-79a6-94e8-36f98e69ad20", + "OpaqueRef:bde28e83-213f-0e65-b6ad-0ae1ecebb98d", + "OpaqueRef:bbfefe67-f65f-98cb-c3fc-cb8ea0588006", + "OpaqueRef:b38ac595-afea-0ca0-49a0-9f5ef2368e3b", + "OpaqueRef:b14ef333-78b1-193d-02da-dc9bfed36912", + "OpaqueRef:afd478bf-57b9-0c79-f257-50aeb81504f1", + "OpaqueRef:a307cd3a-2132-2e42-4ebc-cc1c7780736d", + "OpaqueRef:a1a9df7d-88ba-64fd-a55c-0f6472e1753f", + "OpaqueRef:a0e39c9c-3e0b-fa03-e5d0-93a09aa77393", + "OpaqueRef:9fd5719b-36ab-8e25-7756-20a496ccb331", + "OpaqueRef:9ac4195d-ac07-cfe2-bc19-27ee54cf91fb", + "OpaqueRef:98c5c00c-1e2d-e22b-842e-79e85ce07873", + "OpaqueRef:961129bf-e695-f206-7297-64f9007a64f3", + "OpaqueRef:64368b4c-3488-2808-f0b3-42f2a656df2b", + "OpaqueRef:620dabc0-d7c5-0dc8-52df-3be25194c2fb", + "OpaqueRef:5cee2759-dd8e-7e1a-0727-21e196584030", + "OpaqueRef:58f70163-863d-5787-ffbb-2416cb16ca1e", + "OpaqueRef:4462f848-f396-653d-67f9-2bed13be2c58", + "OpaqueRef:40e800c2-19db-7cd8-c045-5ae93f908cae", + "OpaqueRef:3f84278b-dec6-ded0-1a33-4daa0ce75a2f", + "OpaqueRef:3ef14992-62f6-e1f0-5715-0ee02a834a9c", + "OpaqueRef:3e274c24-c55b-06f5-2c8f-415421043ab2", + "OpaqueRef:35ff27da-f286-7b70-adc1-a200880bb79f", + "OpaqueRef:2511aa53-8660-e442-3cd2-305982d1f751", + "OpaqueRef:21d234e3-138c-81ca-9ed8-febc81b874e9", + "OpaqueRef:1f9b4ee3-dcc7-114e-b401-dc3e94c07efa", + "OpaqueRef:1b94a981-d340-dd07-41c2-b3ff3c545fed", + "OpaqueRef:197ad104-64a8-5af3-8c7a-95f3d301aadd", + "OpaqueRef:1672e747-dc4b-737b-ddcf-0a373f966012", + "OpaqueRef:12ced494-a225-7584-456b-739331bb5114", + "OpaqueRef:0139ff72-62ac-1a6a-8f6f-cb01d8a4ee92" + ], + "hostname": "ansible-test-host-1", + "license_params": { + "address1": "", + "address2": "", + "city": "", + "company": "", + "country": "", + "enable_xha": "true", + "expiry": "20291231T23:00:00Z", + "grace": "no", + "license_type": "", + "name": "", + "platform_filter": "false", + "postalcode": "", + "productcode": "", + "regular_nag_dialog": "false", + "restrict_ad": "false", + "restrict_batch_hotfix_apply": "true", + "restrict_checkpoint": "false", + "restrict_cifs": "true", + "restrict_connection": "false", + "restrict_cpu_masking": "false", + "restrict_dmc": "false", + "restrict_dr": "false", + "restrict_email_alerting": "false", + "restrict_equalogic": "false", + "restrict_export_resource_data": "true", + "restrict_gpu": "false", + "restrict_guest_agent_auto_update": "true", + "restrict_guest_ip_setting": "false", + "restrict_health_check": "false", + "restrict_historical_performance": "false", + "restrict_hotfix_apply": "false", + "restrict_integrated_gpu_passthrough": "false", + "restrict_intellicache": "false", + "restrict_lab": "false", + "restrict_live_patching": "true", + "restrict_marathon": "false", + "restrict_nested_virt": "true", + "restrict_netapp": "false", + "restrict_pci_device_for_auto_update": "true", + "restrict_pool_attached_storage": "false", + "restrict_pooling": "false", + "restrict_pvs_proxy": "true", + "restrict_qos": "false", + "restrict_rbac": "false", + "restrict_read_caching": "true", + "restrict_set_vcpus_number_live": "true", + "restrict_ssl_legacy_switch": "false", + "restrict_stage": "false", + "restrict_storage_xen_motion": "false", + "restrict_storagelink": "false", + "restrict_storagelink_site_recovery": "false", + "restrict_vgpu": "true", + "restrict_vif_locking": "false", + "restrict_vlan": "false", + "restrict_vm_memory_introspection": "true", + "restrict_vmpr": "false", + "restrict_vmss": "false", + "restrict_vss": "false", + "restrict_vswitch_controller": "false", + "restrict_web_selfservice": "true", + "restrict_web_selfservice_manager": "true", + "restrict_wlb": "true", + "restrict_xcm": "true", + "restrict_xen_motion": "false", + "serialnumber": "", + "sku_marketing_name": "Citrix XenServer", + "sku_type": "free", + "sockets": "2", + "state": "", + "version": "" + }, + "license_server": { + "address": "localhost", + "port": "27000" + }, + "local_cache_sr": "OpaqueRef:ed72d7bf-4e53-67fc-17f5-e27b203042ba", + "logging": {}, + "memory_overhead": "4606619648", + "metrics": "OpaqueRef:82b6937a-60c2-96d8-4e78-9f9a1143033f", + "name_description": "", + "name_label": "ansible-test-host-1", + "other_config": { + "agent_start_time": "1532019557.", + "boot_time": "1530023264.", + "iscsi_iqn": "iqn.2018-06.com.example:c8bac750", + "last_blob_sync_time": "1547394076.36", + "multipathhandle": "dmp", + "multipathing": "true" + }, + "patches": [ + "OpaqueRef:f74ca18d-cfb7-e4fe-e5c4-819843de11e2", + "OpaqueRef:f53ff05e-8dd8-3a15-d3b0-8dcf6004fbe2", + "OpaqueRef:ed7f38da-1a50-a48b-60bf-933cabe8d7bc", + "OpaqueRef:e7bb1462-51a5-1aaf-3b56-11b8ebd83a94", + "OpaqueRef:d87b343b-6ba3-db8b-b80e-e02319ba5924", + "OpaqueRef:ccb00450-ed04-4eaa-e6d7-130ef3722374", + "OpaqueRef:b79b8864-11d9-1d5f-09e5-a66d7b64b9e2", + "OpaqueRef:9bebcc7d-61ae-126b-3be0-9156026e586f", + "OpaqueRef:740a1156-b991-00b8-ef50-fdbb22a4d911", + "OpaqueRef:71def430-754b-2bfb-6c93-ec3b67b754e4", + "OpaqueRef:6c73b00d-df66-1740-9578-2b14e46297ba", + "OpaqueRef:6a53d2ae-3d6b-32ed-705f-fd53f1304470", + "OpaqueRef:35a67684-b094-1c77-beff-8237d87c7a27", + "OpaqueRef:33da42c2-c421-9859-79b7-ce9b6c394a1b", + "OpaqueRef:2baa6b4b-9bbe-c1b2-23ce-c8c831ac581d", + "OpaqueRef:2ac3beea-dee2-44e7-9f67-5fd216e593a0", + "OpaqueRef:1bd8f24b-3190-6e7a-b36e-e2998197d062", + "OpaqueRef:1694ea26-4930-6ca1-036e-273438375de9", + "OpaqueRef:09813f03-0c6f-a6af-768f-ef4cdde2c641" + ], + "power_on_config": {}, + "power_on_mode": "", + "resident_VMs": [], + "sched_policy": "credit", + "software_version": { + "build_number": "release/falcon/master/8", + "date": "2017-05-11", + "db_schema": "5.120", + "dbv": "2017.0517", + "hostname": "f7d02093adae", + "linux": "4.4.0+10", + "network_backend": "openvswitch", + "platform_name": "XCP", + "platform_version": "2.3.0", + "product_brand": "XenServer", + "product_version": "7.2.0", + "product_version_text": "7.2", + "product_version_text_short": "7.2", + "xapi": "1.9", + "xen": "4.7.5-2.12", + "xencenter_max": "2.7", + "xencenter_min": "2.7" + }, + "ssl_legacy": true, + "supported_bootloaders": [ + "pygrub", + "eliloader" + ], + "suspend_image_sr": "OpaqueRef:ed72d7bf-4e53-67fc-17f5-e27b203042ba", + "tags": [], + "updates": [ + "OpaqueRef:b71938bf-4c4f-eb17-7e78-588e71297a74", + "OpaqueRef:91cfa47b-52f9-a4e3-4e78-52e3eb3e5141", + "OpaqueRef:e2209ae9-5362-3a20-f691-9294144e49f2", + "OpaqueRef:6ac77a0f-f079-8067-85cc-c9ae2f8dcca9", + "OpaqueRef:a17e721d-faf4-6ad1-c617-dd4899279534", + "OpaqueRef:6c9b814c-e1c2-b8be-198f-de358686b10a", + "OpaqueRef:fbaabbfe-88d5-d89b-5b3f-d6374601ca71", + "OpaqueRef:9eccc765-9726-d220-96b1-2e85adf77ecc", + "OpaqueRef:204558d7-dce0-2304-bdc5-80ec5fd7e3c3", + "OpaqueRef:65b14ae7-f440-0c4d-4af9-c7946b90fd2f", + "OpaqueRef:0760c608-b02e-743a-18a1-fa8f205374d6", + "OpaqueRef:1ced32ca-fec4-8b44-0e8f-753c97f2d93f", + "OpaqueRef:3fffd7c7-f4d1-6b03-a5b8-d75211bb7b8f", + "OpaqueRef:01befb95-412e-e9dd-5b5d-edd50df61cb1", + "OpaqueRef:a3f9481e-fe3d-1f00-235f-44d404f51128", + "OpaqueRef:507ee5fc-59d3-e635-21d5-98a5cace4bf2", + "OpaqueRef:7b4b5da1-54af-d0c4-3fea-394b4257bffe", + "OpaqueRef:f61edc83-91d9-a161-113f-00c110196238", + "OpaqueRef:7efce157-9b93-d116-f3f8-7eb0c6fb1a79" + ], + "updates_requiring_reboot": [], + "uuid": "92ac8132-276b-4d0f-9d3a-54db51e4a438", + "virtual_hardware_platform_versions": [ + "0", + "1", + "2" + ] + } + }, + "network": { + "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724": { + "MTU": "1500", + "PIFs": [], + "VIFs": [], + "allowed_operations": [], + "assigned_ips": { + "OpaqueRef:8171dad1-f902-ec00-7ba2-9f92d8aa75ab": "169.254.0.3", + "OpaqueRef:9754a0ed-e100-d224-6a70-a55a9c2cedf9": "169.254.0.2" + }, + "blobs": {}, + "bridge": "xenapi", + "current_operations": {}, + "default_locking_mode": "unlocked", + "managed": true, + "name_description": "Network on which guests will be assigned a private link-local IP address which can be used to talk XenAPI", + "name_label": "Host internal management network", + "other_config": { + "ip_begin": "169.254.0.1", + "ip_end": "169.254.255.254", + "is_guest_installer_network": "true", + "is_host_internal_management_network": "true", + "netmask": "255.255.0.0" + }, + "tags": [], + "uuid": "dbb96525-944f-0d1a-54ed-e65cb6d07450" + } + } +} diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-facts.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-facts.json new file mode 100644 index 00000000000000..607212c05d3176 --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-facts.json @@ -0,0 +1,87 @@ +{ + "cdrom": { + "type": "none" + }, + "customization_agent": "custom", + "disks": [ + { + "name": "ansible-test-vm-2-root", + "name_desc": "/", + "os_device": "xvda", + "size": 10737418240, + "sr": "Ansible Test Storage 1", + "sr_uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "vbd_userdevice": "0" + }, + { + "name": "ansible-test-vm-2-mysql", + "name_desc": "/var/lib/mysql", + "os_device": "xvdb", + "size": 1073741824, + "sr": "Ansible Test Storage 1", + "sr_uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "vbd_userdevice": "1" + } + ], + "domid": "140", + "folder": "/Ansible/Test", + "hardware": { + "memory_mb": 1024, + "num_cpu_cores_per_socket": 1, + "num_cpus": 1 + }, + "home_server": "ansible-test-host-2", + "is_template": false, + "name": "ansible-test-vm-2", + "name_desc": "Created by Ansible", + "networks": [ + { + "gateway": "10.0.0.1", + "gateway6": "", + "ip": "169.254.0.2", + "ip6": [], + "mac": "16:87:31:70:d6:31", + "mtu": "1500", + "name": "Host internal management network", + "netmask": "255.255.255.0", + "prefix": "24", + "prefix6": "", + "vif_device": "0" + } + ], + "other_config": { + "base_template_name": "CentOS 7", + "folder": "/Ansible/Test", + "import_task": "OpaqueRef:cf1402d3-b6c1-d908-fe62-06502e3b311a", + "install-methods": "cdrom,nfs,http,ftp", + "instant": "true", + "linux_template": "true", + "mac_seed": "0ab46664-f519-5383-166e-e4ea485ede7d" + }, + "platform": { + "acpi": "1", + "apic": "true", + "cores-per-socket": "1", + "device_id": "0001", + "nx": "true", + "pae": "true", + "timeoffset": "0", + "vga": "std", + "videoram": "8", + "viridian": "false" + }, + "state": "poweredon", + "uuid": "0a05d5ad-3e4b-f0dc-6101-8c56623958bc", + "xenstore_data": { + "vm-data": "", + "vm-data/networks": "", + "vm-data/networks/0": "", + "vm-data/networks/0/gateway": "10.0.0.1", + "vm-data/networks/0/ip": "10.0.0.3", + "vm-data/networks/0/mac": "16:87:31:70:d6:31", + "vm-data/networks/0/name": "Host internal management network", + "vm-data/networks/0/netmask": "255.255.255.0", + "vm-data/networks/0/prefix": "24", + "vm-data/networks/0/type": "static" + } +} diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-params.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-params.json new file mode 100644 index 00000000000000..10615f40ac061a --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-2-params.json @@ -0,0 +1,771 @@ +{ + "SR": { + "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f": { + "PBDs": [], + "VDIs": [], + "allowed_operations": [ + "unplug", + "plug", + "pbd_create", + "update", + "pbd_destroy", + "vdi_resize", + "vdi_clone", + "scan", + "vdi_snapshot", + "vdi_mirror", + "vdi_create", + "vdi_destroy" + ], + "blobs": {}, + "clustered": false, + "content_type": "", + "current_operations": {}, + "introduced_by": "OpaqueRef:NULL", + "is_tools_sr": false, + "local_cache_enabled": false, + "name_description": "", + "name_label": "Ansible Test Storage 1", + "other_config": { + "auto-scan": "false" + }, + "physical_size": "2521133219840", + "physical_utilisation": "1551485632512", + "shared": true, + "sm_config": { + "allocation": "thick", + "devserial": "scsi-3600a098038302d353624495242443848", + "multipathable": "true", + "use_vhd": "true" + }, + "tags": [], + "type": "lvmohba", + "uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "virtual_allocation": "1556925644800" + } + }, + "VBD": { + "OpaqueRef:510e214e-f0ba-3bc9-7834-a4f4d3fa33ef": { + "VDI": "OpaqueRef:NULL", + "VM": "OpaqueRef:08632af0-473e-5106-f400-7910229e49be", + "allowed_operations": [ + "attach", + "unpause", + "insert", + "pause" + ], + "bootable": false, + "current_operations": {}, + "currently_attached": true, + "device": "xvdd", + "empty": true, + "metrics": "OpaqueRef:1075bebe-ba71-66ef-ba30-8afbc83bc6b5", + "mode": "RO", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "CD", + "unpluggable": true, + "userdevice": "3", + "uuid": "79ee1d8e-944b-3bfd-ba4c-a0c165d84f3d" + }, + "OpaqueRef:6bc2c353-f132-926d-6e9b-e4d1d55a3760": { + "VDI": "OpaqueRef:102bef39-b134-d23a-9a50-490e1dbca8f7", + "VM": "OpaqueRef:08632af0-473e-5106-f400-7910229e49be", + "allowed_operations": [ + "attach", + "unpause", + "pause" + ], + "bootable": true, + "current_operations": {}, + "currently_attached": true, + "device": "xvda", + "empty": false, + "metrics": "OpaqueRef:1c71ccde-d7e9-10fb-569c-993b880fa790", + "mode": "RW", + "other_config": { + "owner": "" + }, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "Disk", + "unpluggable": false, + "userdevice": "0", + "uuid": "932fdf6d-7ac5-45e8-a48e-694af75726f1" + }, + "OpaqueRef:9bd6decd-2e55-b55e-387d-c40aa67ff151": { + "VDI": "OpaqueRef:87b45ac6-af36-f4fd-6ebd-a08bed9001e4", + "VM": "OpaqueRef:08632af0-473e-5106-f400-7910229e49be", + "allowed_operations": [ + "attach", + "unpause", + "unplug", + "unplug_force", + "pause" + ], + "bootable": false, + "current_operations": {}, + "currently_attached": true, + "device": "xvdb", + "empty": false, + "metrics": "OpaqueRef:b8424146-d3ea-4850-db9a-47f0059c10ac", + "mode": "RW", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "Disk", + "unpluggable": true, + "userdevice": "1", + "uuid": "c0c1e648-3690-e1fb-9f47-24b4df0cb458" + } + }, + "VDI": { + "OpaqueRef:102bef39-b134-d23a-9a50-490e1dbca8f7": { + "SR": "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f", + "VBDs": [ + "OpaqueRef:6bc2c353-f132-926d-6e9b-e4d1d55a3760" + ], + "allow_caching": false, + "allowed_operations": [ + "clone", + "snapshot" + ], + "crash_dumps": [], + "current_operations": {}, + "is_a_snapshot": false, + "is_tools_iso": false, + "location": "fa1202b8-326f-4235-802e-fafbed66b26b", + "managed": true, + "metadata_latest": false, + "metadata_of_pool": "", + "missing": false, + "name_description": "/", + "name_label": "ansible-test-vm-2-root", + "on_boot": "persist", + "other_config": {}, + "parent": "OpaqueRef:NULL", + "physical_utilisation": "10766778368", + "read_only": false, + "sharable": false, + "sm_config": { + "host_OpaqueRef:e87be804-57a1-532e-56ac-6c4910957be0": "RW", + "read-caching-enabled-on-dff6702e-bcb6-4704-8dd4-952e8c883365": "false", + "read-caching-reason-dff6702e-bcb6-4704-8dd4-952e8c883365": "LICENSE_RESTRICTION", + "vdi_type": "vhd" + }, + "snapshot_of": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "storage_lock": false, + "tags": [], + "type": "system", + "uuid": "fa1202b8-326f-4235-802e-fafbed66b26b", + "virtual_size": "10737418240", + "xenstore_data": {} + }, + "OpaqueRef:87b45ac6-af36-f4fd-6ebd-a08bed9001e4": { + "SR": "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f", + "VBDs": [ + "OpaqueRef:9bd6decd-2e55-b55e-387d-c40aa67ff151" + ], + "allow_caching": false, + "allowed_operations": [ + "clone", + "snapshot" + ], + "crash_dumps": [], + "current_operations": {}, + "is_a_snapshot": false, + "is_tools_iso": false, + "location": "ab3a4d72-f498-4687-86ce-ca937046db76", + "managed": true, + "metadata_latest": false, + "metadata_of_pool": "", + "missing": false, + "name_description": "/var/lib/mysql", + "name_label": "ansible-test-vm-2-mysql", + "on_boot": "persist", + "other_config": {}, + "parent": "OpaqueRef:NULL", + "physical_utilisation": "1082130432", + "read_only": false, + "sharable": false, + "sm_config": { + "host_OpaqueRef:e87be804-57a1-532e-56ac-6c4910957be0": "RW", + "read-caching-enabled-on-dff6702e-bcb6-4704-8dd4-952e8c883365": "false", + "read-caching-reason-dff6702e-bcb6-4704-8dd4-952e8c883365": "LICENSE_RESTRICTION", + "vdi_type": "vhd" + }, + "snapshot_of": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "storage_lock": false, + "tags": [], + "type": "user", + "uuid": "ab3a4d72-f498-4687-86ce-ca937046db76", + "virtual_size": "1073741824", + "xenstore_data": {} + } + }, + "VIF": { + "OpaqueRef:9754a0ed-e100-d224-6a70-a55a9c2cedf9": { + "MAC": "16:87:31:70:d6:31", + "MAC_autogenerated": false, + "MTU": "1500", + "VM": "OpaqueRef:08632af0-473e-5106-f400-7910229e49be", + "allowed_operations": [ + "attach", + "unplug" + ], + "current_operations": {}, + "currently_attached": true, + "device": "0", + "ipv4_addresses": [], + "ipv4_allowed": [], + "ipv4_configuration_mode": "None", + "ipv4_gateway": "", + "ipv6_addresses": [], + "ipv6_allowed": [], + "ipv6_configuration_mode": "None", + "ipv6_gateway": "", + "locking_mode": "network_default", + "metrics": "OpaqueRef:d74d5f20-f0ab-ee36-9a74-496ffb994232", + "network": "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "uuid": "07b70134-9396-94fc-5105-179b430ce4f8" + } + }, + "VM": { + "OpaqueRef:08632af0-473e-5106-f400-7910229e49be": { + "HVM_boot_params": { + "order": "cdn" + }, + "HVM_boot_policy": "BIOS order", + "HVM_shadow_multiplier": 1.0, + "PCI_bus": "", + "PV_args": "", + "PV_bootloader": "", + "PV_bootloader_args": "", + "PV_kernel": "", + "PV_legacy_args": "", + "PV_ramdisk": "", + "VBDs": [ + "OpaqueRef:510e214e-f0ba-3bc9-7834-a4f4d3fa33ef", + "OpaqueRef:9bd6decd-2e55-b55e-387d-c40aa67ff151", + "OpaqueRef:6bc2c353-f132-926d-6e9b-e4d1d55a3760" + ], + "VCPUs_at_startup": "1", + "VCPUs_max": "1", + "VCPUs_params": {}, + "VGPUs": [], + "VIFs": [ + "OpaqueRef:9754a0ed-e100-d224-6a70-a55a9c2cedf9" + ], + "VTPMs": [], + "actions_after_crash": "restart", + "actions_after_reboot": "restart", + "actions_after_shutdown": "destroy", + "affinity": "OpaqueRef:e87be804-57a1-532e-56ac-6c4910957be0", + "allowed_operations": [ + "changing_dynamic_range", + "migrate_send", + "pool_migrate", + "changing_VCPUs_live", + "suspend", + "hard_reboot", + "hard_shutdown", + "clean_reboot", + "clean_shutdown", + "pause", + "checkpoint", + "snapshot" + ], + "appliance": "OpaqueRef:NULL", + "attached_PCIs": [], + "bios_strings": { + "bios-vendor": "Xen", + "bios-version": "", + "hp-rombios": "", + "oem-1": "Xen", + "oem-2": "MS_VM_CERT/SHA1/bdbeb6e0a816d43fa6d3fe8aaef04c2bad9d3e3d", + "system-manufacturer": "Xen", + "system-product-name": "HVM domU", + "system-serial-number": "", + "system-version": "" + }, + "blobs": {}, + "blocked_operations": {}, + "children": [], + "consoles": [ + "OpaqueRef:2a24e023-a856-de30-aea3-2024bacdc71f" + ], + "crash_dumps": [], + "current_operations": {}, + "domarch": "", + "domid": "140", + "generation_id": "", + "guest_metrics": "OpaqueRef:150d2dfa-b634-7965-92ab-31fc26382683", + "ha_always_run": false, + "ha_restart_priority": "", + "hardware_platform_version": "0", + "has_vendor_device": false, + "is_a_snapshot": false, + "is_a_template": false, + "is_control_domain": false, + "is_default_template": false, + "is_snapshot_from_vmpp": false, + "is_vmss_snapshot": false, + "last_boot_CPU_flags": { + "features": "17cbfbff-f7fa3223-2d93fbff-00000023-00000001-000007ab-00000000-00000000-00001000-0c000000", + "vendor": "GenuineIntel" + }, + "last_booted_record": "", + "memory_dynamic_max": "1073741824", + "memory_dynamic_min": "1073741824", + "memory_overhead": "11534336", + "memory_static_max": "1073741824", + "memory_static_min": "1073741824", + "memory_target": "1073741824", + "metrics": "OpaqueRef:b56b460b-6476-304d-b143-ce543ffab828", + "name_description": "Created by Ansible", + "name_label": "ansible-test-vm-2", + "order": "0", + "other_config": { + "base_template_name": "CentOS 7", + "folder": "/Ansible/Test", + "import_task": "OpaqueRef:cf1402d3-b6c1-d908-fe62-06502e3b311a", + "install-methods": "cdrom,nfs,http,ftp", + "instant": "true", + "linux_template": "true", + "mac_seed": "0ab46664-f519-5383-166e-e4ea485ede7d" + }, + "parent": "OpaqueRef:NULL", + "platform": { + "acpi": "1", + "apic": "true", + "cores-per-socket": "1", + "device_id": "0001", + "nx": "true", + "pae": "true", + "timeoffset": "0", + "vga": "std", + "videoram": "8", + "viridian": "false" + }, + "power_state": "Running", + "protection_policy": "OpaqueRef:NULL", + "recommendations": "", + "reference_label": "", + "requires_reboot": false, + "resident_on": "OpaqueRef:e87be804-57a1-532e-56ac-6c4910957be0", + "shutdown_delay": "0", + "snapshot_info": {}, + "snapshot_metadata": "", + "snapshot_of": "OpaqueRef:NULL", + "snapshot_schedule": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "start_delay": "0", + "suspend_SR": "OpaqueRef:NULL", + "suspend_VDI": "OpaqueRef:NULL", + "tags": [], + "transportable_snapshot_id": "", + "user_version": "1", + "uuid": "0a05d5ad-3e4b-f0dc-6101-8c56623958bc", + "version": "0", + "xenstore_data": { + "vm-data": "", + "vm-data/networks": "", + "vm-data/networks/0": "", + "vm-data/networks/0/gateway": "10.0.0.1", + "vm-data/networks/0/ip": "10.0.0.3", + "vm-data/networks/0/mac": "16:87:31:70:d6:31", + "vm-data/networks/0/name": "Host internal management network", + "vm-data/networks/0/netmask": "255.255.255.0", + "vm-data/networks/0/prefix": "24", + "vm-data/networks/0/type": "static" + } + } + }, + "VM_guest_metrics": { + "OpaqueRef:150d2dfa-b634-7965-92ab-31fc26382683": { + "PV_drivers_detected": true, + "PV_drivers_up_to_date": true, + "PV_drivers_version": { + "build": "90977", + "major": "6", + "micro": "0", + "minor": "5" + }, + "can_use_hotplug_vbd": "unspecified", + "can_use_hotplug_vif": "unspecified", + "disks": {}, + "last_updated": "20190113T19:36:26Z", + "live": true, + "memory": {}, + "networks": { + "0/ip": "169.254.0.2" + }, + "os_version": { + "distro": "centos", + "major": "7", + "minor": "2", + "name": "CentOS Linux release 7.2.1511 (Core)", + "uname": "3.10.0-327.22.2.el7.x86_64" + }, + "other": { + "feature-balloon": "1", + "feature-shutdown": "1", + "feature-suspend": "1", + "feature-vcpu-hotplug": "1", + "has-vendor-device": "0", + "platform-feature-multiprocessor-suspend": "1" + }, + "other_config": {}, + "uuid": "5c9d1be5-7eee-88f2-46c3-df1d44f9cdb5" + } + }, + "VM_metrics": { + "OpaqueRef:b56b460b-6476-304d-b143-ce543ffab828": { + "VCPUs_CPU": {}, + "VCPUs_flags": {}, + "VCPUs_number": "1", + "VCPUs_params": {}, + "VCPUs_utilisation": {}, + "hvm": true, + "install_time": "20190113T19:32:46Z", + "last_updated": "19700101T00:00:00Z", + "memory_actual": "1073729536", + "nested_virt": false, + "nomigrate": false, + "other_config": {}, + "start_time": "20190113T19:35:15Z", + "state": [], + "uuid": "876dd44c-aad1-97bf-9ee5-4cd58eac7163" + } + }, + "host": { + "OpaqueRef:e87be804-57a1-532e-56ac-6c4910957be0": { + "API_version_major": "2", + "API_version_minor": "7", + "API_version_vendor": "XenSource", + "API_version_vendor_implementation": {}, + "PBDs": [], + "PCIs": [], + "PGPUs": [], + "PIFs": [], + "address": "10.0.0.1", + "allowed_operations": [ + "vm_migrate", + "provision", + "vm_resume", + "evacuate", + "vm_start" + ], + "bios_strings": {}, + "blobs": {}, + "capabilities": [ + "xen-3.0-x86_64", + "xen-3.0-x86_32p", + "hvm-3.0-x86_32", + "hvm-3.0-x86_32p", + "hvm-3.0-x86_64", + "" + ], + "chipset_info": { + "iommu": "true" + }, + "control_domain": "OpaqueRef:ffcc92a1-8fde-df6f-a501-44b37811286b", + "cpu_configuration": {}, + "cpu_info": { + "cpu_count": "40", + "family": "6", + "features": "7ffefbff-bfebfbff-00000021-2c100800", + "features_hvm": "17cbfbff-f7fa3223-2d93fbff-00000023-00000001-000007ab-00000000-00000000-00001000-0c000000", + "features_pv": "17c9cbf5-f6f83203-2191cbf5-00000023-00000001-00000329-00000000-00000000-00001000-0c000000", + "flags": "fpu de tsc msr pae mce cx8 apic sep mca cmov pat clflush acpi mmx fxsr sse sse2 ht syscall nx lm constant_tsc arch_perfmon rep_good nopl nonstop_tsc eagerfpu pni pclmulqdq monitor est ssse3 fma cx16 sse4_1 sse4_2 movbe popcnt aes xsave avx f16c rdrand hypervisor lahf_lm abm ida arat epb pln pts dtherm fsgsbase bmi1 avx2 bmi2 erms xsaveopt cqm_llc cqm_occup_llc", + "model": "63", + "modelname": "Intel(R) Xeon(R) CPU E5-2660 v3 @ 2.60GHz", + "socket_count": "2", + "speed": "2597.070", + "stepping": "2", + "vendor": "GenuineIntel" + }, + "crash_dump_sr": "OpaqueRef:0b984cec-a36c-ce84-7b34-9f0088352d55", + "crashdumps": [], + "current_operations": {}, + "display": "enabled", + "edition": "free", + "enabled": true, + "external_auth_configuration": {}, + "external_auth_service_name": "", + "external_auth_type": "", + "features": [], + "guest_VCPUs_params": {}, + "ha_network_peers": [], + "ha_statefiles": [], + "host_CPUs": [ + "OpaqueRef:ec3ba9c4-9b57-236b-3eaa-b157affc1621", + "OpaqueRef:e6de7ab3-f4ad-f271-e51b-e3d8c041d3fb", + "OpaqueRef:e519ef88-bf41-86ac-16b3-c178cb4b78b1", + "OpaqueRef:e48f1bc1-98ba-89e5-ab69-821c625f7f82", + "OpaqueRef:e2659936-3de6-dbca-cc44-4af50960b2b7", + "OpaqueRef:d0da1e31-20ac-4aff-8897-e80df8200648", + "OpaqueRef:cec473ba-41a8-439d-b397-be0c60467b5d", + "OpaqueRef:ce88014d-b06c-c959-0624-04d79b791885", + "OpaqueRef:c656ca58-41fe-3689-d322-174aa5798beb", + "OpaqueRef:c0a21f14-8f46-19de-1cf4-530a34c4aa17", + "OpaqueRef:bf70c061-7b45-0497-7ef6-65a236e898e8", + "OpaqueRef:b7a2ba0f-f11b-3633-ad47-4f5f76a600a8", + "OpaqueRef:b4fef1fa-3aae-9790-f47e-6a17f645339c", + "OpaqueRef:b4594721-f8f4-4475-61c5-4efeec1733f1", + "OpaqueRef:9dcba36f-c29f-478f-f578-d1ea347410a6", + "OpaqueRef:987897e8-1184-917e-6a5f-e205d0c739e5", + "OpaqueRef:90f06d64-be18-7fdf-36ba-bbd696a26cf3", + "OpaqueRef:90150bc1-e604-4cd4-35ad-9cfa8e985de3", + "OpaqueRef:838f4ad4-8ad2-0d6c-a74e-26baa461de3d", + "OpaqueRef:736fb523-d347-e8c0-089b-c9811d3c1195", + "OpaqueRef:7137b479-87d4-9097-a684-e54cc4de5d09", + "OpaqueRef:6e08fa1d-7d7b-d9be-1574-ffe95bd515fd", + "OpaqueRef:6b9e6ecd-54e5-4248-5aea-ee5b99248818", + "OpaqueRef:65d56b24-3445-b444-5125-c91e6966fd29", + "OpaqueRef:60908eca-1e5c-c938-5b76-e8ff9d8899ab", + "OpaqueRef:46e96878-c076-2164-2373-6cdd108c2436", + "OpaqueRef:40ccdaf4-6008-2b83-92cb-ca197f73433f", + "OpaqueRef:3bc8133a-ccb2-6790-152f-b3f577517751", + "OpaqueRef:38c8edd8-0621-76de-53f6-86bef2a9e05c", + "OpaqueRef:342c1bab-a211-a0eb-79a5-780bd5ad1f23", + "OpaqueRef:1e20e6d0-5502-0dff-4f17-5d35eb833af1", + "OpaqueRef:176baafa-0e63-7000-f754-25e2a6b74959", + "OpaqueRef:16cab1a2-0111-b2af-6dfe-3724b79e6b6b", + "OpaqueRef:0f213647-8362-9c5e-e99b-0ebaefc609ce", + "OpaqueRef:0e019819-b41f-0bfb-d4ee-dd5484fea9b6", + "OpaqueRef:0d39212f-82ba-190c-b304-19b3fa491fff", + "OpaqueRef:087ce3ad-3b66-ae1e-3130-3ae640dcc638", + "OpaqueRef:0730f24c-87ed-8296-8f14-3036e5ad2357", + "OpaqueRef:04c27426-4895-39a7-9ade-ef33d3721c26", + "OpaqueRef:017b27bf-0270-19e7-049a-5a9b3bb54898" + ], + "hostname": "ansible-test-host-2", + "license_params": { + "address1": "", + "address2": "", + "city": "", + "company": "", + "country": "", + "enable_xha": "true", + "expiry": "20291231T23:00:00Z", + "grace": "no", + "license_type": "", + "name": "", + "platform_filter": "false", + "postalcode": "", + "productcode": "", + "regular_nag_dialog": "false", + "restrict_ad": "false", + "restrict_batch_hotfix_apply": "true", + "restrict_checkpoint": "false", + "restrict_cifs": "true", + "restrict_connection": "false", + "restrict_cpu_masking": "false", + "restrict_dmc": "false", + "restrict_dr": "false", + "restrict_email_alerting": "false", + "restrict_equalogic": "false", + "restrict_export_resource_data": "true", + "restrict_gpu": "false", + "restrict_guest_agent_auto_update": "true", + "restrict_guest_ip_setting": "false", + "restrict_health_check": "false", + "restrict_historical_performance": "false", + "restrict_hotfix_apply": "false", + "restrict_integrated_gpu_passthrough": "false", + "restrict_intellicache": "false", + "restrict_lab": "false", + "restrict_live_patching": "true", + "restrict_marathon": "false", + "restrict_nested_virt": "true", + "restrict_netapp": "false", + "restrict_pci_device_for_auto_update": "true", + "restrict_pool_attached_storage": "false", + "restrict_pooling": "false", + "restrict_pvs_proxy": "true", + "restrict_qos": "false", + "restrict_rbac": "false", + "restrict_read_caching": "true", + "restrict_set_vcpus_number_live": "true", + "restrict_ssl_legacy_switch": "false", + "restrict_stage": "false", + "restrict_storage_xen_motion": "false", + "restrict_storagelink": "false", + "restrict_storagelink_site_recovery": "false", + "restrict_vgpu": "true", + "restrict_vif_locking": "false", + "restrict_vlan": "false", + "restrict_vm_memory_introspection": "true", + "restrict_vmpr": "false", + "restrict_vmss": "false", + "restrict_vss": "false", + "restrict_vswitch_controller": "false", + "restrict_web_selfservice": "true", + "restrict_web_selfservice_manager": "true", + "restrict_wlb": "true", + "restrict_xcm": "true", + "restrict_xen_motion": "false", + "serialnumber": "", + "sku_marketing_name": "Citrix XenServer", + "sku_type": "free", + "sockets": "2", + "state": "", + "version": "" + }, + "license_server": { + "address": "localhost", + "port": "27000" + }, + "local_cache_sr": "OpaqueRef:0b984cec-a36c-ce84-7b34-9f0088352d55", + "logging": {}, + "memory_overhead": "4865126400", + "metrics": "OpaqueRef:f55653cb-92eb-8257-f2ee-7a2d1c2d6aef", + "name_description": "", + "name_label": "ansible-test-host-2", + "other_config": { + "agent_start_time": "1532019582.", + "boot_time": "1528986759.", + "iscsi_iqn": "iqn.2018-06.com.example:87b7637d", + "last_blob_sync_time": "1547394065.41", + "multipathhandle": "dmp", + "multipathing": "true" + }, + "patches": [ + "OpaqueRef:f5bd18b6-1423-893a-5d7f-7095338e6a2d", + "OpaqueRef:eecb0b95-87fb-a53e-651c-9741efd18bb6", + "OpaqueRef:e92c9ef3-2e51-1a36-d400-9e237982b782", + "OpaqueRef:cc98226c-2c08-799e-5f15-7761a398e4a0", + "OpaqueRef:c4f35e66-d064-55a7-6946-7f4b145275a6", + "OpaqueRef:c3794494-f894-6141-b811-f37a8fe60094", + "OpaqueRef:bcf61af7-63a9-e430-5b7c-a740ba470596", + "OpaqueRef:b58ac71e-797e-6f66-71ad-fe298c94fd10", + "OpaqueRef:a2ea18fd-5343-f8db-718d-f059c2a8cce0", + "OpaqueRef:929db459-6861-c588-158f-70f763331d6d", + "OpaqueRef:92962d94-2205-f6e1-12f9-b55a99fd824d", + "OpaqueRef:65dfb07a-f90d-dad9-9ab8-1cc2b1e79afb", + "OpaqueRef:537a87c4-3bf4-969f-f06a-2dd8d3a018a2", + "OpaqueRef:32dd1de3-c9c8-bcbb-27a0-83d4a930876d", + "OpaqueRef:30a8ccc8-74a9-b31f-0403-66b117e281b6", + "OpaqueRef:24545c44-ffd1-8a28-18c6-3d008bf4d63e", + "OpaqueRef:1fcef81b-7c44-a4db-f59a-c4a147da9c49", + "OpaqueRef:1e98a240-514b-1863-5518-c771d0ebf579", + "OpaqueRef:1632cab2-b268-6ce8-4f7b-ce7fd4bfa1eb" + ], + "power_on_config": {}, + "power_on_mode": "", + "resident_VMs": [], + "sched_policy": "credit", + "software_version": { + "build_number": "release/falcon/master/8", + "date": "2017-05-11", + "db_schema": "5.120", + "dbv": "2017.0517", + "hostname": "f7d02093adae", + "linux": "4.4.0+10", + "network_backend": "openvswitch", + "platform_name": "XCP", + "platform_version": "2.3.0", + "product_brand": "XenServer", + "product_version": "7.2.0", + "product_version_text": "7.2", + "product_version_text_short": "7.2", + "xapi": "1.9", + "xen": "4.7.5-2.12", + "xencenter_max": "2.7", + "xencenter_min": "2.7" + }, + "ssl_legacy": true, + "supported_bootloaders": [ + "pygrub", + "eliloader" + ], + "suspend_image_sr": "OpaqueRef:0b984cec-a36c-ce84-7b34-9f0088352d55", + "tags": [], + "updates": [ + "OpaqueRef:7b4b5da1-54af-d0c4-3fea-394b4257bffe", + "OpaqueRef:fbaabbfe-88d5-d89b-5b3f-d6374601ca71", + "OpaqueRef:507ee5fc-59d3-e635-21d5-98a5cace4bf2", + "OpaqueRef:6c9b814c-e1c2-b8be-198f-de358686b10a", + "OpaqueRef:a17e721d-faf4-6ad1-c617-dd4899279534", + "OpaqueRef:6ac77a0f-f079-8067-85cc-c9ae2f8dcca9", + "OpaqueRef:f61edc83-91d9-a161-113f-00c110196238", + "OpaqueRef:b71938bf-4c4f-eb17-7e78-588e71297a74", + "OpaqueRef:01befb95-412e-e9dd-5b5d-edd50df61cb1", + "OpaqueRef:a3f9481e-fe3d-1f00-235f-44d404f51128", + "OpaqueRef:0760c608-b02e-743a-18a1-fa8f205374d6", + "OpaqueRef:204558d7-dce0-2304-bdc5-80ec5fd7e3c3", + "OpaqueRef:9eccc765-9726-d220-96b1-2e85adf77ecc", + "OpaqueRef:91cfa47b-52f9-a4e3-4e78-52e3eb3e5141", + "OpaqueRef:3fffd7c7-f4d1-6b03-a5b8-d75211bb7b8f", + "OpaqueRef:7efce157-9b93-d116-f3f8-7eb0c6fb1a79", + "OpaqueRef:e2209ae9-5362-3a20-f691-9294144e49f2", + "OpaqueRef:1ced32ca-fec4-8b44-0e8f-753c97f2d93f", + "OpaqueRef:65b14ae7-f440-0c4d-4af9-c7946b90fd2f" + ], + "updates_requiring_reboot": [], + "uuid": "dff6702e-bcb6-4704-8dd4-952e8c883365", + "virtual_hardware_platform_versions": [ + "0", + "1", + "2" + ] + } + }, + "network": { + "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724": { + "MTU": "1500", + "PIFs": [], + "VIFs": [], + "allowed_operations": [], + "assigned_ips": { + "OpaqueRef:8171dad1-f902-ec00-7ba2-9f92d8aa75ab": "169.254.0.3", + "OpaqueRef:9754a0ed-e100-d224-6a70-a55a9c2cedf9": "169.254.0.2" + }, + "blobs": {}, + "bridge": "xenapi", + "current_operations": {}, + "default_locking_mode": "unlocked", + "managed": true, + "name_description": "Network on which guests will be assigned a private link-local IP address which can be used to talk XenAPI", + "name_label": "Host internal management network", + "other_config": { + "ip_begin": "169.254.0.1", + "ip_end": "169.254.255.254", + "is_guest_installer_network": "true", + "is_host_internal_management_network": "true", + "netmask": "255.255.0.0" + }, + "tags": [], + "uuid": "dbb96525-944f-0d1a-54ed-e65cb6d07450" + } + } +} diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-facts.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-facts.json new file mode 100644 index 00000000000000..5ed7df7f171fc0 --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-facts.json @@ -0,0 +1,75 @@ +{ + "cdrom": { + "type": "none" + }, + "customization_agent": "custom", + "disks": [ + { + "name": "ansible-test-vm-3-root", + "name_desc": "/", + "os_device": "xvda", + "size": 8589934592, + "sr": "Ansible Test Storage 1", + "sr_uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "vbd_userdevice": "0" + } + ], + "domid": "-1", + "folder": "", + "hardware": { + "memory_mb": 1024, + "num_cpu_cores_per_socket": 1, + "num_cpus": 1 + }, + "home_server": "", + "is_template": false, + "name": "ansible-test-vm-3", + "name_desc": "Created by Ansible", + "networks": [ + { + "gateway": "", + "gateway6": "", + "ip": "169.254.0.3", + "ip6": [], + "mac": "72:fb:c7:ac:b9:97", + "mtu": "1500", + "name": "Host internal management network", + "netmask": "", + "prefix": "", + "prefix6": "", + "vif_device": "0" + } + ], + "other_config": { + "auto_poweron": "true", + "base_template_name": "zatemplate", + "import_task": "OpaqueRef:9948fd82-6d79-8882-2f01-4edc8795e361", + "install-methods": "cdrom,nfs,http,ftp", + "install-repository": "http://mirror.centos.org/centos-6/6.2/os/x86_64/", + "instant": "true", + "last_shutdown_action": "Destroy", + "last_shutdown_initiator": "external", + "last_shutdown_reason": "halted", + "last_shutdown_time": "20140314T21:16:41Z", + "linux_template": "true", + "mac_seed": "06e27068-70c2-4c69-614b-7c54b5a4a781", + "rhel6": "true" + }, + "platform": { + "acpi": "true", + "apic": "true", + "cores-per-socket": "1", + "nx": "false", + "pae": "true", + "viridian": "true" + }, + "state": "poweredoff", + "uuid": "8f5bc97c-42fa-d619-aba4-d25eced735e0", + "xenstore_data": { + "vm-data": "", + "vm-data/networks": "", + "vm-data/networks/0": "", + "vm-data/networks/0/mac": "72:fb:c7:ac:b9:97", + "vm-data/networks/0/name": "Host internal management network" + } +} diff --git a/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-params.json b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-params.json new file mode 100644 index 00000000000000..02e224bf080520 --- /dev/null +++ b/test/units/module_utils/xenserver/fixtures/ansible-test-vm-3-params.json @@ -0,0 +1,420 @@ +{ + "SR": { + "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f": { + "PBDs": [], + "VDIs": [], + "allowed_operations": [ + "unplug", + "plug", + "pbd_create", + "update", + "pbd_destroy", + "vdi_resize", + "vdi_clone", + "scan", + "vdi_snapshot", + "vdi_mirror", + "vdi_create", + "vdi_destroy" + ], + "blobs": {}, + "clustered": false, + "content_type": "", + "current_operations": {}, + "introduced_by": "OpaqueRef:NULL", + "is_tools_sr": false, + "local_cache_enabled": false, + "name_description": "", + "name_label": "Ansible Test Storage 1", + "other_config": { + "auto-scan": "false" + }, + "physical_size": "2521133219840", + "physical_utilisation": "1551485632512", + "shared": true, + "sm_config": { + "allocation": "thick", + "devserial": "scsi-3600a098038302d353624495242443848", + "multipathable": "true", + "use_vhd": "true" + }, + "tags": [], + "type": "lvmohba", + "uuid": "767b30e4-f8db-a83d-8ba7-f5e6e732e06f", + "virtual_allocation": "1556925644800" + } + }, + "VBD": { + "OpaqueRef:024b722e-8d0f-65e6-359e-f301a009b683": { + "VDI": "OpaqueRef:NULL", + "VM": "OpaqueRef:957f576a-2347-1789-80db-4beb50466bc2", + "allowed_operations": [ + "attach", + "insert" + ], + "bootable": false, + "current_operations": {}, + "currently_attached": false, + "device": "", + "empty": true, + "metrics": "OpaqueRef:81509584-b22f-bc71-3c4e-e6c3bdca71f0", + "mode": "RO", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "CD", + "unpluggable": true, + "userdevice": "3", + "uuid": "38d850d0-c402-490e-6b97-1d23558c4e0e" + }, + "OpaqueRef:235f4f04-1dc9-9fa5-c229-a1df187ba48c": { + "VDI": "OpaqueRef:4d3e9fc7-ae61-b312-e0a8-b53bee06282e", + "VM": "OpaqueRef:957f576a-2347-1789-80db-4beb50466bc2", + "allowed_operations": [ + "attach" + ], + "bootable": true, + "current_operations": {}, + "currently_attached": false, + "device": "xvda", + "empty": false, + "metrics": "OpaqueRef:529f6071-5627-28c5-1f41-ee8c0733f1da", + "mode": "RW", + "other_config": { + "owner": "" + }, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "storage_lock": false, + "type": "Disk", + "unpluggable": false, + "userdevice": "0", + "uuid": "3fd7d35c-cb9d-f0c4-726b-e188ef0dc446" + } + }, + "VDI": { + "OpaqueRef:4d3e9fc7-ae61-b312-e0a8-b53bee06282e": { + "SR": "OpaqueRef:f746e964-e0fe-c36d-d60b-6897cfde583f", + "VBDs": [ + "OpaqueRef:235f4f04-1dc9-9fa5-c229-a1df187ba48c" + ], + "allow_caching": false, + "allowed_operations": [ + "forget", + "generate_config", + "update", + "resize", + "destroy", + "clone", + "copy", + "snapshot" + ], + "crash_dumps": [], + "current_operations": {}, + "is_a_snapshot": false, + "is_tools_iso": false, + "location": "bdd0baeb-5447-4963-9e71-a5ff6e85fa59", + "managed": true, + "metadata_latest": false, + "metadata_of_pool": "", + "missing": false, + "name_description": "/", + "name_label": "ansible-test-vm-3-root", + "on_boot": "persist", + "other_config": { + "content_id": "cd8e8b2b-f158-c519-02f0-81d130fe83c5" + }, + "parent": "OpaqueRef:NULL", + "physical_utilisation": "8615100416", + "read_only": false, + "sharable": false, + "sm_config": { + "vdi_type": "vhd" + }, + "snapshot_of": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "storage_lock": false, + "tags": [], + "type": "system", + "uuid": "bdd0baeb-5447-4963-9e71-a5ff6e85fa59", + "virtual_size": "8589934592", + "xenstore_data": {} + } + }, + "VIF": { + "OpaqueRef:8171dad1-f902-ec00-7ba2-9f92d8aa75ab": { + "MAC": "72:fb:c7:ac:b9:97", + "MAC_autogenerated": true, + "MTU": "1500", + "VM": "OpaqueRef:957f576a-2347-1789-80db-4beb50466bc2", + "allowed_operations": [ + "attach" + ], + "current_operations": {}, + "currently_attached": false, + "device": "0", + "ipv4_addresses": [], + "ipv4_allowed": [], + "ipv4_configuration_mode": "None", + "ipv4_gateway": "", + "ipv6_addresses": [], + "ipv6_allowed": [], + "ipv6_configuration_mode": "None", + "ipv6_gateway": "", + "locking_mode": "network_default", + "metrics": "OpaqueRef:e5b53fb1-3e99-4bf5-6b00-95fdba1f2610", + "network": "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724", + "other_config": {}, + "qos_algorithm_params": {}, + "qos_algorithm_type": "", + "qos_supported_algorithms": [], + "runtime_properties": {}, + "status_code": "0", + "status_detail": "", + "uuid": "94bd4913-4940-437c-a1c3-50f7eb354c55" + } + }, + "VM": { + "OpaqueRef:957f576a-2347-1789-80db-4beb50466bc2": { + "HVM_boot_params": { + "order": "" + }, + "HVM_boot_policy": "", + "HVM_shadow_multiplier": 1.0, + "PCI_bus": "", + "PV_args": "graphical utf8", + "PV_bootloader": "pygrub", + "PV_bootloader_args": "", + "PV_kernel": "", + "PV_legacy_args": "", + "PV_ramdisk": "", + "VBDs": [ + "OpaqueRef:235f4f04-1dc9-9fa5-c229-a1df187ba48c", + "OpaqueRef:024b722e-8d0f-65e6-359e-f301a009b683" + ], + "VCPUs_at_startup": "1", + "VCPUs_max": "1", + "VCPUs_params": {}, + "VGPUs": [], + "VIFs": [ + "OpaqueRef:8171dad1-f902-ec00-7ba2-9f92d8aa75ab" + ], + "VTPMs": [], + "actions_after_crash": "restart", + "actions_after_reboot": "restart", + "actions_after_shutdown": "destroy", + "affinity": "OpaqueRef:NULL", + "allowed_operations": [ + "changing_dynamic_range", + "changing_shadow_memory", + "changing_static_range", + "make_into_template", + "migrate_send", + "destroy", + "export", + "start_on", + "start", + "clone", + "copy", + "snapshot" + ], + "appliance": "OpaqueRef:NULL", + "attached_PCIs": [], + "bios_strings": { + "bios-vendor": "Xen", + "bios-version": "", + "hp-rombios": "", + "oem-1": "Xen", + "oem-2": "MS_VM_CERT/SHA1/bdbeb6e0a816d43fa6d3fe8aaef04c2bad9d3e3d", + "system-manufacturer": "Xen", + "system-product-name": "HVM domU", + "system-serial-number": "", + "system-version": "" + }, + "blobs": {}, + "blocked_operations": {}, + "children": [], + "consoles": [], + "crash_dumps": [], + "current_operations": {}, + "domarch": "", + "domid": "-1", + "generation_id": "", + "guest_metrics": "OpaqueRef:6a8acd85-4cab-4e52-27d5-5f4a51c1bf69", + "ha_always_run": false, + "ha_restart_priority": "", + "hardware_platform_version": "0", + "has_vendor_device": false, + "is_a_snapshot": false, + "is_a_template": false, + "is_control_domain": false, + "is_default_template": false, + "is_snapshot_from_vmpp": false, + "is_vmss_snapshot": false, + "last_boot_CPU_flags": { + "features": "17c9cbf5-f6f83203-2191cbf5-00000023-00000001-00000329-00000000-00000000-00001000-0c000000", + "vendor": "GenuineIntel" + }, + "last_booted_record": "", + "memory_dynamic_max": "1073741824", + "memory_dynamic_min": "1073741824", + "memory_overhead": "10485760", + "memory_static_max": "1073741824", + "memory_static_min": "536870912", + "memory_target": "0", + "metrics": "OpaqueRef:87fc5829-478b-1dcd-989f-50e8ba58a87d", + "name_description": "Created by Ansible", + "name_label": "ansible-test-vm-3", + "order": "0", + "other_config": { + "auto_poweron": "true", + "base_template_name": "zatemplate", + "import_task": "OpaqueRef:9948fd82-6d79-8882-2f01-4edc8795e361", + "install-methods": "cdrom,nfs,http,ftp", + "install-repository": "http://mirror.centos.org/centos-6/6.2/os/x86_64/", + "instant": "true", + "last_shutdown_action": "Destroy", + "last_shutdown_initiator": "external", + "last_shutdown_reason": "halted", + "last_shutdown_time": "20140314T21:16:41Z", + "linux_template": "true", + "mac_seed": "06e27068-70c2-4c69-614b-7c54b5a4a781", + "rhel6": "true" + }, + "parent": "OpaqueRef:NULL", + "platform": { + "acpi": "true", + "apic": "true", + "cores-per-socket": "1", + "nx": "false", + "pae": "true", + "viridian": "true" + }, + "power_state": "Halted", + "protection_policy": "OpaqueRef:NULL", + "recommendations": "", + "reference_label": "", + "requires_reboot": false, + "resident_on": "OpaqueRef:NULL", + "shutdown_delay": "0", + "snapshot_info": {}, + "snapshot_metadata": "", + "snapshot_of": "OpaqueRef:NULL", + "snapshot_schedule": "OpaqueRef:NULL", + "snapshot_time": "19700101T00:00:00Z", + "snapshots": [], + "start_delay": "0", + "suspend_SR": "OpaqueRef:NULL", + "suspend_VDI": "OpaqueRef:NULL", + "tags": [ + "web-frontend" + ], + "transportable_snapshot_id": "", + "user_version": "1", + "uuid": "8f5bc97c-42fa-d619-aba4-d25eced735e0", + "version": "0", + "xenstore_data": { + "vm-data": "", + "vm-data/networks": "", + "vm-data/networks/0": "", + "vm-data/networks/0/mac": "72:fb:c7:ac:b9:97", + "vm-data/networks/0/name": "Host internal management network" + } + } + }, + "VM_guest_metrics": { + "OpaqueRef:6a8acd85-4cab-4e52-27d5-5f4a51c1bf69": { + "PV_drivers_detected": true, + "PV_drivers_up_to_date": true, + "PV_drivers_version": { + "build": "46676", + "major": "5", + "micro": "100", + "minor": "6" + }, + "can_use_hotplug_vbd": "unspecified", + "can_use_hotplug_vif": "unspecified", + "disks": {}, + "last_updated": "20190113T19:36:07Z", + "live": true, + "memory": {}, + "networks": { + "0/ip": "169.254.0.3" + }, + "os_version": { + "distro": "centos", + "major": "6", + "minor": "10", + "name": "CentOS release 6.10 (Final)", + "uname": "2.6.32-754.6.3.el6.x86_64" + }, + "other": { + "feature-balloon": "1", + "has-vendor-device": "0", + "platform-feature-multiprocessor-suspend": "1" + }, + "other_config": {}, + "uuid": "3928a6a4-1acd-c134-ed35-eb0ccfaed65c" + } + }, + "VM_metrics": { + "OpaqueRef:87fc5829-478b-1dcd-989f-50e8ba58a87d": { + "VCPUs_CPU": {}, + "VCPUs_flags": {}, + "VCPUs_number": "0", + "VCPUs_params": {}, + "VCPUs_utilisation": { + "0": 0.0 + }, + "hvm": false, + "install_time": "20190113T19:35:05Z", + "last_updated": "19700101T00:00:00Z", + "memory_actual": "1073741824", + "nested_virt": false, + "nomigrate": false, + "other_config": {}, + "start_time": "19700101T00:00:00Z", + "state": [], + "uuid": "6cb05fe9-b83e-34c8-29e0-3b793e1da661" + } + }, + "host": {}, + "network": { + "OpaqueRef:8a404c5e-5673-ab69-5d6f-5a35a33b8724": { + "MTU": "1500", + "PIFs": [], + "VIFs": [], + "allowed_operations": [], + "assigned_ips": { + "OpaqueRef:8171dad1-f902-ec00-7ba2-9f92d8aa75ab": "169.254.0.3", + "OpaqueRef:9754a0ed-e100-d224-6a70-a55a9c2cedf9": "169.254.0.2" + }, + "blobs": {}, + "bridge": "xenapi", + "current_operations": {}, + "default_locking_mode": "unlocked", + "managed": true, + "name_description": "Network on which guests will be assigned a private link-local IP address which can be used to talk XenAPI", + "name_label": "Host internal management network", + "other_config": { + "ip_begin": "169.254.0.1", + "ip_end": "169.254.255.254", + "is_guest_installer_network": "true", + "is_host_internal_management_network": "true", + "netmask": "255.255.0.0" + }, + "tags": [], + "uuid": "dbb96525-944f-0d1a-54ed-e65cb6d07450" + } + } +} diff --git a/test/units/module_utils/xenserver/test_gather_vm_params_and_facts.py b/test/units/module_utils/xenserver/test_gather_vm_params_and_facts.py new file mode 100644 index 00000000000000..94b8325d6134a1 --- /dev/null +++ b/test/units/module_utils/xenserver/test_gather_vm_params_and_facts.py @@ -0,0 +1,75 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from .common import testcase_bad_xenapi_refs + + +testcase_gather_vm_params_and_facts = { + "params": [ + ["ansible-test-vm-1-params.json", "ansible-test-vm-1-facts.json"], + ["ansible-test-vm-2-params.json", "ansible-test-vm-2-facts.json"], + ["ansible-test-vm-3-params.json", "ansible-test-vm-3-facts.json"], + ], + "ids": [ + "ansible-test-vm-1", + "ansible-test-vm-2", + "ansible-test-vm-3", + ], +} + + +@pytest.mark.parametrize('vm_ref', testcase_bad_xenapi_refs['params'], ids=testcase_bad_xenapi_refs['ids']) +def test_gather_vm_params_bad_vm_ref(fake_ansible_module, xenserver, vm_ref): + """Tests return of empty dict on bad vm_ref.""" + assert xenserver.gather_vm_params(fake_ansible_module, vm_ref) == {} + + +def test_gather_vm_facts_no_vm_params(fake_ansible_module, xenserver): + """Tests return of empty facts dict when vm_params is not available""" + assert xenserver.gather_vm_facts(fake_ansible_module, None) == {} + assert xenserver.gather_vm_facts(fake_ansible_module, {}) == {} + + +@pytest.mark.parametrize('fixture_data_from_file', + testcase_gather_vm_params_and_facts['params'], + ids=testcase_gather_vm_params_and_facts['ids'], + indirect=True) +def test_gather_vm_params_and_facts(mocker, fake_ansible_module, XenAPI, xenserver, fixture_data_from_file): + """Tests proper parsing of VM parameters and facts.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + if "params" in list(fixture_data_from_file.keys())[0]: + params_file = list(fixture_data_from_file.keys())[0] + facts_file = list(fixture_data_from_file.keys())[1] + else: + params_file = list(fixture_data_from_file.keys())[1] + facts_file = list(fixture_data_from_file.keys())[0] + + mocked_returns = { + "VM.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VM'][obj_ref], + "VM_metrics.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VM_metrics'][obj_ref], + "VM_guest_metrics.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VM_guest_metrics'][obj_ref], + "VBD.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VBD'][obj_ref], + "VDI.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VDI'][obj_ref], + "SR.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['SR'][obj_ref], + "VIF.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['VIF'][obj_ref], + "network.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['network'][obj_ref], + "host.get_record.side_effect": lambda obj_ref: fixture_data_from_file[params_file]['host'][obj_ref], + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('ansible.module_utils.xenserver.get_xenserver_version', return_value=['7', '2']) + + vm_ref = list(fixture_data_from_file[params_file]['VM'].keys())[0] + + assert xenserver.gather_vm_facts(fake_ansible_module, xenserver.gather_vm_params(fake_ansible_module, vm_ref)) == fixture_data_from_file[facts_file] diff --git a/test/units/module_utils/xenserver/test_get_object_ref.py b/test/units/module_utils/xenserver/test_get_object_ref.py new file mode 100644 index 00000000000000..59546a912491fe --- /dev/null +++ b/test/units/module_utils/xenserver/test_get_object_ref.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from .common import fake_xenapi_ref + + +def test_get_object_ref_xenapi_failure(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests catching of XenAPI failures.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi_request', side_effect=XenAPI.Failure('Fake XAPI method call error!')) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, "name") + + assert exc_info.value.kwargs['msg'] == "XAPI ERROR: Fake XAPI method call error!" + + +def test_get_object_ref_bad_uuid_and_name(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests failure on bad object uuid and/or name.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi_request') + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, None, msg_prefix="Test: ") + + mocked_xenapi.xenapi_request.assert_not_called() + assert exc_info.value.kwargs['msg'] == "Test: no valid name or UUID supplied for VM!" + + +def test_get_object_ref_uuid_not_found(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests when object is not found by uuid.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi_request', side_effect=XenAPI.Failure('Fake XAPI not found error!')) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, "name", uuid="fake-uuid", msg_prefix="Test: ") + + assert exc_info.value.kwargs['msg'] == "Test: VM with UUID 'fake-uuid' not found!" + assert xenserver.get_object_ref(fake_ansible_module, "name", uuid="fake-uuid", fail=False, msg_prefix="Test: ") is None + + +def test_get_object_ref_name_not_found(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests when object is not found by name.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi_request', return_value=[]) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, "name", msg_prefix="Test: ") + + assert exc_info.value.kwargs['msg'] == "Test: VM with name 'name' not found!" + assert xenserver.get_object_ref(fake_ansible_module, "name", fail=False, msg_prefix="Test: ") is None + + +def test_get_object_ref_name_multiple_found(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests when multiple objects are found by name.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi_request', return_value=[fake_xenapi_ref('VM'), fake_xenapi_ref('VM')]) + + error_msg = "Test: multiple VMs with name 'name' found! Please use UUID." + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, "name", msg_prefix="Test: ") + + assert exc_info.value.kwargs['msg'] == error_msg + + with pytest.raises(FailJsonException) as exc_info: + xenserver.get_object_ref(fake_ansible_module, "name", fail=False, msg_prefix="Test: ") + + assert exc_info.value.kwargs['msg'] == error_msg diff --git a/test/units/module_utils/xenserver/test_misc.py b/test/units/module_utils/xenserver/test_misc.py new file mode 100644 index 00000000000000..2ac7a5f41e1ed1 --- /dev/null +++ b/test/units/module_utils/xenserver/test_misc.py @@ -0,0 +1,22 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException + + +def test_xapi_to_module_vm_power_state_bad_power_state(xenserver): + """Tests that None is returned on bad power state.""" + assert xenserver.xapi_to_module_vm_power_state("bad") is None + + +def test_module_to_xapi_vm_power_state_bad_power_state(xenserver): + """Tests that None is returned on bad power state.""" + assert xenserver.module_to_xapi_vm_power_state("bad") is None diff --git a/test/units/module_utils/xenserver/test_netaddr_functions.py b/test/units/module_utils/xenserver/test_netaddr_functions.py new file mode 100644 index 00000000000000..8218ea68de8b4f --- /dev/null +++ b/test/units/module_utils/xenserver/test_netaddr_functions.py @@ -0,0 +1,183 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException + + +testcase_is_valid_mac_addr = [ + ('A4-23-8D-F8-C9-E5', True), + ('35:71:F4:11:0B:D8', True), + ('b3-bd-20-59-0c-cf', True), + ('32:61:ca:65:f1:f4', True), + ('asdf', False), + ('A4-23-8D-G8-C9-E5', False), + ('A4-3-8D-F8-C9-E5', False), + ('A4-23-88D-F8-C9-E5', False), + ('A4-23-8D-F8-C9_E5', False), + ('A4-23--8D-F8-C9-E5', False), +] + +testcase_is_valid_ip_addr = [ + ('0.0.0.0', True), + ('10.0.0.1', True), + ('192.168.0.1', True), + ('255.255.255.255', True), + ('asdf', False), + ('a.b.c.d', False), + ('345.345.345.345', False), + ('-10.0.0.1', False), +] + +testcase_is_valid_ip_netmask = [ + ('240.0.0.0', True), + ('255.224.0.0', True), + ('255.255.248.0', True), + ('255.255.255.255', True), + ('asdf', False), + ('a.b.c.d', False), + ('192.168.0.1', False), + ('255.0.248.0', False), +] + +testcase_is_valid_ip_prefix = [ + ('0', True), + ('16', True), + ('24', True), + ('32', True), + ('asdf', False), + ('-10', False), + ('60', False), + ('60s', False), +] + +testcase_ip_prefix_to_netmask = { + "params": [ + ('0', '0.0.0.0'), + ('8', '255.0.0.0'), + ('11', '255.224.0.0'), + ('16', '255.255.0.0'), + ('21', '255.255.248.0'), + ('24', '255.255.255.0'), + ('26', '255.255.255.192'), + ('32', '255.255.255.255'), + ('a', ''), + ('60', ''), + ], + "ids": [ + '0', + '8', + '11', + '16', + '21', + '24', + '26', + '32', + 'a', + '60', + ], +} + +testcase_ip_netmask_to_prefix = { + "params": [ + ('0.0.0.0', '0'), + ('255.0.0.0', '8'), + ('255.224.0.0', '11'), + ('255.255.0.0', '16'), + ('255.255.248.0', '21'), + ('255.255.255.0', '24'), + ('255.255.255.192', '26'), + ('255.255.255.255', '32'), + ('a', ''), + ('60', ''), + ], + "ids": [ + '0.0.0.0', + '255.0.0.0', + '255.224.0.0', + '255.255.0.0', + '255.255.248.0', + '255.255.255.0', + '255.255.255.192', + '255.255.255.255', + 'a', + '60', + ], +} + +testcase_is_valid_ip6_addr = [ + ('::1', True), + ('2001:DB8:0:0:8:800:200C:417A', True), + ('2001:DB8::8:800:200C:417A', True), + ('FF01::101', True), + ('asdf', False), + ('2001:DB8:0:0:8:800:200C:417A:221', False), + ('FF01::101::2', False), + ('2001:db8:85a3::8a2e:370k:7334', False), +] + +testcase_is_valid_ip6_prefix = [ + ('0', True), + ('56', True), + ('78', True), + ('128', True), + ('asdf', False), + ('-10', False), + ('345', False), + ('60s', False), +] + + +@pytest.mark.parametrize('mac_addr, result', testcase_is_valid_mac_addr) +def test_is_valid_mac_addr(xenserver, mac_addr, result): + """Tests against examples of valid and invalid mac addresses.""" + assert xenserver.is_valid_mac_addr(mac_addr) is result + + +@pytest.mark.parametrize('ip_addr, result', testcase_is_valid_ip_addr) +def test_is_valid_ip_addr(xenserver, ip_addr, result): + """Tests against examples of valid and invalid ip addresses.""" + assert xenserver.is_valid_ip_addr(ip_addr) is result + + +@pytest.mark.parametrize('ip_netmask, result', testcase_is_valid_ip_netmask) +def test_is_valid_ip_netmask(xenserver, ip_netmask, result): + """Tests against examples of valid and invalid ip netmasks.""" + assert xenserver.is_valid_ip_netmask(ip_netmask) is result + + +@pytest.mark.parametrize('ip_prefix, result', testcase_is_valid_ip_prefix) +def test_is_valid_ip_prefix(xenserver, ip_prefix, result): + """Tests against examples of valid and invalid ip prefixes.""" + assert xenserver.is_valid_ip_prefix(ip_prefix) is result + + +@pytest.mark.parametrize('ip_prefix, ip_netmask', testcase_ip_prefix_to_netmask['params'], ids=testcase_ip_prefix_to_netmask['ids']) +def test_ip_prefix_to_netmask(xenserver, ip_prefix, ip_netmask): + """Tests ip prefix to netmask conversion.""" + assert xenserver.ip_prefix_to_netmask(ip_prefix) == ip_netmask + + +@pytest.mark.parametrize('ip_netmask, ip_prefix', testcase_ip_netmask_to_prefix['params'], ids=testcase_ip_netmask_to_prefix['ids']) +def test_ip_netmask_to_prefix(xenserver, ip_netmask, ip_prefix): + """Tests ip netmask to prefix conversion.""" + assert xenserver.ip_netmask_to_prefix(ip_netmask) == ip_prefix + + +@pytest.mark.parametrize('ip6_addr, result', testcase_is_valid_ip6_addr) +def test_is_valid_ip6_addr(xenserver, ip6_addr, result): + """Tests against examples of valid and invalid ip6 addresses.""" + assert xenserver.is_valid_ip6_addr(ip6_addr) is result + + +@pytest.mark.parametrize('ip6_prefix, result', testcase_is_valid_ip6_prefix) +def test_is_valid_ip6_prefix(xenserver, ip6_prefix, result): + """Tests against examples of valid and invalid ip6 prefixes.""" + assert xenserver.is_valid_ip6_prefix(ip6_prefix) is result diff --git a/test/units/module_utils/xenserver/test_set_vm_power_state.py b/test/units/module_utils/xenserver/test_set_vm_power_state.py new file mode 100644 index 00000000000000..bbe2180ff19ab0 --- /dev/null +++ b/test/units/module_utils/xenserver/test_set_vm_power_state.py @@ -0,0 +1,413 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from .common import fake_xenapi_ref, testcase_bad_xenapi_refs + + +testcase_set_vm_power_state_bad_transitions = { + "params": [ + ('restarted', 'Halted', "Cannot restart VM in state 'poweredoff'!"), + ('restarted', 'Suspended', "Cannot restart VM in state 'suspended'!"), + ('suspended', 'Halted', "Cannot suspend VM in state 'poweredoff'!"), + ('suspended', 'Paused', "Cannot suspend VM in state 'paused'!"), + ('shutdownguest', 'Halted', "Cannot shutdown guest when VM is in state 'poweredoff'."), + ('shutdownguest', 'Suspended', "Cannot shutdown guest when VM is in state 'suspended'."), + ('shutdownguest', 'Paused', "Cannot shutdown guest when VM is in state 'paused'."), + ('rebootguest', 'Halted', "Cannot reboot guest when VM is in state 'poweredoff'."), + ('rebootguest', 'Suspended', "Cannot reboot guest when VM is in state 'suspended'."), + ('rebootguest', 'Paused', "Cannot reboot guest when VM is in state 'paused'."), + ], + "ids": [ + "poweredoff->restarted", + "suspended->restarted", + "poweredoff->suspended", + "paused->suspended", + "poweredoff->shutdownguest", + "suspended->shutdownguest", + "paused->shutdownguest", + "poweredoff->rebootguest", + "suspended->rebootguest", + "paused->rebootguest", + ], +} + +testcase_set_vm_power_state_task_timeout = { + "params": [ + ('shutdownguest', "Guest shutdown task failed: 'timeout'!"), + ('rebootguest', "Guest reboot task failed: 'timeout'!"), + ], + "ids": [ + "shutdownguest-timeout", + "rebootguest-timeout", + ], +} + +testcase_set_vm_power_state_no_transitions = { + "params": [ + ('poweredon', "Running"), + ('Poweredon', "Running"), + ('powered-on', "Running"), + ('Powered_on', "Running"), + ('poweredoff', "Halted"), + ('Poweredoff', "Halted"), + ('powered-off', "Halted"), + ('powered_off', "Halted"), + ('suspended', "Suspended"), + ('Suspended', "Suspended"), + ], + "ids": [ + "poweredon", + "poweredon-cap", + "poweredon-dash", + "poweredon-under", + "poweredoff", + "poweredoff-cap", + "poweredoff-dash", + "poweredoff-under", + "suspended", + "suspended-cap", + ], +} + +testcase_set_vm_power_state_transitions = { + "params": [ + ('poweredon', 'Halted', 'running', 'VM.start'), + ('Poweredon', 'Halted', 'running', 'VM.start'), + ('powered-on', 'Halted', 'running', 'VM.start'), + ('Powered_on', 'Halted', 'running', 'VM.start'), + ('poweredon', 'Suspended', 'running', 'VM.resume'), + ('Poweredon', 'Suspended', 'running', 'VM.resume'), + ('powered-on', 'Suspended', 'running', 'VM.resume'), + ('Powered_on', 'Suspended', 'running', 'VM.resume'), + ('poweredon', 'Paused', 'running', 'VM.unpause'), + ('Poweredon', 'Paused', 'running', 'VM.unpause'), + ('powered-on', 'Paused', 'running', 'VM.unpause'), + ('Powered_on', 'Paused', 'running', 'VM.unpause'), + ('poweredoff', 'Running', 'halted', 'VM.hard_shutdown'), + ('Poweredoff', 'Running', 'halted', 'VM.hard_shutdown'), + ('powered-off', 'Running', 'halted', 'VM.hard_shutdown'), + ('powered_off', 'Running', 'halted', 'VM.hard_shutdown'), + ('poweredoff', 'Suspended', 'halted', 'VM.hard_shutdown'), + ('Poweredoff', 'Suspended', 'halted', 'VM.hard_shutdown'), + ('powered-off', 'Suspended', 'halted', 'VM.hard_shutdown'), + ('powered_off', 'Suspended', 'halted', 'VM.hard_shutdown'), + ('poweredoff', 'Paused', 'halted', 'VM.hard_shutdown'), + ('Poweredoff', 'Paused', 'halted', 'VM.hard_shutdown'), + ('powered-off', 'Paused', 'halted', 'VM.hard_shutdown'), + ('powered_off', 'Paused', 'halted', 'VM.hard_shutdown'), + ('restarted', 'Running', 'running', 'VM.hard_reboot'), + ('Restarted', 'Running', 'running', 'VM.hard_reboot'), + ('restarted', 'Paused', 'running', 'VM.hard_reboot'), + ('Restarted', 'Paused', 'running', 'VM.hard_reboot'), + ('suspended', 'Running', 'suspended', 'VM.suspend'), + ('Suspended', 'Running', 'suspended', 'VM.suspend'), + ('shutdownguest', 'Running', 'halted', 'VM.clean_shutdown'), + ('Shutdownguest', 'Running', 'halted', 'VM.clean_shutdown'), + ('shutdown-guest', 'Running', 'halted', 'VM.clean_shutdown'), + ('shutdown_guest', 'Running', 'halted', 'VM.clean_shutdown'), + ('rebootguest', 'Running', 'running', 'VM.clean_reboot'), + ('rebootguest', 'Running', 'running', 'VM.clean_reboot'), + ('reboot-guest', 'Running', 'running', 'VM.clean_reboot'), + ('reboot_guest', 'Running', 'running', 'VM.clean_reboot'), + ], + "ids": [ + "poweredoff->poweredon", + "poweredoff->poweredon-cap", + "poweredoff->poweredon-dash", + "poweredoff->poweredon-under", + "suspended->poweredon", + "suspended->poweredon-cap", + "suspended->poweredon-dash", + "suspended->poweredon-under", + "paused->poweredon", + "paused->poweredon-cap", + "paused->poweredon-dash", + "paused->poweredon-under", + "poweredon->poweredoff", + "poweredon->poweredoff-cap", + "poweredon->poweredoff-dash", + "poweredon->poweredoff-under", + "suspended->poweredoff", + "suspended->poweredoff-cap", + "suspended->poweredoff-dash", + "suspended->poweredoff-under", + "paused->poweredoff", + "paused->poweredoff-cap", + "paused->poweredoff-dash", + "paused->poweredoff-under", + "poweredon->restarted", + "poweredon->restarted-cap", + "paused->restarted", + "paused->restarted-cap", + "poweredon->suspended", + "poweredon->suspended-cap", + "poweredon->shutdownguest", + "poweredon->shutdownguest-cap", + "poweredon->shutdownguest-dash", + "poweredon->shutdownguest-under", + "poweredon->rebootguest", + "poweredon->rebootguest-cap", + "poweredon->rebootguest-dash", + "poweredon->rebootguest-under", + ], +} + +testcase_set_vm_power_state_transitions_async = { + "params": [ + ('shutdownguest', 'Running', 'halted', 'Async.VM.clean_shutdown'), + ('Shutdownguest', 'Running', 'halted', 'Async.VM.clean_shutdown'), + ('shutdown-guest', 'Running', 'halted', 'Async.VM.clean_shutdown'), + ('shutdown_guest', 'Running', 'halted', 'Async.VM.clean_shutdown'), + ('rebootguest', 'Running', 'running', 'Async.VM.clean_reboot'), + ('rebootguest', 'Running', 'running', 'Async.VM.clean_reboot'), + ('reboot-guest', 'Running', 'running', 'Async.VM.clean_reboot'), + ('reboot_guest', 'Running', 'running', 'Async.VM.clean_reboot'), + ], + "ids": [ + "poweredon->shutdownguest", + "poweredon->shutdownguest-cap", + "poweredon->shutdownguest-dash", + "poweredon->shutdownguest-under", + "poweredon->rebootguest", + "poweredon->rebootguest-cap", + "poweredon->rebootguest-dash", + "poweredon->rebootguest-under", + ], +} + + +@pytest.mark.parametrize('vm_ref', testcase_bad_xenapi_refs['params'], ids=testcase_bad_xenapi_refs['ids']) +def test_set_vm_power_state_bad_vm_ref(fake_ansible_module, xenserver, vm_ref): + """Tests failure on bad vm_ref.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.set_vm_power_state(fake_ansible_module, vm_ref, None) + + assert exc_info.value.kwargs['msg'] == "Cannot set VM power state. Invalid VM reference supplied!" + + +def test_set_vm_power_state_xenapi_failure(mock_xenapi_failure, fake_ansible_module, xenserver): + """Tests catching of XenAPI failures.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), "poweredon") + + assert exc_info.value.kwargs['msg'] == "XAPI ERROR: %s" % mock_xenapi_failure[1] + + +def test_set_vm_power_state_bad_power_state(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests failure on unsupported power state.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": "Running", + } + + mocked_xenapi.configure_mock(**mocked_returns) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), "bad") + + # Beside VM.get_power_state() no other method should have been + # called additionally. + assert len(mocked_xenapi.method_calls) == 1 + + assert exc_info.value.kwargs['msg'] == "Requested VM power state 'bad' is unsupported!" + + +@pytest.mark.parametrize('power_state_desired, power_state_current, error_msg', + testcase_set_vm_power_state_bad_transitions['params'], + ids=testcase_set_vm_power_state_bad_transitions['ids']) +def test_set_vm_power_state_bad_transition(mocker, fake_ansible_module, XenAPI, xenserver, power_state_desired, power_state_current, error_msg): + """Tests failure on bad power state transition.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": power_state_current, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state_desired) + + # Beside VM.get_power_state() no other method should have been + # called additionally. + assert len(mocked_xenapi.method_calls) == 1 + + assert exc_info.value.kwargs['msg'] == error_msg + + +@pytest.mark.parametrize('power_state, error_msg', + testcase_set_vm_power_state_task_timeout['params'], + ids=testcase_set_vm_power_state_task_timeout['ids']) +def test_set_vm_power_state_task_timeout(mocker, fake_ansible_module, XenAPI, xenserver, power_state, error_msg): + """Tests failure on async task timeout.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": "Running", + "Async.VM.clean_shutdown.return_value": fake_xenapi_ref('task'), + "Async.VM.clean_reboot.return_value": fake_xenapi_ref('task'), + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('ansible.module_utils.xenserver.wait_for_task', return_value="timeout") + + with pytest.raises(FailJsonException) as exc_info: + xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state, timeout=1) + + # Beside VM.get_power_state() only one of Async.VM.clean_shutdown or + # Async.VM.clean_reboot should have been called additionally. + assert len(mocked_xenapi.method_calls) == 2 + + assert exc_info.value.kwargs['msg'] == error_msg + + +@pytest.mark.parametrize('power_state_desired, power_state_current', + testcase_set_vm_power_state_no_transitions['params'], + ids=testcase_set_vm_power_state_no_transitions['ids']) +def test_set_vm_power_state_no_transition(mocker, fake_ansible_module, XenAPI, xenserver, power_state_desired, power_state_current): + """Tests regular invocation without power state transition.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": power_state_current, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + result = xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state_desired) + + # Beside VM.get_power_state() no other method should have been + # called additionally. + assert len(mocked_xenapi.method_calls) == 1 + + assert result[0] is False + assert result[1] == power_state_current.lower() + + +@pytest.mark.parametrize('power_state_desired, power_state_current, power_state_resulting, activated_xenapi_method', + testcase_set_vm_power_state_transitions['params'], + ids=testcase_set_vm_power_state_transitions['ids']) +def test_set_vm_power_state_transition(mocker, + fake_ansible_module, + XenAPI, + xenserver, + power_state_desired, + power_state_current, + power_state_resulting, + activated_xenapi_method): + """Tests regular invocation with power state transition.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": power_state_current, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + result = xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state_desired, timeout=0) + + mocked_xenapi_method = mocked_xenapi + + for activated_xenapi_class in activated_xenapi_method.split('.'): + mocked_xenapi_method = getattr(mocked_xenapi_method, activated_xenapi_class) + + mocked_xenapi_method.assert_called_once() + + # Beside VM.get_power_state() only activated_xenapi_method should have + # been called additionally. + assert len(mocked_xenapi.method_calls) == 2 + + assert result[0] is True + assert result[1] == power_state_resulting + + +@pytest.mark.parametrize('power_state_desired, power_state_current, power_state_resulting, activated_xenapi_method', + testcase_set_vm_power_state_transitions_async['params'], + ids=testcase_set_vm_power_state_transitions_async['ids']) +def test_set_vm_power_state_transition_async(mocker, + fake_ansible_module, + XenAPI, + xenserver, + power_state_desired, + power_state_current, + power_state_resulting, + activated_xenapi_method): + """ + Tests regular invocation with async power state transition + (shutdownguest and rebootguest only). + """ + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": power_state_current, + "%s.return_value" % activated_xenapi_method: fake_xenapi_ref('task'), + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('ansible.module_utils.xenserver.wait_for_task', return_value="") + + result = xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state_desired, timeout=1) + + mocked_xenapi_method = mocked_xenapi + + for activated_xenapi_class in activated_xenapi_method.split('.'): + mocked_xenapi_method = getattr(mocked_xenapi_method, activated_xenapi_class) + + mocked_xenapi_method.assert_called_once() + + # Beside VM.get_power_state() only activated_xenapi_method should have + # been called additionally. + assert len(mocked_xenapi.method_calls) == 2 + + assert result[0] is True + assert result[1] == power_state_resulting + + +@pytest.mark.parametrize('power_state_desired, power_state_current, power_state_resulting, activated_xenapi_method', + testcase_set_vm_power_state_transitions['params'], + ids=testcase_set_vm_power_state_transitions['ids']) +def test_set_vm_power_state_transition_check_mode(mocker, + fake_ansible_module, + XenAPI, + xenserver, + power_state_desired, + power_state_current, + power_state_resulting, + activated_xenapi_method): + """Tests regular invocation with power state transition in check mode.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": power_state_current, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + fake_ansible_module.check_mode = True + result = xenserver.set_vm_power_state(fake_ansible_module, fake_xenapi_ref('VM'), power_state_desired, timeout=0) + + mocked_xenapi_method = mocked_xenapi + + for activated_xenapi_class in activated_xenapi_method.split('.'): + mocked_xenapi_method = getattr(mocked_xenapi_method, activated_xenapi_class) + + mocked_xenapi_method.assert_not_called() + + # Beside VM.get_power_state() no other method should have been + # called additionally. + assert len(mocked_xenapi.method_calls) == 1 + + assert result[0] is True + assert result[1] == power_state_resulting diff --git a/test/units/module_utils/xenserver/test_wait_for_functions.py b/test/units/module_utils/xenserver/test_wait_for_functions.py new file mode 100644 index 00000000000000..d36010291ef687 --- /dev/null +++ b/test/units/module_utils/xenserver/test_wait_for_functions.py @@ -0,0 +1,220 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from .common import fake_xenapi_ref, testcase_bad_xenapi_refs + + +testcase_wait_for_vm_ip_address_bad_power_states = { + "params": [ + 'Halted', + 'Paused', + 'Suspended', + 'Other', + ], + "ids": [ + 'state-halted', + 'state-paused', + 'state-suspended', + 'state-other', + ] +} + +testcase_wait_for_vm_ip_address_bad_guest_metrics = { + "params": [ + ('OpaqueRef:NULL', {"networks": {}}), + (fake_xenapi_ref('VM_guest_metrics'), {"networks": {}}), + ], + "ids": [ + 'vm_guest_metrics_ref-null, no-ip', + 'vm_guest_metrics_ref-ok, no-ip', + ], +} + +testcase_wait_for_task_all_statuses = { + "params": [ + ('Success', ''), + ('Failure', 'failure'), + ('Cancelling', 'cancelling'), + ('Cancelled', 'cancelled'), + ('Other', 'other'), + ], + "ids": [ + 'task-success', + 'task-failure', + 'task-cancelling', + 'task-cancelled', + 'task-other', + ] +} + + +@pytest.mark.parametrize('vm_ref', testcase_bad_xenapi_refs['params'], ids=testcase_bad_xenapi_refs['ids']) +def test_wait_for_vm_ip_address_bad_vm_ref(fake_ansible_module, xenserver, vm_ref): + """Tests failure on bad vm_ref.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_vm_ip_address(fake_ansible_module, vm_ref) + + assert exc_info.value.kwargs['msg'] == "Cannot wait for VM IP address. Invalid VM reference supplied!" + + +def test_wait_for_vm_ip_address_xenapi_failure(mock_xenapi_failure, xenserver, fake_ansible_module): + """Tests catching of XenAPI failures.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref('VM')) + + assert exc_info.value.kwargs['msg'] == "XAPI ERROR: %s" % mock_xenapi_failure[1] + + +@pytest.mark.parametrize('bad_power_state', + testcase_wait_for_vm_ip_address_bad_power_states['params'], + ids=testcase_wait_for_vm_ip_address_bad_power_states['ids']) +def test_wait_for_vm_ip_address_bad_power_state(mocker, fake_ansible_module, XenAPI, xenserver, bad_power_state): + """Tests failure on bad power state.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": bad_power_state, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref('VM')) + + assert exc_info.value.kwargs['msg'] == ("Cannot wait for VM IP address when VM is in state '%s'." % + xenserver.xapi_to_module_vm_power_state(bad_power_state.lower())) + + +@pytest.mark.parametrize('bad_guest_metrics_ref, bad_guest_metrics', + testcase_wait_for_vm_ip_address_bad_guest_metrics['params'], + ids=testcase_wait_for_vm_ip_address_bad_guest_metrics['ids']) +def test_wait_for_vm_ip_address_timeout(mocker, fake_ansible_module, XenAPI, xenserver, bad_guest_metrics_ref, bad_guest_metrics): + """Tests timeout.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "VM.get_power_state.return_value": "Running", + "VM.get_guest_metrics.return_value": bad_guest_metrics_ref, + "VM_guest_metrics.get_record.return_value": bad_guest_metrics, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('time.sleep') + + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref('VM'), timeout=1) + + assert exc_info.value.kwargs['msg'] == "Timed out waiting for VM IP address!" + + +def test_wait_for_vm_ip_address(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests regular invocation.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + # This mock simulates regular VM IP acquirement lifecycle: + # + # 1) First, no guest metrics are available because VM is not yet fully + # booted and guest agent is not yet started. + # 2) Next, guest agent is started and guest metrics are available but + # IP address is still not acquired. + # 3) Lastly, IP address is acquired by VM on its primary VIF. + mocked_returns = { + "VM.get_power_state.return_value": "Running", + "VM.get_guest_metrics.side_effect": [ + 'OpaqueRef:NULL', + fake_xenapi_ref('VM_guest_metrics'), + fake_xenapi_ref('VM_guest_metrics'), + ], + "VM_guest_metrics.get_record.side_effect": [ + { + "networks": {}, + }, + { + "networks": { + "0/ip": "192.168.0.1", + "1/ip": "10.0.0.1", + }, + }, + ], + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('time.sleep') + + fake_guest_metrics = xenserver.wait_for_vm_ip_address(fake_ansible_module, fake_xenapi_ref('VM')) + + assert fake_guest_metrics == mocked_returns['VM_guest_metrics.get_record.side_effect'][1] + + +@pytest.mark.parametrize('task_ref', testcase_bad_xenapi_refs['params'], ids=testcase_bad_xenapi_refs['ids']) +def test_wait_for_task_bad_task_ref(fake_ansible_module, xenserver, task_ref): + """Tests failure on bad task_ref.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_task(fake_ansible_module, task_ref) + + assert exc_info.value.kwargs['msg'] == "Cannot wait for task. Invalid task reference supplied!" + + +def test_wait_for_task_xenapi_failure(mock_xenapi_failure, fake_ansible_module, xenserver): + """Tests catching of XenAPI failures.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref('task')) + + assert exc_info.value.kwargs['msg'] == "XAPI ERROR: %s" % mock_xenapi_failure[1] + + +def test_wait_for_task_timeout(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests timeout.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "task.get_status.return_value": "Pending", + "task.destroy.return_value": None, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('time.sleep') + + fake_result = xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref('task'), timeout=1) + + mocked_xenapi.task.destroy.assert_called_once() + assert fake_result == "timeout" + + +@pytest.mark.parametrize('task_status, result', + testcase_wait_for_task_all_statuses['params'], + ids=testcase_wait_for_task_all_statuses['ids']) +def test_wait_for_task(mocker, fake_ansible_module, XenAPI, xenserver, task_status, result): + """Tests regular invocation.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + # Mock will first return Pending status and on second invocation it will + # return one of possible final statuses. + mocked_returns = { + "task.get_status.side_effect": [ + 'Pending', + task_status, + ], + "task.destroy.return_value": None, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + mocker.patch('time.sleep') + + fake_result = xenserver.wait_for_task(fake_ansible_module, fake_xenapi_ref('task')) + + mocked_xenapi.task.destroy.assert_called_once() + assert fake_result == result diff --git a/test/units/module_utils/xenserver/test_xapi.py b/test/units/module_utils/xenserver/test_xapi.py new file mode 100644 index 00000000000000..0f60d2abccb80e --- /dev/null +++ b/test/units/module_utils/xenserver/test_xapi.py @@ -0,0 +1,175 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest +import atexit + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from ansible.module_utils.ansible_release import __version__ as ANSIBLE_VERSION + + +testcase_module_local_conn = { + "params": [ + { + "hostname": "localhost", + "username": "someuser", + "password": "somepwd", + "validate_certs": True, + }, + ], + "ids": [ + "local-conn", + ], +} + +testcase_module_remote_conn = { + "params": [ + { + "hostname": "somehost", + "username": "someuser", + "password": "somepwd", + "validate_certs": True, + }, + ], + "ids": [ + "remote-conn", + ], +} + +testcase_module_remote_conn_scheme = { + "params": [ + { + "hostname": "http://somehost", + "username": "someuser", + "password": "somepwd", + "validate_certs": True, + }, + { + "hostname": "https://somehost", + "username": "someuser", + "password": "somepwd", + "validate_certs": True, + }, + ], + "ids": [ + "remote-conn-http", + "remote-conn-https", + ], +} + + +@pytest.mark.parametrize('fake_ansible_module', testcase_module_local_conn['params'], ids=testcase_module_local_conn['ids'], indirect=True) +def test_xapi_connect_local_session(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests that connection to localhost uses XenAPI.xapi_local() function.""" + mocker.patch('XenAPI.xapi_local') + + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + XenAPI.xapi_local.assert_called_once() + + +@pytest.mark.parametrize('fake_ansible_module', testcase_module_local_conn['params'], ids=testcase_module_local_conn['ids'], indirect=True) +def test_xapi_connect_local_login(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests that connection to localhost uses empty username and password.""" + mocker.patch.object(XenAPI.Session, 'login_with_password', create=True) + + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + XenAPI.Session.login_with_password.assert_called_once_with('', '', ANSIBLE_VERSION, 'Ansible') + + +def test_xapi_connect_login(mocker, fake_ansible_module, XenAPI, xenserver): + """ + Tests that username and password are properly propagated to + XenAPI.Session.login_with_password() function. + """ + mocker.patch.object(XenAPI.Session, 'login_with_password', create=True) + + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + username = fake_ansible_module.params['username'] + password = fake_ansible_module.params['password'] + + XenAPI.Session.login_with_password.assert_called_once_with(username, password, ANSIBLE_VERSION, 'Ansible') + + +def test_xapi_connect_login_failure(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests that login failure is properly handled.""" + fake_error_msg = "Fake XAPI login error!" + + mocked_login = mocker.patch.object(XenAPI.Session, 'login_with_password', create=True) + mocked_login.side_effect = XenAPI.Failure(fake_error_msg) + + hostname = fake_ansible_module.params['hostname'] + username = fake_ansible_module.params['username'] + + with pytest.raises(FailJsonException) as exc_info: + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + assert exc_info.value.kwargs['msg'] == "Unable to log on to XenServer at http://%s as %s: %s" % (hostname, username, fake_error_msg) + + +@pytest.mark.parametrize('fake_ansible_module', testcase_module_remote_conn_scheme['params'], ids=testcase_module_remote_conn_scheme['ids'], indirect=True) +def test_xapi_connect_remote_scheme(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests that explicit scheme in hostname param is preserved.""" + mocker.patch('XenAPI.Session') + + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + hostname = fake_ansible_module.params['hostname'] + ignore_ssl = not fake_ansible_module.params['validate_certs'] + + XenAPI.Session.assert_called_once_with(hostname, ignore_ssl=ignore_ssl) + + +@pytest.mark.parametrize('fake_ansible_module', testcase_module_remote_conn['params'], ids=testcase_module_remote_conn['ids'], indirect=True) +def test_xapi_connect_remote_no_scheme(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests that proper scheme is prepended to hostname without scheme.""" + mocker.patch('XenAPI.Session') + + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + hostname = fake_ansible_module.params['hostname'] + ignore_ssl = not fake_ansible_module.params['validate_certs'] + + XenAPI.Session.assert_called_once_with("http://%s" % hostname, ignore_ssl=ignore_ssl) + + +def test_xapi_connect_support_ignore_ssl(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests proper handling of ignore_ssl support.""" + mocked_session = mocker.patch('XenAPI.Session') + mocked_session.side_effect = TypeError() + + with pytest.raises(TypeError) as exc_info: + xapi_session = xenserver.XAPI.connect(fake_ansible_module) + + hostname = fake_ansible_module.params['hostname'] + ignore_ssl = not fake_ansible_module.params['validate_certs'] + + XenAPI.Session.assert_called_with("http://%s" % hostname) + + +def test_xapi_connect_no_disconnect_atexit(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests skipping registration of atexit disconnect handler.""" + mocker.patch('atexit.register') + + xapi_session = xenserver.XAPI.connect(fake_ansible_module, disconnect_atexit=False) + + atexit.register.assert_not_called() + + +def test_xapi_connect_singleton(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests if XAPI.connect() returns singleton.""" + mocker.patch('XenAPI.Session') + + xapi_session1 = xenserver.XAPI.connect(fake_ansible_module) + xapi_session2 = xenserver.XAPI.connect(fake_ansible_module) + + XenAPI.Session.assert_called_once() + assert xapi_session1 == xapi_session2 diff --git a/test/units/module_utils/xenserver/test_xenserverobject.py b/test/units/module_utils/xenserver/test_xenserverobject.py new file mode 100644 index 00000000000000..9aba308a554586 --- /dev/null +++ b/test/units/module_utils/xenserver/test_xenserverobject.py @@ -0,0 +1,50 @@ +# -*- coding: utf-8 -*- +# +# Copyright: (c) 2019, Bojan Vitnik +# 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 pytest + +from .FakeAnsibleModule import FakeAnsibleModule, ExitJsonException, FailJsonException +from .common import fake_xenapi_ref + + +def test_xenserverobject_xenapi_lib_detection(mocker, fake_ansible_module, xenserver): + """Tests XenAPI lib detection code.""" + mocker.patch('ansible.module_utils.xenserver.HAS_XENAPI', new=False) + + with pytest.raises(FailJsonException) as exc_info: + xenserver.XenServerObject(fake_ansible_module) + + assert exc_info.value.kwargs['msg'] == "XenAPI.py required for this module! Please download XenServer SDK and copy XenAPI.py to your site-packages." + + +def test_xenserverobject_xenapi_failure(mock_xenapi_failure, fake_ansible_module, xenserver): + """Tests catching of XenAPI failures.""" + with pytest.raises(FailJsonException) as exc_info: + xenserver.XenServerObject(fake_ansible_module) + + assert exc_info.value.kwargs['msg'] == "XAPI ERROR: %s" % mock_xenapi_failure[1] + + +def test_xenserverobject(mocker, fake_ansible_module, XenAPI, xenserver): + """Tests successful creation of XenServerObject.""" + mocked_xenapi = mocker.patch.object(XenAPI.Session, 'xenapi', create=True) + + mocked_returns = { + "pool.get_all.return_value": [fake_xenapi_ref('pool')], + "pool.get_default_SR.return_value": fake_xenapi_ref('SR'), + "session.get_this_host.return_value": fake_xenapi_ref('host'), + "host.get_software_version.return_value": {"product_version_text_short": "7.2"}, + } + + mocked_xenapi.configure_mock(**mocked_returns) + + xso = xenserver.XenServerObject(fake_ansible_module) + + assert xso.pool_ref == fake_xenapi_ref('pool') + assert xso.xenserver_version == ['7', '2']