diff --git a/changelogs/fragments/2342-vmware_drs_override.yml b/changelogs/fragments/2342-vmware_drs_override.yml new file mode 100644 index 0000000000..373a0520a2 --- /dev/null +++ b/changelogs/fragments/2342-vmware_drs_override.yml @@ -0,0 +1,4 @@ +minor_changes: + - vmware_drs_override - Add new state attribute with present, absent, disabled options. + You can now remove existing DRS override or set DRS Override to disabled. + Add option to chose VM with name, uuid or moid (https://github.com/ansible-collections/community.vmware/pull/2342). diff --git a/plugins/modules/vmware_drs_override.py b/plugins/modules/vmware_drs_override.py index 751e7ba607..95280d2cb4 100644 --- a/plugins/modules/vmware_drs_override.py +++ b/plugins/modules/vmware_drs_override.py @@ -12,33 +12,90 @@ description: - This module allows setting a DRS behavior override for individual VMs within a DRS-enabled VMware vSphere cluster. options: - vm_name: - description: - - Name of the VM for which the DRS override is set. - required: true - type: str + name: + description: + - Name of the VM for which the DRS override is set. + - This is required if O(uuid) or O(moid) is not supplied. + type: str + aliases: [ vm_name ] + uuid: + description: + - UUID of the instance to manage if known, this is VMware's unique identifier. + - This is required if O(name) or O(moid) is not supplied. + type: str + use_instance_uuid: + description: + - Whether to use the VMware instance UUID rather than the BIOS UUID. + default: false + type: bool + moid: + description: + - Managed Object ID of the instance to manage if known, this is a unique identifier only within a single vCenter instance. + - This is required if O(name) or O(uuid) is not supplied. + type: str + folder: + description: + - Destination folder, absolute or relative path to find an existing guest. + - The folder should include the datacenter. ESXi server's datacenter is ha-datacenter. + - 'Examples:' + - ' folder: /ha-datacenter/vm' + - ' folder: ha-datacenter/vm' + - ' folder: /datacenter1/vm' + - ' folder: datacenter1/vm' + - ' folder: /datacenter1/vm/folder1' + - ' folder: datacenter1/vm/folder1' + - ' folder: /folder1/datacenter1/vm' + - ' folder: folder1/datacenter1/vm' + - ' folder: /folder1/datacenter1/vm/folder2' + type: str + datacenter: + description: + - The datacenter name to which virtual machine belongs to. + type: str drs_behavior: description: - Desired DRS behavior for the VM. + - manual: DRS generates both power-on placement recommendation, and migration recommendation for VM. Recommendations need to be manually applied or ignored. + - partiallyAutomated: DRS automatically place VM onto host at VM power-on. Migration recommendations need to be manually applied or ignored. + - fullyAutomated: DRS automatically place VM onto host at VM power-on, and VM is automatically migrated from one host to another to optimize resource utilization. choices: ['manual', 'partiallyAutomated', 'fullyAutomated'] default: 'manual' type: str + state: + description: + - State of the override setting. + - disabled: You will disable DRS automation completely for this VM. + - absent: You will remove the DRS override. + choices: ['present', 'absent', 'disabled'] + default: 'present' + type: str extends_documentation_fragment: - community.vmware.vmware.documentation author: - Sergey Goncharov (@svg1007) + - Michał Gąsior (@Rogacz) ''' EXAMPLES = ''' - name: Set DRS behavior for a VM - vmware_drs_override: + community.vmware.vmware_drs_override: hostname: "vcenter.example.com" username: "administrator@vsphere.local" password: "yourpassword" port: 443 validate_certs: False - vm_name: "my_vm_name" + name: "my_vm_name" drs_behavior: "manual" + delegate_to: localhost + +- name: Remove DRS override for a VM + community.vmware.vmware_drs_override: + hostname: "vcenter.example.com" + username: "administrator@vsphere.local" + password: "yourpassword" + moid: vm-42 + state: absent + delegate_to: localhost ''' RETURN = ''' @@ -65,57 +122,107 @@ class VmwareDrsOverride(PyVmomi): def __init__(self, module): super(VmwareDrsOverride, self).__init__(module) - self.vm_name = self.params.get('vm_name', None) self.drs_behavior = module.params['drs_behavior'] - self.params['name'] = self.vm_name + self.drs_state = module.params['state'] + self.drs_enabled = self.drs_state == 'present' + self.drs_vm_config_spec = None + self.msg = "Unexpected exit." self.vm = self.get_vm() if not self.vm: - self.module.fail_json(msg="VM '%s' not found." % self.vm_name) - + self.module.fail_json(msg=f"VM '{self.params['name']}' not found.") if not self.is_vcenter(): self.module.fail_json(msg="DRS configuration is only supported in vCenter environments.") + self.cluster = self.vm.runtime.host.parent + if not self.cluster: + self.module.fail_json(msg="VM is not in a Cluster.") def set_drs_override(self): - cluster = self.vm.runtime.host.parent - # Check current DRS settings - existing_config = next((config for config in cluster.configuration.drsVmConfig if config.key == self.vm), None) - if existing_config and existing_config.behavior == self.drs_behavior: - self.module.exit_json(changed=False, msg="DRS behavior is already set to the desired state.") - - # Create DRS VM config spec - drs_vm_config_spec = vim.cluster.DrsVmConfigSpec( - operation='add', - info=vim.cluster.DrsVmConfigInfo( - key=self.vm, - enabled=True, - behavior=self.drs_behavior + existing_config = next((config for config in self.cluster.configuration.drsVmConfig if config.key == self.vm), None) + if existing_config: + if self.drs_state == 'absent': + # Remove the DRS override + self.drs_vm_config_spec = vim.cluster.DrsVmConfigSpec( + operation=vim.option.ArrayUpdateSpec.Operation.remove, + removeKey=self.vm, + ) + self.msg = "DRS override removed successfully." + self.execute() + if existing_config.behavior == self.drs_behavior and existing_config.enabled == self.drs_enabled: + # Nothing to do + self.module.exit_json(changed=False, msg="DRS behavior is already set to the desired state.") + else: + # Update the DRS override + self.drs_vm_config_spec = vim.cluster.DrsVmConfigSpec( + operation=vim.option.ArrayUpdateSpec.Operation.edit, + info=vim.cluster.DrsVmConfigInfo( + key=self.vm, + enabled=self.drs_enabled, + behavior=self.drs_behavior, + ), + ) + self.msg = "DRS override updated successfully." + self.execute() + else: + if self.drs_state == 'absent': + # Nothing to do + self.module.exit_json(changed=False, msg="DRS override is already absent.") + # Define the DRS override as it does not exist + self.drs_vm_config_spec = vim.cluster.DrsVmConfigSpec( + operation=vim.option.ArrayUpdateSpec.Operation.add, + info=vim.cluster.DrsVmConfigInfo( + key=self.vm, + enabled=self.drs_enabled, + behavior=self.drs_behavior, + ), ) - ) + self.msg = "DRS override applied successfully." + + self.execute() + + def execute(self): + if self.module.check_mode: + # Exit if in check mode + self.module.exit_json(changed=True, msg=self.msg) # Apply the cluster reconfiguration cluster_config_spec = vim.cluster.ConfigSpec() - cluster_config_spec.drsVmConfigSpec = [drs_vm_config_spec] + cluster_config_spec.drsVmConfigSpec = [self.drs_vm_config_spec] try: - task = cluster.ReconfigureCluster_Task(spec=cluster_config_spec, modify=True) + task = self.cluster.ReconfigureCluster_Task(spec=cluster_config_spec, modify=True) wait_for_task(task) - self.module.exit_json(changed=True, msg="DRS override applied successfully.") except vmodl.MethodFault as error: - self.module.fail_json(msg="Failed to set DRS override: %s" % error.msg) + self.module.fail_json(msg=f"Failed to set DRS override: {error.msg}") + + self.module.exit_json(changed=True, msg=self.msg) def main(): argument_spec = base_argument_spec() - argument_spec.update(dict( - vm_name=dict(type='str', required=True), - drs_behavior=dict(type='str', choices=['manual', 'partiallyAutomated', 'fullyAutomated'], default='manual') - )) + argument_spec.update({ + 'name': {'type': 'str', 'aliases': ['vm_name']}, + 'uuid': {'type': 'str'}, + 'moid': {'type': 'str'}, + 'use_instance_uuid': {'type': 'bool', 'default': False}, + 'folder': {'type': 'str'}, + 'datacenter': {'type': 'str'}, + 'drs_behavior': {'type': 'str', 'choices': ['manual', 'partiallyAutomated', 'fullyAutomated'], 'default': 'manual'}, + 'state': {'type': 'str', 'choices': ['present', 'absent', 'disabled'], 'default': 'present'}, + }) module = AnsibleModule( argument_spec=argument_spec, - supports_check_mode=True + supports_check_mode=True, + required_one_of=[ + ['name', 'uuid', 'moid'], + ], ) + if module.params['folder']: + # FindByInventoryPath() does not require an absolute path + # so we should leave the input folder path unmodified + module.params['folder'] = module.params['folder'].rstrip('/') + drs_override = VmwareDrsOverride(module) drs_override.set_drs_override() diff --git a/tests/integration/targets/vmware_drs_override/tasks/main.yml b/tests/integration/targets/vmware_drs_override/tasks/main.yml index 00f14d3428..dd0fe24c1d 100644 --- a/tests/integration/targets/vmware_drs_override/tasks/main.yml +++ b/tests/integration/targets/vmware_drs_override/tasks/main.yml @@ -18,6 +18,21 @@ - name: Set fact for the first VM name set_fact: first_vm_name: "{{ vm_info.virtual_machines[0].guest_name }}" + first_vm_moid: "{{ vm_info.virtual_machines[0].moid }}" + first_vm_cluster: "{{ vm_info.virtual_machines[0].cluster }}" + +- name: Gather info about the first vm cluster + vmware_cluster_info: + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + validate_certs: false + cluster_name: "{{ first_vm_cluster }}" + register: cluster_result + +- name: Set fact for the DRS + set_fact: + drs_enabled: "{{ cluster_result['clusters'][first_vm_cluster].enabled_drs }}" # Test case: Add DRS Override - DRS enabled - name: Add DRS override 'manual' for a VM in a DRS-enabled cluster @@ -38,6 +53,25 @@ - "'DRS override applied successfully' in drs_override_result.msg" when: drs_enabled is defined and drs_enabled +# Test case: Remove DRS Override - DRS enabled +- name: Remove DRS override for a VM in a DRS-enabled cluster + vmware_drs_override: + validate_certs: false + hostname: "{{ vcenter_hostname }}" + username: "{{ vcenter_username }}" + password: "{{ vcenter_password }}" + moid: "{{ first_vm_moid }}" + state: "absent" + register: drs_override_remove_result + when: drs_enabled is defined and drs_enabled + +- name: Assert DRS override removed successfully + assert: + that: + - drs_override_remove_result.changed == true + - "'DRS override removed successfully' in drs_override_remove_result.msg" + when: drs_enabled is defined and drs_enabled + # Test case: Ensure proper error for standalone ESXi without DRS - name: Attempt to add DRS override for VM in a non-DRS environment vmware_drs_override: @@ -69,11 +103,11 @@ drs_behavior: "manual" register: drs_override_drs_disabled_result ignore_errors: true - when: drs_disabled is defined and drs_disabled + when: drs_enabled is defined and drs_enabled == false - name: Assert error for DRS-disabled cluster assert: that: - drs_override_drs_disabled_result.failed == true - "'DRS is not enabled on the cluster' in drs_override_drs_disabled_result.msg" - when: drs_disabled is defined and drs_disabled + when: drs_enabled is defined and drs_enabled == false