community.general/lib/ansible/modules/cloud/vmware/vmware_vmotion.py
Dag Wieers e2cac8cc93 Fix all VMware examples to use delegate_to (#43426)
Some users have problems using the VMware modules because they use the
vCenter as target, and Ansible uses SSH to connect to the targets.

Eventually we need to update the VMware guide to explain how the modules
work, but the first fix is to update the examples.

(We should backport to v2.6 and v2.5 too)
2018-08-01 09:10:57 +05:30

304 lines
12 KiB
Python

#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright: (c) 2015, Bede Carroll <bc+github () bedecarroll.com>
# Copyright: (c) 2018, Abhijeet Kasurde <akasurde@redhat.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_vmotion
short_description: Move a virtual machine using vMotion, and/or its vmdks using storage vMotion.
description:
- Using VMware vCenter, move a virtual machine using vMotion to a different
host, and/or its vmdks to another datastore using storage vMotion.
version_added: 2.2
author:
- Bede Carroll (@bedecarroll)
- Olivier Boukili (@oboukili)
notes:
- Tested on vSphere 6.0
requirements:
- "python >= 2.6"
- pyVmomi
options:
vm_name:
description:
- Name of the VM to perform a vMotion on.
- This is required parameter, if C(vm_uuid) is not set.
- Version 2.6 onwards, this parameter is not a required parameter, unlike the previous versions.
aliases: ['vm']
vm_uuid:
description:
- UUID of the virtual machine to perform a vMotion operation on.
- This is a required parameter, if C(vm_name) is not set.
aliases: ['uuid']
version_added: 2.7
destination_host:
description:
- Name of the destination host the virtual machine should be running on.
- Version 2.6 onwards, this parameter is not a required parameter, unlike the previous versions.
aliases: ['destination']
destination_datastore:
description:
- "Name of the destination datastore the virtual machine's vmdk should be moved on."
aliases: ['datastore']
version_added: 2.7
extends_documentation_fragment: vmware.documentation
'''
EXAMPLES = '''
- name: Perform vMotion of virtual machine
vmware_vmotion:
hostname: 'vcenter_hostname'
username: 'vcenter_username'
password: 'vcenter_password'
validate_certs: False
vm_name: 'vm_name_as_per_vcenter'
destination_host: 'destination_host_as_per_vcenter'
delegate_to: localhost
- name: Perform storage vMotion of of virtual machine
vmware_vmotion:
hostname: 'vcenter_hostname'
username: 'vcenter_username'
password: 'vcenter_password'
validate_certs: False
vm_name: 'vm_name_as_per_vcenter'
destination_datastore: 'destination_datastore_as_per_vcenter'
delegate_to: localhost
- name: Perform storage vMotion and host vMotion of virtual machine
vmware_vmotion:
hostname: 'vcenter_hostname'
username: 'vcenter_username'
password: 'vcenter_password'
validate_certs: False
vm_name: 'vm_name_as_per_vcenter'
destination_host: 'destination_host_as_per_vcenter'
destination_datastore: 'destination_datastore_as_per_vcenter'
delegate_to: localhost
'''
RETURN = '''
running_host:
description: List the host the virtual machine is registered to
returned: changed or success
type: string
sample: 'host1.example.com'
'''
try:
from pyVmomi import vim
except ImportError:
pass
from ansible.module_utils._text import to_native
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.vmware import (PyVmomi, find_hostsystem_by_name,
find_vm_by_id, find_datastore_by_name,
vmware_argument_spec, wait_for_task, TaskError)
class VmotionManager(PyVmomi):
def __init__(self, module):
super(VmotionManager, self).__init__(module)
self.vm = None
self.vm_uuid = self.params.get('vm_uuid', None)
self.vm_name = self.params.get('vm_name', None)
result = dict()
self.get_vm()
if self.vm is None:
self.module.fail_json(msg="Failed to find the virtual"
" machine with %s" % (self.vm_uuid or self.vm_name))
# Get Destination Host System if specified by user
dest_host_name = self.params.get('destination_host', None)
self.host_object = None
if dest_host_name is not None:
self.host_object = find_hostsystem_by_name(content=self.content,
hostname=dest_host_name)
# Get Destination Datastore if specified by user
dest_datastore = self.params.get('destination_datastore', None)
self.datastore_object = None
if dest_datastore is not None:
self.datastore_object = find_datastore_by_name(content=self.content,
datastore_name=dest_datastore)
# Atleast one of datastore, host system is required to migrate
if self.datastore_object is None and self.host_object is None:
self.module.fail_json(msg="Unable to find destination datastore"
" and destination host system.")
# Check if datastore is required, this check is required if destination
# and source host system does not share same datastore.
host_datastore_required = []
for vm_datastore in self.vm.datastore:
if self.host_object and vm_datastore not in self.host_object.datastore:
host_datastore_required.append(True)
else:
host_datastore_required.append(False)
if any(host_datastore_required) and dest_datastore is None:
msg = "Destination host system does not share" \
" datastore ['%s'] with source host system ['%s'] on which" \
" virtual machine is located. Please specify destination_datastore" \
" to rectify this problem." % ("', '".join([ds.name for ds in self.host_object.datastore]),
"', '".join([ds.name for ds in self.vm.datastore]))
self.module.fail_json(msg=msg)
storage_vmotion_needed = True
change_required = True
if self.host_object and self.datastore_object:
# We have both host system and datastore object
if not self.datastore_object.summary.accessible:
# Datastore is not accessible
self.module.fail_json(msg='Destination datastore %s is'
' not accessible.' % dest_datastore)
if self.datastore_object not in self.host_object.datastore:
# Datastore is not associated with host system
self.module.fail_json(msg="Destination datastore %s provided"
" is not associated with destination"
" host system %s. Please specify"
" datastore value ['%s'] associated with"
" the given host system." % (dest_datastore,
dest_host_name,
"', '".join([ds.name for ds in self.host_object.datastore])))
if self.vm.runtime.host.name == dest_host_name and dest_datastore in [ds.name for ds in self.vm.datastore]:
change_required = False
if self.host_object and self.datastore_object is None:
if self.vm.runtime.host.name == dest_host_name:
# VM is already located on same host
change_required = False
storage_vmotion_needed = False
elif self.datastore_object and self.host_object is None:
if self.datastore_object in self.vm.datastore:
# VM is already located on same datastore
change_required = False
if not self.datastore_object.summary.accessible:
# Datastore is not accessible
self.module.fail_json(msg='Destination datastore %s is'
' not accessible.' % dest_datastore)
if module.check_mode:
result['running_host'] = module.params['destination_host']
result['changed'] = True
module.exit_json(**result)
if change_required:
# Migrate VM and get Task object back
task_object = self.migrate_vm()
# Wait for task to complete
try:
wait_for_task(task_object)
except TaskError as task_error:
self.module.fail_json(msg=to_native(task_error))
# If task was a success the VM has moved, update running_host and complete module
if task_object.info.state == vim.TaskInfo.State.success:
# The storage layout is not automatically refreshed, so we trigger it to get coherent module return values
if storage_vmotion_needed:
self.vm.RefreshStorageInfo()
result['running_host'] = module.params['destination_host']
result['changed'] = True
module.exit_json(**result)
else:
msg = 'Unable to migrate virtual machine due to an error, please check vCenter'
if task_object.info.error is not None:
msg += " : %s" % task_object.info.error
module.fail_json(msg=msg)
else:
try:
host = self.vm.summary.runtime.host
result['running_host'] = host.summary.config.name
except vim.fault.NoPermission:
result['running_host'] = 'NA'
result['changed'] = False
module.exit_json(**result)
def migrate_vm(self):
"""
Migrate virtual machine and return the task.
"""
relocate_spec = vim.vm.RelocateSpec(host=self.host_object,
datastore=self.datastore_object)
task_object = self.vm.Relocate(relocate_spec)
return task_object
def get_vm(self):
"""
Find unique virtual machine either by UUID or Name.
Returns: virtual machine object if found, else None.
"""
vms = []
if self.vm_uuid:
vm_obj = find_vm_by_id(self.content, vm_id=self.params['vm_uuid'], vm_id_type="uuid")
vms = [vm_obj]
elif self.vm_name:
objects = self.get_managed_objects_properties(vim_type=vim.VirtualMachine, properties=['name'])
for temp_vm_object in objects:
if len(temp_vm_object.propSet) != 1:
continue
if temp_vm_object.obj.name == self.vm_name:
vms.append(temp_vm_object.obj)
break
if len(vms) > 1:
self.module.fail_json(msg="Multiple virtual machines with same name %s found."
" Please specify vm_uuid instead of vm_name." % self.vm_name)
self.vm = vms[0]
def main():
argument_spec = vmware_argument_spec()
argument_spec.update(
dict(
vm_name=dict(aliases=['vm']),
vm_uuid=dict(aliases=['uuid']),
destination_host=dict(aliases=['destination']),
destination_datastore=dict(aliases=['datastore'])
)
)
module = AnsibleModule(
argument_spec=argument_spec,
supports_check_mode=True,
required_one_of=[
['destination_host', 'destination_datastore'],
['vm_uuid', 'vm_name'],
],
mutually_exclusive=[
['vm_uuid', 'vm_name'],
],
)
vmotion_manager = VmotionManager(module)
if __name__ == '__main__':
main()