summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJose Angel <imjoseangel@users.noreply.github.com>2018-07-27 16:51:45 +0200
committerAbhijeet Kasurde <akasurde@redhat.com>2018-07-27 20:21:45 +0530
commit711dd9f8340c08f070e0e7bb88e3a69169d4e471 (patch)
tree90fd04e03d0cfe6fac43206ee0cce0524aea0821
parentf2c9a6b12612de7452138bc783609f5be30ee7f8 (diff)
downloadansible-711dd9f8340c08f070e0e7bb88e3a69169d4e471.tar.gz
VMware Module - vmware_guest_move (#42149)
-rw-r--r--lib/ansible/modules/cloud/vmware/vmware_guest_move.py218
-rw-r--r--test/integration/targets/vmware_guest_move/aliases2
-rw-r--r--test/integration/targets/vmware_guest_move/tasks/main.yml78
3 files changed, 298 insertions, 0 deletions
diff --git a/lib/ansible/modules/cloud/vmware/vmware_guest_move.py b/lib/ansible/modules/cloud/vmware/vmware_guest_move.py
new file mode 100644
index 0000000000..24fe2d916b
--- /dev/null
+++ b/lib/ansible/modules/cloud/vmware/vmware_guest_move.py
@@ -0,0 +1,218 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2018, Jose Angel Munoz <josea.munoz () gmail.com>
+# Copyright: (c) 2018, Ansible Project
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+__metaclass__ = type
+
+ANSIBLE_METADATA = {
+ 'metadata_version': '1.1',
+ 'status': ['preview'],
+ 'supported_by': 'community'
+}
+
+DOCUMENTATION = r'''
+---
+module: vmware_guest_move
+short_description: Moves virtual machines in vCenter
+description:
+ - This module can be used to move virtual machines between folders.
+version_added: '2.7'
+author:
+ - Jose Angel Munoz (@imjoseangel)
+notes:
+ - Tested on vSphere 5.5 and vSphere 6.5
+requirements:
+ - python >= 2.6
+ - PyVmomi
+options:
+ name:
+ description:
+ - Name of the existing virtual machine to move.
+ - This is required if C(UUID) is not supplied.
+ uuid:
+ description:
+ - UUID of the virtual machine to manage if known, this is VMware's unique identifier.
+ - This is required if C(name) is not supplied.
+ name_match:
+ description:
+ - If multiple virtual machines matching the name, use the first or last found.
+ default: 'first'
+ choices: [ first, last ]
+ dest_folder:
+ description:
+ - Absolute path to move an existing guest
+ - The dest_folder should include the datacenter. ESX's datacenter is ha-datacenter.
+ - This parameter is case sensitive.
+ - 'Examples:'
+ - ' dest_folder: /ha-datacenter/vm'
+ - ' dest_folder: ha-datacenter/vm'
+ - ' dest_folder: /datacenter1/vm'
+ - ' dest_folder: datacenter1/vm'
+ - ' dest_folder: /datacenter1/vm/folder1'
+ - ' dest_folder: datacenter1/vm/folder1'
+ - ' dest_folder: /folder1/datacenter1/vm'
+ - ' dest_folder: folder1/datacenter1/vm'
+ - ' dest_folder: /folder1/datacenter1/vm/folder2'
+ required: True
+ datacenter:
+ description:
+ - Destination datacenter for the move operation
+ required: True
+extends_documentation_fragment: vmware.documentation
+'''
+
+EXAMPLES = r'''
+- name: Move Virtual Machine
+ vmware_guest_move:
+ hostname: "{{ vcenter_ip }}"
+ username: "{{ vcenter_username }}"
+ password: "{{ vcenter_password }}"
+ datacenter: datacenter
+ validate_certs: False
+ name: testvm-1
+ dest_folder: datacenter/vm/prodvms
+
+- name: Get VM UUID
+ vmware_guest_facts:
+ hostname: "{{ vcenter_ip }}"
+ username: "{{ vcenter_username }}"
+ password: "{{ vcenter_password }}"
+ validate_certs: no
+ datacenter: "{{ datacenter }}"
+ folder: "/{{datacenter}}/vm"
+ name: "{{ vm_name }}"
+ register: vm_facts
+
+- name: Get UUID from previous task and pass it to this task
+ vmware_guest_move:
+ hostname: "{{ vcenter_ip }}"
+ username: "{{ vcenter_username }}"
+ password: "{{ vcenter_password }}"
+ validate_certs: no
+ uuid: "{{ vm_facts.instance.hw_product_uuid }}"
+ dest_folder: "/DataCenter/vm/path/to/new/folder/where/we/want"
+ datacenter: "{{ datacenter }}"
+ delegate_to: localhost
+ register: facts
+'''
+
+RETURN = """
+instance:
+ description: metadata about the virtual machine
+ returned: always
+ type: dict
+ sample: {
+ "annotation": null,
+ "current_snapshot": null,
+ "customvalues": {},
+ "guest_consolidation_needed": false,
+ "guest_question": null,
+ "guest_tools_status": null,
+ "guest_tools_version": "0",
+ "hw_cores_per_socket": 1,
+ "hw_datastores": [
+ "LocalDS_0"
+ ],
+ "hw_esxi_host": "DC0_H0",
+ "hw_eth0": {
+ "addresstype": "generated",
+ "ipaddresses": null,
+ "label": "ethernet-0",
+ "macaddress": "00:0c:29:6b:34:2c",
+ "macaddress_dash": "00-0c-29-6b-34-2c",
+ "summary": "DVSwitch: 43cdd1db-1ef7-4016-9bbe-d96395616199"
+ },
+ "hw_files": [
+ "[LocalDS_0] DC0_H0_VM0/DC0_H0_VM0.vmx"
+ ],
+ "hw_folder": "/F0/DC0/vm/F0",
+ "hw_guest_full_name": null,
+ "hw_guest_ha_state": null,
+ "hw_guest_id": "otherGuest",
+ "hw_interfaces": [
+ "eth0"
+ ],
+ "hw_is_template": false,
+ "hw_memtotal_mb": 32,
+ "hw_name": "DC0_H0_VM0",
+ "hw_power_status": "poweredOn",
+ "hw_processor_count": 1,
+ "hw_product_uuid": "581c2808-64fb-45ee-871f-6a745525cb29",
+ "instance_uuid": "8bcb0b6e-3a7d-4513-bf6a-051d15344352",
+ "ipv4": null,
+ "ipv6": null,
+ "module_hw": true,
+ "snapshots": []
+ }
+"""
+
+try:
+ import pyVmomi
+ from pyVmomi import vim
+except ImportError:
+ pass
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible.module_utils.vmware import PyVmomi, vmware_argument_spec, connect_to_api, wait_for_task
+
+
+class PyVmomiHelper(PyVmomi):
+ def __init__(self, module):
+ super(PyVmomiHelper, self).__init__(module)
+
+
+def main():
+ argument_spec = vmware_argument_spec()
+ argument_spec.update(
+ name=dict(type='str'),
+ name_match=dict(
+ type='str', choices=['first', 'last'], default='first'),
+ uuid=dict(type='str'),
+ dest_folder=dict(type='str', required=True),
+ datacenter=dict(type='str', required=True),
+ )
+ module = AnsibleModule(
+ argument_spec=argument_spec, required_one_of=[['name', 'uuid']])
+
+ # FindByInventoryPath() does not require an absolute path
+ # so we should leave the input folder path unmodified
+ module.params['dest_folder'] = module.params['dest_folder'].rstrip('/')
+
+ pyv = PyVmomiHelper(module)
+ search_index = pyv.content.searchIndex
+
+ # Check if the VM exists before continuing
+ vm = pyv.get_vm()
+
+ # VM exists
+ if vm:
+ try:
+ vm_path = pyv.get_vm_path(pyv.content, vm).lstrip('/')
+ vm_full = vm_path + '/' + module.params['name']
+ folder = search_index.FindByInventoryPath(
+ module.params['dest_folder'])
+ vm_to_move = search_index.FindByInventoryPath(vm_full)
+ if vm_path != module.params['dest_folder'].lstrip('/'):
+ move_task = folder.MoveInto([vm_to_move])
+ changed, err = wait_for_task(move_task)
+ if changed:
+ module.exit_json(
+ changed=True, instance=pyv.gather_facts(vm))
+ else:
+ module.exit_json(instance=pyv.gather_facts(vm))
+ except Exception as exc:
+ module.fail_json(msg="Failed to move VM with exception %s" %
+ to_native(exc))
+ else:
+ module.fail_json(msg="Unable to find VM %s to move to %s" % (
+ (module.params.get('uuid') or module.params.get('name')),
+ module.params.get('dest_folder')))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/test/integration/targets/vmware_guest_move/aliases b/test/integration/targets/vmware_guest_move/aliases
new file mode 100644
index 0000000000..32dd9d5345
--- /dev/null
+++ b/test/integration/targets/vmware_guest_move/aliases
@@ -0,0 +1,2 @@
+shippable/vcenter/group1
+cloud/vcenter
diff --git a/test/integration/targets/vmware_guest_move/tasks/main.yml b/test/integration/targets/vmware_guest_move/tasks/main.yml
new file mode 100644
index 0000000000..f03f352134
--- /dev/null
+++ b/test/integration/targets/vmware_guest_move/tasks/main.yml
@@ -0,0 +1,78 @@
+# Test code for the vmware_guest_move module
+# Copyright: (c) 2018, Jose Angel Munoz <josea.munoz@gmail.com>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+- name: store the vcenter container ip
+ set_fact:
+ vcsim: "{{ lookup('env', 'vcenter_host') }}"
+- debug: var=vcsim
+
+- name: Wait for Flask controller to come up online
+ wait_for:
+ host: "{{ vcsim }}"
+ port: 5000
+ state: started
+
+- name: kill vcsim
+ uri:
+ url: http://{{ vcsim }}:5000/killall
+
+- name: start vcsim
+ uri:
+ url: http://{{ vcsim }}:5000/spawn?folder=2&dc=2
+ register: vcsim_instance
+
+- name: Wait for Flask controller to come up online
+ wait_for:
+ host: "{{ vcsim }}"
+ port: 443
+ state: started
+
+- debug: var=vcsim_instance
+
+- name: get a list of virtual machines from vcsim
+ uri:
+ url: http://{{ vcsim }}:5000/govc_find?filter=VM
+ register: vms
+
+- set_fact: vm1="{{ vms['json'][0] }}"
+
+# Testcase 0001: Move vm and get changed status
+- name: Move VM (Changed)
+ vmware_guest_move:
+ validate_certs: false
+ hostname: "{{ vcsim }}"
+ username: "{{ vcsim_instance['json']['username'] }}"
+ password: "{{ vcsim_instance['json']['password'] }}"
+ datacenter: "{{ (vm1|basename).split('_')[0] }}"
+ name: "{{ vm1|basename }}"
+ dest_folder: F1/DC1/vm/F1
+ register: vm_facts_0001
+
+
+# Testcase 0002: Move vm and get OK status (Already Moved)
+- name: Move VM (OK)
+ vmware_guest_move:
+ validate_certs: false
+ hostname: "{{ vcsim }}"
+ username: "{{ vcsim_instance['json']['username'] }}"
+ password: "{{ vcsim_instance['json']['password'] }}"
+ datacenter: "{{ (vm1|basename).split('_')[0] }}"
+ name: "{{ vm1|basename }}"
+ dest_folder: F1/DC1/vm/F1
+ register: vm_facts_0002
+
+- debug:
+ msg: "{{ vm_facts_0001 }}"
+
+- debug:
+ msg: "{{ vm_facts_0002 }}"
+
+- name: get all VMs
+ uri:
+ url: http://{{ vcsim }}:5000/govc_find?filter=VM
+ register: vms_diff
+
+- name: Difference
+ debug:
+ var: vms_diff.json | difference(vms.json)