diff --git a/common/add_host_in_memory_inventory.yml b/common/add_host_in_memory_inventory.yml index 00fece23e..e68f75cc0 100644 --- a/common/add_host_in_memory_inventory.yml +++ b/common/add_host_in_memory_inventory.yml @@ -38,8 +38,10 @@ ansible_ssh_pipelining: "{{ add_host_in_memory_inventory_ssh_pipeline | default(false) }}" ansible_remote_tmp: "{{ add_host_in_memory_inventory_remote_tmp | default(omit) }}" ansible_shell_executable: "{{ add_host_in_memory_inventory_shell | default(omit) }}" - register: add_host_result + no_log: "{{ hide_secrets | default(false) }}" + - name: Display the add host result ansible.builtin.debug: var=add_host_result when: enable_debug is defined and enable_debug + no_log: "{{ hide_secrets | default(false) }}" diff --git a/common/set_vmware_module_hostname.yml b/common/set_vmware_module_hostname.yml index 920029c5e..c3162c071 100644 --- a/common/set_vmware_module_hostname.yml +++ b/common/set_vmware_module_hostname.yml @@ -17,6 +17,7 @@ - vcenter_username is defined and vcenter_username - vcenter_password is defined and vcenter_password - datacenter is defined and datacenter + no_log: "{{ hide_secrets | default(false) }}" - name: "Set ESXi hostname for Ansible VMware modules to connect" ansible.builtin.set_fact: @@ -25,6 +26,7 @@ vsphere_host_user_password: "{{ esxi_password }}" vsphere_host_datacenter: "ha-datacenter" when: vcenter_is_defined is undefined + no_log: "{{ hide_secrets | default(false) }}" - name: "Set default VM folder when it's undefined" ansible.builtin.set_fact: diff --git a/env_setup/env_cleanup.yml b/env_setup/env_cleanup.yml index 9d878a874..5d419c31e 100644 --- a/env_setup/env_cleanup.yml +++ b/env_setup/env_cleanup.yml @@ -13,8 +13,12 @@ # Need to revert to base snapshot of target VM firstly, or removing portgroup would fail - name: "Revert to base snapshot then cleanup network testbed configurations" + when: + - router_vm_deployed is defined + - router_vm_deployed | bool block: - - include_tasks: ../common/vm_revert_snapshot.yml + - name: "Revert to snapshot {{ base_snapshot_name }}" + include_tasks: ../common/vm_revert_snapshot.yml vars: snapshot_name: "{{ base_snapshot_name }}" skip_if_not_exist: true @@ -22,20 +26,42 @@ - base_snapshot_exists is defined - base_snapshot_exists | bool # Clean up router VM, vSwitch and portgroup - - include_tasks: ../common/network_testbed_cleanup.yml - when: - - router_vm_deployed is defined - - router_vm_deployed | bool + - name: "Cleanup router VM and vSwitch" + include_tasks: ../common/network_testbed_cleanup.yml - - name: Cleanup new deployed VM + - name: "Cleanup new deployed VM" + when: + - cleanup_vm | bool + - new_vm is defined and new_vm | bool block: - - include_tasks: ../common/vm_set_power_state.yml + - name: "Power off VM" + include_tasks: ../common/vm_set_power_state.yml vars: vm_power_state_set: 'powered-off' - - include_tasks: ../common/vm_remove.yml - - name: "Set VM cleaned flag is True" + - name: "Remove VM" + include_tasks: ../common/vm_remove.yml + - name: "Set VM cleaned flag to true and VM exists to false" ansible.builtin.set_fact: vm_cleaned_up: true + vm_exists: false + + - name: "Restore base snapshot for Windows Update installation" when: - - cleanup_vm | bool - - new_vm is defined and new_vm | bool + - win_update_snapshot_name is defined + - win_update_snapshot_name + - (vm_exists is defined and vm_exists) + block: + - name: "Revert to original base snapshot {{ win_update_snapshot_name }}" + include_tasks: ../common/vm_revert_snapshot.yml + vars: + snapshot_name: "{{ win_update_snapshot_name }}" + - name: "Remove the current base snapshot {{ base_snapshot_name }}" + include_tasks: ../common/vm_remove_snapshot.yml + vars: + snapshot_name: "{{ base_snapshot_name }}" + when: base_snapshot_exists + - name: "Rename snapshot {{ win_update_snapshot_name }} to {{ base_snapshot_name }}" + include_tasks: ../common/vm_rename_snapshot.yml + vars: + current_snapshot_name: "{{ win_update_snapshot_name }}" + new_snapshot_name: "{{ base_snapshot_name }}" diff --git a/main.yml b/main.yml index 143753fda..ebff95cc5 100644 --- a/main.yml +++ b/main.yml @@ -4,6 +4,7 @@ # Main playbook for launching guest OS validation testing on vSphere - name: main hosts: localhost + gather_facts: false tasks: - name: "Read variables from vars file" ansible.builtin.include_vars: diff --git a/vars/test.yml b/vars/test.yml index 16a50da15..d097a0d1f 100644 --- a/vars/test.yml +++ b/vars/test.yml @@ -71,6 +71,13 @@ base_snapshot_name: "BaseSnapshot" # # http_proxy_localhost: myproxy.company.com:8080 +# To keep sensitive values out of your logs, such as passwords and usernames, mark tasks that expose them +# with the "no_log: True" attribute. +# This var is the value for attribute no_log. +# Default value is false. +# +# hide_secrets: false + ##################################### # Testbed parameters ##################################### @@ -353,6 +360,16 @@ windows_product_key_upgrade: "XXXXX-XXXXX-XXXXX-XXXXX-XXXXX" # # gosc_dhcp_network: "VM Network" +# 7. windows_update_install +# For Windows testing only. +# Windows shared folder for storing the .msu file. E.g. \\WINDOWS_SERVER_ADDRESS\SHARE_FOLDER +windows_nfs_share: '' +# The path of the .msu file in above Windows shared folder. E.g. WindowsClient\Windows11\23H2 +windows_nfs_msu_path: '' +# The use name and password for accessing above Windows shared folder. +# windows_nfs_username: "CHANGEME" +# windows_nfs_password: "CHANGEME" + ##################################### # GOS related parameters ##################################### diff --git a/windows/gosv_testcase_list.yml b/windows/gosv_testcase_list.yml index 134e21527..0f8672fb7 100644 --- a/windows/gosv_testcase_list.yml +++ b/windows/gosv_testcase_list.yml @@ -4,6 +4,7 @@ - import_playbook: deploy_vm/deploy_vm.yml - import_playbook: check_inbox_driver/check_inbox_driver.yml - import_playbook: wintools_complete_install_verify/wintools_complete_install_verify.yml +- import_playbook: windows_update_install/windows_update_install.yml - import_playbook: guest_os_inplace_upgrade/guest_os_inplace_upgrade.yml - import_playbook: secureboot_enable_disable/secureboot_enable_disable.yml - import_playbook: check_efi_firmware/check_efi_firmware.yml diff --git a/windows/utils/add_windows_host.yml b/windows/utils/add_windows_host.yml index 905f29f24..364b4cf3f 100644 --- a/windows/utils/add_windows_host.yml +++ b/windows/utils/add_windows_host.yml @@ -21,6 +21,7 @@ when: - win_ansible_connection is defined - win_ansible_connection | lower == 'winrm' + no_log: "{{ hide_secrets | default(false) }}" - name: "Add specified Windows IP to the hosts" add_host: @@ -47,6 +48,8 @@ when: > (win_ansible_connection is undefined) or (win_ansible_connection | lower == 'psrp') + no_log: "{{ hide_secrets | default(false) }}" - ansible.builtin.debug: var=add_host_result when: enable_debug is defined and enable_debug + no_log: "{{ hide_secrets | default(false) }}" diff --git a/windows/utils/win_execute_cmd.yml b/windows/utils/win_execute_cmd.yml index f8b072a16..9c12c2375 100644 --- a/windows/utils/win_execute_cmd.yml +++ b/windows/utils/win_execute_cmd.yml @@ -1,10 +1,11 @@ # Copyright 2021-2023 VMware, Inc. # SPDX-License-Identifier: BSD-2-Clause --- -# Execute specified powershell command in Windows guest OS +# Execute specified PowerShell command in Windows guest OS # Parameters: -# win_powershell_cmd: powershell command +# win_powershell_cmd: PowerShell command # win_execute_cmd_ignore_error: true or false +# win_execute_cmd_no_log: true or false, default false. # Return: # win_powershell_cmd_output # @@ -19,12 +20,13 @@ ansible.builtin.set_fact: win_powershell_cmd_output: "" -- name: "Execute powershell command '{{ win_powershell_cmd }}'" +- name: "Execute PowerShell command" ansible.windows.win_shell: "{{ win_powershell_cmd }}" register: win_powershell_cmd_output ignore_errors: "{{ win_execute_cmd_ignore_error | default(false) }}" delegate_to: "{{ vm_guest_ip }}" ignore_unreachable: true + no_log: "{{ win_execute_cmd_no_log | default(false) }}" - name: "Test VM and guest connection when guest unreachable" block: @@ -48,6 +50,7 @@ - win_powershell_cmd_output.unreachable is defined - win_powershell_cmd_output.unreachable -- name: "Display the powershell commmand result" +- name: "Display the PowerShell commmand result" ansible.builtin.debug: var=win_powershell_cmd_output when: enable_debug + no_log: "{{ win_execute_cmd_no_log | default(false) }}" diff --git a/windows/windows_update_install/install_msu_file.yml b/windows/windows_update_install/install_msu_file.yml new file mode 100644 index 000000000..eea974561 --- /dev/null +++ b/windows/windows_update_install/install_msu_file.yml @@ -0,0 +1,74 @@ +# Copyright 2023 VMware, Inc. +# SPDX-License-Identifier: BSD-2-Clause +--- +- name: "Set the .msu file installation related parameters" + ansible.builtin.set_fact: + msu_kb_number: "{{ msu_file_name | regex_search('(?i)KB\\d+') | upper }}" + msu_file_dest_path: "{{ msu_dir_path }}\\{{ msu_file_name }}" + msu_install_timeout: 7200 + msu_become_user: "{{ 'SYSTEM' if guest_os_ansible_architecture == '32-bit' else 'Administrator' }}" + +- name: "Enable the user Administrator" + include_tasks: ../utils/win_execute_cmd.yml + vars: + win_powershell_cmd: "net user Administrator /active:yes" + when: vm_username | lower != "administrator" + +- name: "Install the .msu file" + ansible.windows.win_shell: >- + wusa.exe {{ msu_file_dest_path }} /quiet /norestart + delegate_to: "{{ vm_guest_ip }}" + register: install_msu_result + become: true + become_method: runas + become_user: "{{ msu_become_user }}" + async: "{{ msu_install_timeout }}" + poll: 0 + environment: + ANSIBLE_WIN_ASYNC_STARTUP_TIMEOUT: 10 + +- name: "Check if the .msu file installation task started" + ansible.builtin.assert: + that: + - install_msu_result is defined + - install_msu_result.ansible_job_id is defined + fail_msg: "The result of above .msu file installation task executed in guest OS is not returned." + +- name: "Check if the .msu file is installed before restart" + ansible.windows.win_shell: "Get-HotFix | Where-Object HotFixID -eq {{ msu_kb_number }}" + register: win_get_hotfix_result + ignore_errors: true + delegate_to: "{{ vm_guest_ip }}" + ignore_unreachable: true + until: + - win_get_hotfix_result.rc is defined + - win_get_hotfix_result.rc == 0 + - win_get_hotfix_result.stdout_lines | length != 0 + retries: "{{ msu_install_timeout // 300 }}" + delay: 300 + +- name: "The .msu file installation failed" + ansible.builtin.fail: + msg: "Failed to install the .msu file {{ msu_file_name }} in {{ msu_install_timeout }} seconds." + when: + - win_get_hotfix_result.failed is defined + - win_get_hotfix_result.failed + +- name: "Restart guest OS after installing the .msu file" + include_tasks: ../utils/win_shutdown_restart.yml + vars: + set_win_power_state: "restart" + win_reboot_timeout: 1800 + +- name: "Check if the .msu file is installed after restart" + include_tasks: ../utils/win_execute_cmd.yml + vars: + win_powershell_cmd: "Get-HotFix | Where-Object HotFixID -eq {{ msu_kb_number }}" + +- name: "Check the msu installation result after restart" + ansible.builtin.assert: + that: + - win_powershell_cmd_output.stdout_lines is defined + - win_powershell_cmd_output.stdout_lines | length != 0 + fail_msg: "The .msu file {{ msu_file_name }} is not installed in guest OS." + success_msg: "The .msu file {{ msu_file_name }} is installed in guest OS." \ No newline at end of file diff --git a/windows/windows_update_install/prepare_msu_file.yml b/windows/windows_update_install/prepare_msu_file.yml new file mode 100644 index 000000000..15e0cd610 --- /dev/null +++ b/windows/windows_update_install/prepare_msu_file.yml @@ -0,0 +1,60 @@ +# Copyright 2023 VMware, Inc. +# SPDX-License-Identifier: BSD-2-Clause +--- +- name: "Get unused driver letter" + include_tasks: ../utils/win_get_unused_drive_letter.yml + +- name: "Initialize the .msu file path" + ansible.builtin.set_fact: + msu_file_src_path: "{{ drive_letter_new }}:\\{{ windows_nfs_msu_path }}\\*" + msu_dir_path: "C:\\msu" + msu_file_name: "" + +- name: "Check if folder {{ msu_dir_path }} exists on guest OS" + include_tasks: ../utils/win_is_folder.yml + vars: + win_is_folder_path: "{{ msu_dir_path }}" + +- name: "Create folder {{ msu_dir_path }} on guest OS" + include_tasks: ../utils/win_execute_cmd.yml + vars: + win_powershell_cmd: "mkdir {{ msu_dir_path }}" + when: not win_is_folder_result + +- name: "Set mount command for accessing the shared folder" + ansible.builtin.set_fact: + win_nfs_mount_cmd: |- + {%- if windows_nfs_username is defined and windows_nfs_username and + windows_nfs_password is defined and windows_nfs_password -%} + {{ 'net use ' ~ drive_letter_new ~ ': ' ~ windows_nfs_share ~ ' ' ~ windows_nfs_password ~ ' /user:' ~ windows_nfs_username }} + {%- else -%} + {{ 'net use ' ~ drive_letter_new ~ ': ' ~ windows_nfs_share }} + {%- endif -%} + no_log: "{{ hide_secrets | default(false) }}" + +- name: "Copy the .msu file to local disk of guest OS" + include_tasks: ../utils/win_execute_cmd.yml + vars: + win_powershell_cmd: >- + {{ win_nfs_mount_cmd }}; + Copy-Item -Path {{ msu_file_src_path }} -Include *.msu -Destination {{ msu_dir_path }} -ErrorAction Stop; + net use {{ drive_letter_new }}: /delete + win_execute_cmd_no_log: "{{ hide_secrets | default(false) }}" + +- name: "Get the .msu file name" + include_tasks: ../utils/win_execute_cmd.yml + vars: + win_powershell_cmd: >- + Get-ChildItem -Path {{ msu_dir_path }} -Include *.msu -Name -ErrorAction Stop; + +- name: "Check if the .msu file is copied to {{ msu_dir_path }}" + ansible.builtin.assert: + that: + - win_powershell_cmd_output.stdout_lines is defined + - win_powershell_cmd_output.stdout_lines | length != 0 + fail_msg: "The .msu file is not found in {{ msu_dir_path }} in guest OS." + success_msg: "The .msu file is copied to {{ msu_dir_path }} in guest OS." + +- name: "Set the .msu file name" + ansible.builtin.set_fact: + msu_file_name: "{{ win_powershell_cmd_output.stdout_lines[0] }}" \ No newline at end of file diff --git a/windows/windows_update_install/windows_update_install.yml b/windows/windows_update_install/windows_update_install.yml new file mode 100644 index 000000000..359715620 --- /dev/null +++ b/windows/windows_update_install/windows_update_install.yml @@ -0,0 +1,64 @@ +# Copyright 2023 VMware, Inc. +# SPDX-License-Identifier: BSD-2-Clause +--- +# Description: +# This case is to test Microsoft's Windows updates. +# Install Windows .msu update package. +# +- name: windows_update_install + hosts: localhost + gather_facts: no + tasks: + - name: "Test case block" + block: + - name: "Test setup" + include_tasks: ../setup/test_setup.yml + vars: + create_current_test_folder: true + + - name: "Skip test case" + include_tasks: ../../common/skip_test_case.yml + vars: + skip_msg: >- + Skip test case due to parameter windows_nfs_share or windows_nfs_msu_path is not provided + and no need to test the .msu file installation. + skip_reason: "Skipped" + when: >- + (windows_nfs_msu_path is undefined or not windows_nfs_msu_path) or + (windows_nfs_share is undefined or not windows_nfs_share) + + - name: "Prepare the .msu file" + include_tasks: prepare_msu_file.yml + + - name: "Install the .msu file" + include_tasks: install_msu_file.yml + + - name: "Reset base snapshot after installing the .msu file" + include_tasks: ../../common/reset_base_snapshot.yml + + - name: "Save the new name of the old base snapshot" + ansible.builtin.set_fact: + win_update_snapshot_name: "{{ old_snapshot_new_name }}" + rescue: + - name: "Test case failure" + include_tasks: ../../common/test_rescue.yml + vars: + exit_testing_when_fail: true + always: + - name: "Delete the .msu file from guest OS" + when: + - msu_file_dest_path is defined + - msu_file_dest_path + block: + - name: "Check if the .msu file exists in guest OS" + include_tasks: ../utils/win_check_file_exist.yml + vars: + win_check_file_exist_file: "{{ msu_file_dest_path }}" + + - name: "Delete the .msu file from guest OS" + ansible.windows.win_file: + path: "{{ msu_file_dest_path }}" + state: absent + delegate_to: "{{ vm_guest_ip }}" + register: delete_file_result + when: win_check_file_exist_result \ No newline at end of file