diff --git a/common/set_current_testcase_facts.yml b/common/set_current_testcase_facts.yml new file mode 100644 index 000000000..e439bf80e --- /dev/null +++ b/common/set_current_testcase_facts.yml @@ -0,0 +1,30 @@ +# Copyright 2023 VMware, Inc. +# SPDX-License-Identifier: BSD-2-Clause +--- +# Set current test case index, name and log folder +# Parameters: +# create_test_log_folder: True to create log folder for current test case. +# test_log_folder_mode: The mode of log folder for current test case. Default is 0755. +# +# Supposing there are N test cases, +# if N < 10, test cases will be 1 ~ N +# if N >= 10, test cases will be 01 ~ N +# so that test cases log folders are sorted +- name: "Set current test case index and name" + ansible.builtin.set_fact: + current_testcase_index: "{{ '{:<}'.format(((current_testcase_index | default(0) | int + 1) | string). + rjust(gosv_testcases_count | string | length, '0')) }}" + current_testcase_name: "{{ ansible_play_name }}" + +- name: "Set the log folder path for current test case on local machine" + ansible.builtin.set_fact: + current_test_log_folder: "{{ testrun_log_path }}/{{ current_testcase_index }}_{{ ansible_play_name }}" + +- name: "Create log folder for current test case" + include_tasks: create_directory.yml + vars: + dir_path: "{{ current_test_log_folder }}" + dir_mode: "{{ test_log_folder_mode | default('0755') }}" + when: + - create_test_log_folder is defined + - create_test_log_folder | bool diff --git a/common/test_rescue.yml b/common/test_rescue.yml index 9917e6f49..30a9e0c71 100644 --- a/common/test_rescue.yml +++ b/common/test_rescue.yml @@ -10,10 +10,6 @@ - name: "Set timestamp of failure state" ansible.builtin.set_fact: timestamp: "{{ lookup('pipe', 'date +%Y-%m-%d-%H-%M-%S') }}" -- name: "Set test case name" - ansible.builtin.set_fact: - current_testcase_name: "{{ deploy_casename | default(ansible_play_name) }}" - when: ansible_play_name == "deploy_vm" - name: "Print failed test case" ansible.builtin.debug: diff --git a/linux/deploy_vm/deploy_vm.yml b/linux/deploy_vm/deploy_vm.yml index 6a843a85a..d8212d6b2 100644 --- a/linux/deploy_vm/deploy_vm.yml +++ b/linux/deploy_vm/deploy_vm.yml @@ -9,6 +9,11 @@ vars_files: - "{{ testing_vars_file | default('../../vars/test.yml') }}" tasks: + - name: "Set current test case index, name and log folder" + include_tasks: ../../common/set_current_testcase_facts.yml + vars: + create_test_log_folder: "{{ new_vm is defined and new_vm | bool }}" + - name: "Skip test case" include_tasks: ../../common/skip_test_case.yml vars: @@ -16,17 +21,6 @@ skip_reason: "Skipped" when: new_vm is undefined or (not new_vm | bool) - - name: "Set current test case log path on local machine" - ansible.builtin.set_fact: - current_test_log_folder: "{{ testrun_log_path }}/{{ ansible_play_name }}" - - - name: "Create the current test case log folder with mode '0755'" - ansible.builtin.file: - path: "{{ current_test_log_folder }}" - state: directory - mode: '0755' - register: create_log_path - # Change vm_username to root if it is not. And add a new user after VM deployment - name: "Set user account for new VM" include_tasks: set_new_vm_user_account.yml diff --git a/linux/deploy_vm/deploy_vm_from_iso.yml b/linux/deploy_vm/deploy_vm_from_iso.yml index 0c5fe7cc8..8ed638df4 100644 --- a/linux/deploy_vm/deploy_vm_from_iso.yml +++ b/linux/deploy_vm/deploy_vm_from_iso.yml @@ -1,6 +1,8 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause --- +# Deploy a new VM and install guest OS automatically from an ISO image +# # Initialize undefined variables - name: "Initialize variables for new VM settings" ansible.builtin.set_fact: @@ -16,9 +18,9 @@ - cpu_number is undefined or not cpu_number - cpu_cores_per_socket is defined and cpu_cores_per_socket -- name: "Set fact of the deploy VM test case name" +- name: "Update test case name for deploying VM from ISO image" ansible.builtin.set_fact: - deploy_casename: "deploy_vm_{{ firmware }}_{{ boot_disk_controller }}_{{ network_adapter_type }}" + current_testcase_name: "deploy_vm_{{ firmware }}_{{ boot_disk_controller }}_{{ network_adapter_type }}" - name: "Initialize the fact whether to install guest OS with desktop" ansible.builtin.set_fact: diff --git a/linux/deploy_vm/deploy_vm_from_ova.yml b/linux/deploy_vm/deploy_vm_from_ova.yml index 11e2702fb..9e16d42c6 100644 --- a/linux/deploy_vm/deploy_vm_from_ova.yml +++ b/linux/deploy_vm/deploy_vm_from_ova.yml @@ -1,10 +1,11 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause --- -# Initialize deploy_casename with guest_id -- name: "Set fact of deploy VM test case name" +# Deploy a new VM from OVA image +# +- name: "Update test case name for deploying VM from OVA image" ansible.builtin.set_fact: - deploy_casename: "deploy_ova" + current_testcase_name: "deploy_vm_ova" - name: "Test case block" block: diff --git a/linux/deploy_vm/flatcar/reconfigure_flatcar_vm.yml b/linux/deploy_vm/flatcar/reconfigure_flatcar_vm.yml index df60a196f..be16c3267 100644 --- a/linux/deploy_vm/flatcar/reconfigure_flatcar_vm.yml +++ b/linux/deploy_vm/flatcar/reconfigure_flatcar_vm.yml @@ -1,11 +1,6 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause --- -# Reset deploy_casename in case user doesn't provide correct guest_id -- name: "Set fact of deploy VM test case name" - ansible.builtin.set_fact: - deploy_casename: "deploy_flatcar_ova" - # Use Ignition to configure ssh authorized key and user password - name: "Generate Ignition config file" include_tasks: generate_ignition_config.yml diff --git a/linux/deploy_vm/reconfigure_vm_with_cloudinit.yml b/linux/deploy_vm/reconfigure_vm_with_cloudinit.yml index 47c415303..3ba3221de 100644 --- a/linux/deploy_vm/reconfigure_vm_with_cloudinit.yml +++ b/linux/deploy_vm/reconfigure_vm_with_cloudinit.yml @@ -1,10 +1,6 @@ # Copyright 2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause --- -- name: "Set fact of the deploy VM test case name" - ansible.builtin.set_fact: - deploy_casename: "deploy_{{ ova_guest_os_type }}_ova" - - name: "Set fact of cloud-init final message" ansible.builtin.set_fact: cloudinit_final_msg: "The system is finally up, after $UPTIME seconds" diff --git a/linux/setup/test_setup.yml b/linux/setup/test_setup.yml index ad85f1930..c7ec3b7cb 100644 --- a/linux/setup/test_setup.yml +++ b/linux/setup/test_setup.yml @@ -5,25 +5,23 @@ # If base snapshot does not exist, will take a snapshot of VM as the # base snapshot, if it exists, then revert to it directly. # -- name: "Set current test case name and log path on local machine" - ansible.builtin.set_fact: - current_testcase_name: "{{ ansible_play_name }}" - current_test_log_folder: "{{ testrun_log_path }}/{{ ansible_play_name }}" +- name: "Set current test case index, name and log folder" + include_tasks: ../../common/set_current_testcase_facts.yml -# Check base snapshot existence and/or revert to it -- include_tasks: base_snapshot_check_revert.yml +- name: "Check base snapshot existence and/or revert to it" + include_tasks: base_snapshot_check_revert.yml -# Get VM guest IP -- include_tasks: ../../common/update_inventory.yml +- name: "Get VM guest IP" + include_tasks: ../../common/update_inventory.yml - name: "Print VM guest IP address" ansible.builtin.debug: var=vm_guest_ip -# Get VMware tools status if required -- include_tasks: ../../common/vm_get_vmtools_status.yml +- name: "Get VMware tools status" + include_tasks: ../../common/vm_get_vmtools_status.yml -# Skip test case run when VMware tools is required but not installed or not running -- include_tasks: ../../common/skip_test_case.yml +- name: "Block test case because VMware Toos is not installed or not running" + include_tasks: ../../common/skip_test_case.yml vars: skip_msg: "Test case is blocked because VMware tools installed: {{ vmtools_is_installed | default(false) }}, running: {{ vmtools_is_running | default(false) }}" skip_reason: "Blocked" @@ -32,26 +30,26 @@ - skip_test_no_vmtools - not (vmtools_is_running is defined and vmtools_is_running | bool) -# Get guest OS system info -- include_tasks: ../utils/get_linux_system_info.yml +- name: "Get guest OS system info" + include_tasks: ../utils/get_linux_system_info.yml when: guest_os_system_info_retrieved is undefined or not guest_os_system_info_retrieved -# Get VMware tools version info -- include_tasks: ../utils/get_guest_ovt_version_build.yml +- name: "Get VMware Tools version and build" + include_tasks: ../utils/get_guest_ovt_version_build.yml when: - vmtools_is_installed is defined - vmtools_is_installed | bool - vmtools_info_from_vmtoolsd is undefined or not vmtools_info_from_vmtoolsd -# Get VM guest info guest id, guest full name and guest detailed data -- include_tasks: ../../common/vm_get_guest_info.yml +- name: "Get VM guest info including guest id, full name, family and detailed data" + include_tasks: ../../common/vm_get_guest_info.yml when: - vmtools_is_running is defined - vmtools_is_running | bool - guestinfo_gathered is undefined or not guestinfo_gathered -# Create a base snapshot if it does not exist -- include_tasks: create_base_snapshot.yml +- name: "Take a base snapshot if it does not exist" + include_tasks: create_base_snapshot.yml when: not (base_snapshot_exists | bool) # Hit issue in Ubuntu 22.04 casually: Temporary failure in name resolution. Restarting servcie can fix it @@ -62,7 +60,8 @@ delegate_to: "{{ vm_guest_ip }}" register: dns_servers_output - - include_tasks: ../utils/service_operation.yml + - name: "Restart service 'systemd-resolved'" + include_tasks: ../utils/service_operation.yml vars: service_name: "systemd-resolved" service_enabled: true diff --git a/main.yml b/main.yml index 243fb2998..8ff20569a 100644 --- a/main.yml +++ b/main.yml @@ -20,6 +20,20 @@ vars: dir_path: "{{ local_cache }}" dir_mode: "0777" + + - name: "Get test cases number in total" + ansible.builtin.set_fact: + gosv_testcases_count: >- + {{ + lookup('file', testing_testcase_file | + default('linux/gosv_testcase_list.yml')) | + from_yaml | length + }} + + - name: "Print test cases number in total" + ansible.builtin.debug: + msg: "There are {{ gosv_testcases_count }} test cases to run in total." + # Prepare testing environment - import_playbook: env_setup/env_setup.yml # Execute test case one by one diff --git a/plugin/ansible_vsphere_gosv_log.py b/plugin/ansible_vsphere_gosv_log.py index 79c06e742..31900eb66 100644 --- a/plugin/ansible_vsphere_gosv_log.py +++ b/plugin/ansible_vsphere_gosv_log.py @@ -1,34 +1,31 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause +""" Ansible vSphere GOS Validation Log Plugin """ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' - name: ansible_vsphere_gosv_log - type: notification - short_description: Write Ansible output and test results to log files - description: - - This callback writes detail running log and test results to log file. -''' - import os import time import json import sys -import yaml -import re import importlib import shutil import logging -from datetime import datetime from collections import OrderedDict from textwrap import TextWrapper - +import yaml from ansible import context from ansible import constants as C from ansible.playbook.task_include import TaskInclude from ansible.plugins.callback import CallbackBase -from ansible.module_utils._text import to_bytes, to_native, to_text + +DOCUMENTATION = ''' + name: ansible_vsphere_gosv_log + type: notification + short_description: Write Ansible output and test results to log files + description: + - This callback writes detail running log and test results to log file. +''' if sys.version_info.major == 2: reload(sys) @@ -36,69 +33,233 @@ else: importlib.reload(sys) -"""_summary_ -Extract error message from task result -""" def extract_error_msg(json_obj): + """ + Extract error message from task result + """ message = '' try: for key, value in json_obj.items(): - if key == 'msg': - if isinstance(value, str): - message += value.strip() - # Extract stderr or stdout from command output when rc != 0 - if 'non-zero return code' in value: - if 'rc' in json_obj and str(json_obj['rc']) != '': - message += ': ' + str(json_obj['rc']) - if 'stderr_lines' in json_obj and len(json_obj['stderr_lines']) > 0: - if "" in json_obj['stderr_lines']: - json_obj['stderr_lines'].remove("") - message += '\n' + '\n'.join(json_obj['stderr_lines']).strip() - elif 'stdout_lines' in json_obj and len(json_obj['stdout_lines']) > 0: - if "" in json_obj['stderr_lines']: - json_obj['stdout_lines'].remove("") - message += '\n' + '\n'.join(json_obj['stdout_lines']).strip() - if 'MODULE FAILURE' in value: - if 'module_stderr' in json_obj and str(json_obj['module_stderr']) != '': - message += '\n' + json_obj['module_stderr'].strip() - elif 'module_stdout' in json_obj and str(json_obj['module_stdout']) != '': - message += '\n' + json_obj['module_stdout'].strip() - - elif isinstance(value, list): - message += '\n'.join(value) - elif isinstance(value, dict): - message += extract_error_msg(value) - else: - message += str(value).strip() + if key != 'msg': + continue + + if isinstance(value, str): + message += value.strip() + # Extract stderr or stdout from command output when rc != 0 + if 'non-zero return code' in value: + if str(json_obj.get('rc','')): + message += ': ' + str(json_obj['rc']) + if len(json_obj.get('stderr_lines', [])) > 0: + message += '\n' + '\n'.join(list(filter(None, json_obj['stderr_lines']))).strip() + if len(json_obj.get('stdout_lines', [])) > 0: + message += '\n' + '\n'.join(list(filter(None, json_obj['stdout_lines']))).strip() + if 'MODULE FAILURE' in value: + if 'module_stderr' in json_obj and str(json_obj['module_stderr']) != '': + message += '\n' + json_obj['module_stderr'].strip() + elif 'module_stdout' in json_obj and str(json_obj['module_stdout']) != '': + message += '\n' + json_obj['module_stdout'].strip() + + elif isinstance(value, list): + message += '\n'.join(value) + elif isinstance(value, dict): + message += extract_error_msg(value) + else: + message += str(value).strip() - if message != '' and not message.endswith('\n'): - message += '\n' + if message != '' and not message.endswith('\n'): + message += '\n' - except TypeError as e: - print("Failed to extract msg from below text as it is not in json format.\n" + str(e)) - pass + except TypeError as type_error: + print("Failed to extract msg from below text as it is not in json format.\n" + str(type_error)) return message -class VmInfo(object): - def __init__(self, vm_name): +class vSphereInfo(object): + def __init__(self, product, hostname): + self.product = product + self.hostname = hostname + self.version = '' + self.build = '' + self.model = '' + self.cpu_model = '' + + def __str__(self): + info = {'hostname': self.hostname, + 'version': self.version, + 'build': self.build} + if self.product.lower() == 'esxi': + info['model'] = self.model + info['cpu_model'] = self.cpu_model + return json.dumps(info, indent=4) + + def update_property(self, p_name, p_value): + setattr(self, p_name, p_value) + +class TestbedInfo(object): + def __init__(self, vcenter_hostname, esxi_hostname, + ansible_gosv_facts): + """ + Initialize vCenter and ESXi server info with ansible facts + :param vcenter_hostname: + :param esxi_hostname: + :param ansible_gosv_facts: + """ + self.vcenter_info = vSphereInfo('vCenter', vcenter_hostname) + self.esxi_info = vSphereInfo('ESXi', esxi_hostname) + self.set_vcenter_info(ansible_gosv_facts) + self.set_esxi_info(ansible_gosv_facts) + + def set_vcenter_info(self, ansible_gosv_facts=None): + """ + Update vCenter server info with ansible facts + :param ansible_gosv_facts: + :return: + """ + if ansible_gosv_facts: + self.vcenter_info.update_property('version', + ansible_gosv_facts.get('vcenter_version', '')) + self.vcenter_info.update_property('build', + ansible_gosv_facts.get('vcenter_build', '')) + + def set_esxi_info(self, ansible_gosv_facts=None): + """ + Update ESXi server info with ansible facts + :param ansible_gosv_facts: + :return: + """ + if ansible_gosv_facts: + esxi_version = ansible_gosv_facts.get('esxi_version', '') + esxi_update_version = ansible_gosv_facts.get('esxi_update_version', '') + if esxi_version and esxi_update_version and esxi_update_version != 'N/A': + esxi_version += ' U' + esxi_update_version + self.esxi_info.update_property('version', esxi_version) + self.esxi_info.update_property('build', + ansible_gosv_facts.get('esxi_build', '')) + self.esxi_info.update_property('model', + ansible_gosv_facts.get('esxi_model_info', '')) + self.esxi_info.update_property('cpu_model', + ansible_gosv_facts.get('esxi_cpu_model_info', '')) + + def __str__(self): + """ + Print testbed information as below: + Testbed information: + +-----------------------------------------------+--------------------------------------------+ + | Product | Version | Build | Hostname or IP | Server Model | + +-----------------------------------------------+--------------------------------------------+ + | vCenter | 7.0.2 | 17694817 | 192.168.10.10 | | + +-----------------------------------------------+--------------------------------------------+ + | ESXi | 7.0.2 | 17630552 | 192.168.10.11 | Dell Inc. PowerEdge R650 | + | | | | | Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz | + +-----------------------------------------------+--------------------------------------------+ + """ + msg = "Testbed information:\n" + if ((not self.vcenter_info or not self.vcenter_info.hostname) and + (not self.esxi_info or not self.esxi_info.hostname)): + msg += "Not found vCenter or ESXi server information\n" + return msg + + # Get version column width + version_col_width = max([len('Version'), len(self.vcenter_info.version), len(self.esxi_info.version)]) + # Get build column width + build_col_width = max([len('Build'), len(self.vcenter_info.build), len(self.esxi_info.build)]) + # Get hostname or IP column width + hostname_col_width = max( + [len('Hostname or IP'), len(self.vcenter_info.hostname), len(self.esxi_info.hostname)]) + # Get server model column width + server_model_col_width = max( + [len('Server Model'), len(self.esxi_info.model), len(self.esxi_info.cpu_model)]) + + # Table width + table_width = sum([9, version_col_width, build_col_width, + hostname_col_width, server_model_col_width]) + 14 + + row_border = "+{}+\n".format("".ljust(table_width - 2, "-")) + row_format = "| {:<7} | {:<} | {:<} | {:<} | {:<} |\n" + + # Table head + msg += row_border + msg += row_format.format("Product", + "Version".ljust(version_col_width), + "Build".ljust(build_col_width), + "Hostname or IP".ljust(hostname_col_width), + "Server Model".ljust(server_model_col_width)) + msg += row_border + + # vCenter row + if self.vcenter_info.hostname: + msg += row_format.format("vCenter", + self.vcenter_info.version.ljust(version_col_width), + self.vcenter_info.build.ljust(build_col_width), + self.vcenter_info.hostname.ljust(hostname_col_width), + ''.ljust(server_model_col_width)) + msg += row_border + + # Server row + if self.esxi_info.hostname: + msg += row_format.format("ESXi", + self.esxi_info.version.ljust(version_col_width), + self.esxi_info.build.ljust(build_col_width), + self.esxi_info.hostname.ljust(hostname_col_width), + self.esxi_info.model.ljust(server_model_col_width)) + if self.esxi_info.cpu_model: + msg += row_format.format('', + ''.ljust(version_col_width), + ''.ljust(build_col_width), + ''.ljust(hostname_col_width), + self.esxi_info.cpu_model.ljust(server_model_col_width)) + msg += row_border + + msg += "\n" + return msg + +class VmGuestInfo(object): + def __init__(self, ansible_gosv_facts={}): + """ + Initialize VM guest info with ansible facts + :param ansible_gosv_facts: + """ + self.Guest_OS_Distribution = ansible_gosv_facts.get('vm_guest_os_distribution', '') + self.ESXi_Version = ansible_gosv_facts.get('esxi_version', '') + if (self.ESXi_Version and + ansible_gosv_facts.get('esxi_update_version', '') and + ansible_gosv_facts['esxi_update_version'] != 'N/A'): + self.ESXi_Version += ' U' + ansible_gosv_facts['esxi_update_version'] + self.ESXi_Build = ansible_gosv_facts.get('esxi_build', '') + self.Hardware_Version = ansible_gosv_facts.get('vm_hardware_version','') + self.VMTools_Version = ansible_gosv_facts.get('guestinfo_vmtools_info', '') + self.Config_Guest_Id = ansible_gosv_facts.get('vm_guest_id', '') + self.GuestInfo_Guest_Id = ansible_gosv_facts.get('guestinfo_guest_id', '') + self.GuestInfo_Guest_Full_Name = ansible_gosv_facts.get('guestinfo_guest_full_name', '') + self.GuestInfo_Guest_Family = ansible_gosv_facts.get('guestinfo_guest_family', '') + self.GuestInfo_Detailed_Data = ansible_gosv_facts.get('guestinfo_detailed_data', '') + + def __str__(self): + """ + Convert guest info into a json formatted string + """ + guestinfo_in_dict = OrderedDict() + for attr_name, attr_value in vars(self).items(): + if not attr_name.startswith('__') and attr_value is not None: + guestinfo_in_dict[attr_name] = attr_value + return json.dumps(guestinfo_in_dict, indent=4) + +class VmDetailInfo(VmGuestInfo): + def __init__(self, vm_name, ansible_gosv_facts): self.Name = vm_name - self.IP = '' - self.Guest_OS_Distribution = '' - self.Hardware_Version = '' - self.VMTools_Version = '' - self.CloudInit_Version = '' - self.GUI_Installed = None - self.Config_Guest_Id = '' - self.GuestInfo_Guest_Id = '' - self.GuestInfo_Guest_Full_Name = '' - self.GuestInfo_Guest_Family = '' - self.GuestInfo_Detailed_Data = '' + if vm_name: + if not ansible_gosv_facts or not isinstance(ansible_gosv_facts, dict): + ansible_gosv_facts = {} + + self.Guest_OS_Distribution = ansible_gosv_facts.get('vm_guest_os_distribution', '') + self.IP = ansible_gosv_facts.get('vm_guest_ip', '') + self.GUI_Installed = str(ansible_gosv_facts.get('guest_os_with_gui', '')) + self.CloudInit_Version = ansible_gosv_facts.get('cloudinit_version', '') + super().__init__(ansible_gosv_facts) def __str__(self): """ Display VM information as below: - VM information: +---------------------------------------------------------------+ | Name | test_vm | @@ -107,14 +268,14 @@ def __str__(self): +---------------------------------------------------------------+ | Guest OS Distribution | VMware Photon OS 4.0 x86_64 | +---------------------------------------------------------------+ - | Hardware Version | vmx-19 | - +---------------------------------------------------------------+ - | VMTools Version | 11.2.5.26209 (build-17337674) | - +---------------------------------------------------------------+ | CloudInit Version | 20.4.1 | +---------------------------------------------------------------+ | GUI Installed | False | +---------------------------------------------------------------+ + | Hardware Version | vmx-19 | + +---------------------------------------------------------------+ + | VMTools Version | 11.2.5.26209 (build-17337674) | + +---------------------------------------------------------------+ | Config Guest Id | vmwarePhoton64Guest | +---------------------------------------------------------------+ | GuestInfo Guest Id | vmwarePhoton64Guest | @@ -135,22 +296,25 @@ def __str__(self): # Get VM name from testing vars file and set log dir msg = 'VM information:\n' - wrap_width = 50 - wrapped_vm_info = {} + if not self.Name: + msg += "Not found VM information\n" + return msg + wrap_width = 80 + wrapped_vm_info = {} # Get column width head_col_width = 0 info_col_width = 0 for attr_name, attr_value in vars(self).items(): - if not attr_name.startswith('__') and attr_value is not None: + if not attr_name.startswith('__') and not attr_name.startswith('ESXi') and attr_value is not None: head_col_width = max([head_col_width, len(attr_name)]) - if len(attr_value) > wrap_width: + if len(str(attr_value)) > wrap_width: if attr_name == 'GuestInfo_Detailed_Data': wrapped_vm_info[attr_name] = self.GuestInfo_Detailed_Data.replace("' ", "'\n").split('\n') else: textwrap = TextWrapper(width=wrap_width) wrapped_vm_info[attr_name] = textwrap.wrap(attr_value) - elif (attr_name == 'CloudInit_Version' and + elif (attr_name in ['CloudInit_Version', 'GUI_Installed'] and ('windows' in self.Config_Guest_Id.lower() or 'windows' in self.Guest_OS_Distribution.lower() or 'windows' in self.GuestInfo_Guest_Id.lower())): @@ -172,8 +336,8 @@ def __str__(self): for attr_name in wrapped_vm_info: head_name = attr_name.replace('_', ' ') if (len(wrapped_vm_info[attr_name]) == 1 and - ('GuestInfo' in attr_name or - len(wrapped_vm_info[attr_name][0]) > 0)): + ('GuestInfo' in attr_name or + len(wrapped_vm_info[attr_name][0]) > 0)): msg += row_format.format(head_name.ljust(head_col_width), wrapped_vm_info[attr_name][0].ljust(info_col_width)) else: @@ -191,6 +355,34 @@ def __str__(self): return msg +class TestRun(object): + """ + Data about an individual test case run + """ + + def __init__(self, test_id, test_name): + self.id = test_id + self.name = test_name + self.status = "No Run" + self.start_time = None + self.duration = 0 + def __str__(self): + return str({"id": self.id, + "name": self.name, + "status": self.status, + "start_time": self.start_time, + "duration": self.duration}) + + def start(self): + self.start_time = time.time() + self.status = "Running" + # print("DEBUG: Test {} is started.".format(self.id)) + + def complete(self, status): + self.duration = int(time.time() - self.start_time) + self.status = status + # print("DEBUG: Test {} is completed, result is {}.".format(self.id, self.status)) + class CallbackModule(CallbackBase): CALLBACK_NAME = 'ansible_vsphere_gosv_log' CALLBACK_TYPE = 'notification' @@ -202,78 +394,53 @@ def __init__(self): self.hosts = [] self.play = None self.log_msg = '' - self.testcases = OrderedDict() - - # Testbed Info - self.vcenter_info = {'hostname':'', - 'version':'', - 'build':''} - self.esxi_info = {'hostname':'', - 'version':'', - 'update_version':'', - 'build':'', - 'model':'', - 'cpu_model':''} - self.vm_info = None - - self.os_distribution = "" - self.os_distribution_ver = "" - self.os_arch = "" - self.os_cloudinit_version = None - self.os_ovt_version = None - - self.started_at = None - self.finished_at = None + self.testcases_count = 0 + self.test_runs = OrderedDict() + self.not_completed_testcases = [] - self.testing_vars_file = None - self.testing_testcase_file = None - self.testing_vars = {} + self._ansible_gosv_facts = {} + + self.start_time = None + self.end_time = None self.plugin_dir = os.path.dirname(os.path.realpath(__file__)) self.cwd = os.path.dirname(self.plugin_dir) - self.log_dir = os.path.join(self.cwd, "logs", time.strftime("%Y-%m-%d-%H-%M-%S")) - self.current_log_dir = os.path.join(self.cwd, "logs/current") - self.testrun_log_dir = None + self.log_dir = None + self.current_log_dir = None self.full_debug_log = "full_debug.log" self.failed_tasks_log = "failed_tasks.log" self.known_issues_log = "known_issues.log" self.test_results_log = "results.log" - self.test_results_yml = "test_results.yml" - self.os_release_info_file = None + self.guest_info_json_file = "guest_info.json" + self.collected_guest_info = {} - # Plays and Tasks + # Testing vars file and testcase list file + self.testing_vars_file = None + self.testing_testcase_file = None + self.testing_vars = {} + + # The play name and path of current playbook self._play_name = None self._play_path = None - self._last_test_name = None - self._play_tasks_cache = {} + # The last test case id, composed by _ + self._last_test_id = None + + # A tasks cache of current play + self._play_tasks_cache = {} self._last_task_uuid = None self._last_task_name = None self._task_type_cache = {} - if not os.path.exists(self.log_dir): - os.makedirs(self.log_dir) - - if os.path.exists(self.current_log_dir): - try: - if os.path.islink(self.current_log_dir): - os.unlink(self.current_log_dir) - else: - shutil.rmtree(self.current_log_dir) - except OSError as e: - self._display.display("Error: {} : {}".format(self.current_log_dir, e.strerror), color=C.COLOR_ERROR) - - os.symlink(self.log_dir, self.current_log_dir, target_is_directory=True) - # Set logger self.logger_name = "ansible-vsphere-gos-validation" self.logger = logging.getLogger(self.logger_name) self.logger.setLevel(logging.DEBUG) msg = self._banner("PLUGIN [{}]".format(os.path.realpath(__file__))) - msg += "Plugin directory: {}\nProject directory: {}\nCurrent log directory: {}".format( - self.plugin_dir, self.cwd, self.current_log_dir) - self._display.display(msg, color=C.COLOR_VERBOSE) + msg += "Project directory: " + self.cwd + msg += "\nPlugin directory: " + self.plugin_dir + self._display.display(msg, color=C.COLOR_DEBUG) def add_logger_file_handler(self, log_file=None): """ @@ -285,8 +452,8 @@ def add_logger_file_handler(self, log_file=None): log_file_path = os.path.join(self.log_dir, log_file) for lh in self.logger.handlers: - if isinstance(lh, logging.FileHandler) and \ - os.path.realpath(log_file_path) == lh.baseFilename: + if (isinstance(lh, logging.FileHandler) and + os.path.realpath(log_file_path) == lh.baseFilename): return log_handler = logging.FileHandler(log_file_path) @@ -310,8 +477,8 @@ def remove_logger_file_handler(self, log_file=None): log_file_path = os.path.join(self.log_dir, log_file) for lh in self.logger.handlers: - if isinstance(lh, logging.FileHandler) and \ - os.path.realpath(log_file_path) == lh.baseFilename: + if (isinstance(lh, logging.FileHandler) and + os.path.realpath(log_file_path) == lh.baseFilename): lh.flush() lh.close() self.logger.removeHandler(lh) @@ -334,20 +501,19 @@ def _task_start(self, task, prefix=None): self._last_task_name = task.get_name().strip() def _banner(self, msg): - if msg.startswith("Included"): - return "\n{} | {:<}".format(time.strftime("%Y-%m-%d %H:%M:%S,%03d"), - (msg + " ").ljust(60, '*')) - else: - return "\n{} | {:<}\n".format(time.strftime("%Y-%m-%d %H:%M:%S,%03d"), - (msg + " ").ljust(60, '*')) + formatted_msg = "\n{} | {:<}".format(time.strftime("%Y-%m-%d %H:%M:%S,%03d"), + (msg + " ").ljust(60, '*')) + if not msg.startswith("Included"): + formatted_msg += "\n" + + return formatted_msg def _print_task_details(self, result, - task_status=None, - delegated_vars=None, - loop_item=None, - ignore_errors=False): + task_status=None, + delegated_vars=None, + loop_item=None, + ignore_errors=False): task = result._task - task_tags = task.tags prefix = self._task_type_cache.get(task._uuid, 'TASK') # Use cached task name @@ -357,8 +523,8 @@ def _print_task_details(self, result, # Get the current test case name or play name current_play = self._play_path - if self._last_test_name: - current_play = self._last_test_name + if self._last_test_id: + current_play = self._last_test_id elif self._play_name: current_play = self._play_name @@ -378,10 +544,10 @@ def _print_task_details(self, result, # Log exception traceback if task_status == "failed": - e_traceback = self._get_exception_traceback(result._result) - if e_traceback: - task_details += str(e_traceback) - task_details += "\n" + e_traceback = self._get_exception_traceback(result._result) + if e_traceback: + task_details += str(e_traceback) + task_details += "\n" if delegated_vars: result_host = "[{} -> {}]".format(result._host.get_name(), delegated_vars['ansible_host']) @@ -415,12 +581,16 @@ def _print_task_details(self, result, task_details += " => {}".format(self._dump_results(result._result, indent=4)) + if 'include_vars' in str(task.action): + # update testing vars with include_vars + self.testing_vars.update(result._result.get('ansible_facts', {})) + if ignore_errors: task_details += "\n...ignoring" log_failed_tasks = False - if 'known_issue' in str(task_tags) and 'msg' in result._result: - self._display.display("TAGS: known_issue", color=C.COLOR_VERBOSE) + if 'known_issue' in task.tags and 'msg' in result._result: + self._display.display("TAGS: known_issue", color=C.COLOR_WARN) log_header = "" if 'known_issue' not in self._play_tasks_cache: self._play_tasks_cache['known_issue'] = [] @@ -458,34 +628,49 @@ def _print_task_details(self, result, self.logger.info(task_details) # Remove logger handler for known issues and failed tasks - if 'known_issue' in str(task_tags) and 'msg' in result._result: + if 'known_issue' in task.tags and 'msg' in result._result: self.remove_logger_file_handler(self.known_issues_log) if log_failed_tasks: self.remove_logger_file_handler(self.failed_tasks_log) - def _get_testing_vars(self): - if not self.testing_vars_file or not os.path.exists(self.testing_vars_file): - self.logger.error("Failed to get testing vars file") - return - - with open(self.testing_vars_file, 'r') as fd: - self.testing_vars = yaml.load(fd, Loader=yaml.Loader) + def _load_testing_vars(self, play): + # Update testing vars with play vars + play_vars = play.get_vars() + if play_vars: + self.testing_vars.update(play_vars) + + # Update testing vars with play vars files + play_vars_files = play.get_vars_files() + play_path = self._get_play_path(play) + if play_vars_files: + for vars_file in play_vars_files: + if 'testing_vars_file' in vars_file: + # Update testing vars with testing_vars_file + if self.testing_vars_file and os.path.exists(self.testing_vars_file): + with open(self.testing_vars_file, 'r') as fd: + self.testing_vars.update(yaml.load(fd, Loader=yaml.Loader)) + else: + vars_file_path = os.path.join(os.path.dirname(play_path), vars_file) + if os.path.exists(vars_file_path): + with open(vars_file_path, 'r') as fd: + self.testing_vars.update(yaml.load(fd, Loader=yaml.Loader)) # Get all of test cases at play start and set status to "No Run" - def _get_testcase_list(self): - if not self.testing_testcase_file or not os.path.exists(self.testing_testcase_file): - self.logger.error("Failed to get test cases file") + def _get_testcase_list(self, testcase_file_path): + if not testcase_file_path or not os.path.exists(testcase_file_path): + self.logger.error("Test cases file {} doesn't exist".format(testcase_file_path)) return - with open(self.testing_testcase_file, 'r') as fd: - lines = yaml.load(fd, Loader=yaml.Loader) - for line in lines: - test_name = os.path.basename(line['import_playbook']).replace('.yml', '') - self.testcases[test_name] = {"status":"No Run", - "started_at": None, - "finished_at": None, - "duration": 0} + with open(testcase_file_path, 'r') as fd: + playbooks = yaml.load(fd, Loader=yaml.Loader) + self.testcases_count = len(playbooks) + for index, playbook in enumerate(playbooks): + test_name = os.path.basename(playbook['import_playbook']).replace('.yml', '') + test_id = "{}_{}".format(str(index+1).rjust(len(str(self.testcases_count)), '0'), test_name) + # print("DEBUG: Get test id: {}".format(test_id)) + self.test_runs[test_id] = TestRun(test_id, test_name) + self.not_completed_testcases.append(test_name) def _get_play_path(self, play): path = "" @@ -500,158 +685,94 @@ def write_to_logfile(self, log_file, msg): fd.write(msg) fd.close() - def _print_testbed_info(self): - """ - Print testbed information as below: - - Testbed information: - +-----------------------------------------------+--------------------------------------------+ - | Product | Version | Build | Hostname or IP | Server Model | - +-----------------------------------------------+--------------------------------------------+ - | vCenter | 7.0.2 | 17694817 | 192.168.10.10 | | - +-----------------------------------------------+--------------------------------------------+ - | ESXi | 7.0.2 | 17630552 | 192.168.10.11 | Dell Inc. PowerEdge R650 | - | | | | | Intel(R) Xeon(R) Silver 4314 CPU @ 2.40GHz | - +-----------------------------------------------+--------------------------------------------+ - """ - - if (self.testing_vars and - not self.vcenter_info['hostname'] and - 'vcenter_hostname' in self.testing_vars and - self.testing_vars['vcenter_hostname']): - self.vcenter_info['hostname'] = self.testing_vars['vcenter_hostname'] - if (self.testing_vars and - not self.esxi_info['hostname'] and - 'esxi_hostname' in self.testing_vars and - self.testing_vars['esxi_hostname']): - self.esxi_info['hostname'] = self.testing_vars['esxi_hostname'] - - msg = "Testbed information:\n" - if not self.vcenter_info['hostname'] and not self.esxi_info['hostname']: - msg += "Not found vCenter or ESXi server information\n" - self.logger.debug(msg) - self._display.display(msg, color=C.COLOR_VERBOSE) - return - - # Get version column width - if self.esxi_info['update_version'] and \ - self.esxi_info['update_version'] != 'N/A' and \ - int(self.esxi_info['update_version']) > 0: - self.esxi_info['version'] += " Update {}".format(self.esxi_info['update_version']) - version_col_width = max([len('Version'), len(self.vcenter_info['version']), len(self.esxi_info['version'])]) - # Get build column width - build_col_width = max([len('Build'), len(self.vcenter_info['build']), len(self.esxi_info['build'])]) - # Get hostname or IP column width - hostname_col_width = max([len('Hostname or IP'), len(self.vcenter_info['hostname']), len(self.esxi_info['hostname'])]) - # Get server model column width - server_model_col_width = max([len('Server Model'), len(self.esxi_info['model']), len(self.esxi_info['cpu_model'])]) - - # Table width - table_width = sum([9, version_col_width, build_col_width, hostname_col_width, server_model_col_width]) + 14 - - row_border = "+{}+\n".format("".ljust(table_width - 2, "-")) - row_format = "| {:<7} | {:<} | {:<} | {:<} | {:<} |\n" - - # Table head - msg += row_border - msg += row_format.format("Product", - "Version".ljust(version_col_width), - "Build".ljust(build_col_width), - "Hostname or IP".ljust(hostname_col_width), - "Server Model".ljust(server_model_col_width)) - msg += row_border - - # vCenter row - if self.vcenter_info['hostname']: - msg += row_format.format("vCenter", - self.vcenter_info['version'].ljust(version_col_width), - self.vcenter_info['build'].ljust(build_col_width), - self.vcenter_info['hostname'].ljust(hostname_col_width), - ''.ljust(server_model_col_width)) - msg += row_border - - # Server row - if self.esxi_info['hostname']: - msg += row_format.format("ESXi", - self.esxi_info['version'].ljust(version_col_width), - self.esxi_info['build'].ljust(build_col_width), - self.esxi_info['hostname'].ljust(hostname_col_width), - self.esxi_info['model'].ljust(server_model_col_width)) - if self.esxi_info['cpu_model']: - msg += row_format.format('', - ''.ljust(version_col_width), - ''.ljust(build_col_width), - ''.ljust(hostname_col_width), - self.esxi_info['cpu_model'].ljust(server_model_col_width)) - msg += row_border - - msg += "\n" - self.logger.info(msg) - self._display.display(msg, color=C.COLOR_VERBOSE) - - def _print_test_results(self): """ Print test results in a table as below - Test Results (Total: 3, Failed: 1, No Run: 1, Elapsed Time: 00:18:42): - +---------------------------------------------+ - | Name | Status | Exec Time | - +---------------------------------------------+ - | deploy_vm | * No Run | 00:00:01 | - +---------------------------------------------+ - | vgauth_check_service | Passed | 00:03:02 | - +---------------------------------------------+ - | gosc_cloudinit_dhcp | * Failed | 00:12:58 | - +---------------------------------------------+ + Test Results (Total: 30, Passed: 27, Skipped: 3, Elapsed Time: 02:22:32) + +-------------------------------------------------------------------------+ + | ID | Name | Status | Exec Time | + +-------------------------------------------------------------------------+ + | 01 | deploy_vm_efi_paravirtual_vmxnet3 | Passed | 00:22:03 | + | 02 | check_inbox_driver | Passed | 00:01:17 | + | 03 | ovt_verify_install | Passed | 00:26:03 | + | .. | ... | ... | ... | + | 30 | ovt_verify_uninstall | Passed | 00:02:09 | + +-------------------------------------------------------------------------+ """ - total_exec_time = "" - total_count = len(self.testcases) + total_count = len(self.test_runs) - if self.started_at and self.finished_at: - total_exec_time = int(self.finished_at - self.started_at) + if self.start_time and self.end_time: + total_exec_time = int(self.end_time - self.start_time) # No test run if total_count == 0: - msg = "Test Results (Total: 0, Elapsed Time: {}):\n".format(time.strftime("%H:%M:%S", time.gmtime(total_exec_time))) + msg = "Test Results (Total: 0, Elapsed Time: {}):\n".format( + time.strftime("%H:%M:%S", time.gmtime(total_exec_time))) self.logger.info(msg) self._display.display(msg, color=C.COLOR_VERBOSE) return + # Block test cases when deploy_vm failed + deploy_vm_failed = False + for test_id in self.test_runs: + if 'deploy_vm' in self.test_runs[test_id].name: + deploy_vm_failed = (self.test_runs[test_id].status == 'Failed') + continue + + # For test cases after deploy_vm, set their status to 'Blocked' + if deploy_vm_failed: + self.test_runs[test_id].status = 'Blocked' + + # Get the column width - name_col_width = max([len(testname) for testname in self.testcases.keys()]) - status_col_width = max([(len(test['status']) + 2) - if test['status'].lower() != "passed" - else len(test['status']) - for test in self.testcases.values()]) + idx_col_width = max([len(str(total_count)), 2]) + name_col_width = max([len(test_result.name) for test_result in self.test_runs.values()]) + status_col_width = max([(len(test_result.status) + 2) + if test_result.status != "Passed" + else len(test_result.status) + for test_result in self.test_runs.values()]) status_mark = "" if status_col_width > len('passed'): status_mark = " " - row_border = "+{}+\n".format("".ljust(name_col_width + status_col_width + 17, "-")) - row_format = "| {:<} | {:<} | {:<9} |\n" + row_border = "+{}+\n".format("".ljust(idx_col_width + name_col_width + status_col_width + 20, "-")) + row_format = "| {:<} | {:<} | {:<} | {:<9} |\n" # Table head msg = row_border - msg += row_format.format("Name".ljust(name_col_width), (status_mark + "Status").ljust(status_col_width), "Exec Time") + msg += row_format.format("ID", + "Name".ljust(name_col_width), + (status_mark + "Status").ljust(status_col_width), + "Exec Time") msg += row_border # Table rows status_stats = OrderedDict([('Passed', 0), ('Failed', 0), ('Blocked', 0), ('Skipped', 0), ('No Run', 0)]) - for testname in self.testcases: - test_exec_time = time.strftime('%H:%M:%S', time.gmtime(self.testcases[testname]['duration'])) - test_status = self.testcases[testname]['status'] - if test_status == 'Passed': - msg += row_format.format(testname.ljust(name_col_width), \ - (status_mark + test_status).ljust(status_col_width), test_exec_time) - status_stats[test_status] += 1 + test_idx = 0 + for test_id in self.test_runs: + test_result = self.test_runs[test_id] + test_idx += 1 + test_exec_time = time.strftime('%H:%M:%S', time.gmtime(test_result.duration)) + if test_result.status == 'Passed': + if len(str(total_count)) == 1: + align_char = ' ' + else: + align_char = '0' + msg += row_format.format(str(test_idx).rjust(idx_col_width, align_char), + test_result.name.ljust(name_col_width), + (status_mark + test_result.status).ljust(status_col_width), + test_exec_time) + status_stats[test_result.status] += 1 else: - msg += row_format.format(testname.ljust(name_col_width), \ - ("* " + test_status).ljust(status_col_width), test_exec_time) - if test_status in ['Failed', 'Blocked', 'No Run']: - status_stats[test_status] += 1 + msg += row_format.format(str(test_idx).rjust(idx_col_width, '0'), + test_result.name.ljust(name_col_width), + ("* " + test_result.status).ljust(status_col_width), + test_exec_time) + if test_result.status in ['Failed', 'Blocked', 'No Run']: + status_stats[test_result.status] += 1 else: status_stats['Skipped'] += 1 @@ -660,7 +781,7 @@ def _print_test_results(self): # Test summary test_summary = "Test Results (Total: " + str(total_count) for key in status_stats: - if status_stats[key] > 0: + if status_stats[key] > 0: test_summary += ", {}: {}".format(key, status_stats[key]) test_summary += ", Elapsed Time: {})\n".format(time.strftime("%H:%M:%S", time.gmtime(total_exec_time))) @@ -674,27 +795,73 @@ def _print_os_release_info(self): Print OS release information into a JSON file, which includes open-vm-tools version, cloud-init version, inbox drivers versions. """ - if self.os_release_info_file and os.path.exists(self.os_release_info_file): - os_release_info_detail = None - with open(self.os_release_info_file, 'r') as json_input: - os_release_info_detail = json.load(json_input, object_pairs_hook=OrderedDict) - - # Update cloud-init version or open-vm-tools version in OS releas info - if os_release_info_detail and len(os_release_info_detail) == 1: - data_changed = False - if self.os_cloudinit_version and 'cloud-init' not in os_release_info_detail[0]: - os_release_info_detail[0]['cloud-init'] = self.os_cloudinit_version - os_release_info_detail[0].move_to_end('cloud-init', last=False) - data_changed = True - if self.os_ovt_version and 'open-vm-tools' not in os_release_info_detail[0]: - os_release_info_detail[0]['open-vm-tools'] = self.os_ovt_version - os_release_info_detail[0].move_to_end('open-vm-tools', last=False) - data_changed = True - - if data_changed: - os_release_info_detail[0].move_to_end('Release', last=False) - with open(self.os_release_info_file, 'w') as json_output: - json.dump(os_release_info_detail, json_output, indent=4) + os_release_info_file = self._ansible_gosv_facts.get('os_release_info_file_path', None) + if (os_release_info_file and os.path.exists(os_release_info_file)): + with open(os_release_info_file, 'r') as json_input: + os_release_info_detail = json.load(json_input, + object_pairs_hook=OrderedDict) + # Update cloud-init version or open-vm-tools version in OS releas info + if os_release_info_detail and len(os_release_info_detail) == 1: + data_changed = False + if (self._ansible_gosv_facts.get('cloudinit_version','') and + 'cloud-init' not in os_release_info_detail[0]): + os_release_info_detail[0]['cloud-init'] = self._ansible_gosv_facts['cloudinit_version'] + os_release_info_detail[0].move_to_end('cloud-init', last=False) + data_changed = True + if (self._ansible_gosv_facts.get('vmtools_info_from_vmtoolsd','') and + 'open-vm-tools' not in os_release_info_detail[0]): + os_release_info_detail[0]['open-vm-tools'] = self._ansible_gosv_facts['vmtools_info_from_vmtoolsd'] + os_release_info_detail[0].move_to_end('open-vm-tools', last=False) + data_changed = True + + if data_changed: + os_release_info_detail[0].move_to_end('Release', last=False) + with open(os_release_info_file, 'w') as json_output: + json.dump(os_release_info_detail, json_output, indent=4) + + def _get_exception_traceback(self, result): + msg = '' + if 'exception' in result: + msg = "An exception occurred during task execution. " + msg += "The full traceback is:\n" + str(result['exception']) + del result['exception'] + return msg + + def v2_runner_on_failed(self, result, ignore_errors=False): + delegated_vars = result._result.get('_ansible_delegated_vars', None) + self._clean_results(result._result, result._task.action) + + if (not ignore_errors and self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): + if 'reason: Blocked' in result._task.name: + self.test_runs[self._last_test_id].complete('Blocked') + else: + self.test_runs[self._last_test_id].complete('Failed') + # Pop up the completed test case + self.not_completed_testcases.pop(0) + + if result._task.loop and 'results' in result._result: + self._process_items(result) + return + + self._print_task_details(result, 'failed', delegated_vars, ignore_errors=ignore_errors) + + def _dump_guest_info(self): + """ + Dump collected guest info into a json file + :return: + """ + json_file_path = os.path.join(self.log_dir, self.guest_info_json_file) + if len(self.collected_guest_info) > 0: + with open(json_file_path, 'w') as json_file: + json_objs = [] + for guest_info in self.collected_guest_info.values(): + json_objs.append(json.loads(str(guest_info))) + + json.dump(json_objs, json_file, indent=4) + self._display.display("VM guest info is dumped into:\n{}".format(json_file_path), + color=C.COLOR_DEBUG) def _get_exception_traceback(self, result): if 'exception' in result: @@ -707,18 +874,15 @@ def v2_runner_on_failed(self, result, ignore_errors=False): delegated_vars = result._result.get('_ansible_delegated_vars', None) self._clean_results(result._result, result._task.action) - if not ignore_errors and \ - self._last_test_name in self.testcases and \ - self.testcases[self._last_test_name]['status'] == 'Running': + if (not ignore_errors and self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): if 'reason: Blocked' in result._task.name: - self.testcases[self._last_test_name]['status'] = 'Blocked' + self.test_runs[self._last_test_id].complete('Blocked') else: - self.testcases[self._last_test_name]['status'] = 'Failed' - self.testcases[self._last_test_name]['finished_at'] = time.time() - self.testcases[self._last_test_name]['duration'] = int(self.testcases[self._last_test_name]['finished_at'] - - self.testcases[self._last_test_name]['started_at']) - self.write_to_logfile(self.test_results_yml, - "{}: {}\n".format(self._last_test_name, self.testcases[self._last_test_name]['status'])) + self.test_runs[self._last_test_id].complete('Failed') + # Pop up the completed test case + self.not_completed_testcases.pop(0) if result._task.loop and 'results' in result._result: self._process_items(result) @@ -726,15 +890,63 @@ def v2_runner_on_failed(self, result, ignore_errors=False): self._print_task_details(result, 'failed', delegated_vars, ignore_errors=ignore_errors) + def _collect_ansible_gosv_facts(self, result): + task = result._task + task_result = result._result + task_args = task.args + task_file = os.path.basename(task.get_path()).split(':')[0].strip() + if ((task_file in ["create_local_log_path.yml", + "set_current_testcase_facts.yml", + "vcenter_get_version_build.yml", + "esxi_get_version_build.yml", + "esxi_get_model.yml", + "vm_get_vm_info.yml", + "vm_upgrade_hardware_version.yml", + "vm_get_guest_info.yml", + "get_linux_system_info.yml", + "get_cloudinit_version.yml", + "check_guest_os_gui.yml", + "get_guest_ovt_version_build.yml", + "get_windows_system_info.yml", + "win_get_vmtools_version_build.yml", + "check_inbox_driver.yml"] or + "deploy_vm_from" in task_file) and + str(task.action) == "ansible.builtin.set_fact"): + ansible_facts = task_result.get('ansible_facts', None) + if ansible_facts: + non_empty_facts = dict(filter(lambda item: item[1] != '', + ansible_facts.items())) + self._ansible_gosv_facts.update(non_empty_facts) + if ("current_testcase_name" in non_empty_facts and + self._last_test_id and + self._last_test_id in self.test_runs): + # Update deploy_vm test case name + self.test_runs[self._last_test_id].name = non_empty_facts['current_testcase_name'] + elif task_file == "vm_get_guest_info.yml": + # Save guest info + vm_guest_info = VmGuestInfo(self._ansible_gosv_facts) + if vm_guest_info.VMTools_Version: + guestinfo_hash = str(hash("{}{}{}".format(vm_guest_info.ESXi_Build, + vm_guest_info.VMTools_Version, + vm_guest_info.Hardware_Version))) + + if guestinfo_hash not in self.collected_guest_info: + self.collected_guest_info[guestinfo_hash] = vm_guest_info + + elif (task_file in ["deploy_vm.yml", "test_setup.yml"] and + str(task.action) == "ansible.builtin.debug"): + if 'var' in task_args and task_args['var'] == "vm_guest_ip": + if task_result["vm_guest_ip"]: + self._ansible_gosv_facts["vm_guest_ip"] = task_result["vm_guest_ip"] def v2_runner_on_ok(self, result): task = result._task + task_args = task.args + if isinstance(task, TaskInclude): return task_result = result._result - task_args = task.args - task_file = os.path.basename(task.get_path()).split(':')[0].strip() delegated_vars = task_result.get('_ansible_delegated_vars', None) if result._task.loop and 'results' in result._result: @@ -747,102 +959,21 @@ def v2_runner_on_ok(self, result): else: self._print_task_details(result, "ok", delegated_vars) - if str(task.action) == "ansible.builtin.set_fact": - set_fact_result = task_result.get('ansible_facts', None) - if set_fact_result: - # Update deploy_vm test case name if deploy_casename is set - if self._last_test_name and self._last_test_name.startswith("deploy"): - deploy_casename = set_fact_result.get("deploy_casename", None) - if self._last_test_name in self.testcases and deploy_casename: - old_test_name = self._last_test_name - self._last_test_name = deploy_casename - self.testcases[self._last_test_name] = self.testcases[old_test_name] - del self.testcases[old_test_name] - self.testcases.move_to_end(self._last_test_name, last=False) - if "get_windows_system_info.yml" == task_file or "get_linux_system_info.yml" == task_file: - vm_guest_os_distribution = set_fact_result.get("vm_guest_os_distribution", None) - if vm_guest_os_distribution and self.vm_info: - self.vm_info.Guest_OS_Distribution = vm_guest_os_distribution - if "vm_get_vm_info.yml" == task_file: - if self.vm_info: - self.vm_info.Config_Guest_Id = set_fact_result.get("vm_guest_id", '') - self.vm_info.Hardware_Version = set_fact_result.get("vm_hardware_version", '') - if "vm_upgrade_hardware_version.yml" == task_file: - if self.vm_info: - self.vm_info.Hardware_Version = set_fact_result.get("vm_hardware_version", '') - if "vm_get_guest_info.yml" == task_file: - if self.vm_info: - self.vm_info.GuestInfo_Guest_Id = set_fact_result.get("guestinfo_guest_id", '') - self.vm_info.GuestInfo_Guest_Full_Name = set_fact_result.get("guestinfo_guest_full_name", '') - self.vm_info.GuestInfo_Guest_Family = set_fact_result.get("guestinfo_guest_family", '') - self.vm_info.GuestInfo_Detailed_Data = set_fact_result.get("guestinfo_detailed_data", '') - self.vm_info.VMTools_Version = set_fact_result.get("guestinfo_vmtools_info", '') - if "check_guest_os_gui.yml" == task_file: - guest_os_with_gui = str(set_fact_result.get("guest_os_with_gui", '')) - if self.vm_info and guest_os_with_gui != '': - self.vm_info.GUI_Installed = guest_os_with_gui - - elif str(task.action) == "ansible.builtin.debug": - if "skip_test_case.yml" == task_file and "Skip testcase:" in task.name: - [test_name, test_result] = task.name.split(',') - test_name = test_name.split(':')[-1].strip() - test_result = test_result.split(':')[-1].strip() - if test_name in self.testcases: - self.testcases[test_name]['status'] = test_result - self.testcases[test_name]['finished_at'] = time.time() - self.testcases[test_name]['duration'] = int(self.testcases[test_name]['finished_at'] - - self.testcases[test_name]['started_at']) - self.write_to_logfile(self.test_results_yml, - "{}: {}\n".format(self._last_test_name, self.testcases[self._last_test_name]['status'])) - elif 'var' in task_args: - debug_var_name = str(task_args['var']) - debug_var_value = str(task_result[debug_var_name]) - if not self.testrun_log_dir and debug_var_name == 'testrun_log_path': - self.testrun_log_dir = debug_var_value - if "check_inbox_driver.yml" == task_file: - if debug_var_name == "os_release_info_file_path": - self.os_release_info_file = debug_var_value - if "deploy_vm.yml" == task_file: - if debug_var_name == "vm_guest_ip" and self.vm_info and not self.vm_info.IP: - self.vm_info.IP = debug_var_value - if "test_setup.yml" == task_file: - if debug_var_name == "vm_guest_ip" and self.vm_info and not self.vm_info.IP: - self.vm_info.IP = debug_var_value - if ("get_guest_ovt_version_build.yml" == task_file or - "win_get_vmtools_version_build.yml" == task_file): - if debug_var_name == "vmtools_info_from_vmtoolsd" and debug_var_value: - if "get_guest_ovt_version_build.yml" == task_file and not self.os_ovt_version: - self.os_ovt_version = debug_var_value - if (self.vm_info and - (not self.vm_info.VMTools_Version or - self.vm_info.VMTools_Version != debug_var_value)): - self.vm_info.VMTools_Version = debug_var_value - if "esxi_get_version_build.yml" == task_file: - if not self.esxi_info['hostname'] and debug_var_name == "esxi_hostname": - self.esxi_info['hostname'] = debug_var_value - if not self.esxi_info['version'] and debug_var_name == "esxi_version": - self.esxi_info['version'] = debug_var_value - if not self.esxi_info['build'] and debug_var_name == "esxi_build": - self.esxi_info['build'] = debug_var_value - if not self.esxi_info['update_version'] and debug_var_name == "esxi_update_version": - self.esxi_info['update_version'] = debug_var_value - if "esxi_get_model.yml" == task_file: - if not self.esxi_info['model'] and debug_var_name == "esxi_model_info": - self.esxi_info['model'] = debug_var_value - if not self.esxi_info['cpu_model'] and debug_var_name == "esxi_cpu_model_info": - self.esxi_info['cpu_model'] = debug_var_value - if "vcenter_get_version_build.yml" == task_file: - if not self.vcenter_info['hostname'] and debug_var_name == "vcenter_hostname": - self.vcenter_info['hostname'] = debug_var_value - if not self.vcenter_info['version'] and debug_var_name == "vcenter_version": - self.vcenter_info['version'] = debug_var_value - if not self.vcenter_info['build'] and debug_var_name == "vcenter_build": - self.vcenter_info['build'] = debug_var_value - if "get_cloudinit_version.yml" == task_file and debug_var_name == "cloudinit_version": - if self.vm_info and not self.vm_info.CloudInit_Version: - self.vm_info.CloudInit_Version = debug_var_value - if not self.os_cloudinit_version: - self.os_cloudinit_version = debug_var_value + # Collect ansible_facts from set_fact or debug modules + self._collect_ansible_gosv_facts(result) + + # Set skipped test case result + task_file = os.path.basename(task.get_path()).split(':')[0].strip() + if (task_file == "skip_test_case.yml" and + str(task.action) == "ansible.builtin.debug" and + "Skip testcase:" in task.name): + test_status = task.name.split(':')[-1].strip() + if (self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): + self.test_runs[self._last_test_id].complete(test_status) + # Pop up the completed test case + self.not_completed_testcases.pop(0) def v2_runner_on_skipped(self, result): self._clean_results(result._result, result._task.action) @@ -855,18 +986,17 @@ def v2_runner_on_unreachable(self, result): delegated_vars = result._result.get('_ansible_delegated_vars', None) self._print_task_details(result, "unreachable", delegated_vars) - if self._last_test_name in self.testcases and \ - self.testcases[self._last_test_name]['status'] == 'Running': - self.testcases[self._last_test_name]['status'] = 'Failed' - self.testcases[self._last_test_name]['finished_at'] = time.time() - self.testcases[self._last_test_name]['duration'] = int(self.testcases[self._last_test_name]['finished_at'] - - self.testcases[self._last_test_name]['started_at']) - self.write_to_logfile(self.test_results_yml, - "{}: {}\n".format(self._last_test_name, self.testcases[self._last_test_name]['status'])) + if (self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): + self.test_runs[self._last_test_id].complete('Failed') + # Pop up the completed test case + self.not_completed_testcases.pop(0) def v2_runner_retry(self, result): task_name = result.task_name or result._task - msg = "FAILED - RETRYING: {} ({} retries left).".format(task_name, result._result['retries'] - result._result['attempts']) + msg = "FAILED - RETRYING: {} ({} retries left).".format(task_name, + result._result['retries'] - result._result['attempts']) msg += "Result was: %s" % self._dump_results(result._result, indent=4) self.logger.debug(msg) @@ -890,72 +1020,112 @@ def v2_runner_item_on_skipped(self, result): self._clean_results(result._result, result._task.action) self._print_task_details(result, "skipped", loop_item=self._get_item_label(result._result)) + def _set_log_dir(self, local_log_path): + # Set log dir + if local_log_path and os.path.exists(local_log_path): + self.log_dir = os.path.join(local_log_path, time.strftime("%Y-%m-%d-%H-%M-%S")) + self.current_log_dir = os.path.join(local_log_path, "current") + else: + self.log_dir = os.path.join(self.cwd, "logs", time.strftime("%Y-%m-%d-%H-%M-%S")) + self.current_log_dir = os.path.join(self.cwd, "logs/current") + + # Unlink existing symbolic link for current log dir + if os.path.exists(self.current_log_dir): + try: + if os.path.islink(self.current_log_dir): + os.unlink(self.current_log_dir) + else: + shutil.rmtree(self.current_log_dir) + except OSError as os_error: + self._display.display("Error: {} : {}".format(self.current_log_dir, os_error.strerror), + color=C.COLOR_ERROR) + + # Create new log dir + if not os.path.exists(self.log_dir): + os.makedirs(self.log_dir) + + os.symlink(self.log_dir, self.current_log_dir, target_is_directory=True) + def v2_playbook_on_start(self, playbook): playbook_path = os.path.realpath(playbook._file_name) - self.started_at = time.time() + self.start_time = time.time() # Parse extra vars extra_vars = {} if context.CLIARGS and context.CLIARGS['extra_vars']: for extra_vars_str in context.CLIARGS['extra_vars']: if extra_vars_str: - #extra_var_str is tuple + # extra_var_str is tuple extra_vars_list = extra_vars_str.split() for extra_vars_item in extra_vars_list: if extra_vars_item.find("=") != -1: extra_vars[extra_vars_item.split("=")[0].strip()] = extra_vars_item.split("=")[1].strip() - #Use user-defined testing vars file + # Update testing vars file with extra variable if 'testing_vars_file' in extra_vars.keys(): self.testing_vars_file = extra_vars['testing_vars_file'] else: - #Use default testing vars file self.testing_vars_file = os.path.join(self.cwd, "vars/test.yml") + # Load testing vars + if os.path.exists(self.testing_vars_file): + with open(self.testing_vars_file, 'r') as fd: + self.testing_vars.update(yaml.load(fd, Loader=yaml.Loader)) - self._get_testing_vars() + # Update log dir + self._set_log_dir(self.testing_vars.get('local_log_path', '')) + # Get testcase list if 'main.yml' in os.path.basename(playbook_path): - #Use user-defined testcase file + # Update testcase list file with extra variable if 'testing_testcase_file' in extra_vars.keys(): self.testing_testcase_file = extra_vars['testing_testcase_file'] else: - #Use default testing vars file self.testing_testcase_file = os.path.join(self.cwd, "linux/gosv_testcase_list.yml") - - self._get_testcase_list() - - if (self.testing_vars and - 'vm_name' in self.testing_vars and - self.testing_vars['vm_name']): - self.vm_info = VmInfo(self.testing_vars['vm_name']) + self._get_testcase_list(self.testing_testcase_file) self.add_logger_file_handler(self.full_debug_log) msg = self._banner("PLAYBOOK: {}".format(playbook_path)) msg += "Positional arguments: {}\n".format(' '.join(context.CLIARGS['args'])) msg += "Tesing vars file: {}\n".format(self.testing_vars_file) msg += "Tesing testcase file: {}\n".format(self.testing_testcase_file) - msg += "Playbook dir: {}\n".format(self.cwd) - msg += "Plugin dir: {}\n".format(self.plugin_dir) - msg += "Log dir: {}".format(self.log_dir) + msg += "Playbook directory: {}\n".format(self.cwd) + msg += "Log directory: {}\n".format(self.log_dir) + msg += "Current log directory: {}\n".format(self.current_log_dir) self.logger.info(msg) - self._display.display(msg, color=C.COLOR_VERBOSE) + self._display.display(msg, color=C.COLOR_DEBUG) def v2_playbook_on_play_start(self, play): + # Finish the last test case + if (self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): + self.test_runs[self._last_test_id].complete('Passed') + # Pop up the completed test case + self.not_completed_testcases.pop(0) + + # Move to new started playbook + self._last_test_id = None self._play_name = play.get_name() self._play_path = self._get_play_path(play) + self._load_testing_vars(play) + + test_index = len(self.test_runs) - len(self.not_completed_testcases) + # print("DEBUG: Current test index is: {}, play name: {}".format(test_index, self._play_name)) + # Start new test case + if (test_index < len(self.test_runs) and + self.not_completed_testcases[0] == self._play_name): + # Start new test case + self._last_test_id = "{}_{}".format(str(test_index+1).rjust(len(str(self.testcases_count)), '0'), + self._play_name) + + if self._last_test_id in self.test_runs: + self.test_runs[self._last_test_id].start() + else: + self._last_test_id = None - # Update the previous test case result - if (self._last_test_name and - self._last_test_name in self.testcases and - self.testcases[self._last_test_name]['status'] == 'Running'): - self.testcases[self._last_test_name]['status'] = 'Passed' - self.testcases[self._last_test_name]['finished_at'] = time.time() - self.testcases[self._last_test_name]['duration'] = int(self.testcases[self._last_test_name]['finished_at'] - - self.testcases[self._last_test_name]['started_at']) - self.write_to_logfile(self.test_results_yml, - "{}: {}\n".format(self._last_test_name, self.testcases[self._last_test_name]['status'])) - - if self._play_name: + if self._last_test_id: + msg = self._banner("PLAY [{}]".format(self._last_test_id)) + elif self._play_name: msg = self._banner("PLAY [{}]".format(self._play_name)) else: msg = self._banner("PLAY") @@ -970,26 +1140,16 @@ def v2_playbook_on_play_start(self, play): # Clear play tasks cache self._play_tasks_cache.clear() - # Update testcase status to Running and set its start time - if self._play_name and self._play_name in self.testcases: - self.testcases[self._play_name]["status"] = "Running" - self.testcases[self._play_name]["started_at"] = time.time() - self._last_test_name = self._play_name - def v2_playbook_on_stats(self, stats): - self.finished_at = time.time() + self.end_time = time.time() # Update the last testcase status - if self._last_test_name and \ - self._last_test_name != self._play_name and \ - self._last_test_name in self.testcases and \ - self.testcases[self._last_test_name]['status'] == 'Running': - self.testcases[self._last_test_name]['status'] = 'Passed' - self.testcases[self._last_test_name]['finished_at'] = time.time() - self.testcases[self._last_test_name]['duration'] = int(self.testcases[self._last_test_name]['finished_at'] - - self.testcases[self._last_test_name]['started_at']) - self.write_to_logfile(self.test_results_yml, - "{}: {}\n".format(self._last_test_name, self.testcases[self._last_test_name]['status'])) + if (self._last_test_id and + self._last_test_id in self.test_runs and + self.test_runs[self._last_test_id].status == "Running"): + self.test_runs[self._last_test_id].complete('Passed') + # Pop up the completed test case + self.not_completed_testcases.pop(0) # Log play stats msg = self._banner("PLAY RECAP") @@ -1007,46 +1167,61 @@ def v2_playbook_on_stats(self, stats): self.logger.info(msg) - # Log testcases results + # Update open-vm-tools and cloud-init version in inbox driver info file self._print_os_release_info() - # Only print test summary when there is test case - if len(self.testcases) > 0: + # Dump guest info into a json file + self._dump_guest_info() + + # Debug about ansible facts + # print("DEBUG: retieved ansible facts\n{}".format(json.dumps(self._ansible_gosv_facts, + # indent=4))) + + # Print test summary when there is test run + if len(self.test_runs) > 0: self._display.banner("TEST SUMMARY") self.logger.info(self._banner("TEST SUMMARY")) self.add_logger_file_handler(self.test_results_log) - self._print_testbed_info() - # Print VM information - if self.vm_info: - vm_info_str = str(self.vm_info) - else: - vm_info_str = "Not found VM information" + # Print testbed information + vcenter_hostname = self.testing_vars.get('vcenter_hostname', '') + esxi_hostname = self.testing_vars.get('esxi_hostname', '') + testbed_info = TestbedInfo(vcenter_hostname, esxi_hostname, self._ansible_gosv_facts) + self.logger.info(str(testbed_info)) + self._display.display(str(testbed_info), color=C.COLOR_VERBOSE) - self.logger.info(vm_info_str) - self._display.display(vm_info_str, color=C.COLOR_VERBOSE) + # Print VM information + vm_name = self.testing_vars.get('vm_name', None) + vm_info = VmDetailInfo(vm_name, self._ansible_gosv_facts) + self.logger.info(str(vm_info)) + self._display.display(str(vm_info), color=C.COLOR_VERBOSE) + # Print test results self._print_test_results() self.remove_logger_file_handler(self.test_results_log) - if self.testrun_log_dir and self.log_dir != self.testrun_log_dir: - os.system("cp -rf {}/* {}".format(self.log_dir, self.testrun_log_dir)) + if ('testrun_log_path' in self._ansible_gosv_facts and + self._ansible_gosv_facts['testrun_log_path'] and + self.log_dir != self._ansible_gosv_facts['testrun_log_path']): + os.system("cp -rf {}/* {}".format(self.log_dir, self._ansible_gosv_facts['testrun_log_path'])) os.unlink(self.current_log_dir) os.system("rm -rf {}".format(self.log_dir)) - os.symlink(self.testrun_log_dir, self.current_log_dir, target_is_directory=True) + os.symlink(self._ansible_gosv_facts['testrun_log_path'], self.current_log_dir, target_is_directory=True) def v2_playbook_on_task_start(self, task, is_conditional): self._task_start(task, prefix='TASK') def v2_playbook_on_include(self, included_file): - msg = self._banner('Included: {} for {}'.format(included_file._filename, ", ".join([h.name for h in included_file._hosts]))) + msg = self._banner( + 'Included: {} for {}'.format(included_file._filename, ", ".join([h.name for h in included_file._hosts]))) label = self._get_item_label(included_file._vars) if label: msg += " => (item={})".format(label) self.logger.info(msg) def v2_playbook_on_import_for_host(self, result, imported_file): - msg = self._banner('Imported: {} for {}'.format(imported_file._filename, ", ".join([h.name for h in imported_file._hosts]))) + msg = self._banner( + 'Imported: {} for {}'.format(imported_file._filename, ", ".join([h.name for h in imported_file._hosts]))) label = self._get_item_label(imported_file._vars) if label: msg += " => (item={})".format(label) diff --git a/windows/deploy_vm/deploy_vm.yml b/windows/deploy_vm/deploy_vm.yml index ed9dee59c..eb24bd03a 100644 --- a/windows/deploy_vm/deploy_vm.yml +++ b/windows/deploy_vm/deploy_vm.yml @@ -13,31 +13,28 @@ vars_files: - "{{ testing_vars_file | default('../../vars/test.yml') }}" tasks: - - include_tasks: ../../common/skip_test_case.yml + - name: "Set current test case index, name and log folder" + include_tasks: ../../common/set_current_testcase_facts.yml + vars: + create_test_log_folder: "{{ new_vm is defined and new_vm | bool }}" + + - name: "Skip test case" + include_tasks: ../../common/skip_test_case.yml vars: skip_msg: "Skip test case due to new_vm is set to '{{ new_vm | default(false) }}'" skip_reason: "Skipped" when: new_vm is undefined or not new_vm | bool - - block: - - name: "Set current test case log path on local machine" - ansible.builtin.set_fact: - current_test_log_folder: "{{ testrun_log_path }}/{{ ansible_play_name }}" - - name: "Create the current test case log folder with mode '0755'" - ansible.builtin.file: - path: "{{ current_test_log_folder }}" - state: directory - mode: '0755' - register: create_log_path - - ansible.builtin.debug: var=create_log_path - when: enable_debug is defined and enable_debug - - - include_tasks: deploy_vm_from_iso.yml + - name: "Deploy VM" + block: + - name: "Deploy VM by creating a new VM and install OS from ISO image on it" + include_tasks: deploy_vm_from_iso.yml when: > (vm_deploy_method is undefined) or (vm_deploy_method | lower == 'iso') - - include_tasks: deploy_vm_from_ova.yml + - name: "Deploy VM from an OVF template" + include_tasks: deploy_vm_from_ova.yml when: - vm_deploy_method is defined - vm_deploy_method | lower == 'ova' @@ -46,24 +43,26 @@ ansible.builtin.debug: var=vm_guest_ip when: vm_guest_ip is defined - # Take screenshot of VM after guest OS install - - include_tasks: ../../common/vm_take_screenshot.yml + - name: "Take screenshot of VM after guest OS install" + include_tasks: ../../common/vm_take_screenshot.yml vars: vm_take_screenshot_local_path: "{{ current_test_log_folder }}" rescue: - - include_tasks: ../../common/test_rescue.yml + - name: "Test case failure" + include_tasks: ../../common/test_rescue.yml vars: exit_testing_when_fail: true always: - - block: - # Umount NFS share points - - include_tasks: ../../common/local_unmount.yml + - name: "Remove NFS mount point" + block: + - name: "Umount NFS share point" + include_tasks: ../../common/local_unmount.yml vars: mount_path: "{{ nfs_mount_dir }}" local_unmount_ignore_errors: true when: nfs_mounted is defined and nfs_mounted | bool - # Remove temporary folders - - include_tasks: ../../common/delete_local_file.yml + - name: "Remove NFS mount folder" + include_tasks: ../../common/delete_local_file.yml vars: local_path: "{{ nfs_mount_dir }}" del_local_file_ignore_errors: true diff --git a/windows/deploy_vm/deploy_vm_from_iso.yml b/windows/deploy_vm/deploy_vm_from_iso.yml index c5228f92a..f0b780fb7 100644 --- a/windows/deploy_vm/deploy_vm_from_iso.yml +++ b/windows/deploy_vm/deploy_vm_from_iso.yml @@ -13,9 +13,9 @@ firmware: "{{ firmware if (firmware is defined and firmware) else 'efi' }}" boot_disk_size_gb: "{{ [64, boot_disk_size_gb | default(64) | int] | max }}" -- name: "Set fact of the deploy VM test case name" +- name: "Update test case name for deploying VM from ISO image" ansible.builtin.set_fact: - deploy_casename: "deploy_vm_{{ firmware }}_{{ boot_disk_controller }}_{{ network_adapter_type }}" + current_testcase_name: "deploy_vm_{{ firmware }}_{{ boot_disk_controller }}_{{ network_adapter_type }}" # Check configured VM CPU number and cores per socket number - include_tasks: check_cpu_socket.yml diff --git a/windows/deploy_vm/deploy_vm_from_ova.yml b/windows/deploy_vm/deploy_vm_from_ova.yml index 0a1e002f8..a302a66d0 100644 --- a/windows/deploy_vm/deploy_vm_from_ova.yml +++ b/windows/deploy_vm/deploy_vm_from_ova.yml @@ -3,9 +3,9 @@ --- # Deploy a new Windows VM through the Windows OVF/OVA template # -- name: "Set fact of the deploy VM test case name" +- name: "Update test case name for deploying VM from OVF image" ansible.builtin.set_fact: - deploy_casename: "deploy_vm_ovf" + current_testcase_name: "deploy_vm_ovf" # OVA file on local machine - name: "Get OVA path and file name" diff --git a/windows/setup/test_setup.yml b/windows/setup/test_setup.yml index 65f5952d4..df699da10 100755 --- a/windows/setup/test_setup.yml +++ b/windows/setup/test_setup.yml @@ -5,37 +5,38 @@ # if base snapshot exists, revert to the base snapshot when it exists. # If base snapshot does not exist, then take a snapshot of VM as the base snapshot. # -- name: "Set current test case name and log path on local machine" - ansible.builtin.set_fact: - current_testcase_name: "{{ ansible_play_name }}" - current_test_log_folder: "{{ testrun_log_path }}/{{ ansible_play_name }}" - -- name: "Create current test case log folder when required" - include_tasks: ../../common/create_directory.yml +- name: "Set current test case index, name and log folder" + include_tasks: ../../common/set_current_testcase_facts.yml vars: - dir_path: "{{ current_test_log_folder }}" - dir_mode: "0777" - when: create_current_test_folder is defined and create_current_test_folder + create_test_log_folder: "{{ create_current_test_folder is defined and create_current_test_folder }}" + test_log_folder_mode: "0777" -- include_tasks: base_snapshot_check_revert.yml +- name: "Check base snapshot existence and/or revert to it" + include_tasks: base_snapshot_check_revert.yml -- include_tasks: ../../common/vm_get_ip.yml +- name: "Get VM guest IP" + include_tasks: ../../common/vm_get_ip.yml vars: vm_get_ip_timeout: 600 -- include_tasks: ../utils/win_check_winrm.yml -- include_tasks: ../utils/add_windows_host.yml + +- name: "Check Windows winrm is connectable" + include_tasks: ../utils/win_check_winrm.yml + +- name: "Add Windows host to in-memory inventory" + include_tasks: ../utils/add_windows_host.yml + - name: "Print VM guest IP address" ansible.builtin.debug: var=vm_guest_ip -# Pause Windows Update -- include_tasks: ../utils/win_pause_windows_update.yml +- name: "Pause Windows Update" + include_tasks: ../utils/win_pause_windows_update.yml when: not base_snapshot_exists -# Get VMware tools status -- include_tasks: ../../common/vm_get_vmtools_status.yml +- name: "Get VMware Tools status" + include_tasks: ../../common/vm_get_vmtools_status.yml -# Skip test case run when VMware tools is required but not installed or not running -- include_tasks: ../../common/skip_test_case.yml +- name: "Block test case because VMware Toos is not installed or not running" + include_tasks: ../../common/skip_test_case.yml vars: skip_msg: "Test case '{{ current_testcase_name }}' is blocked because VMware tools installed: {{ vmtools_is_installed | default(false) }}, running: {{ vmtools_is_running | default(false) }}" skip_reason: "Blocked" @@ -44,23 +45,24 @@ - skip_test_no_vmtools - not (vmtools_is_running is defined and vmtools_is_running | bool) -- include_tasks: ../utils/win_get_vmtools_version_build.yml +- name: "Get VMware Tools version and build" + include_tasks: ../utils/win_get_vmtools_version_build.yml when: - vmtools_is_installed is defined - vmtools_is_installed | bool - vmtools_info_from_vmtoolsd is undefined or not vmtools_info_from_vmtoolsd -# Get guest OS info if not defined -- include_tasks: ../utils/get_windows_system_info.yml +- name: "Get guest OS system info" + include_tasks: ../utils/get_windows_system_info.yml when: guest_os_system_info_retrieved is undefined or not guest_os_system_info_retrieved -# Get VM guest info guest id, guest full name and guest detailed data -- include_tasks: ../../common/vm_get_guest_info.yml +- name: "Get VM guest info including guest id, full name, family and detailed data" + include_tasks: ../../common/vm_get_guest_info.yml when: - vmtools_is_running is defined - vmtools_is_running | bool - guestinfo_gathered is undefined or not guestinfo_gathered -# Take base snapshot if not exist -- include_tasks: create_base_snapshot.yml +- name: "Take a base snapshot if it does not exist" + include_tasks: create_base_snapshot.yml when: not base_snapshot_exists