VMware: new module: vmware_guest_disk (#36165)
1. Add support for SCSI controller creation 2. Support for SCSI Type 3. Warn about Disk Unit Number 7 4. Allow specifying SCSI controller 5. Allow specifying Disk Unit Number 6. Idempotency 7. Remove disks 8. Do not allow to reduce disk 9. More than 15 disks support 10. No SCSI controller and unit number check 11. Thin support 12. Update documentation and example 13. Multiple Datastore and datastore cluster support 14. Check datatype of disk unit number and SCSI controller 15. Handle disk_size when it is int or float Signed-off-by: Abhijeet Kasurde <akasurde@redhat.com>
This commit is contained in:
parent
2d66695fd7
commit
b48526ca65
1 changed files with 662 additions and 0 deletions
662
lib/ansible/modules/cloud/vmware/vmware_guest_disk.py
Normal file
662
lib/ansible/modules/cloud/vmware/vmware_guest_disk.py
Normal file
|
@ -0,0 +1,662 @@
|
||||||
|
#!/usr/bin/python
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Copyright: (c) 2018, Ansible Project
|
||||||
|
# Copyright: (c) 2018, Abhijeet Kasurde <akasurde@redhat.com>
|
||||||
|
# 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 = '''
|
||||||
|
---
|
||||||
|
module: vmware_guest_disk
|
||||||
|
short_description: Manage disks related to virtual machine in given vCenter infrastructure
|
||||||
|
description:
|
||||||
|
- This module can be used to add, remove and update disks belonging to given virtual machine.
|
||||||
|
- All parameters and VMware object names are case sensitive.
|
||||||
|
- This module is destructive in nature, please read documentation carefully before proceeding.
|
||||||
|
- Be careful while removing disk specified as this may lead to data loss.
|
||||||
|
version_added: 2.8
|
||||||
|
author:
|
||||||
|
- Abhijeet Kasurde (@akasurde) <akasurde@redhat.com>
|
||||||
|
notes:
|
||||||
|
- Tested on vSphere 6.0 and 6.5
|
||||||
|
requirements:
|
||||||
|
- "python >= 2.6"
|
||||||
|
- PyVmomi
|
||||||
|
options:
|
||||||
|
name:
|
||||||
|
description:
|
||||||
|
- Name of the virtual machine.
|
||||||
|
- This is a required parameter, if parameter C(uuid) is not supplied.
|
||||||
|
uuid:
|
||||||
|
description:
|
||||||
|
- UUID of the instance to gather facts if known, this is VMware's unique identifier.
|
||||||
|
- This is a required parameter, if parameter C(name) is not supplied.
|
||||||
|
folder:
|
||||||
|
description:
|
||||||
|
- Destination folder, absolute or relative path to find an existing guest.
|
||||||
|
- This is a required parameter, only if multiple VMs are found with same name.
|
||||||
|
- The folder should include the datacenter. ESX'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'
|
||||||
|
datacenter:
|
||||||
|
description:
|
||||||
|
- The datacenter name to which virtual machine belongs to.
|
||||||
|
required: True
|
||||||
|
disk:
|
||||||
|
description:
|
||||||
|
- A list of disks to add.
|
||||||
|
- The virtual disk related information is provided using this list.
|
||||||
|
- All values and parameters are case sensitive.
|
||||||
|
- 'Valid attributes are:'
|
||||||
|
- ' - C(size[_tb,_gb,_mb,_kb]) (integer): Disk storage size in specified unit.'
|
||||||
|
- ' If C(size) specified then unit must be specified. There is no space allowed in between size number and unit.'
|
||||||
|
- ' Only first occurance in disk element will be considered, even if there are multiple size* parameters available.'
|
||||||
|
- ' - C(type) (string): Valid values are:'
|
||||||
|
- ' - C(thin) thin disk'
|
||||||
|
- ' - C(eagerzeroedthick) eagerzeroedthick disk'
|
||||||
|
- ' - C(thick) thick disk'
|
||||||
|
- ' Default: C(thick) thick disk, no eagerzero.'
|
||||||
|
- ' - C(datastore) (string): Name of datastore or datastore cluster to be used for the disk.'
|
||||||
|
- ' - C(autoselect_datastore) (bool): Select the less used datastore. Specify only if C(datastore) is not specified.'
|
||||||
|
- ' - C(scsi_controller) (integer): SCSI controller number. Valid value range from 0 to 3.'
|
||||||
|
- ' Only 4 SCSI controllers are allowed per VM.'
|
||||||
|
- ' Care should be taken while specifying C(scsi_controller) is 0 and C(unit_number) as 0 as this disk may contain OS.'
|
||||||
|
- ' - C(unit_number) (integer): Disk Unit Number. Valid value range from 0 to 15. Only 15 disks are allowed per SCSI Controller.'
|
||||||
|
- ' - C(scsi_type) (string): Type of SCSI controller. This value is required only for the first occurance of SCSI Controller.'
|
||||||
|
- ' This value is ignored, if SCSI Controller is already present or C(state) is C(absent).'
|
||||||
|
- ' Valid values are C(buslogic), C(lsilogic), C(lsilogicsas) and C(paravirtual).'
|
||||||
|
- ' C(paravirtual) is default value for this parameter.'
|
||||||
|
- ' - C(state) (string): State of disk. This is either "absent" or "present".'
|
||||||
|
- ' If C(state) is set to C(absent), disk will be removed permanently from virtual machine configuration and from VMware storage.'
|
||||||
|
- ' If C(state) is set to C(present), disk will be added if not present at given SCSI Controller and Unit Number.'
|
||||||
|
- ' If C(state) is set to C(present) and disk exists with different size, disk size is increased.'
|
||||||
|
- ' Reducing disk size is not allowed.'
|
||||||
|
default: []
|
||||||
|
extends_documentation_fragment: vmware.documentation
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
- name: Add disks to virtual machine using UUID
|
||||||
|
vmware_guest_disk:
|
||||||
|
hostname: "{{ vcenter_hostname }}"
|
||||||
|
username: "{{ vcenter_username }}"
|
||||||
|
password: "{{ vcenter_password }}"
|
||||||
|
datacenter: "{{ datacenter_name }}"
|
||||||
|
validate_certs: no
|
||||||
|
uuid: 421e4592-c069-924d-ce20-7e7533fab926
|
||||||
|
disk:
|
||||||
|
- size_mb: 10
|
||||||
|
type: thin
|
||||||
|
datastore: datacluster0
|
||||||
|
state: present
|
||||||
|
scsi_controller: 1
|
||||||
|
unit_number: 1
|
||||||
|
scsi_type: 'paravirtual'
|
||||||
|
- size_gb: 10
|
||||||
|
type: eagerzeroedthick
|
||||||
|
state: present
|
||||||
|
autoselect_datastore: True
|
||||||
|
scsi_controller: 2
|
||||||
|
scsi_type: 'buslogic'
|
||||||
|
unit_number: 12
|
||||||
|
- size: 10Gb
|
||||||
|
type: eagerzeroedthick
|
||||||
|
state: present
|
||||||
|
autoselect_datastore: True
|
||||||
|
scsi_controller: 2
|
||||||
|
scsi_type: 'buslogic'
|
||||||
|
unit_number: 1
|
||||||
|
delegate_to: localhost
|
||||||
|
register: disk_facts
|
||||||
|
|
||||||
|
- name: Remove disks from virtual machine using name
|
||||||
|
vmware_guest_disk:
|
||||||
|
hostname: "{{ vcenter_hostname }}"
|
||||||
|
username: "{{ vcenter_username }}"
|
||||||
|
password: "{{ vcenter_password }}"
|
||||||
|
datacenter: "{{ datacenter_name }}"
|
||||||
|
validate_certs: no
|
||||||
|
name: VM_225
|
||||||
|
disk:
|
||||||
|
- state: absent
|
||||||
|
scsi_controller: 1
|
||||||
|
unit_number: 1
|
||||||
|
delegate_to: localhost
|
||||||
|
register: disk_facts
|
||||||
|
'''
|
||||||
|
|
||||||
|
RETURN = """
|
||||||
|
disk_status:
|
||||||
|
description: metadata about the virtual machine's disks after managing them
|
||||||
|
returned: always
|
||||||
|
type: dict
|
||||||
|
sample: {
|
||||||
|
"0": {
|
||||||
|
"backing_datastore": "datastore2",
|
||||||
|
"backing_disk_mode": "persistent",
|
||||||
|
"backing_eagerlyscrub": false,
|
||||||
|
"backing_filename": "[datastore2] VM_225/VM_225.vmdk",
|
||||||
|
"backing_thinprovisioned": false,
|
||||||
|
"backing_writethrough": false,
|
||||||
|
"capacity_in_bytes": 10485760,
|
||||||
|
"capacity_in_kb": 10240,
|
||||||
|
"controller_key": 1000,
|
||||||
|
"key": 2000,
|
||||||
|
"label": "Hard disk 1",
|
||||||
|
"summary": "10,240 KB",
|
||||||
|
"unit_number": 0
|
||||||
|
},
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
|
||||||
|
import re
|
||||||
|
try:
|
||||||
|
from pyVmomi import vim, vmodl
|
||||||
|
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, wait_for_task, find_obj, get_all_objs
|
||||||
|
|
||||||
|
|
||||||
|
class PyVmomiHelper(PyVmomi):
|
||||||
|
def __init__(self, module):
|
||||||
|
super(PyVmomiHelper, self).__init__(module)
|
||||||
|
self.desired_disks = self.params['disk'] # Match with vmware_guest parameter
|
||||||
|
self.vm = None
|
||||||
|
self.scsi_device_type = dict(lsilogic=vim.vm.device.VirtualLsiLogicController,
|
||||||
|
paravirtual=vim.vm.device.ParaVirtualSCSIController,
|
||||||
|
buslogic=vim.vm.device.VirtualBusLogicController,
|
||||||
|
lsilogicsas=vim.vm.device.VirtualLsiLogicSASController)
|
||||||
|
self.config_spec = vim.vm.ConfigSpec()
|
||||||
|
self.config_spec.deviceChange = []
|
||||||
|
|
||||||
|
def create_scsi_controller(self, scsi_type, scsi_bus_number):
|
||||||
|
"""
|
||||||
|
Create SCSI Controller with given SCSI Type and SCSI Bus Number
|
||||||
|
Args:
|
||||||
|
scsi_type: Type of SCSI
|
||||||
|
scsi_bus_number: SCSI Bus number to be assigned
|
||||||
|
|
||||||
|
Returns: Virtual device spec for SCSI Controller
|
||||||
|
|
||||||
|
"""
|
||||||
|
scsi_ctl = vim.vm.device.VirtualDeviceSpec()
|
||||||
|
scsi_ctl.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
|
||||||
|
scsi_ctl.device = self.scsi_device_type[scsi_type]()
|
||||||
|
scsi_ctl.device.unitNumber = 3
|
||||||
|
scsi_ctl.device.busNumber = scsi_bus_number
|
||||||
|
scsi_ctl.device.hotAddRemove = True
|
||||||
|
scsi_ctl.device.sharedBus = 'noSharing'
|
||||||
|
scsi_ctl.device.scsiCtlrUnitNumber = 7
|
||||||
|
|
||||||
|
return scsi_ctl
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def create_scsi_disk(scsi_ctl_key, disk_index):
|
||||||
|
"""
|
||||||
|
Create Virtual Device Spec for virtual disk
|
||||||
|
Args:
|
||||||
|
scsi_ctl_key: Unique SCSI Controller Key
|
||||||
|
disk_index: Disk unit number at which disk needs to be attached
|
||||||
|
|
||||||
|
Returns: Virtual Device Spec for virtual disk
|
||||||
|
|
||||||
|
"""
|
||||||
|
disk_spec = vim.vm.device.VirtualDeviceSpec()
|
||||||
|
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
|
||||||
|
disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.create
|
||||||
|
disk_spec.device = vim.vm.device.VirtualDisk()
|
||||||
|
disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
|
||||||
|
disk_spec.device.backing.diskMode = 'persistent'
|
||||||
|
disk_spec.device.controllerKey = scsi_ctl_key
|
||||||
|
disk_spec.device.unitNumber = disk_index
|
||||||
|
return disk_spec
|
||||||
|
|
||||||
|
def reconfigure_vm(self, config_spec, device_type):
|
||||||
|
"""
|
||||||
|
Reconfigure virtual machine after modifying device spec
|
||||||
|
Args:
|
||||||
|
config_spec: Config Spec
|
||||||
|
device_type: Type of device being modified
|
||||||
|
|
||||||
|
Returns: Boolean status 'changed' and actual task result
|
||||||
|
|
||||||
|
"""
|
||||||
|
changed, results = (False, '')
|
||||||
|
try:
|
||||||
|
# Perform actual VM reconfiguration
|
||||||
|
task = self.vm.ReconfigVM_Task(spec=config_spec)
|
||||||
|
changed, results = wait_for_task(task)
|
||||||
|
except vim.fault.InvalidDeviceSpec as invalid_device_spec:
|
||||||
|
self.module.fail_json(msg="Failed to manage %s on given virtual machine due to invalid"
|
||||||
|
" device spec : %s" % (device_type, to_native(invalid_device_spec.msg)),
|
||||||
|
details="Please check ESXi server logs for more details.")
|
||||||
|
except vim.fault.RestrictedVersion as e:
|
||||||
|
self.module.fail_json(msg="Failed to reconfigure virtual machine due to"
|
||||||
|
" product versioning restrictions: %s" % to_native(e.msg))
|
||||||
|
|
||||||
|
return changed, results
|
||||||
|
|
||||||
|
def ensure_disks(self, vm_obj=None):
|
||||||
|
"""
|
||||||
|
Manage internal state of virtual machine disks
|
||||||
|
Args:
|
||||||
|
vm_obj: Managed object of virtual machine
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Set vm object
|
||||||
|
self.vm = vm_obj
|
||||||
|
# Sanitize user input
|
||||||
|
disk_data = self.sanitize_disk_inputs()
|
||||||
|
# Create stateful information about SCSI devices
|
||||||
|
current_scsi_info = dict()
|
||||||
|
results = dict(changed=False, disk_data=None, disk_changes=dict())
|
||||||
|
|
||||||
|
# Deal with SCSI Controller
|
||||||
|
for device in vm_obj.config.hardware.device:
|
||||||
|
if isinstance(device, tuple(self.scsi_device_type.values())):
|
||||||
|
# Found SCSI device
|
||||||
|
if device.busNumber not in current_scsi_info:
|
||||||
|
device_bus_number = 1000 + device.busNumber
|
||||||
|
current_scsi_info[device_bus_number] = dict(disks=dict())
|
||||||
|
|
||||||
|
scsi_changed = False
|
||||||
|
for disk in disk_data:
|
||||||
|
scsi_controller = disk['scsi_controller'] + 1000
|
||||||
|
if scsi_controller not in current_scsi_info and disk['state'] == 'present':
|
||||||
|
scsi_ctl = self.create_scsi_controller(disk['scsi_type'], disk['scsi_controller'])
|
||||||
|
current_scsi_info[scsi_controller] = dict(disks=dict())
|
||||||
|
self.config_spec.deviceChange.append(scsi_ctl)
|
||||||
|
scsi_changed = True
|
||||||
|
if scsi_changed:
|
||||||
|
self.reconfigure_vm(self.config_spec, 'SCSI Controller')
|
||||||
|
self.config_spec = vim.vm.ConfigSpec()
|
||||||
|
self.config_spec.deviceChange = []
|
||||||
|
|
||||||
|
# Deal with Disks
|
||||||
|
for device in vm_obj.config.hardware.device:
|
||||||
|
if isinstance(device, vim.vm.device.VirtualDisk):
|
||||||
|
# Found Virtual Disk device
|
||||||
|
if device.controllerKey not in current_scsi_info:
|
||||||
|
current_scsi_info[device.controllerKey] = dict(disks=dict())
|
||||||
|
current_scsi_info[device.controllerKey]['disks'][device.unitNumber] = device
|
||||||
|
|
||||||
|
vm_name = self.vm.name
|
||||||
|
disk_change_list = []
|
||||||
|
for disk in disk_data:
|
||||||
|
disk_change = False
|
||||||
|
scsi_controller = disk['scsi_controller'] + 1000 # VMware auto assign 1000 + SCSI Controller
|
||||||
|
if disk['disk_unit_number'] not in current_scsi_info[scsi_controller]['disks'] and disk['state'] == 'present':
|
||||||
|
# Add new disk
|
||||||
|
disk_spec = self.create_scsi_disk(scsi_controller, disk['disk_unit_number'])
|
||||||
|
disk_spec.device.capacityInKB = disk['size']
|
||||||
|
if disk['disk_type'] == 'thin':
|
||||||
|
disk_spec.device.backing.thinProvisioned = True
|
||||||
|
elif disk['disk_type'] == 'eagerzeroedthick':
|
||||||
|
disk_spec.device.backing.eagerlyScrub = True
|
||||||
|
disk_spec.device.backing.fileName = "[%s] %s/%s_%s_%s.vmdk" % (disk['datastore'].name,
|
||||||
|
vm_name, vm_name,
|
||||||
|
str(scsi_controller),
|
||||||
|
str(disk['disk_unit_number']))
|
||||||
|
disk_spec.device.backing.datastore = disk['datastore']
|
||||||
|
self.config_spec.deviceChange.append(disk_spec)
|
||||||
|
disk_change = True
|
||||||
|
current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']] = disk_spec.device
|
||||||
|
results['disk_changes'][disk['disk_index']] = "Disk created."
|
||||||
|
elif disk['disk_unit_number'] in current_scsi_info[scsi_controller]['disks']:
|
||||||
|
if disk['state'] == 'present':
|
||||||
|
disk_spec = vim.vm.device.VirtualDeviceSpec()
|
||||||
|
# set the operation to edit so that it knows to keep other settings
|
||||||
|
disk_spec.device = current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']]
|
||||||
|
# Edit and no resizing allowed
|
||||||
|
if disk['size'] < disk_spec.device.capacityInKB:
|
||||||
|
self.module.fail_json(msg="Given disk size at disk index [%s] is smaller than found (%d < %d)."
|
||||||
|
" Reducing disks is not allowed." % (disk['disk_index'],
|
||||||
|
disk['size'],
|
||||||
|
disk_spec.device.capacityInKB))
|
||||||
|
if disk['size'] != disk_spec.device.capacityInKB:
|
||||||
|
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
|
||||||
|
disk_spec.device.capacityInKB = disk['size']
|
||||||
|
self.config_spec.deviceChange.append(disk_spec)
|
||||||
|
disk_change = True
|
||||||
|
results['disk_changes'][disk['disk_index']] = "Disk size increased."
|
||||||
|
else:
|
||||||
|
results['disk_changes'][disk['disk_index']] = "Disk already exists."
|
||||||
|
|
||||||
|
elif disk['state'] == 'absent':
|
||||||
|
# Disk already exists, deleting
|
||||||
|
disk_spec = vim.vm.device.VirtualDeviceSpec()
|
||||||
|
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.remove
|
||||||
|
disk_spec.fileOperation = vim.vm.device.VirtualDeviceSpec.FileOperation.destroy
|
||||||
|
disk_spec.device = current_scsi_info[scsi_controller]['disks'][disk['disk_unit_number']]
|
||||||
|
self.config_spec.deviceChange.append(disk_spec)
|
||||||
|
disk_change = True
|
||||||
|
results['disk_changes'][disk['disk_index']] = "Disk deleted."
|
||||||
|
|
||||||
|
if disk_change:
|
||||||
|
# Adding multiple disks in a single attempt raises weird errors
|
||||||
|
# So adding single disk at a time.
|
||||||
|
self.reconfigure_vm(self.config_spec, 'disks')
|
||||||
|
self.config_spec = vim.vm.ConfigSpec()
|
||||||
|
self.config_spec.deviceChange = []
|
||||||
|
disk_change_list.append(disk_change)
|
||||||
|
|
||||||
|
if any(disk_change_list):
|
||||||
|
results['changed'] = True
|
||||||
|
results['disk_data'] = self.gather_disk_facts(vm_obj=self.vm)
|
||||||
|
self.module.exit_json(**results)
|
||||||
|
|
||||||
|
def sanitize_disk_inputs(self):
|
||||||
|
"""
|
||||||
|
Check correctness of disk input provided by user
|
||||||
|
Returns: A list of dictionary containing disk information
|
||||||
|
|
||||||
|
"""
|
||||||
|
disks_data = list()
|
||||||
|
if not self.desired_disks:
|
||||||
|
self.module.exit_json(changed=False, msg="No disks provided for virtual"
|
||||||
|
" machine '%s' for management." % self.vm.name)
|
||||||
|
|
||||||
|
for disk_index, disk in enumerate(self.desired_disks):
|
||||||
|
# Initialize default value for disk
|
||||||
|
current_disk = dict(disk_index=disk_index,
|
||||||
|
state='present',
|
||||||
|
datastore=None,
|
||||||
|
autoselect_datastore=True,
|
||||||
|
disk_unit_number=0,
|
||||||
|
scsi_controller=0)
|
||||||
|
# Check state
|
||||||
|
if 'state' in disk:
|
||||||
|
if disk['state'] not in ['absent', 'present']:
|
||||||
|
self.module.fail_json(msg="Invalid state provided '%s' for disk index [%s]."
|
||||||
|
" State can be either - 'absent', 'present'" % (disk['state'],
|
||||||
|
disk_index))
|
||||||
|
else:
|
||||||
|
current_disk['state'] = disk['state']
|
||||||
|
|
||||||
|
if current_disk['state'] == 'present':
|
||||||
|
# Select datastore or datastore cluster
|
||||||
|
if 'datastore' in disk:
|
||||||
|
if 'autoselect_datastore' in disk:
|
||||||
|
self.module.fail_json(msg="Please specify either 'datastore' "
|
||||||
|
"or 'autoselect_datastore' for disk index [%s]" % disk_index)
|
||||||
|
|
||||||
|
# Check if given value is datastore or datastore cluster
|
||||||
|
datastore_name = disk['datastore']
|
||||||
|
datastore_cluster = find_obj(self.content, [vim.StoragePod], datastore_name)
|
||||||
|
if datastore_cluster:
|
||||||
|
# If user specified datastore cluster so get recommended datastore
|
||||||
|
datastore_name = self.get_recommended_datastore(datastore_cluster_obj=datastore_cluster)
|
||||||
|
# Check if get_recommended_datastore or user specified datastore exists or not
|
||||||
|
datastore = find_obj(self.content, [vim.Datastore], datastore_name)
|
||||||
|
if datastore is None:
|
||||||
|
self.module.fail_json(msg="Failed to find datastore named '%s' "
|
||||||
|
"in given configuration." % disk['datastore'])
|
||||||
|
current_disk['datastore'] = datastore
|
||||||
|
current_disk['autoselect_datastore'] = False
|
||||||
|
elif 'autoselect_datastore' in disk:
|
||||||
|
# Find datastore which fits requirement
|
||||||
|
datastores = get_all_objs(self.content, [vim.Datastore])
|
||||||
|
if not datastores:
|
||||||
|
self.module.fail_json(msg="Failed to gather information about"
|
||||||
|
" available datastores in given datacenter.")
|
||||||
|
datastore = None
|
||||||
|
datastore_freespace = 0
|
||||||
|
for ds in datastores:
|
||||||
|
if ds.summary.freeSpace > datastore_freespace:
|
||||||
|
# If datastore field is provided, filter destination datastores
|
||||||
|
datastore = ds
|
||||||
|
datastore_freespace = ds.summary.freeSpace
|
||||||
|
current_disk['datastore'] = datastore
|
||||||
|
|
||||||
|
if 'datastore' not in disk and 'autoselect_datastore' not in disk:
|
||||||
|
self.module.fail_json(msg="Either 'datastore' or 'autoselect_datastore' is"
|
||||||
|
" required parameter while creating disk for "
|
||||||
|
"disk index [%s]." % disk_index)
|
||||||
|
|
||||||
|
if [x for x in disk.keys() if x.startswith('size_') or x == 'size']:
|
||||||
|
# size, size_tb, size_gb, size_mb, size_kb
|
||||||
|
disk_size_parse_failed = False
|
||||||
|
if 'size' in disk:
|
||||||
|
size_regex = re.compile(r'(\d+(?:\.\d+)?)([tgmkTGMK][bB])')
|
||||||
|
disk_size_m = size_regex.match(disk['size'])
|
||||||
|
if disk_size_m:
|
||||||
|
expected = disk_size_m.group(1)
|
||||||
|
unit = disk_size_m.group(2)
|
||||||
|
else:
|
||||||
|
disk_size_parse_failed = True
|
||||||
|
try:
|
||||||
|
if re.match(r'\d+\.\d+', expected):
|
||||||
|
# We found float value in string, let's typecast it
|
||||||
|
expected = float(expected)
|
||||||
|
else:
|
||||||
|
# We found int value in string, let's typecast it
|
||||||
|
expected = int(expected)
|
||||||
|
except (TypeError, ValueError, NameError):
|
||||||
|
disk_size_parse_failed = True
|
||||||
|
else:
|
||||||
|
# Even multiple size_ parameter provided by user,
|
||||||
|
# consider first value only
|
||||||
|
param = [x for x in disk.keys() if x.startswith('size_')][0]
|
||||||
|
unit = param.split('_')[-1]
|
||||||
|
disk_size = disk[param]
|
||||||
|
if isinstance(disk_size, (float, int)):
|
||||||
|
disk_size = str(disk_size)
|
||||||
|
|
||||||
|
try:
|
||||||
|
if re.match(r'\d+\.\d+', disk_size):
|
||||||
|
# We found float value in string, let's typecast it
|
||||||
|
expected = float(disk_size)
|
||||||
|
else:
|
||||||
|
# We found int value in string, let's typecast it
|
||||||
|
expected = int(disk_size)
|
||||||
|
except (TypeError, ValueError, NameError):
|
||||||
|
disk_size_parse_failed = True
|
||||||
|
|
||||||
|
if disk_size_parse_failed:
|
||||||
|
# Common failure
|
||||||
|
self.module.fail_json(msg="Failed to parse disk size for disk index [%s],"
|
||||||
|
" please review value provided"
|
||||||
|
" using documentation." % disk_index)
|
||||||
|
|
||||||
|
disk_units = dict(tb=3, gb=2, mb=1, kb=0)
|
||||||
|
unit = unit.lower()
|
||||||
|
if unit in disk_units:
|
||||||
|
current_disk['size'] = expected * (1024 ** disk_units[unit])
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg="%s is not a supported unit for disk size for disk index [%s]."
|
||||||
|
" Supported units are ['%s']." % (unit,
|
||||||
|
disk_index,
|
||||||
|
"', '".join(disk_units.keys())))
|
||||||
|
|
||||||
|
else:
|
||||||
|
# No size found but disk, fail
|
||||||
|
self.module.fail_json(msg="No size, size_kb, size_mb, size_gb or size_tb"
|
||||||
|
" attribute found into disk index [%s] configuration." % disk_index)
|
||||||
|
# Check SCSI controller key
|
||||||
|
if 'scsi_controller' in disk:
|
||||||
|
try:
|
||||||
|
temp_disk_controller = int(disk['scsi_controller'])
|
||||||
|
except ValueError:
|
||||||
|
self.module.fail_json(msg="Invalid SCSI controller ID '%s' specified"
|
||||||
|
" at index [%s]" % (disk['scsi_controller'], disk_index))
|
||||||
|
if temp_disk_controller not in range(0, 4):
|
||||||
|
# Only 4 SCSI controllers are allowed per VM
|
||||||
|
self.module.fail_json(msg="Invalid SCSI controller ID specified [%s],"
|
||||||
|
" please specify value between 0 to 3 only." % temp_disk_controller)
|
||||||
|
current_disk['scsi_controller'] = temp_disk_controller
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg="Please specify 'scsi_controller' under disk parameter"
|
||||||
|
" at index [%s], which is required while creating disk." % disk_index)
|
||||||
|
# Check for disk unit number
|
||||||
|
if 'unit_number' in disk:
|
||||||
|
try:
|
||||||
|
temp_disk_unit_number = int(disk['unit_number'])
|
||||||
|
except ValueError:
|
||||||
|
self.module.fail_json(msg="Invalid Disk unit number ID '%s'"
|
||||||
|
" specified at index [%s]" % (disk['unit_number'], disk_index))
|
||||||
|
if temp_disk_unit_number not in range(0, 16):
|
||||||
|
self.module.fail_json(msg="Invalid Disk unit number ID specified for disk [%s] at index [%s],"
|
||||||
|
" please specify value between 0 to 15"
|
||||||
|
" only (excluding 7)." % (temp_disk_unit_number, disk_index))
|
||||||
|
|
||||||
|
if temp_disk_unit_number == 7:
|
||||||
|
self.module.fail_json(msg="Invalid Disk unit number ID specified for disk at index [%s],"
|
||||||
|
" please specify value other than 7 as it is reserved"
|
||||||
|
"for SCSI Controller" % disk_index)
|
||||||
|
current_disk['disk_unit_number'] = temp_disk_unit_number
|
||||||
|
|
||||||
|
else:
|
||||||
|
self.module.fail_json(msg="Please specify 'unit_number' under disk parameter"
|
||||||
|
" at index [%s], which is required while creating disk." % disk_index)
|
||||||
|
|
||||||
|
# Type of Disk
|
||||||
|
disk_type = disk.get('type', 'thick').lower()
|
||||||
|
if disk_type not in ['thin', 'thick', 'eagerzeroedthick']:
|
||||||
|
self.module.fail_json(msg="Invalid 'disk_type' specified for disk index [%s]. Please specify"
|
||||||
|
" 'disk_type' value from ['thin', 'thick', 'eagerzeroedthick']." % disk_index)
|
||||||
|
current_disk['disk_type'] = disk_type
|
||||||
|
|
||||||
|
# SCSI Controller Type
|
||||||
|
scsi_contrl_type = disk.get('scsi_type', 'paravirtual').lower()
|
||||||
|
if scsi_contrl_type not in self.scsi_device_type.keys():
|
||||||
|
self.module.fail_json(msg="Invalid 'scsi_type' specified for disk index [%s]. Please specify"
|
||||||
|
" 'scsi_type' value from ['%s']" % (disk_index,
|
||||||
|
"', '".join(self.scsi_device_type.keys())))
|
||||||
|
current_disk['scsi_type'] = scsi_contrl_type
|
||||||
|
|
||||||
|
disks_data.append(current_disk)
|
||||||
|
return disks_data
|
||||||
|
|
||||||
|
def get_recommended_datastore(self, datastore_cluster_obj):
|
||||||
|
"""
|
||||||
|
Return Storage DRS recommended datastore from datastore cluster
|
||||||
|
Args:
|
||||||
|
datastore_cluster_obj: datastore cluster managed object
|
||||||
|
|
||||||
|
Returns: Name of recommended datastore from the given datastore cluster,
|
||||||
|
Returns None if no datastore recommendation found.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# Check if Datastore Cluster provided by user is SDRS ready
|
||||||
|
sdrs_status = datastore_cluster_obj.podStorageDrsEntry.storageDrsConfig.podConfig.enabled
|
||||||
|
if sdrs_status:
|
||||||
|
# We can get storage recommendation only if SDRS is enabled on given datastorage cluster
|
||||||
|
pod_sel_spec = vim.storageDrs.PodSelectionSpec()
|
||||||
|
pod_sel_spec.storagePod = datastore_cluster_obj
|
||||||
|
storage_spec = vim.storageDrs.StoragePlacementSpec()
|
||||||
|
storage_spec.podSelectionSpec = pod_sel_spec
|
||||||
|
storage_spec.type = 'create'
|
||||||
|
|
||||||
|
try:
|
||||||
|
rec = self.content.storageResourceManager.RecommendDatastores(storageSpec=storage_spec)
|
||||||
|
rec_action = rec.recommendations[0].action[0]
|
||||||
|
return rec_action.destination.name
|
||||||
|
except Exception as e:
|
||||||
|
# There is some error so we fall back to general workflow
|
||||||
|
pass
|
||||||
|
datastore = None
|
||||||
|
datastore_freespace = 0
|
||||||
|
for ds in datastore_cluster_obj.childEntity:
|
||||||
|
if ds.summary.freeSpace > datastore_freespace:
|
||||||
|
# If datastore field is provided, filter destination datastores
|
||||||
|
datastore = ds
|
||||||
|
datastore_freespace = ds.summary.freeSpace
|
||||||
|
if datastore:
|
||||||
|
return datastore.name
|
||||||
|
return None
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def gather_disk_facts(vm_obj):
|
||||||
|
"""
|
||||||
|
Gather facts about VM's disks
|
||||||
|
Args:
|
||||||
|
vm_obj: Managed object of virtual machine
|
||||||
|
|
||||||
|
Returns: A list of dict containing disks information
|
||||||
|
|
||||||
|
"""
|
||||||
|
disks_facts = dict()
|
||||||
|
if vm_obj is None:
|
||||||
|
return disks_facts
|
||||||
|
|
||||||
|
disk_index = 0
|
||||||
|
for disk in vm_obj.config.hardware.device:
|
||||||
|
if isinstance(disk, vim.vm.device.VirtualDisk):
|
||||||
|
disks_facts[disk_index] = dict(
|
||||||
|
key=disk.key,
|
||||||
|
label=disk.deviceInfo.label,
|
||||||
|
summary=disk.deviceInfo.summary,
|
||||||
|
backing_filename=disk.backing.fileName,
|
||||||
|
backing_datastore=disk.backing.datastore.name,
|
||||||
|
backing_disk_mode=disk.backing.diskMode,
|
||||||
|
backing_writethrough=disk.backing.writeThrough,
|
||||||
|
backing_thinprovisioned=disk.backing.thinProvisioned,
|
||||||
|
backing_eagerlyscrub=bool(disk.backing.eagerlyScrub),
|
||||||
|
controller_key=disk.controllerKey,
|
||||||
|
unit_number=disk.unitNumber,
|
||||||
|
capacity_in_kb=disk.capacityInKB,
|
||||||
|
capacity_in_bytes=disk.capacityInBytes,
|
||||||
|
)
|
||||||
|
disk_index += 1
|
||||||
|
return disks_facts
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_spec = vmware_argument_spec()
|
||||||
|
argument_spec.update(
|
||||||
|
name=dict(type='str'),
|
||||||
|
uuid=dict(type='str'),
|
||||||
|
folder=dict(type='str'),
|
||||||
|
datacenter=dict(type='str', required=True),
|
||||||
|
disk=dict(type=list, default=[]),
|
||||||
|
)
|
||||||
|
module = AnsibleModule(argument_spec=argument_spec,
|
||||||
|
required_one_of=[['name', 'uuid']])
|
||||||
|
|
||||||
|
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('/')
|
||||||
|
|
||||||
|
pyv = PyVmomiHelper(module)
|
||||||
|
# Check if the VM exists before continuing
|
||||||
|
vm = pyv.get_vm()
|
||||||
|
|
||||||
|
if not vm:
|
||||||
|
# We unable to find the virtual machine user specified
|
||||||
|
# Bail out
|
||||||
|
module.fail_json(msg="Unable to manage disks for non-existing"
|
||||||
|
" virtual machine '%s'." % (module.params.get('uuid') or module.params.get('name')))
|
||||||
|
|
||||||
|
# VM exists
|
||||||
|
try:
|
||||||
|
pyv.ensure_disks(vm_obj=vm)
|
||||||
|
except Exception as exc:
|
||||||
|
module.fail_json(msg="Failed to manage disks for virtual machine"
|
||||||
|
" '%s' with exception : %s" % (vm.name,
|
||||||
|
to_native(exc)))
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
Loading…
Reference in a new issue