Mini Shell
# pylint: disable=C0302
"""
VMware Cloud Module
===================
.. versionadded:: 2015.5.4
The VMware cloud module allows you to manage VMware ESX, ESXi, and vCenter.
See :ref:`Getting started with VMware <cloud-getting-started-vmware>` to get started.
:codeauthor: Nitin Madhok <nmadhok@g.clemson.edu>
Dependencies
============
- pyVmomi Python Module
pyVmomi
-------
PyVmomi can be installed via pip:
.. code-block:: bash
pip install pyVmomi
.. note::
Version 6.0 of pyVmomi has some problems with SSL error handling on certain
versions of Python. If using version 6.0 of pyVmomi, Python 2.6,
Python 2.7.9, or newer must be present. This is due to an upstream dependency
in pyVmomi 6.0 that is not supported in Python versions 2.7 to 2.7.8. If the
version of Python is not in the supported range, you will need to install an
earlier version of pyVmomi. See `Issue #29537`_ for more information.
.. _Issue #29537: https://github.com/saltstack/salt/issues/29537
Based on the note above, to install an earlier version of pyVmomi than the
version currently listed in PyPi, run the following:
.. code-block:: bash
pip install pyVmomi==5.5.0.2014.1.1
The 5.5.0.2014.1.1 is a known stable version that this original VMware cloud
driver was developed against.
.. note::
Ensure python pyVmomi module is installed by running following one-liner
check. The output should be 0.
.. code-block:: bash
python -c "import pyVmomi" ; echo $?
Configuration
=============
To use this module, set up the vCenter or ESX/ESXi URL, username and password in the
cloud configuration at
``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/vmware.conf``:
.. code-block:: yaml
my-vmware-config:
driver: vmware
user: 'DOMAIN\\user'
password: 'verybadpass'
url: '10.20.30.40'
vcenter01:
driver: vmware
user: 'DOMAIN\\user'
password: 'verybadpass'
url: 'vcenter01.domain.com'
protocol: 'https'
port: 443
vcenter02:
driver: vmware
user: 'DOMAIN\\user'
password: 'verybadpass'
url: 'vcenter02.domain.com'
protocol: 'http'
port: 80
esx01:
driver: vmware
user: 'admin'
password: 'verybadpass'
url: 'esx01.domain.com'
.. note::
Optionally, ``protocol`` and ``port`` can be specified if the vCenter
server is not using the defaults. Default is ``protocol: https`` and
``port: 443``.
.. note::
.. versionchanged:: 2015.8.0
The ``provider`` parameter in cloud provider configuration was renamed to ``driver``.
This change was made to avoid confusion with the ``provider`` parameter that is
used in cloud profile configuration. Cloud provider configuration now uses ``driver``
to refer to the salt-cloud driver that provides the underlying functionality to
connect to a cloud provider, while cloud profile configuration continues to use
``provider`` to refer to the cloud provider configuration that you define.
To test the connection for ``my-vmware-config`` specified in the cloud
configuration, run :py:func:`test_vcenter_connection`
"""
import logging
import os.path
import pprint
import re
import subprocess
import time
from random import randint
import salt.config as config
import salt.utils.cloud
import salt.utils.network
import salt.utils.stringutils
import salt.utils.vmware
import salt.utils.xmlutil
from salt.exceptions import SaltCloudSystemExit
try:
# Attempt to import pyVmomi libs
from pyVmomi import vim # pylint: disable=no-name-in-module
HAS_PYVMOMI = True
except ImportError:
HAS_PYVMOMI = False
# Disable InsecureRequestWarning generated on python > 2.6
try:
from requests.packages.urllib3 import ( # pylint: disable=no-name-in-module
disable_warnings,
)
disable_warnings()
except ImportError:
pass
ESX_5_5_NAME_PORTION = "VMware ESXi 5.5"
SAFE_ESX_5_5_CONTROLLER_KEY_INDEX = 200
FLATTEN_DISK_FULL_CLONE = "moveAllDiskBackingsAndDisallowSharing"
COPY_ALL_DISKS_FULL_CLONE = "moveAllDiskBackingsAndAllowSharing"
CURRENT_STATE_LINKED_CLONE = "moveChildMostDiskBacking"
QUICK_LINKED_CLONE = "createNewChildDiskBacking"
IP_RE = r"^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
# Get logging started
log = logging.getLogger(__name__)
__virtualname__ = "vmware"
# Only load in this module if the VMware configurations are in place
def __virtual__():
"""
Check for VMware configuration and if required libs are available.
"""
if get_configured_provider() is False:
return False
if get_dependencies() is False:
return False
return __virtualname__
def _get_active_provider_name():
try:
return __active_provider_name__.value()
except AttributeError:
return __active_provider_name__
def get_configured_provider():
"""
Return the first configured instance.
"""
return config.is_provider_configured(
__opts__,
_get_active_provider_name() or __virtualname__,
(
"url",
"user",
"password",
),
)
def get_dependencies():
"""
Warn if dependencies aren't met.
"""
deps = {
"pyVmomi": HAS_PYVMOMI,
}
return config.check_driver_dependencies(__virtualname__, deps)
def script(vm_):
"""
Return the script deployment object
"""
script_name = config.get_cloud_config_value("script", vm_, __opts__)
if not script_name:
script_name = "bootstrap-salt"
return salt.utils.cloud.os_script(
script_name,
vm_,
__opts__,
salt.utils.cloud.salt_config_to_yaml(
salt.utils.cloud.minion_config(__opts__, vm_)
),
)
def _str_to_bool(var):
if isinstance(var, bool):
return var
if isinstance(var, str):
return True if var.lower() == "true" else False
return None
def _get_si():
"""
Authenticate with vCenter server and return service instance object.
"""
url = config.get_cloud_config_value(
"url", get_configured_provider(), __opts__, search_global=False
)
username = config.get_cloud_config_value(
"user", get_configured_provider(), __opts__, search_global=False
)
password = config.get_cloud_config_value(
"password", get_configured_provider(), __opts__, search_global=False
)
protocol = config.get_cloud_config_value(
"protocol",
get_configured_provider(),
__opts__,
search_global=False,
default="https",
)
port = config.get_cloud_config_value(
"port", get_configured_provider(), __opts__, search_global=False, default=443
)
verify_ssl = config.get_cloud_config_value(
"verify_ssl",
get_configured_provider(),
__opts__,
search_global=False,
default=True,
)
return salt.utils.vmware.get_service_instance(
url, username, password, protocol=protocol, port=port, verify_ssl=verify_ssl
)
def _edit_existing_hard_disk_helper(disk, size_kb=None, size_gb=None, mode=None):
if size_kb or size_gb:
disk.capacityInKB = size_kb if size_kb else int(size_gb * 1024.0 * 1024.0)
if mode:
disk.backing.diskMode = mode
disk_spec = vim.vm.device.VirtualDeviceSpec()
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
disk_spec.device = disk
return disk_spec
def _add_new_hard_disk_helper(
disk_label,
size_gb,
unit_number,
controller_key=1000,
thin_provision=False,
eagerly_scrub=False,
datastore=None,
vm_name=None,
):
random_key = randint(-2099, -2000)
size_kb = int(size_gb * 1024.0 * 1024.0)
disk_spec = vim.vm.device.VirtualDeviceSpec()
disk_spec.fileOperation = "create"
disk_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
disk_spec.device = vim.vm.device.VirtualDisk()
disk_spec.device.key = random_key
disk_spec.device.deviceInfo = vim.Description()
disk_spec.device.deviceInfo.label = disk_label
disk_spec.device.deviceInfo.summary = f"{size_gb} GB"
disk_spec.device.backing = vim.vm.device.VirtualDisk.FlatVer2BackingInfo()
disk_spec.device.backing.thinProvisioned = thin_provision
disk_spec.device.backing.eagerlyScrub = eagerly_scrub
disk_spec.device.backing.diskMode = "persistent"
if datastore:
datastore_ref = salt.utils.vmware.get_mor_using_container_view(
_get_si(), vim.Datastore, datastore
)
if not datastore_ref:
# check if it is a datastore cluster instead
datastore_cluster_ref = salt.utils.vmware.get_mor_using_container_view(
_get_si(), vim.StoragePod, datastore
)
if not datastore_cluster_ref:
# datastore/datastore cluster specified does not exist
raise SaltCloudSystemExit(
"Specified datastore/datastore cluster ({}) for disk ({}) does not"
" exist".format(datastore, disk_label)
)
# datastore cluster has been specified
# find datastore with most free space available
#
# TODO: Get DRS Recommendations instead of finding datastore with most free space
datastore_list = salt.utils.vmware.get_datastores(
_get_si(), datastore_cluster_ref, get_all_datastores=True
)
datastore_free_space = 0
for ds_ref in datastore_list:
log.trace(
"Found datastore (%s) with free space (%s) in datastore "
"cluster (%s)",
ds_ref.name,
ds_ref.summary.freeSpace,
datastore,
)
if (
ds_ref.summary.accessible
and ds_ref.summary.freeSpace > datastore_free_space
):
datastore_free_space = ds_ref.summary.freeSpace
datastore_ref = ds_ref
if not datastore_ref:
# datastore cluster specified does not have any accessible datastores
raise SaltCloudSystemExit(
"Specified datastore cluster ({}) for disk ({}) does not have any"
" accessible datastores available".format(datastore, disk_label)
)
datastore_path = "[" + str(datastore_ref.name) + "] " + vm_name
disk_spec.device.backing.fileName = datastore_path + "/" + disk_label + ".vmdk"
disk_spec.device.backing.datastore = datastore_ref
log.trace(
"Using datastore (%s) for disk (%s), vm_name (%s)",
datastore_ref.name,
disk_label,
vm_name,
)
disk_spec.device.controllerKey = controller_key
disk_spec.device.unitNumber = unit_number
disk_spec.device.capacityInKB = size_kb
return disk_spec
def _edit_existing_network_adapter(
network_adapter, new_network_name, adapter_type, switch_type, container_ref=None
):
adapter_type.strip().lower()
switch_type.strip().lower()
if adapter_type in ["vmxnet", "vmxnet2", "vmxnet3", "e1000", "e1000e"]:
edited_network_adapter = salt.utils.vmware.get_network_adapter_type(
adapter_type
)
if isinstance(network_adapter, type(edited_network_adapter)):
edited_network_adapter = network_adapter
else:
log.debug(
"Changing type of '%s' from '%s' to '%s'",
network_adapter.deviceInfo.label,
type(network_adapter).__name__.rsplit(".", 1)[1][7:].lower(),
adapter_type,
)
else:
# If type not specified or does not match, don't change adapter type
if adapter_type:
log.error(
"Cannot change type of '%s' to '%s'. Not changing type",
network_adapter.deviceInfo.label,
adapter_type,
)
edited_network_adapter = network_adapter
if switch_type == "standard":
network_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.Network, new_network_name, container_ref=container_ref
)
edited_network_adapter.backing = (
vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
)
edited_network_adapter.backing.deviceName = new_network_name
edited_network_adapter.backing.network = network_ref
elif switch_type == "distributed":
network_ref = salt.utils.vmware.get_mor_by_property(
_get_si(),
vim.dvs.DistributedVirtualPortgroup,
new_network_name,
container_ref=container_ref,
)
dvs_port_connection = vim.dvs.PortConnection(
portgroupKey=network_ref.key,
switchUuid=network_ref.config.distributedVirtualSwitch.uuid,
)
edited_network_adapter.backing = (
vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo()
)
edited_network_adapter.backing.port = dvs_port_connection
else:
# If switch type not specified or does not match, show error and return
if not switch_type:
err_msg = (
"The switch type to be used by '{}' has not been specified".format(
network_adapter.deviceInfo.label
)
)
else:
err_msg = "Cannot create '{}'. Invalid/unsupported switch type '{}'".format(
network_adapter.deviceInfo.label, switch_type
)
raise SaltCloudSystemExit(err_msg)
edited_network_adapter.key = network_adapter.key
edited_network_adapter.deviceInfo = network_adapter.deviceInfo
edited_network_adapter.deviceInfo.summary = new_network_name
edited_network_adapter.connectable = network_adapter.connectable
edited_network_adapter.slotInfo = network_adapter.slotInfo
edited_network_adapter.controllerKey = network_adapter.controllerKey
edited_network_adapter.unitNumber = network_adapter.unitNumber
edited_network_adapter.addressType = network_adapter.addressType
edited_network_adapter.macAddress = network_adapter.macAddress
edited_network_adapter.wakeOnLanEnabled = network_adapter.wakeOnLanEnabled
network_spec = vim.vm.device.VirtualDeviceSpec()
network_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
network_spec.device = edited_network_adapter
return network_spec
def _add_new_network_adapter_helper(
network_adapter_label,
network_name,
adapter_type,
switch_type,
mac,
container_ref=None,
):
random_key = randint(-4099, -4000)
adapter_type.strip().lower()
switch_type.strip().lower()
network_spec = vim.vm.device.VirtualDeviceSpec()
if adapter_type in ["vmxnet", "vmxnet2", "vmxnet3", "e1000", "e1000e"]:
network_spec.device = salt.utils.vmware.get_network_adapter_type(adapter_type)
else:
# If type not specified or does not match, create adapter of type vmxnet3
if not adapter_type:
log.debug(
"The type of '%s' has not been specified. "
"Creating default type 'vmxnet3'",
network_adapter_label,
)
else:
log.error(
"Cannot create network adapter of type '%s'. "
"Creating '%s' of default type 'vmxnet3'",
adapter_type,
network_adapter_label,
)
network_spec.device = vim.vm.device.VirtualVmxnet3()
network_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
if switch_type == "standard":
network_spec.device.backing = (
vim.vm.device.VirtualEthernetCard.NetworkBackingInfo()
)
network_spec.device.backing.deviceName = network_name
network_spec.device.backing.network = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.Network, network_name, container_ref=container_ref
)
elif switch_type == "distributed":
network_ref = salt.utils.vmware.get_mor_by_property(
_get_si(),
vim.dvs.DistributedVirtualPortgroup,
network_name,
container_ref=container_ref,
)
dvs_port_connection = vim.dvs.PortConnection(
portgroupKey=network_ref.key,
switchUuid=network_ref.config.distributedVirtualSwitch.uuid,
)
network_spec.device.backing = (
vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo()
)
network_spec.device.backing.port = dvs_port_connection
else:
# If switch type not specified or does not match, show error and return
if not switch_type:
err_msg = (
"The switch type to be used by '{}' has not been specified".format(
network_adapter_label
)
)
else:
err_msg = "Cannot create '{}'. Invalid/unsupported switch type '{}'".format(
network_adapter_label, switch_type
)
raise SaltCloudSystemExit(err_msg)
if mac != "":
network_spec.device.addressType = "assigned"
network_spec.device.macAddress = mac
network_spec.device.key = random_key
network_spec.device.deviceInfo = vim.Description()
network_spec.device.deviceInfo.label = network_adapter_label
network_spec.device.deviceInfo.summary = network_name
network_spec.device.wakeOnLanEnabled = True
network_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
network_spec.device.connectable.startConnected = True
network_spec.device.connectable.allowGuestControl = True
return network_spec
def _edit_existing_scsi_controller(scsi_controller, bus_sharing):
scsi_controller.sharedBus = bus_sharing
scsi_spec = vim.vm.device.VirtualDeviceSpec()
scsi_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
scsi_spec.device = scsi_controller
return scsi_spec
def _add_new_scsi_controller_helper(scsi_controller_label, properties, bus_number):
random_key = randint(-1050, -1000)
adapter_type = properties["type"].strip().lower() if "type" in properties else None
bus_sharing = (
properties["bus_sharing"].strip().lower()
if "bus_sharing" in properties
else None
)
scsi_spec = vim.vm.device.VirtualDeviceSpec()
if adapter_type == "lsilogic":
summary = "LSI Logic"
scsi_spec.device = vim.vm.device.VirtualLsiLogicController()
elif adapter_type == "lsilogic_sas":
summary = "LSI Logic Sas"
scsi_spec.device = vim.vm.device.VirtualLsiLogicSASController()
elif adapter_type == "paravirtual":
summary = "VMware paravirtual SCSI"
scsi_spec.device = vim.vm.device.ParaVirtualSCSIController()
else:
# If type not specified or does not match, show error and return
if not adapter_type:
err_msg = "The type of '{}' has not been specified".format(
scsi_controller_label
)
else:
err_msg = "Cannot create '{}'. Invalid/unsupported type '{}'".format(
scsi_controller_label, adapter_type
)
raise SaltCloudSystemExit(err_msg)
scsi_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
scsi_spec.device.key = random_key
scsi_spec.device.busNumber = bus_number
scsi_spec.device.deviceInfo = vim.Description()
scsi_spec.device.deviceInfo.label = scsi_controller_label
scsi_spec.device.deviceInfo.summary = summary
if bus_sharing == "virtual":
# Virtual disks can be shared between virtual machines on the same server
scsi_spec.device.sharedBus = (
vim.vm.device.VirtualSCSIController.Sharing.virtualSharing
)
elif bus_sharing == "physical":
# Virtual disks can be shared between virtual machines on any server
scsi_spec.device.sharedBus = (
vim.vm.device.VirtualSCSIController.Sharing.physicalSharing
)
else:
# Virtual disks cannot be shared between virtual machines
scsi_spec.device.sharedBus = (
vim.vm.device.VirtualSCSIController.Sharing.noSharing
)
return scsi_spec
def _add_new_ide_controller_helper(ide_controller_label, controller_key, bus_number):
"""
Helper function for adding new IDE controllers
.. versionadded:: 2016.3.0
Args:
ide_controller_label: label of the IDE controller
controller_key: if not None, the controller key to use; otherwise it is randomly generated
bus_number: bus number
Returns: created device spec for an IDE controller
"""
if controller_key is None:
controller_key = randint(-200, 250)
ide_spec = vim.vm.device.VirtualDeviceSpec()
ide_spec.device = vim.vm.device.VirtualIDEController()
ide_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
ide_spec.device.key = controller_key
ide_spec.device.busNumber = bus_number
ide_spec.device.deviceInfo = vim.Description()
ide_spec.device.deviceInfo.label = ide_controller_label
ide_spec.device.deviceInfo.summary = ide_controller_label
return ide_spec
def _set_cd_or_dvd_backing_type(drive, device_type, mode, iso_path):
if device_type == "datastore_iso_file":
drive.backing = vim.vm.device.VirtualCdrom.IsoBackingInfo()
drive.backing.fileName = iso_path
datastore = iso_path.partition("[")[-1].rpartition("]")[0]
datastore_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.Datastore, datastore
)
if datastore_ref:
drive.backing.datastore = datastore_ref
drive.deviceInfo.summary = f"ISO {iso_path}"
elif device_type == "client_device":
if mode == "passthrough":
drive.backing = vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
drive.deviceInfo.summary = "Remote Device"
elif mode == "atapi":
drive.backing = vim.vm.device.VirtualCdrom.RemoteAtapiBackingInfo()
drive.deviceInfo.summary = "Remote ATAPI"
return drive
def _edit_existing_cd_or_dvd_drive(drive, device_type, mode, iso_path):
device_type.strip().lower()
mode.strip().lower()
drive_spec = vim.vm.device.VirtualDeviceSpec()
drive_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.edit
drive_spec.device = _set_cd_or_dvd_backing_type(drive, device_type, mode, iso_path)
return drive_spec
def _add_new_cd_or_dvd_drive_helper(
drive_label, controller_key, device_type, mode, iso_path
):
random_key = randint(-3025, -3000)
device_type.strip().lower()
mode.strip().lower()
drive_spec = vim.vm.device.VirtualDeviceSpec()
drive_spec.operation = vim.vm.device.VirtualDeviceSpec.Operation.add
drive_spec.device = vim.vm.device.VirtualCdrom()
drive_spec.device.deviceInfo = vim.Description()
if device_type in ["datastore_iso_file", "client_device"]:
drive_spec.device = _set_cd_or_dvd_backing_type(
drive_spec.device, device_type, mode, iso_path
)
else:
# If device_type not specified or does not match, create drive of Client type with Passthough mode
if not device_type:
log.debug(
"The 'device_type' of '%s' has not been specified. "
"Creating default type 'client_device'",
drive_label,
)
else:
log.error(
"Cannot create CD/DVD drive of type '%s'. "
"Creating '%s' of default type 'client_device'",
device_type,
drive_label,
)
drive_spec.device.backing = (
vim.vm.device.VirtualCdrom.RemotePassthroughBackingInfo()
)
drive_spec.device.deviceInfo.summary = "Remote Device"
drive_spec.device.key = random_key
drive_spec.device.deviceInfo.label = drive_label
drive_spec.device.controllerKey = controller_key
drive_spec.device.connectable = vim.vm.device.VirtualDevice.ConnectInfo()
drive_spec.device.connectable.startConnected = True
drive_spec.device.connectable.allowGuestControl = True
return drive_spec
def _set_network_adapter_mapping(adapter_specs):
adapter_mapping = vim.vm.customization.AdapterMapping()
adapter_mapping.adapter = vim.vm.customization.IPSettings()
if "domain" in list(adapter_specs.keys()):
domain = adapter_specs["domain"]
adapter_mapping.adapter.dnsDomain = domain
if "gateway" in list(adapter_specs.keys()):
gateway = adapter_specs["gateway"]
adapter_mapping.adapter.gateway = gateway
if "ip" in list(adapter_specs.keys()):
ip = str(adapter_specs["ip"])
subnet_mask = str(adapter_specs["subnet_mask"])
adapter_mapping.adapter.ip = vim.vm.customization.FixedIp(ipAddress=ip)
adapter_mapping.adapter.subnetMask = subnet_mask
else:
adapter_mapping.adapter.ip = vim.vm.customization.DhcpIpGenerator()
return adapter_mapping
def _get_mode_spec(device, mode, disk_spec):
if device.backing.diskMode != mode:
if not disk_spec:
disk_spec = _edit_existing_hard_disk_helper(disk=device, mode=mode)
else:
disk_spec.device.backing.diskMode = mode
return disk_spec
def _get_size_spec(device, size_gb=None, size_kb=None):
if size_kb is None and size_gb is not None:
size_kb = int(size_gb * 1024.0 * 1024.0)
disk_spec = (
_edit_existing_hard_disk_helper(disk=device, size_kb=size_kb)
if device.capacityInKB < size_kb
else None
)
return disk_spec
def _iter_disk_unit_number(unit_number):
"""
Apparently vmware reserves ID 7 for SCSI controllers, so we cannot specify
hard drives for 7.
Skip 7 to make sure.
"""
unit_number += 1
if unit_number == 7:
unit_number += 1
return unit_number
def _manage_devices(devices, vm=None, container_ref=None, new_vm_name=None):
unit_number = 0
bus_number = 0
device_specs = []
existing_disks_label = []
existing_scsi_controllers_label = []
existing_ide_controllers_label = []
existing_network_adapters_label = []
existing_cd_drives_label = []
ide_controllers = {}
nics_map = []
cloning_from_vm = vm is not None
if cloning_from_vm:
# loop through all the devices the vm/template has
# check if the device needs to be created or configured
for device in vm.config.hardware.device:
if isinstance(device, vim.vm.device.VirtualDisk):
# this is a hard disk
if "disk" in list(devices.keys()):
# there is atleast one disk specified to be created/configured
unit_number = _iter_disk_unit_number(unit_number)
existing_disks_label.append(device.deviceInfo.label)
if device.deviceInfo.label in list(devices["disk"].keys()):
disk_spec = None
if "size" in devices["disk"][device.deviceInfo.label]:
size_gb = float(
devices["disk"][device.deviceInfo.label]["size"]
)
size_kb = int(size_gb * 1024.0 * 1024.0)
else:
# User didn't specify disk size in the cloud
# profile so use the existing disk size
size_kb = device.capacityInKB
size_gb = size_kb / (1024.0 * 1024.0)
log.debug(
"Virtual disk size for '%s' was not "
"specified in the cloud profile or map file. "
"Using existing virtual disk size of '%sGB'",
device.deviceInfo.label,
size_gb,
)
if device.capacityInKB > size_kb:
raise SaltCloudSystemExit(
"The specified disk size '{}GB' for '{}' is "
"smaller than the disk image size '{}GB'. It must "
"be equal to or greater than the disk image".format(
float(
devices["disk"][device.deviceInfo.label]["size"]
),
device.deviceInfo.label,
float(device.capacityInKB / (1024.0 * 1024.0)),
)
)
else:
disk_spec = _get_size_spec(device=device, size_kb=size_kb)
if "mode" in devices["disk"][device.deviceInfo.label]:
if devices["disk"][device.deviceInfo.label]["mode"] in [
"independent_persistent",
"independent_nonpersistent",
"dependent",
]:
mode = devices["disk"][device.deviceInfo.label]["mode"]
disk_spec = _get_mode_spec(device, mode, disk_spec)
else:
raise SaltCloudSystemExit(
"Invalid disk backing mode specified!"
)
if disk_spec is not None:
device_specs.append(disk_spec)
elif isinstance(
device.backing,
(
vim.vm.device.VirtualEthernetCard.NetworkBackingInfo,
vim.vm.device.VirtualEthernetCard.DistributedVirtualPortBackingInfo,
),
):
# this is a network adapter
if "network" in list(devices.keys()):
# there is atleast one network adapter specified to be created/configured
existing_network_adapters_label.append(device.deviceInfo.label)
if device.deviceInfo.label in list(devices["network"].keys()):
network_name = devices["network"][device.deviceInfo.label][
"name"
]
adapter_type = (
devices["network"][device.deviceInfo.label]["adapter_type"]
if "adapter_type"
in devices["network"][device.deviceInfo.label]
else ""
)
switch_type = (
devices["network"][device.deviceInfo.label]["switch_type"]
if "switch_type"
in devices["network"][device.deviceInfo.label]
else ""
)
network_spec = _edit_existing_network_adapter(
device,
network_name,
adapter_type,
switch_type,
container_ref,
)
adapter_mapping = _set_network_adapter_mapping(
devices["network"][device.deviceInfo.label]
)
device_specs.append(network_spec)
nics_map.append(adapter_mapping)
elif hasattr(device, "scsiCtlrUnitNumber"):
# this is a SCSI controller
if "scsi" in list(devices.keys()):
# there is atleast one SCSI controller specified to be created/configured
bus_number += 1
existing_scsi_controllers_label.append(device.deviceInfo.label)
if device.deviceInfo.label in list(devices["scsi"].keys()):
# Modify the existing SCSI controller
scsi_controller_properties = devices["scsi"][
device.deviceInfo.label
]
bus_sharing = (
scsi_controller_properties["bus_sharing"].strip().lower()
if "bus_sharing" in scsi_controller_properties
else None
)
if bus_sharing and bus_sharing in ["virtual", "physical", "no"]:
bus_sharing = f"{bus_sharing}Sharing"
if bus_sharing != device.sharedBus:
# Only edit the SCSI controller if bus_sharing is different
scsi_spec = _edit_existing_scsi_controller(
device, bus_sharing
)
device_specs.append(scsi_spec)
elif isinstance(device, vim.vm.device.VirtualCdrom):
# this is a cd/dvd drive
if "cd" in list(devices.keys()):
# there is atleast one cd/dvd drive specified to be created/configured
existing_cd_drives_label.append(device.deviceInfo.label)
if device.deviceInfo.label in list(devices["cd"].keys()):
device_type = (
devices["cd"][device.deviceInfo.label]["device_type"]
if "device_type" in devices["cd"][device.deviceInfo.label]
else ""
)
mode = (
devices["cd"][device.deviceInfo.label]["mode"]
if "mode" in devices["cd"][device.deviceInfo.label]
else ""
)
iso_path = (
devices["cd"][device.deviceInfo.label]["iso_path"]
if "iso_path" in devices["cd"][device.deviceInfo.label]
else ""
)
cd_drive_spec = _edit_existing_cd_or_dvd_drive(
device, device_type, mode, iso_path
)
device_specs.append(cd_drive_spec)
elif isinstance(device, vim.vm.device.VirtualIDEController):
# this is an IDE controller to add new cd drives to
ide_controllers[device.key] = len(device.device)
if "network" in list(devices.keys()):
network_adapters_to_create = list(
set(devices["network"].keys()) - set(existing_network_adapters_label)
)
network_adapters_to_create.sort()
if network_adapters_to_create:
log.debug("Networks adapters to create: %s", network_adapters_to_create)
for network_adapter_label in network_adapters_to_create:
network_name = devices["network"][network_adapter_label]["name"]
adapter_type = (
devices["network"][network_adapter_label]["adapter_type"]
if "adapter_type" in devices["network"][network_adapter_label]
else ""
)
switch_type = (
devices["network"][network_adapter_label]["switch_type"]
if "switch_type" in devices["network"][network_adapter_label]
else ""
)
mac = (
devices["network"][network_adapter_label]["mac"]
if "mac" in devices["network"][network_adapter_label]
else ""
)
# create the network adapter
network_spec = _add_new_network_adapter_helper(
network_adapter_label,
network_name,
adapter_type,
switch_type,
mac,
container_ref,
)
adapter_mapping = _set_network_adapter_mapping(
devices["network"][network_adapter_label]
)
device_specs.append(network_spec)
nics_map.append(adapter_mapping)
if "scsi" in list(devices.keys()):
scsi_controllers_to_create = list(
set(devices["scsi"].keys()) - set(existing_scsi_controllers_label)
)
scsi_controllers_to_create.sort()
if scsi_controllers_to_create:
log.debug("SCSI controllers to create: %s", scsi_controllers_to_create)
for scsi_controller_label in scsi_controllers_to_create:
# create the SCSI controller
scsi_controller_properties = devices["scsi"][scsi_controller_label]
scsi_spec = _add_new_scsi_controller_helper(
scsi_controller_label, scsi_controller_properties, bus_number
)
device_specs.append(scsi_spec)
bus_number += 1
if "ide" in list(devices.keys()):
ide_controllers_to_create = list(
set(devices["ide"].keys()) - set(existing_ide_controllers_label)
)
ide_controllers_to_create.sort()
if ide_controllers_to_create:
log.debug("IDE controllers to create: %s", ide_controllers_to_create)
# ESX 5.5 (and possibly earlier?) set the IDE controller key themselves, indexed starting at
# 200. Rather than doing a create task/get vm/reconfig task dance we query the server and
# if it's ESX 5.5 we supply a controller starting at 200 and work out way upwards from there
# ESX 6 (and, one assumes, vCenter) does not display this problem and so continues to use
# the randomly generated indexes
vcenter_name = get_vcenter_version(call="function")
controller_index = (
SAFE_ESX_5_5_CONTROLLER_KEY_INDEX
if ESX_5_5_NAME_PORTION in vcenter_name
else None
)
for ide_controller_label in ide_controllers_to_create:
# create the IDE controller
ide_spec = _add_new_ide_controller_helper(
ide_controller_label, controller_index, bus_number
)
device_specs.append(ide_spec)
bus_number += 1
if controller_index is not None:
controller_index += 1
if "disk" in list(devices.keys()):
disks_to_create = list(set(devices["disk"].keys()) - set(existing_disks_label))
disks_to_create.sort()
if disks_to_create:
log.debug("Hard disks to create: %s", disks_to_create)
for disk_label in disks_to_create:
# create the disk
size_gb = float(devices["disk"][disk_label]["size"])
thin_provision = (
bool(devices["disk"][disk_label]["thin_provision"])
if "thin_provision" in devices["disk"][disk_label]
else False
)
eagerly_scrub = (
bool(devices["disk"][disk_label]["eagerly_scrub"])
if "eagerly_scrub" in devices["disk"][disk_label]
else False
)
datastore = devices["disk"][disk_label].get("datastore", None)
disk_spec = _add_new_hard_disk_helper(
disk_label,
size_gb,
unit_number,
thin_provision=thin_provision,
eagerly_scrub=eagerly_scrub,
datastore=datastore,
vm_name=new_vm_name,
)
# when creating both SCSI controller and Hard disk at the same time we need the randomly
# assigned (temporary) key of the newly created SCSI controller
if "controller" in devices["disk"][disk_label]:
for spec in device_specs:
if (
spec.device.deviceInfo.label
== devices["disk"][disk_label]["controller"]
):
disk_spec.device.controllerKey = spec.device.key
break
device_specs.append(disk_spec)
unit_number = _iter_disk_unit_number(unit_number)
if "cd" in list(devices.keys()):
cd_drives_to_create = list(
set(devices["cd"].keys()) - set(existing_cd_drives_label)
)
cd_drives_to_create.sort()
if cd_drives_to_create:
log.debug("CD/DVD drives to create: %s", cd_drives_to_create)
for cd_drive_label in cd_drives_to_create:
# create the CD/DVD drive
device_type = (
devices["cd"][cd_drive_label]["device_type"]
if "device_type" in devices["cd"][cd_drive_label]
else ""
)
mode = (
devices["cd"][cd_drive_label]["mode"]
if "mode" in devices["cd"][cd_drive_label]
else ""
)
iso_path = (
devices["cd"][cd_drive_label]["iso_path"]
if "iso_path" in devices["cd"][cd_drive_label]
else ""
)
controller_key = None
# When creating both IDE controller and CD/DVD drive at the same time we need the randomly
# assigned (temporary) key of the newly created IDE controller
if "controller" in devices["cd"][cd_drive_label]:
for spec in device_specs:
if (
spec.device.deviceInfo.label
== devices["cd"][cd_drive_label]["controller"]
):
controller_key = spec.device.key
ide_controllers[controller_key] = 0
break
else:
for ide_controller_key, num_devices in ide_controllers.items():
if num_devices < 2:
controller_key = ide_controller_key
break
if not controller_key:
log.error(
"No more available controllers for '%s'. "
"All IDE controllers are currently in use",
cd_drive_label,
)
else:
cd_drive_spec = _add_new_cd_or_dvd_drive_helper(
cd_drive_label, controller_key, device_type, mode, iso_path
)
device_specs.append(cd_drive_spec)
ide_controllers[controller_key] += 1
ret = {"device_specs": device_specs, "nics_map": nics_map}
return ret
def _wait_for_vmware_tools(vm_ref, max_wait):
time_counter = 0
starttime = time.time()
while time_counter < max_wait:
if time_counter % 5 == 0:
log.info(
"[ %s ] Waiting for VMware tools to be running [%s s]",
vm_ref.name,
time_counter,
)
if str(vm_ref.summary.guest.toolsRunningStatus) == "guestToolsRunning":
log.info(
"[ %s ] Successfully got VMware tools running on the guest in "
"%s seconds",
vm_ref.name,
time_counter,
)
return True
time.sleep(1.0 - ((time.time() - starttime) % 1.0))
time_counter += 1
log.warning(
"[ %s ] Timeout Reached. VMware tools still not running after waiting "
"for %s seconds",
vm_ref.name,
max_wait,
)
return False
def _valid_ip(ip_address):
"""
Check if the IP address is valid
Return either True or False
"""
# Make sure IP has four octets
octets = ip_address.split(".")
if len(octets) != 4:
return False
# convert octet from string to int
for i, octet in enumerate(octets):
try:
octets[i] = int(octet)
except ValueError:
# couldn't convert octet to an integer
return False
# map variables to elements of octets list
first_octet, second_octet, third_octet, fourth_octet = octets
# Check first_octet meets conditions
if first_octet < 1 or first_octet > 223 or first_octet == 127:
return False
# Check 169.254.X.X condition
if first_octet == 169 and second_octet == 254:
return False
# Check 2nd - 4th octets
for octet in (second_octet, third_octet, fourth_octet):
if (octet < 0) or (octet > 255):
return False
# Passed all of the checks
return True
def _wait_for_ip(vm_ref, max_wait):
max_wait_vmware_tools = max_wait
max_wait_ip = max_wait
vmware_tools_status = _wait_for_vmware_tools(vm_ref, max_wait_vmware_tools)
if not vmware_tools_status:
# VMware will only report the IP if VMware tools are installed. Try to
# determine the IP using DNS
vm_name = vm_ref.summary.config.name
resolved_ips = salt.utils.network.host_to_ips(vm_name)
log.debug(
"Timeout waiting for VMware tools. The name %s resolved to %s",
vm_name,
resolved_ips,
)
if isinstance(resolved_ips, list) and resolved_ips:
return resolved_ips[0]
return False
time_counter = 0
starttime = time.time()
while time_counter < max_wait_ip:
if time_counter % 5 == 0:
log.info(
"[ %s ] Waiting to retrieve IPv4 information [%s s]",
vm_ref.name,
time_counter,
)
if vm_ref.summary.guest.ipAddress and _valid_ip(vm_ref.summary.guest.ipAddress):
log.info(
"[ %s ] Successfully retrieved IPv4 information in %s seconds",
vm_ref.name,
time_counter,
)
return vm_ref.summary.guest.ipAddress
for net in vm_ref.guest.net:
if net.ipConfig.ipAddress:
for current_ip in net.ipConfig.ipAddress:
if _valid_ip(current_ip.ipAddress):
log.info(
"[ %s ] Successfully retrieved IPv4 information "
"in %s seconds",
vm_ref.name,
time_counter,
)
return current_ip.ipAddress
time.sleep(1.0 - ((time.time() - starttime) % 1.0))
time_counter += 1
log.warning(
"[ %s ] Timeout Reached. Unable to retrieve IPv4 information after "
"waiting for %s seconds",
vm_ref.name,
max_wait_ip,
)
return False
def _wait_for_host(host_ref, task_type, sleep_seconds=5, log_level="debug"):
time_counter = 0
starttime = time.time()
while host_ref.runtime.connectionState != "notResponding":
if time_counter % sleep_seconds == 0:
log.log(
logging.INFO if log_level == "info" else logging.DEBUG,
"[ %s ] Waiting for host %s to finish [%s s]",
host_ref.name,
task_type,
time_counter,
)
time.sleep(1.0 - ((time.time() - starttime) % 1.0))
time_counter += 1
while host_ref.runtime.connectionState != "connected":
if time_counter % sleep_seconds == 0:
log.log(
logging.INFO if log_level == "info" else logging.DEBUG,
"[ %s ] Waiting for host %s to finish [%s s]",
host_ref.name,
task_type,
time_counter,
)
time.sleep(1.0 - ((time.time() - starttime) % 1.0))
time_counter += 1
if host_ref.runtime.connectionState == "connected":
log.log(
logging.INFO if log_level == "info" else logging.DEBUG,
"[ %s ] Successfully completed host %s in %s seconds",
host_ref.name,
task_type,
time_counter,
)
else:
log.error("Could not connect back to the host system")
def _format_instance_info_select(vm, selection):
def defaultto(machine, section, default="N/A"):
"""
Return either a named value from a VirtualMachineConfig or a
default string "N/A".
"""
return default if section not in machine else machine[section]
vm_select_info = {}
if "id" in selection:
vm_select_info["id"] = vm["name"]
if "image" in selection:
vm_select_info["image"] = "{} (Detected)".format(
defaultto(vm, "config.guestFullName")
)
if "size" in selection:
cpu = defaultto(vm, "config.hardware.numCPU")
ram = "{} MB".format(defaultto(vm, "config.hardware.memoryMB"))
vm_select_info["size"] = f"cpu: {cpu}\nram: {ram}"
vm_select_info["size_dict"] = {
"cpu": cpu,
"memory": ram,
}
if "state" in selection:
vm_select_info["state"] = str(defaultto(vm, "summary.runtime.powerState"))
if "guest_id" in selection:
vm_select_info["guest_id"] = defaultto(vm, "config.guestId")
if "hostname" in selection:
vm_select_info["hostname"] = vm["object"].guest.hostName
if "path" in selection:
vm_select_info["path"] = defaultto(vm, "config.files.vmPathName")
if "tools_status" in selection:
vm_select_info["tools_status"] = str(defaultto(vm, "guest.toolsStatus"))
if "private_ips" in selection or "networks" in selection:
network_full_info = {}
ip_addresses = []
if "guest.net" in vm:
for net in vm["guest.net"]:
network_full_info[net.network] = {
"connected": net.connected,
"ip_addresses": net.ipAddress,
"mac_address": net.macAddress,
}
ip_addresses.extend(net.ipAddress)
if "private_ips" in selection:
vm_select_info["private_ips"] = ip_addresses
if "networks" in selection:
vm_select_info["networks"] = network_full_info
if any(x in ["devices", "mac_address", "mac_addresses"] for x in selection):
device_full_info = {}
device_mac_addresses = []
if "config.hardware.device" in vm:
for device in vm["config.hardware.device"]:
device_full_info[device.deviceInfo.label] = {}
if "devices" in selection:
device_full_info[device.deviceInfo.label]["key"] = (device.key,)
device_full_info[device.deviceInfo.label]["label"] = (
device.deviceInfo.label,
)
device_full_info[device.deviceInfo.label]["summary"] = (
device.deviceInfo.summary,
)
device_full_info[device.deviceInfo.label]["type"] = type(
device
).__name__.rsplit(".", 1)[1]
if device.unitNumber:
device_full_info[device.deviceInfo.label][
"unitNumber"
] = device.unitNumber
if hasattr(device, "connectable") and device.connectable:
device_full_info[device.deviceInfo.label][
"startConnected"
] = device.connectable.startConnected
device_full_info[device.deviceInfo.label][
"allowGuestControl"
] = device.connectable.allowGuestControl
device_full_info[device.deviceInfo.label][
"connected"
] = device.connectable.connected
device_full_info[device.deviceInfo.label][
"status"
] = device.connectable.status
if hasattr(device, "controllerKey") and device.controllerKey:
device_full_info[device.deviceInfo.label][
"controllerKey"
] = device.controllerKey
if hasattr(device, "addressType"):
device_full_info[device.deviceInfo.label][
"addressType"
] = device.addressType
if hasattr(device, "busNumber"):
device_full_info[device.deviceInfo.label][
"busNumber"
] = device.busNumber
if hasattr(device, "device"):
device_full_info[device.deviceInfo.label][
"deviceKeys"
] = device.device
if hasattr(device, "videoRamSizeInKB"):
device_full_info[device.deviceInfo.label][
"videoRamSizeInKB"
] = device.videoRamSizeInKB
if isinstance(device, vim.vm.device.VirtualDisk):
device_full_info[device.deviceInfo.label][
"capacityInKB"
] = device.capacityInKB
device_full_info[device.deviceInfo.label][
"diskMode"
] = device.backing.diskMode
device_full_info[device.deviceInfo.label][
"fileName"
] = device.backing.fileName
if hasattr(device, "macAddress"):
device_full_info[device.deviceInfo.label][
"macAddress"
] = device.macAddress
device_mac_addresses.append(device.macAddress)
if "devices" in selection:
vm_select_info["devices"] = device_full_info
if "mac_address" in selection or "mac_addresses" in selection:
vm_select_info["mac_addresses"] = device_mac_addresses
if "storage" in selection:
storage_full_info = {
"committed": (
int(vm["summary.storage.committed"])
if "summary.storage.committed" in vm
else "N/A"
),
"uncommitted": (
int(vm["summary.storage.uncommitted"])
if "summary.storage.uncommitted" in vm
else "N/A"
),
"unshared": (
int(vm["summary.storage.unshared"])
if "summary.storage.unshared" in vm
else "N/A"
),
}
vm_select_info["storage"] = storage_full_info
if "files" in selection:
file_full_info = {}
if "layoutEx.file" in vm:
for filename in vm["layoutEx.file"]:
file_full_info[filename.key] = {
"key": filename.key,
"name": filename.name,
"size": filename.size,
"type": filename.type,
}
vm_select_info["files"] = file_full_info
return vm_select_info
def _format_instance_info(vm):
device_full_info = {}
device_mac_addresses = []
if "config.hardware.device" in vm:
for device in vm["config.hardware.device"]:
device_full_info[device.deviceInfo.label] = {
"key": device.key,
"label": device.deviceInfo.label,
"summary": device.deviceInfo.summary,
"type": type(device).__name__.rsplit(".", 1)[1],
}
if device.unitNumber:
device_full_info[device.deviceInfo.label][
"unitNumber"
] = device.unitNumber
if hasattr(device, "connectable") and device.connectable:
device_full_info[device.deviceInfo.label][
"startConnected"
] = device.connectable.startConnected
device_full_info[device.deviceInfo.label][
"allowGuestControl"
] = device.connectable.allowGuestControl
device_full_info[device.deviceInfo.label][
"connected"
] = device.connectable.connected
device_full_info[device.deviceInfo.label][
"status"
] = device.connectable.status
if hasattr(device, "controllerKey") and device.controllerKey:
device_full_info[device.deviceInfo.label][
"controllerKey"
] = device.controllerKey
if hasattr(device, "addressType"):
device_full_info[device.deviceInfo.label][
"addressType"
] = device.addressType
if hasattr(device, "macAddress"):
device_full_info[device.deviceInfo.label][
"macAddress"
] = device.macAddress
device_mac_addresses.append(device.macAddress)
if hasattr(device, "busNumber"):
device_full_info[device.deviceInfo.label][
"busNumber"
] = device.busNumber
if hasattr(device, "device"):
device_full_info[device.deviceInfo.label]["deviceKeys"] = device.device
if hasattr(device, "videoRamSizeInKB"):
device_full_info[device.deviceInfo.label][
"videoRamSizeInKB"
] = device.videoRamSizeInKB
if isinstance(device, vim.vm.device.VirtualDisk):
device_full_info[device.deviceInfo.label][
"capacityInKB"
] = device.capacityInKB
device_full_info[device.deviceInfo.label][
"diskMode"
] = device.backing.diskMode
device_full_info[device.deviceInfo.label][
"fileName"
] = device.backing.fileName
storage_full_info = {
"committed": (
int(vm["summary.storage.committed"])
if "summary.storage.committed" in vm
else "N/A"
),
"uncommitted": (
int(vm["summary.storage.uncommitted"])
if "summary.storage.uncommitted" in vm
else "N/A"
),
"unshared": (
int(vm["summary.storage.unshared"])
if "summary.storage.unshared" in vm
else "N/A"
),
}
file_full_info = {}
if "layoutEx.file" in vm:
for filename in vm["layoutEx.file"]:
file_full_info[filename.key] = {
"key": filename.key,
"name": filename.name,
"size": filename.size,
"type": filename.type,
}
network_full_info = {}
ip_addresses = []
if "guest.net" in vm:
for net in vm["guest.net"]:
network_full_info[net.network] = {
"connected": net.connected,
"ip_addresses": net.ipAddress,
"mac_address": net.macAddress,
}
ip_addresses.extend(net.ipAddress)
cpu = vm["config.hardware.numCPU"] if "config.hardware.numCPU" in vm else "N/A"
ram = (
"{} MB".format(vm["config.hardware.memoryMB"])
if "config.hardware.memoryMB" in vm
else "N/A"
)
vm_full_info = {
"id": str(vm["name"]),
"image": (
"{} (Detected)".format(vm["config.guestFullName"])
if "config.guestFullName" in vm
else "N/A"
),
"size": f"cpu: {cpu}\nram: {ram}",
"size_dict": {"cpu": cpu, "memory": ram},
"state": (
str(vm["summary.runtime.powerState"])
if "summary.runtime.powerState" in vm
else "N/A"
),
"private_ips": ip_addresses,
"public_ips": [],
"devices": device_full_info,
"storage": storage_full_info,
"files": file_full_info,
"guest_id": str(vm["config.guestId"]) if "config.guestId" in vm else "N/A",
"hostname": str(vm["object"].guest.hostName),
"mac_addresses": device_mac_addresses,
"networks": network_full_info,
"path": (
str(vm["config.files.vmPathName"])
if "config.files.vmPathName" in vm
else "N/A"
),
"tools_status": (
str(vm["guest.toolsStatus"]) if "guest.toolsStatus" in vm else "N/A"
),
}
return vm_full_info
def _get_snapshots(snapshot_list, current_snapshot=None, parent_snapshot_path=""):
snapshots = {}
for snapshot in snapshot_list:
snapshot_path = f"{parent_snapshot_path}/{snapshot.name}"
snapshots[snapshot_path] = {
"name": snapshot.name,
"description": snapshot.description,
"created": str(snapshot.createTime).split(".", maxsplit=1)[0],
"state": snapshot.state,
"path": snapshot_path,
}
if current_snapshot and current_snapshot == snapshot.snapshot:
return snapshots[snapshot_path]
# Check if child snapshots exist
if snapshot.childSnapshotList:
ret = _get_snapshots(
snapshot.childSnapshotList, current_snapshot, snapshot_path
)
if current_snapshot:
return ret
snapshots.update(ret)
return snapshots
def _get_snapshot_ref_helper(base_snapshot, snapshot_name):
if base_snapshot.name == snapshot_name:
return base_snapshot
for snapshot in base_snapshot.childSnapshotList:
snapshot_ref = _get_snapshot_ref_helper(snapshot, snapshot_name)
if snapshot_ref is not None:
return snapshot_ref
return None
def _get_snapshot_ref_by_name(vm_ref, snapshot_name):
snapshot_ref = None
try:
for root_snapshot in vm_ref.snapshot.rootSnapshotList:
snapshot_ref = _get_snapshot_ref_helper(root_snapshot, snapshot_name)
if snapshot_ref is not None:
break
except (IndexError, AttributeError):
snapshot_ref = None
return snapshot_ref
def _upg_tools_helper(vm, reboot=False):
# Exit if template
if vm.config.template:
status = "VMware tools cannot be updated on a template"
# Exit if VMware tools is already up to date
elif vm.guest.toolsStatus == "toolsOk":
status = "VMware tools is already up to date"
# Exit if VM is not powered on
elif vm.summary.runtime.powerState != "poweredOn":
status = "VM must be powered on to upgrade tools"
# Exit if VMware tools is either not running or not installed
elif vm.guest.toolsStatus in ["toolsNotRunning", "toolsNotInstalled"]:
status = "VMware tools is either not running or not installed"
# If vmware tools is out of date, check major OS family
# Upgrade tools on Linux and Windows guests
elif vm.guest.toolsStatus == "toolsOld":
log.info("Upgrading VMware tools on %s", vm.name)
try:
if vm.guest.guestFamily == "windowsGuest" and not reboot:
log.info("Reboot suppressed on %s", vm.name)
task = vm.UpgradeTools('/S /v"/qn REBOOT=R"')
elif vm.guest.guestFamily in ["linuxGuest", "windowsGuest"]:
task = vm.UpgradeTools()
else:
return "Only Linux and Windows guests are currently supported"
salt.utils.vmware.wait_for_task(
task, vm.name, "tools upgrade", sleep_seconds=5, log_level="info"
)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while upgrading VMware tools on VM %s: %s",
vm.name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "VMware tools upgrade failed"
status = "VMware tools upgrade succeeded"
else:
status = "VMWare tools could not be upgraded"
return status
def _get_hba_type(hba_type):
"""
Convert a string representation of a HostHostBusAdapter into an
object reference.
"""
if hba_type == "parallel":
return vim.host.ParallelScsiHba
elif hba_type == "block":
return vim.host.BlockHba
elif hba_type == "iscsi":
return vim.host.InternetScsiHba
elif hba_type == "fibre":
return vim.host.FibreChannelHba
raise ValueError("Unknown Host Bus Adapter Type")
def test_vcenter_connection(kwargs=None, call=None):
"""
Test if the connection can be made to the vCenter server using
the specified credentials inside ``/etc/salt/cloud.providers``
or ``/etc/salt/cloud.providers.d/vmware.conf``
CLI Example:
.. code-block:: bash
salt-cloud -f test_vcenter_connection my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The test_vcenter_connection function must be called with -f or --function."
)
try:
# Get the service instance object
_get_si()
except Exception as exc: # pylint: disable=broad-except
return f"failed to connect: {exc}"
return "connection successful"
def get_vcenter_version(kwargs=None, call=None):
"""
Show the vCenter Server version with build number.
CLI Example:
.. code-block:: bash
salt-cloud -f get_vcenter_version my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The get_vcenter_version function must be called with -f or --function."
)
# Get the inventory
inv = salt.utils.vmware.get_inventory(_get_si())
return inv.about.fullName
def list_datacenters(kwargs=None, call=None):
"""
List all the data centers for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_datacenters my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_datacenters function must be called with -f or --function."
)
return {"Datacenters": salt.utils.vmware.list_datacenters(_get_si())}
def list_portgroups(kwargs=None, call=None):
"""
List all the distributed virtual portgroups for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_portgroups my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_portgroups function must be called with -f or --function."
)
return {"Portgroups": salt.utils.vmware.list_portgroups(_get_si())}
def list_clusters(kwargs=None, call=None):
"""
List all the clusters for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_clusters my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_clusters function must be called with -f or --function."
)
return {"Clusters": salt.utils.vmware.list_clusters(_get_si())}
def list_datastore_clusters(kwargs=None, call=None):
"""
List all the datastore clusters for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_datastore_clusters my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_datastore_clusters function must be called with -f or --function."
)
return {"Datastore Clusters": salt.utils.vmware.list_datastore_clusters(_get_si())}
def list_datastores(kwargs=None, call=None):
"""
List all the datastores for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_datastores my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_datastores function must be called with -f or --function."
)
return {"Datastores": salt.utils.vmware.list_datastores(_get_si())}
def list_hosts(kwargs=None, call=None):
"""
List all the hosts for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_hosts my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_hosts function must be called with -f or --function."
)
return {"Hosts": salt.utils.vmware.list_hosts(_get_si())}
def list_resourcepools(kwargs=None, call=None):
"""
List all the resource pools for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_resourcepools my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_resourcepools function must be called with -f or --function."
)
return {"Resource Pools": salt.utils.vmware.list_resourcepools(_get_si())}
def list_networks(kwargs=None, call=None):
"""
List all the standard networks for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_networks my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_networks function must be called with -f or --function."
)
return {"Networks": salt.utils.vmware.list_networks(_get_si())}
def list_nodes_min(kwargs=None, call=None):
"""
Return a list of all VMs and templates that are on the specified provider, with no details
CLI Example:
.. code-block:: bash
salt-cloud -f list_nodes_min my-vmware-config
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_min function must be called with -f or --function."
)
ret = {}
vm_properties = ["name"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
ret[vm["name"]] = {"state": "Running", "id": vm["name"]}
return ret
def list_nodes(kwargs=None, call=None):
"""
Return a list of all VMs and templates that are on the specified provider, with basic fields
CLI Example:
.. code-block:: bash
salt-cloud -f list_nodes my-vmware-config
To return a list of all VMs and templates present on ALL configured providers, with basic
fields:
CLI Example:
.. code-block:: bash
salt-cloud -Q
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes function must be called with -f or --function."
)
ret = {}
vm_properties = [
"name",
"guest.ipAddress",
"config.guestFullName",
"config.hardware.numCPU",
"config.hardware.memoryMB",
"summary.runtime.powerState",
]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
cpu = vm["config.hardware.numCPU"] if "config.hardware.numCPU" in vm else "N/A"
ram = (
"{} MB".format(vm["config.hardware.memoryMB"])
if "config.hardware.memoryMB" in vm
else "N/A"
)
vm_info = {
"id": vm["name"],
"image": (
"{} (Detected)".format(vm["config.guestFullName"])
if "config.guestFullName" in vm
else "N/A"
),
"size": f"cpu: {cpu}\nram: {ram}",
"size_dict": {"cpu": cpu, "memory": ram},
"state": (
str(vm["summary.runtime.powerState"])
if "summary.runtime.powerState" in vm
else "N/A"
),
"private_ips": [vm["guest.ipAddress"]] if "guest.ipAddress" in vm else [],
"public_ips": [],
}
ret[vm_info["id"]] = vm_info
return ret
def list_nodes_full(kwargs=None, call=None):
"""
Return a list of all VMs and templates that are on the specified provider, with full details
CLI Example:
.. code-block:: bash
salt-cloud -f list_nodes_full my-vmware-config
To return a list of all VMs and templates present on ALL configured providers, with full
details:
CLI Example:
.. code-block:: bash
salt-cloud -F
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_full function must be called with -f or --function."
)
ret = {}
vm_properties = [
"config.hardware.device",
"summary.storage.committed",
"summary.storage.uncommitted",
"summary.storage.unshared",
"layoutEx.file",
"config.guestFullName",
"config.guestId",
"guest.net",
"config.hardware.memoryMB",
"name",
"config.hardware.numCPU",
"config.files.vmPathName",
"summary.runtime.powerState",
"guest.toolsStatus",
]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
ret[vm["name"]] = _format_instance_info(vm)
return ret
def list_nodes_select(call=None):
"""
Return a list of all VMs and templates that are on the specified provider, with fields
specified under ``query.selection`` in ``/etc/salt/cloud``
CLI Example:
.. code-block:: bash
salt-cloud -f list_nodes_select my-vmware-config
To return a list of all VMs and templates present on ALL configured providers, with
fields specified under ``query.selection`` in ``/etc/salt/cloud``:
CLI Example:
.. code-block:: bash
salt-cloud -S
"""
if call == "action":
raise SaltCloudSystemExit(
"The list_nodes_select function must be called with -f or --function."
)
ret = {}
vm_properties = []
selection = __opts__.get("query.selection")
if not selection:
raise SaltCloudSystemExit("query.selection not found in /etc/salt/cloud")
if "id" in selection:
vm_properties.append("name")
if "image" in selection:
vm_properties.append("config.guestFullName")
if "size" in selection:
vm_properties.extend(["config.hardware.numCPU", "config.hardware.memoryMB"])
if "state" in selection:
vm_properties.append("summary.runtime.powerState")
if "private_ips" in selection or "networks" in selection:
vm_properties.append("guest.net")
if (
"devices" in selection
or "mac_address" in selection
or "mac_addresses" in selection
):
vm_properties.append("config.hardware.device")
if "storage" in selection:
vm_properties.extend(
[
"config.hardware.device",
"summary.storage.committed",
"summary.storage.uncommitted",
"summary.storage.unshared",
]
)
if "files" in selection:
vm_properties.append("layoutEx.file")
if "guest_id" in selection:
vm_properties.append("config.guestId")
if "hostname" in selection:
vm_properties.append("guest.hostName")
if "path" in selection:
vm_properties.append("config.files.vmPathName")
if "tools_status" in selection:
vm_properties.append("guest.toolsStatus")
if not vm_properties:
return {}
elif "name" not in vm_properties:
vm_properties.append("name")
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
ret[vm["name"]] = _format_instance_info_select(vm, selection)
return ret
def show_instance(name, call=None):
"""
List all available details of the specified VM
CLI Example:
.. code-block:: bash
salt-cloud -a show_instance vmname
"""
if call != "action":
raise SaltCloudSystemExit(
"The show_instance action must be called with -a or --action."
)
vm_properties = [
"config.hardware.device",
"summary.storage.committed",
"summary.storage.uncommitted",
"summary.storage.unshared",
"layoutEx.file",
"config.guestFullName",
"config.guestId",
"guest.net",
"config.hardware.memoryMB",
"name",
"config.hardware.numCPU",
"config.files.vmPathName",
"summary.runtime.powerState",
"guest.toolsStatus",
]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
return _format_instance_info(vm)
return {}
def avail_images(call=None):
"""
Return a list of all the templates present in this VMware environment with basic
details
CLI Example:
.. code-block:: bash
salt-cloud --list-images my-vmware-config
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_images function must be called with "
"-f or --function, or with the --list-images option."
)
templates = {}
vm_properties = [
"name",
"config.template",
"config.guestFullName",
"config.hardware.numCPU",
"config.hardware.memoryMB",
]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if "config.template" in vm and vm["config.template"]:
templates[vm["name"]] = {
"name": vm["name"],
"guest_fullname": (
vm["config.guestFullName"]
if "config.guestFullName" in vm
else "N/A"
),
"cpus": (
vm["config.hardware.numCPU"]
if "config.hardware.numCPU" in vm
else "N/A"
),
"ram": (
vm["config.hardware.memoryMB"]
if "config.hardware.memoryMB" in vm
else "N/A"
),
}
return templates
def avail_locations(call=None):
"""
Return a list of all the available locations/datacenters in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud --list-locations my-vmware-config
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_locations function must be called with "
"-f or --function, or with the --list-locations option."
)
return list_datacenters(call="function")
def avail_sizes(call=None):
"""
Return a list of all the available sizes in this VMware environment.
CLI Example:
.. code-block:: bash
salt-cloud --list-sizes my-vmware-config
.. note::
Since sizes are built into templates, this function will return
an empty dictionary.
"""
if call == "action":
raise SaltCloudSystemExit(
"The avail_sizes function must be called with "
"-f or --function, or with the --list-sizes option."
)
log.warning(
"Because sizes are built into templates with VMware, there are no sizes "
"to return."
)
return {}
def list_templates(kwargs=None, call=None):
"""
List all the templates present in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_templates my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_templates function must be called with -f or --function."
)
return {"Templates": avail_images(call="function")}
def list_folders(kwargs=None, call=None):
"""
List all the folders for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_folders my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_folders function must be called with -f or --function."
)
return {"Folders": salt.utils.vmware.list_folders(_get_si())}
def list_snapshots(kwargs=None, call=None):
"""
List snapshots either for all VMs and templates or for a specific VM/template
in this VMware environment
To list snapshots for all VMs and templates:
CLI Example:
.. code-block:: bash
salt-cloud -f list_snapshots my-vmware-config
To list snapshots for a specific VM/template:
CLI Example:
.. code-block:: bash
salt-cloud -f list_snapshots my-vmware-config name="vmname"
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_snapshots function must be called with -f or --function."
)
ret = {}
vm_properties = ["name", "rootSnapshot", "snapshot"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["rootSnapshot"]:
if kwargs and kwargs.get("name") == vm["name"]:
return {vm["name"]: _get_snapshots(vm["snapshot"].rootSnapshotList)}
else:
ret[vm["name"]] = _get_snapshots(vm["snapshot"].rootSnapshotList)
else:
if kwargs and kwargs.get("name") == vm["name"]:
return {}
return ret
def start(name, call=None):
"""
To start/power on a VM using its name
CLI Example:
.. code-block:: bash
salt-cloud -a start vmname
"""
if call != "action":
raise SaltCloudSystemExit(
"The start action must be called with -a or --action."
)
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if vm["summary.runtime.powerState"] == "poweredOn":
ret = "already powered on"
log.info("VM %s %s", name, ret)
return ret
try:
log.info("Starting VM %s", name)
task = vm["object"].PowerOn()
salt.utils.vmware.wait_for_task(task, name, "power on")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while powering on VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to power on"
return "powered on"
def stop(name, soft=False, call=None):
"""
To stop/power off a VM using its name
.. note::
If ``soft=True`` then issues a command to the guest operating system
asking it to perform a clean shutdown of all services.
Default is soft=False
For ``soft=True`` vmtools should be installed on guest system.
CLI Example:
.. code-block:: bash
salt-cloud -a stop vmname
salt-cloud -a stop vmname soft=True
"""
if call != "action":
raise SaltCloudSystemExit("The stop action must be called with -a or --action.")
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if vm["summary.runtime.powerState"] == "poweredOff":
ret = "already powered off"
log.info("VM %s %s", name, ret)
return ret
try:
log.info("Stopping VM %s", name)
if soft:
vm["object"].ShutdownGuest()
else:
task = vm["object"].PowerOff()
salt.utils.vmware.wait_for_task(task, name, "power off")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while powering off VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to power off"
return "powered off"
def suspend(name, call=None):
"""
To suspend a VM using its name
CLI Example:
.. code-block:: bash
salt-cloud -a suspend vmname
"""
if call != "action":
raise SaltCloudSystemExit(
"The suspend action must be called with -a or --action."
)
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if vm["summary.runtime.powerState"] == "poweredOff":
ret = "cannot suspend in powered off state"
log.info("VM %s %s", name, ret)
return ret
elif vm["summary.runtime.powerState"] == "suspended":
ret = "already suspended"
log.info("VM %s %s", name, ret)
return ret
try:
log.info("Suspending VM %s", name)
task = vm["object"].Suspend()
salt.utils.vmware.wait_for_task(task, name, "suspend")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while suspending VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to suspend"
return "suspended"
def reset(name, soft=False, call=None):
"""
To reset a VM using its name
.. note::
If ``soft=True`` then issues a command to the guest operating system
asking it to perform a reboot. Otherwise hypervisor will terminate VM and start it again.
Default is soft=False
For ``soft=True`` vmtools should be installed on guest system.
CLI Example:
.. code-block:: bash
salt-cloud -a reset vmname
salt-cloud -a reset vmname soft=True
"""
if call != "action":
raise SaltCloudSystemExit(
"The reset action must be called with -a or --action."
)
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if (
vm["summary.runtime.powerState"] == "suspended"
or vm["summary.runtime.powerState"] == "poweredOff"
):
ret = "cannot reset in suspended/powered off state"
log.info("VM %s %s", name, ret)
return ret
try:
log.info("Resetting VM %s", name)
if soft:
vm["object"].RebootGuest()
else:
task = vm["object"].ResetVM_Task()
salt.utils.vmware.wait_for_task(task, name, "reset")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while resetting VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to reset"
return "reset"
def terminate(name, call=None):
"""
To do an immediate power off of a VM using its name. A ``SIGKILL``
is issued to the vmx process of the VM
CLI Example:
.. code-block:: bash
salt-cloud -a terminate vmname
"""
if call != "action":
raise SaltCloudSystemExit(
"The terminate action must be called with -a or --action."
)
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if vm["summary.runtime.powerState"] == "poweredOff":
ret = "already powered off"
log.info("VM %s %s", name, ret)
return ret
try:
log.info("Terminating VM %s", name)
vm["object"].Terminate()
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while terminating VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to terminate"
return "terminated"
def destroy(name, call=None):
"""
To destroy a VM from the VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -d vmname
salt-cloud --destroy vmname
salt-cloud -a destroy vmname
"""
if call == "function":
raise SaltCloudSystemExit(
"The destroy action must be called with -d, --destroy, -a or --action."
)
__utils__["cloud.fire_event"](
"event",
"destroying instance",
f"salt/cloud/{name}/destroying",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
vm_properties = ["name", "summary.runtime.powerState"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
if vm["name"] == name:
if vm["summary.runtime.powerState"] != "poweredOff":
# Power off the vm first
try:
log.info("Powering Off VM %s", name)
task = vm["object"].PowerOff()
salt.utils.vmware.wait_for_task(task, name, "power off")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while powering off VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to destroy"
try:
log.info("Destroying VM %s", name)
task = vm["object"].Destroy_Task()
salt.utils.vmware.wait_for_task(task, name, "destroy")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while destroying VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to destroy"
__utils__["cloud.fire_event"](
"event",
"destroyed instance",
f"salt/cloud/{name}/destroyed",
args={"name": name},
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
if __opts__.get("update_cachedir", False) is True:
__utils__["cloud.delete_minion_cachedir"](
name, _get_active_provider_name().split(":")[0], __opts__
)
return True
def create(vm_):
"""
To create a single VM in the VMware environment.
Sample profile and arguments that can be specified in it can be found
:ref:`here. <vmware-cloud-profile>`
CLI Example:
.. code-block:: bash
salt-cloud -p vmware-centos6.5 vmname
"""
try:
# Check for required profile parameters before sending any API calls.
if (
vm_["profile"]
and config.is_profile_configured(
__opts__,
_get_active_provider_name() or "vmware",
vm_["profile"],
vm_=vm_,
)
is False
):
return False
except AttributeError:
pass
__utils__["cloud.fire_event"](
"event",
"starting create",
"salt/cloud/{}/creating".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"creating", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
vm_name = config.get_cloud_config_value("name", vm_, __opts__, default=None)
folder = config.get_cloud_config_value("folder", vm_, __opts__, default=None)
datacenter = config.get_cloud_config_value(
"datacenter", vm_, __opts__, default=None
)
resourcepool = config.get_cloud_config_value(
"resourcepool", vm_, __opts__, default=None
)
cluster = config.get_cloud_config_value("cluster", vm_, __opts__, default=None)
datastore = config.get_cloud_config_value("datastore", vm_, __opts__, default=None)
host = config.get_cloud_config_value("host", vm_, __opts__, default=None)
template = config.get_cloud_config_value("template", vm_, __opts__, default=False)
num_cpus = config.get_cloud_config_value("num_cpus", vm_, __opts__, default=None)
cores_per_socket = config.get_cloud_config_value(
"cores_per_socket", vm_, __opts__, default=None
)
instant_clone = config.get_cloud_config_value(
"instant_clone", vm_, __opts__, default=False
)
memory = config.get_cloud_config_value("memory", vm_, __opts__, default=None)
devices = config.get_cloud_config_value("devices", vm_, __opts__, default=None)
extra_config = config.get_cloud_config_value(
"extra_config", vm_, __opts__, default=None
)
annotation = config.get_cloud_config_value(
"annotation", vm_, __opts__, default=None
)
power = config.get_cloud_config_value("power_on", vm_, __opts__, default=True)
key_filename = config.get_cloud_config_value(
"private_key", vm_, __opts__, search_global=False, default=None
)
deploy = config.get_cloud_config_value(
"deploy", vm_, __opts__, search_global=True, default=True
)
wait_for_ip_timeout = config.get_cloud_config_value(
"wait_for_ip_timeout", vm_, __opts__, default=20 * 60
)
domain = config.get_cloud_config_value(
"domain", vm_, __opts__, search_global=False, default="local"
)
hardware_version = config.get_cloud_config_value(
"hardware_version", vm_, __opts__, search_global=False, default=None
)
guest_id = config.get_cloud_config_value(
"image", vm_, __opts__, search_global=False, default=None
)
customization = config.get_cloud_config_value(
"customization", vm_, __opts__, search_global=False, default=True
)
customization_spec = config.get_cloud_config_value(
"customization_spec", vm_, __opts__, search_global=False, default=None
)
win_password = config.get_cloud_config_value(
"win_password", vm_, __opts__, search_global=False, default=None
)
win_organization_name = config.get_cloud_config_value(
"win_organization_name",
vm_,
__opts__,
search_global=False,
default="Organization",
)
plain_text = config.get_cloud_config_value(
"plain_text", vm_, __opts__, search_global=False, default=False
)
win_user_fullname = config.get_cloud_config_value(
"win_user_fullname", vm_, __opts__, search_global=False, default="Windows User"
)
win_run_once = config.get_cloud_config_value(
"win_run_once", vm_, __opts__, search_global=False, default=None
)
cpu_hot_add = config.get_cloud_config_value(
"cpu_hot_add", vm_, __opts__, search_global=False, default=None
)
cpu_hot_remove = config.get_cloud_config_value(
"cpu_hot_remove", vm_, __opts__, search_global=False, default=None
)
mem_hot_add = config.get_cloud_config_value(
"mem_hot_add", vm_, __opts__, search_global=False, default=None
)
nested_hv = config.get_cloud_config_value(
"nested_hv", vm_, __opts__, search_global=False, default=None
)
vpmc = config.get_cloud_config_value(
"vpmc", vm_, __opts__, search_global=False, default=None
)
# Get service instance object
si = _get_si()
container_ref = None
# If datacenter is specified, set the container reference to start search from it instead
if datacenter:
datacenter_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.Datacenter, datacenter
)
container_ref = datacenter_ref if datacenter_ref else None
if "clonefrom" in vm_:
# If datacenter is specified, set the container reference to start search from it instead
if datacenter:
datacenter_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datacenter, datacenter
)
container_ref = datacenter_ref if datacenter_ref else None
# Clone VM/template from specified VM/template
object_ref = salt.utils.vmware.get_mor_by_property(
si, vim.VirtualMachine, vm_["clonefrom"], container_ref=container_ref
)
if object_ref:
clone_type = "template" if object_ref.config.template else "vm"
else:
raise SaltCloudSystemExit(
"The VM/template that you have specified under clonefrom does not"
" exist."
)
else:
clone_type = None
object_ref = None
# Either a cluster, or a resource pool must be specified when cloning from template or creating.
if resourcepool:
resourcepool_ref = salt.utils.vmware.get_mor_by_property(
si, vim.ResourcePool, resourcepool, container_ref=container_ref
)
if not resourcepool_ref:
log.error("Specified resource pool: '%s' does not exist", resourcepool)
if not clone_type or clone_type == "template":
raise SaltCloudSystemExit(
"You must specify a resource pool that exists."
)
elif cluster:
cluster_ref = salt.utils.vmware.get_mor_by_property(
si, vim.ClusterComputeResource, cluster, container_ref=container_ref
)
if not cluster_ref:
log.error("Specified cluster: '%s' does not exist", cluster)
if not clone_type or clone_type == "template":
raise SaltCloudSystemExit("You must specify a cluster that exists.")
else:
resourcepool_ref = cluster_ref.resourcePool
elif clone_type == "template":
raise SaltCloudSystemExit(
"You must either specify a cluster or a resource pool when cloning from a"
" template."
)
elif not clone_type:
raise SaltCloudSystemExit(
"You must either specify a cluster or a resource pool when creating."
)
else:
log.debug("Using resource pool used by the %s %s", clone_type, vm_["clonefrom"])
# Either a datacenter or a folder can be optionally specified when cloning, required when creating.
# If not specified when cloning, the existing VM/template\'s parent folder is used.
if folder:
folder_parts = folder.split("/")
search_reference = container_ref
for folder_part in folder_parts:
if folder_part:
folder_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Folder, folder_part, container_ref=search_reference
)
search_reference = folder_ref
if not folder_ref:
log.error("Specified folder: '%s' does not exist", folder)
log.debug(
"Using folder in which %s %s is present", clone_type, vm_["clonefrom"]
)
folder_ref = object_ref.parent
elif datacenter:
if not datacenter_ref:
log.error("Specified datacenter: '%s' does not exist", datacenter)
log.debug(
"Using datacenter folder in which %s %s is present",
clone_type,
vm_["clonefrom"],
)
folder_ref = object_ref.parent
else:
folder_ref = datacenter_ref.vmFolder
elif not clone_type:
raise SaltCloudSystemExit(
"You must either specify a folder or a datacenter when creating not"
" cloning."
)
else:
log.debug(
"Using folder in which %s %s is present", clone_type, vm_["clonefrom"]
)
folder_ref = object_ref.parent
if "clonefrom" in vm_:
# Create the relocation specs
reloc_spec = vim.vm.RelocateSpec()
if (resourcepool and resourcepool_ref) or (cluster and cluster_ref):
reloc_spec.pool = resourcepool_ref
# Either a datastore/datastore cluster can be optionally specified.
# If not specified, the current datastore is used.
if datastore:
datastore_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datastore, datastore, container_ref=container_ref
)
if datastore_ref:
# specific datastore has been specified
reloc_spec.datastore = datastore_ref
else:
datastore_cluster_ref = salt.utils.vmware.get_mor_by_property(
si, vim.StoragePod, datastore, container_ref=container_ref
)
if not datastore_cluster_ref:
log.error(
"Specified datastore/datastore cluster: '%s' does not exist",
datastore,
)
log.debug(
"Using datastore used by the %s %s",
clone_type,
vm_["clonefrom"],
)
else:
log.debug("No datastore/datastore cluster specified")
log.debug("Using datastore used by the %s %s", clone_type, vm_["clonefrom"])
if host:
host_ref = salt.utils.vmware.get_mor_by_property(
si, vim.HostSystem, host, container_ref=container_ref
)
if host_ref:
reloc_spec.host = host_ref
else:
log.error("Specified host: '%s' does not exist", host)
if instant_clone:
instant_clone_spec = vim.vm.InstantCloneSpec()
instant_clone_spec.name = vm_name
instant_clone_spec.location = reloc_spec
event_kwargs = vm_.copy()
if event_kwargs.get("password"):
del event_kwargs["password"]
try:
__utils__["cloud.fire_event"](
"event",
"requesting instance",
"salt/cloud/{}/requesting".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"requesting", event_kwargs, list(event_kwargs)
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
log.info(
"Creating %s from %s(%s)", vm_["name"], clone_type, vm_["clonefrom"]
)
if datastore and not datastore_ref and datastore_cluster_ref:
# datastore cluster has been specified so apply Storage DRS recommendations
pod_spec = vim.storageDrs.PodSelectionSpec(
storagePod=datastore_cluster_ref
)
storage_spec = vim.storageDrs.StoragePlacementSpec(
type="clone",
vm=object_ref,
podSelectionSpec=pod_spec,
cloneName=vm_name,
folder=folder_ref,
)
# get recommended datastores
recommended_datastores = (
si.content.storageResourceManager.RecommendDatastores(
storageSpec=storage_spec
)
)
# apply storage DRS recommendations
task = si.content.storageResourceManager.ApplyStorageDrsRecommendation_Task(
recommended_datastores.recommendations[0].key
)
salt.utils.vmware.wait_for_task(
task, vm_name, "apply storage DRS recommendations", 5, "info"
)
else:
# Instant clone the VM
task = object_ref.InstantClone_Task(spec=instant_clone_spec)
salt.utils.vmware.wait_for_task(
task, vm_name, "Instantclone", 5, "info"
)
except Exception as exc: # pylint: disable=broad-except
err_msg = "Error Instant cloning {}: {}".format(vm_["name"], exc)
log.error(
err_msg,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {"Error": err_msg}
new_vm_ref = salt.utils.vmware.get_mor_by_property(
si, vim.VirtualMachine, vm_name, container_ref=container_ref
)
out = None
if not template and power:
ip = _wait_for_ip(new_vm_ref, wait_for_ip_timeout)
if ip:
log.info("[ %s ] IPv4 is: %s", vm_name, ip)
# ssh or smb using ip and install salt only if deploy is True
if deploy:
vm_["key_filename"] = key_filename
# if specified, prefer ssh_host to the discovered ip address
if "ssh_host" not in vm_:
vm_["ssh_host"] = ip
log.info("[ %s ] Deploying to %s", vm_name, vm_["ssh_host"])
out = __utils__["cloud.bootstrap"](vm_, __opts__)
data = show_instance(vm_name, call="action")
if deploy and isinstance(out, dict):
data["deploy_kwargs"] = out.get("deploy_kwargs", {})
__utils__["cloud.fire_event"](
"event",
"created instance",
"salt/cloud/{}/created".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"created", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return {"Instant Clone created successfully": data}
else:
if not datastore:
raise SaltCloudSystemExit(
"You must specify a datastore when creating not cloning."
)
else:
datastore_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datastore, datastore
)
if not datastore_ref:
raise SaltCloudSystemExit(
f"Specified datastore: '{datastore}' does not exist"
)
if host:
host_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.HostSystem, host, container_ref=container_ref
)
if not host_ref:
log.error("Specified host: '%s' does not exist", host)
# Create the config specs
config_spec = vim.vm.ConfigSpec()
# If the hardware version is specified and if it is different from the current
# hardware version, then schedule a hardware version upgrade
if hardware_version and object_ref is not None:
hardware_version = f"vmx-{hardware_version:02}"
if hardware_version != object_ref.config.version:
log.debug(
"Scheduling hardware version upgrade from %s to %s",
object_ref.config.version,
hardware_version,
)
scheduled_hardware_upgrade = vim.vm.ScheduledHardwareUpgradeInfo()
scheduled_hardware_upgrade.upgradePolicy = "always"
scheduled_hardware_upgrade.versionKey = hardware_version
config_spec.scheduledHardwareUpgradeInfo = scheduled_hardware_upgrade
else:
log.debug("Virtual hardware version already set to %s", hardware_version)
if num_cpus:
log.debug("Setting cpu to: %s", num_cpus)
config_spec.numCPUs = int(num_cpus)
if cores_per_socket:
log.debug("Setting cores per socket to: %s", cores_per_socket)
config_spec.numCoresPerSocket = int(cores_per_socket)
if memory:
try:
memory_num, memory_unit = re.findall(r"[^\W\d_]+|\d+.\d+|\d+", memory)
if memory_unit.lower() == "mb":
memory_mb = int(memory_num)
elif memory_unit.lower() == "gb":
memory_mb = int(float(memory_num) * 1024.0)
else:
err_msg = f"Invalid memory type specified: '{memory_unit}'"
log.error(err_msg)
return {"Error": err_msg}
except (TypeError, ValueError):
memory_mb = int(memory)
log.debug("Setting memory to: %s MB", memory_mb)
config_spec.memoryMB = memory_mb
if devices:
specs = _manage_devices(
devices, vm=object_ref, container_ref=container_ref, new_vm_name=vm_name
)
config_spec.deviceChange = specs["device_specs"]
if cpu_hot_add and hasattr(config_spec, "cpuHotAddEnabled"):
config_spec.cpuHotAddEnabled = bool(cpu_hot_add)
if cpu_hot_remove and hasattr(config_spec, "cpuHotRemoveEnabled"):
config_spec.cpuHotRemoveEnabled = bool(cpu_hot_remove)
if mem_hot_add and hasattr(config_spec, "memoryHotAddEnabled"):
config_spec.memoryHotAddEnabled = bool(mem_hot_add)
if nested_hv and hasattr(config_spec, "nestedHVEnabled"):
config_spec.nestedHVEnabled = bool(nested_hv)
if vpmc and hasattr(config_spec, "vPMCEnabled"):
config_spec.vPMCEnabled = bool(vpmc)
if extra_config:
for key, value in extra_config.items():
option = vim.option.OptionValue(key=key, value=value)
config_spec.extraConfig.append(option)
if annotation:
config_spec.annotation = str(annotation)
if "clonefrom" in vm_:
clone_spec = handle_snapshot(config_spec, object_ref, reloc_spec, template, vm_)
if not clone_spec:
clone_spec = build_clonespec(config_spec, object_ref, reloc_spec, template)
if customization and customization_spec:
customization_spec = salt.utils.vmware.get_customizationspec_ref(
si=si, customization_spec_name=customization_spec
)
clone_spec.customization = customization_spec.spec
elif customization and (devices and "network" in list(devices.keys())):
global_ip = vim.vm.customization.GlobalIPSettings()
if "dns_servers" in list(vm_.keys()):
global_ip.dnsServerList = vm_["dns_servers"]
if "domain" in list(vm_.keys()):
global_ip.dnsSuffixList = vm_["domain"]
non_hostname_chars = re.compile(r"[^\w-]")
if re.search(non_hostname_chars, vm_name):
host_name = re.split(non_hostname_chars, vm_name, maxsplit=1)[0]
domain_name = re.split(non_hostname_chars, vm_name, maxsplit=1)[-1]
else:
host_name = vm_name
domain_name = domain
if "Windows" not in object_ref.config.guestFullName:
identity = vim.vm.customization.LinuxPrep()
identity.hostName = vim.vm.customization.FixedName(name=host_name)
identity.domain = domain_name
else:
identity = vim.vm.customization.Sysprep()
identity.guiUnattended = vim.vm.customization.GuiUnattended()
identity.guiUnattended.autoLogon = True
identity.guiUnattended.autoLogonCount = 1
identity.guiUnattended.password = vim.vm.customization.Password()
identity.guiUnattended.password.value = win_password
identity.guiUnattended.password.plainText = plain_text
if win_run_once:
identity.guiRunOnce = vim.vm.customization.GuiRunOnce()
identity.guiRunOnce.commandList = win_run_once
identity.userData = vim.vm.customization.UserData()
identity.userData.fullName = win_user_fullname
identity.userData.orgName = win_organization_name
identity.userData.computerName = vim.vm.customization.FixedName()
identity.userData.computerName.name = host_name
identity.identification = vim.vm.customization.Identification()
custom_spec = vim.vm.customization.Specification(
globalIPSettings=global_ip,
identity=identity,
nicSettingMap=specs["nics_map"],
)
clone_spec.customization = custom_spec
if not template:
clone_spec.powerOn = power
log.debug("clone_spec set to:\n%s", pprint.pformat(clone_spec))
else:
config_spec.name = vm_name
config_spec.files = vim.vm.FileInfo()
config_spec.files.vmPathName = "[{0}] {1}/{1}.vmx".format(datastore, vm_name)
config_spec.guestId = guest_id
log.debug("config_spec set to:\n%s", pprint.pformat(config_spec))
event_kwargs = vm_.copy()
if event_kwargs.get("password"):
del event_kwargs["password"]
try:
__utils__["cloud.fire_event"](
"event",
"requesting instance",
"salt/cloud/{}/requesting".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"requesting", event_kwargs, list(event_kwargs)
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
if "clonefrom" in vm_:
log.info(
"Creating %s from %s(%s)", vm_["name"], clone_type, vm_["clonefrom"]
)
if datastore and not datastore_ref and datastore_cluster_ref:
# datastore cluster has been specified so apply Storage DRS recommendations
pod_spec = vim.storageDrs.PodSelectionSpec(
storagePod=datastore_cluster_ref
)
storage_spec = vim.storageDrs.StoragePlacementSpec(
type="clone",
vm=object_ref,
podSelectionSpec=pod_spec,
cloneSpec=clone_spec,
cloneName=vm_name,
folder=folder_ref,
)
# get recommended datastores
recommended_datastores = (
si.content.storageResourceManager.RecommendDatastores(
storageSpec=storage_spec
)
)
# apply storage DRS recommendations
task = si.content.storageResourceManager.ApplyStorageDrsRecommendation_Task(
recommended_datastores.recommendations[0].key
)
salt.utils.vmware.wait_for_task(
task, vm_name, "apply storage DRS recommendations", 5, "info"
)
else:
# clone the VM/template
task = object_ref.Clone(folder_ref, vm_name, clone_spec)
salt.utils.vmware.wait_for_task(task, vm_name, "clone", 5, "info")
else:
log.info("Creating %s", vm_["name"])
if host:
task = folder_ref.CreateVM_Task(config_spec, resourcepool_ref, host_ref)
else:
task = folder_ref.CreateVM_Task(config_spec, resourcepool_ref)
salt.utils.vmware.wait_for_task(task, vm_name, "create", 15, "info")
except Exception as exc: # pylint: disable=broad-except
err_msg = "Error creating {}: {}".format(vm_["name"], exc)
log.error(
err_msg,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {"Error": err_msg}
new_vm_ref = salt.utils.vmware.get_mor_by_property(
si, vim.VirtualMachine, vm_name, container_ref=container_ref
)
# Find how to power on in CreateVM_Task (if possible), for now this will do
try:
if not clone_type and power:
task = new_vm_ref.PowerOn()
salt.utils.vmware.wait_for_task(task, vm_name, "power", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.info("Powering on the VM threw this exception. Ignoring.")
log.info(exc)
# If it a template or if it does not need to be powered on then do not wait for the IP
out = None
if not template and power:
ip = _wait_for_ip(new_vm_ref, wait_for_ip_timeout)
if ip:
log.info("[ %s ] IPv4 is: %s", vm_name, ip)
# ssh or smb using ip and install salt only if deploy is True
if deploy:
vm_["key_filename"] = key_filename
# if specified, prefer ssh_host to the discovered ip address
if "ssh_host" not in vm_:
vm_["ssh_host"] = ip
log.info("[ %s ] Deploying to %s", vm_name, vm_["ssh_host"])
out = __utils__["cloud.bootstrap"](vm_, __opts__)
data = show_instance(vm_name, call="action")
if deploy and isinstance(out, dict):
data["deploy_kwargs"] = out.get("deploy_kwargs", {})
__utils__["cloud.fire_event"](
"event",
"created instance",
"salt/cloud/{}/created".format(vm_["name"]),
args=__utils__["cloud.filter_event"](
"created", vm_, ["name", "profile", "provider", "driver"]
),
sock_dir=__opts__["sock_dir"],
transport=__opts__["transport"],
)
return data
def handle_snapshot(config_spec, object_ref, reloc_spec, template, vm_):
"""
Returns a clone spec for cloning from shapshots
:rtype vim.vm.CloneSpec
"""
if "snapshot" not in vm_:
return None
allowed_types = [
FLATTEN_DISK_FULL_CLONE,
COPY_ALL_DISKS_FULL_CLONE,
CURRENT_STATE_LINKED_CLONE,
QUICK_LINKED_CLONE,
]
clone_spec = get_clonespec_for_valid_snapshot(
config_spec, object_ref, reloc_spec, template, vm_
)
if not clone_spec:
raise SaltCloudSystemExit(
"Invalid disk move type specified supported types are {}".format(
" ".join(allowed_types)
)
)
return clone_spec
def get_clonespec_for_valid_snapshot(
config_spec, object_ref, reloc_spec, template, vm_
):
"""
return clonespec only if values are valid
"""
moving = True
if QUICK_LINKED_CLONE == vm_["snapshot"]["disk_move_type"]:
reloc_spec.diskMoveType = QUICK_LINKED_CLONE
elif CURRENT_STATE_LINKED_CLONE == vm_["snapshot"]["disk_move_type"]:
reloc_spec.diskMoveType = CURRENT_STATE_LINKED_CLONE
elif COPY_ALL_DISKS_FULL_CLONE == vm_["snapshot"]["disk_move_type"]:
reloc_spec.diskMoveType = COPY_ALL_DISKS_FULL_CLONE
elif FLATTEN_DISK_FULL_CLONE == vm_["snapshot"]["disk_move_type"]:
reloc_spec.diskMoveType = FLATTEN_DISK_FULL_CLONE
else:
moving = False
if moving:
return build_clonespec(config_spec, object_ref, reloc_spec, template)
return None
def build_clonespec(config_spec, object_ref, reloc_spec, template):
"""
Returns the clone spec
"""
if reloc_spec.diskMoveType == QUICK_LINKED_CLONE:
return vim.vm.CloneSpec(
template=template,
location=reloc_spec,
config=config_spec,
snapshot=object_ref.snapshot.currentSnapshot,
)
return vim.vm.CloneSpec(template=template, location=reloc_spec, config=config_spec)
def create_datacenter(kwargs=None, call=None):
"""
Create a new data center in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f create_datacenter my-vmware-config name="MyNewDatacenter"
"""
if call != "function":
raise SaltCloudSystemExit(
"The create_datacenter function must be called with -f or --function."
)
datacenter_name = kwargs.get("name") if kwargs and "name" in kwargs else None
if not datacenter_name:
raise SaltCloudSystemExit(
"You must specify name of the new datacenter to be created."
)
if not datacenter_name or len(datacenter_name) >= 80:
raise SaltCloudSystemExit(
"The datacenter name must be a non empty string of less than 80 characters."
)
# Get the service instance
si = _get_si()
# Check if datacenter already exists
datacenter_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datacenter, datacenter_name
)
if datacenter_ref:
return {datacenter_name: "datacenter already exists"}
folder = si.content.rootFolder
# Verify that the folder is of type vim.Folder
if isinstance(folder, vim.Folder):
try:
folder.CreateDatacenter(name=datacenter_name)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error creating datacenter %s: %s",
datacenter_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return False
log.debug("Created datacenter %s", datacenter_name)
return {datacenter_name: "created"}
return False
def create_cluster(kwargs=None, call=None):
"""
Create a new cluster under the specified datacenter in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f create_cluster my-vmware-config name="myNewCluster" datacenter="datacenterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The create_cluster function must be called with -f or --function."
)
cluster_name = kwargs.get("name") if kwargs and "name" in kwargs else None
datacenter = kwargs.get("datacenter") if kwargs and "datacenter" in kwargs else None
if not cluster_name:
raise SaltCloudSystemExit(
"You must specify name of the new cluster to be created."
)
if not datacenter:
raise SaltCloudSystemExit(
"You must specify name of the datacenter where the cluster should be"
" created."
)
# Get the service instance
si = _get_si()
if not isinstance(datacenter, vim.Datacenter):
datacenter = salt.utils.vmware.get_mor_by_property(
si, vim.Datacenter, datacenter
)
if not datacenter:
raise SaltCloudSystemExit("The specified datacenter does not exist.")
# Check if cluster already exists
cluster_ref = salt.utils.vmware.get_mor_by_property(
si, vim.ClusterComputeResource, cluster_name
)
if cluster_ref:
return {cluster_name: "cluster already exists"}
cluster_spec = vim.cluster.ConfigSpecEx()
folder = datacenter.hostFolder
# Verify that the folder is of type vim.Folder
if isinstance(folder, vim.Folder):
try:
folder.CreateClusterEx(name=cluster_name, spec=cluster_spec)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error creating cluster %s: %s",
cluster_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return False
log.debug(
"Created cluster %s under datacenter %s", cluster_name, datacenter.name
)
return {cluster_name: "created"}
return False
def rescan_hba(kwargs=None, call=None):
"""
To rescan a specified HBA or all the HBAs on the Host System
CLI Example:
.. code-block:: bash
salt-cloud -f rescan_hba my-vmware-config host="hostSystemName"
salt-cloud -f rescan_hba my-vmware-config hba="hbaDeviceName" host="hostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The rescan_hba function must be called with -f or --function."
)
hba = kwargs.get("hba") if kwargs and "hba" in kwargs else None
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
host_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.HostSystem, host_name
)
try:
if hba:
log.info("Rescanning HBA %s on host %s", hba, host_name)
host_ref.configManager.storageSystem.RescanHba(hba)
ret = f"rescanned HBA {hba}"
else:
log.info("Rescanning all HBAs on host %s", host_name)
host_ref.configManager.storageSystem.RescanAllHba()
ret = "rescanned all HBAs"
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while rescaning HBA on host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to rescan HBA"}
return {host_name: ret}
def upgrade_tools_all(call=None):
"""
To upgrade VMware Tools on all virtual machines present in
the specified provider
.. note::
If the virtual machine is running Windows OS, this function
will attempt to suppress the automatic reboot caused by a
VMware Tools upgrade.
CLI Example:
.. code-block:: bash
salt-cloud -f upgrade_tools_all my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The upgrade_tools_all function must be called with -f or --function."
)
ret = {}
vm_properties = ["name"]
vm_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.VirtualMachine, vm_properties
)
for vm in vm_list:
ret[vm["name"]] = _upg_tools_helper(vm["object"])
return ret
def upgrade_tools(name, reboot=False, call=None):
"""
To upgrade VMware Tools on a specified virtual machine.
.. note::
If the virtual machine is running Windows OS, use ``reboot=True``
to reboot the virtual machine after VMware tools upgrade. Default
is ``reboot=False``
CLI Example:
.. code-block:: bash
salt-cloud -a upgrade_tools vmname
salt-cloud -a upgrade_tools vmname reboot=True
"""
if call != "action":
raise SaltCloudSystemExit(
"The upgrade_tools action must be called with -a or --action."
)
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
return _upg_tools_helper(vm_ref, reboot)
def list_hosts_by_cluster(kwargs=None, call=None):
"""
List hosts for each cluster; or hosts for a specified cluster in
this VMware environment
To list hosts for each cluster:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hosts_by_cluster my-vmware-config
To list hosts for a specified cluster:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hosts_by_cluster my-vmware-config cluster="clusterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_hosts_by_cluster function must be called with -f or --function."
)
ret = {}
cluster_name = kwargs.get("cluster") if kwargs and "cluster" in kwargs else None
cluster_properties = ["name"]
cluster_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.ClusterComputeResource, cluster_properties
)
for cluster in cluster_list:
ret[cluster["name"]] = []
for host in cluster["object"].host:
if isinstance(host, vim.HostSystem):
ret[cluster["name"]].append(host.name)
if cluster_name and cluster_name == cluster["name"]:
return {"Hosts by Cluster": {cluster_name: ret[cluster_name]}}
return {"Hosts by Cluster": ret}
def list_clusters_by_datacenter(kwargs=None, call=None):
"""
List clusters for each datacenter; or clusters for a specified datacenter in
this VMware environment
To list clusters for each datacenter:
CLI Example:
.. code-block:: bash
salt-cloud -f list_clusters_by_datacenter my-vmware-config
To list clusters for a specified datacenter:
CLI Example:
.. code-block:: bash
salt-cloud -f list_clusters_by_datacenter my-vmware-config datacenter="datacenterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_clusters_by_datacenter function must be called with "
"-f or --function."
)
ret = {}
datacenter_name = (
kwargs.get("datacenter") if kwargs and "datacenter" in kwargs else None
)
datacenter_properties = ["name"]
datacenter_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.Datacenter, datacenter_properties
)
for datacenter in datacenter_list:
ret[datacenter["name"]] = []
for cluster in datacenter["object"].hostFolder.childEntity:
if isinstance(cluster, vim.ClusterComputeResource):
ret[datacenter["name"]].append(cluster.name)
if datacenter_name and datacenter_name == datacenter["name"]:
return {"Clusters by Datacenter": {datacenter_name: ret[datacenter_name]}}
return {"Clusters by Datacenter": ret}
def list_hosts_by_datacenter(kwargs=None, call=None):
"""
List hosts for each datacenter; or hosts for a specified datacenter in
this VMware environment
To list hosts for each datacenter:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hosts_by_datacenter my-vmware-config
To list hosts for a specified datacenter:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hosts_by_datacenter my-vmware-config datacenter="datacenterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_hosts_by_datacenter function must be called with "
"-f or --function."
)
ret = {}
datacenter_name = (
kwargs.get("datacenter") if kwargs and "datacenter" in kwargs else None
)
datacenter_properties = ["name"]
datacenter_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.Datacenter, datacenter_properties
)
for datacenter in datacenter_list:
ret[datacenter["name"]] = []
for cluster in datacenter["object"].hostFolder.childEntity:
if isinstance(cluster, vim.ClusterComputeResource):
for host in cluster.host:
if isinstance(host, vim.HostSystem):
ret[datacenter["name"]].append(host.name)
if datacenter_name and datacenter_name == datacenter["name"]:
return {"Hosts by Datacenter": {datacenter_name: ret[datacenter_name]}}
return {"Hosts by Datacenter": ret}
def list_hbas(kwargs=None, call=None):
"""
List all HBAs for each host system; or all HBAs for a specified host
system; or HBAs of specified type for each host system; or HBAs of
specified type for a specified host system in this VMware environment
.. note::
You can specify type as either ``parallel``, ``iscsi``, ``block``
or ``fibre``.
To list all HBAs for each host system:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hbas my-vmware-config
To list all HBAs for a specified host system:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hbas my-vmware-config host="hostSystemName"
To list HBAs of specified type for each host system:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hbas my-vmware-config type="HBAType"
To list HBAs of specified type for a specified host system:
CLI Example:
.. code-block:: bash
salt-cloud -f list_hbas my-vmware-config host="hostSystemName" type="HBAtype"
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_hbas function must be called with -f or --function."
)
ret = {}
hba_type = kwargs.get("type").lower() if kwargs and "type" in kwargs else None
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
host_properties = ["name", "config.storageDevice.hostBusAdapter"]
if hba_type and hba_type not in ["parallel", "block", "iscsi", "fibre"]:
raise SaltCloudSystemExit(
f"Specified hba type {hba_type} currently not supported."
)
host_list = salt.utils.vmware.get_mors_with_properties(
_get_si(), vim.HostSystem, host_properties
)
for host in host_list:
ret[host["name"]] = {}
for hba in host["config.storageDevice.hostBusAdapter"]:
hba_spec = {
"driver": hba.driver,
"status": hba.status,
"type": type(hba).__name__.rsplit(".", 1)[1],
}
if hba_type:
if isinstance(hba, _get_hba_type(hba_type)):
if hba.model in ret[host["name"]]:
ret[host["name"]][hba.model][hba.device] = hba_spec
else:
ret[host["name"]][hba.model] = {hba.device: hba_spec}
else:
if hba.model in ret[host["name"]]:
ret[host["name"]][hba.model][hba.device] = hba_spec
else:
ret[host["name"]][hba.model] = {hba.device: hba_spec}
if host["name"] == host_name:
return {"HBAs by Host": {host_name: ret[host_name]}}
return {"HBAs by Host": ret}
def list_dvs(kwargs=None, call=None):
"""
List all the distributed virtual switches for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_dvs my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_dvs function must be called with -f or --function."
)
return {"Distributed Virtual Switches": salt.utils.vmware.list_dvs(_get_si())}
def list_vapps(kwargs=None, call=None):
"""
List all the vApps for this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f list_vapps my-vmware-config
"""
if call != "function":
raise SaltCloudSystemExit(
"The list_vapps function must be called with -f or --function."
)
return {"vApps": salt.utils.vmware.list_vapps(_get_si())}
def enter_maintenance_mode(kwargs=None, call=None):
"""
To put the specified host system in maintenance mode in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f enter_maintenance_mode my-vmware-config host="myHostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The enter_maintenance_mode function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
host_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.HostSystem, host_name
)
if not host_name or not host_ref:
raise SaltCloudSystemExit("You must specify a valid name of the host system.")
if host_ref.runtime.inMaintenanceMode:
return {host_name: "already in maintenance mode"}
try:
task = host_ref.EnterMaintenanceMode(timeout=0, evacuatePoweredOffVms=True)
salt.utils.vmware.wait_for_task(task, host_name, "enter maintenance mode")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while moving host system %s in maintenance mode: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to enter maintenance mode"}
return {host_name: "entered maintenance mode"}
def exit_maintenance_mode(kwargs=None, call=None):
"""
To take the specified host system out of maintenance mode in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f exit_maintenance_mode my-vmware-config host="myHostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The exit_maintenance_mode function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
host_ref = salt.utils.vmware.get_mor_by_property(
_get_si(), vim.HostSystem, host_name
)
if not host_name or not host_ref:
raise SaltCloudSystemExit("You must specify a valid name of the host system.")
if not host_ref.runtime.inMaintenanceMode:
return {host_name: "already not in maintenance mode"}
try:
task = host_ref.ExitMaintenanceMode(timeout=0)
salt.utils.vmware.wait_for_task(task, host_name, "exit maintenance mode")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while moving host system %s out of maintenance mode: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to exit maintenance mode"}
return {host_name: "exited maintenance mode"}
def create_folder(kwargs=None, call=None):
"""
Create the specified folder path in this VMware environment
.. note::
To create a Host and Cluster Folder under a Datacenter, specify
``path="/yourDatacenterName/host/yourFolderName"``
To create a Network Folder under a Datacenter, specify
``path="/yourDatacenterName/network/yourFolderName"``
To create a Storage Folder under a Datacenter, specify
``path="/yourDatacenterName/datastore/yourFolderName"``
To create a VM and Template Folder under a Datacenter, specify
``path="/yourDatacenterName/vm/yourFolderName"``
CLI Example:
.. code-block:: bash
salt-cloud -f create_folder my-vmware-config path="/Local/a/b/c"
salt-cloud -f create_folder my-vmware-config path="/MyDatacenter/vm/MyVMFolder"
salt-cloud -f create_folder my-vmware-config path="/MyDatacenter/host/MyHostFolder"
salt-cloud -f create_folder my-vmware-config path="/MyDatacenter/network/MyNetworkFolder"
salt-cloud -f create_folder my-vmware-config path="/MyDatacenter/storage/MyStorageFolder"
"""
if call != "function":
raise SaltCloudSystemExit(
"The create_folder function must be called with -f or --function."
)
# Get the service instance object
si = _get_si()
folder_path = kwargs.get("path") if kwargs and "path" in kwargs else None
if not folder_path:
raise SaltCloudSystemExit("You must specify a non empty folder path.")
folder_refs = []
inventory_path = "/"
path_exists = True
# Split the path in a list and loop over it to check for its existence
for index, folder_name in enumerate(
os.path.normpath(folder_path.strip("/")).split("/")
):
inventory_path = os.path.join(inventory_path, folder_name)
folder_ref = si.content.searchIndex.FindByInventoryPath(
inventoryPath=inventory_path
)
if isinstance(folder_ref, vim.Folder):
# This is a folder that exists so just append and skip it
log.debug("Path %s/ exists in the inventory", inventory_path)
folder_refs.append(folder_ref)
elif isinstance(folder_ref, vim.Datacenter):
# This is a datacenter that exists so just append and skip it
log.debug("Path %s/ exists in the inventory", inventory_path)
folder_refs.append(folder_ref)
else:
path_exists = False
if not folder_refs:
# If this is the first folder, create it under the rootFolder
log.debug(
"Creating folder %s under rootFolder in the inventory", folder_name
)
folder_refs.append(si.content.rootFolder.CreateFolder(folder_name))
else:
# Create the folder under the parent folder
log.debug("Creating path %s/ in the inventory", inventory_path)
folder_refs.append(folder_refs[index - 1].CreateFolder(folder_name))
if path_exists:
return {inventory_path: "specified path already exists"}
return {inventory_path: "created the specified path"}
def create_snapshot(name, kwargs=None, call=None):
"""
Create a snapshot of the specified virtual machine in this VMware
environment
.. note::
If the VM is powered on, the internal state of the VM (memory
dump) is included in the snapshot by default which will also set
the power state of the snapshot to "powered on". You can set
``memdump=False`` to override this. This field is ignored if
the virtual machine is powered off or if the VM does not support
snapshots with memory dumps. Default is ``memdump=True``
.. note::
If the VM is powered on when the snapshot is taken, VMware Tools
can be used to quiesce the file system in the virtual machine by
setting ``quiesce=True``. This field is ignored if the virtual
machine is powered off; if VMware Tools are not available or if
``memdump=True``. Default is ``quiesce=False``
CLI Example:
.. code-block:: bash
salt-cloud -a create_snapshot vmname snapshot_name="mySnapshot"
salt-cloud -a create_snapshot vmname snapshot_name="mySnapshot" [description="My snapshot"] [memdump=False] [quiesce=True]
"""
if call != "action":
raise SaltCloudSystemExit(
"The create_snapshot action must be called with -a or --action."
)
if kwargs is None:
kwargs = {}
snapshot_name = (
kwargs.get("snapshot_name") if kwargs and "snapshot_name" in kwargs else None
)
if not snapshot_name:
raise SaltCloudSystemExit(
"You must specify snapshot name for the snapshot to be created."
)
memdump = _str_to_bool(kwargs.get("memdump", True))
quiesce = _str_to_bool(kwargs.get("quiesce", False))
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
if vm_ref.summary.runtime.powerState != "poweredOn":
log.debug(
"VM %s is not powered on. Setting both memdump and quiesce to False", name
)
memdump = False
quiesce = False
if memdump and quiesce:
# Either memdump or quiesce should be set to True
log.warning(
"You can only set either memdump or quiesce to True. Setting quiesce=False"
)
quiesce = False
desc = kwargs.get("description") if "description" in kwargs else ""
try:
task = vm_ref.CreateSnapshot(snapshot_name, desc, memdump, quiesce)
salt.utils.vmware.wait_for_task(task, name, "create snapshot", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while creating snapshot of %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to create snapshot"
return {
"Snapshot created successfully": _get_snapshots(
vm_ref.snapshot.rootSnapshotList, vm_ref.snapshot.currentSnapshot
)
}
def revert_to_snapshot(name, kwargs=None, call=None):
"""
Revert virtual machine to its current snapshot. If no snapshot
exists, the state of the virtual machine remains unchanged
.. note::
The virtual machine will be powered on if the power state of
the snapshot when it was created was set to "Powered On". Set
``power_off=True`` so that the virtual machine stays powered
off regardless of the power state of the snapshot when it was
created. Default is ``power_off=False``.
If the power state of the snapshot when it was created was
"Powered On" and if ``power_off=True``, the VM will be put in
suspended state after it has been reverted to the snapshot.
CLI Example:
.. code-block:: bash
salt-cloud -a revert_to_snapshot vmame [power_off=True]
salt-cloud -a revert_to_snapshot vmame snapshot_name="selectedSnapshot" [power_off=True]
"""
if call != "action":
raise SaltCloudSystemExit(
"The revert_to_snapshot action must be called with -a or --action."
)
if kwargs is None:
kwargs = {}
snapshot_name = (
kwargs.get("snapshot_name") if kwargs and "snapshot_name" in kwargs else None
)
suppress_power_on = _str_to_bool(kwargs.get("power_off", False))
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
if not vm_ref.rootSnapshot:
log.error("VM %s does not contain any current snapshots", name)
return "revert failed"
msg = "reverted to current snapshot"
try:
if snapshot_name is None:
log.debug("Reverting VM %s to current snapshot", name)
task = vm_ref.RevertToCurrentSnapshot(suppressPowerOn=suppress_power_on)
else:
log.debug("Reverting VM %s to snapshot %s", name, snapshot_name)
msg = f"reverted to snapshot {snapshot_name}"
snapshot_ref = _get_snapshot_ref_by_name(vm_ref, snapshot_name)
if snapshot_ref is None:
return f"specified snapshot '{snapshot_name}' does not exist"
task = snapshot_ref.snapshot.Revert(suppressPowerOn=suppress_power_on)
salt.utils.vmware.wait_for_task(task, name, "revert to snapshot", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while reverting VM %s to snapshot: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "revert failed"
return msg
def remove_snapshot(name, kwargs=None, call=None):
"""
Remove a snapshot of the specified virtual machine in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -a remove_snapshot vmname snapshot_name="mySnapshot"
salt-cloud -a remove_snapshot vmname snapshot_name="mySnapshot" [remove_children="True"]
"""
if call != "action":
raise SaltCloudSystemExit(
"The create_snapshot action must be called with -a or --action."
)
if kwargs is None:
kwargs = {}
snapshot_name = (
kwargs.get("snapshot_name") if kwargs and "snapshot_name" in kwargs else None
)
remove_children = _str_to_bool(kwargs.get("remove_children", False))
if not snapshot_name:
raise SaltCloudSystemExit(
"You must specify snapshot name for the snapshot to be deleted."
)
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
if not _get_snapshot_ref_by_name(vm_ref, snapshot_name):
raise SaltCloudSystemExit(
"Сould not find the snapshot with the specified name."
)
try:
snap_obj = _get_snapshot_ref_by_name(vm_ref, snapshot_name).snapshot
task = snap_obj.RemoveSnapshot_Task(remove_children)
salt.utils.vmware.wait_for_task(task, name, "remove snapshot", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while removing snapshot of %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to remove snapshot"
if vm_ref.snapshot:
return {
"Snapshot removed successfully": _get_snapshots(
vm_ref.snapshot.rootSnapshotList, vm_ref.snapshot.currentSnapshot
)
}
return "Snapshots removed successfully"
def remove_all_snapshots(name, kwargs=None, call=None):
"""
Remove all the snapshots present for the specified virtual machine.
.. note::
All the snapshots higher up in the hierarchy of the current snapshot tree
are consolidated and their virtual disks are merged. To override this
behavior and only remove all snapshots, set ``merge_snapshots=False``.
Default is ``merge_snapshots=True``
CLI Example:
.. code-block:: bash
salt-cloud -a remove_all_snapshots vmname [merge_snapshots=False]
"""
if call != "action":
raise SaltCloudSystemExit(
"The remove_all_snapshots action must be called with -a or --action."
)
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
try:
task = vm_ref.RemoveAllSnapshots()
salt.utils.vmware.wait_for_task(task, name, "remove snapshots", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while removing snapshots on VM %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "Failed to remove snapshots"
return "Removed all snapshots"
def convert_to_template(name, kwargs=None, call=None):
"""
Convert the specified virtual machine to template.
CLI Example:
.. code-block:: bash
salt-cloud -a convert_to_template vmname
"""
if call != "action":
raise SaltCloudSystemExit(
"The convert_to_template action must be called with -a or --action."
)
vm_ref = salt.utils.vmware.get_mor_by_property(_get_si(), vim.VirtualMachine, name)
if vm_ref.config.template:
raise SaltCloudSystemExit(f"{name} already a template")
try:
vm_ref.MarkAsTemplate()
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while converting VM to template %s: %s",
name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return "failed to convert to teamplate"
return f"{name} converted to template"
def add_host(kwargs=None, call=None):
"""
Add a host system to the specified cluster or datacenter in this VMware environment
.. note::
To use this function, you need to specify ``esxi_host_user`` and
``esxi_host_password`` under your provider configuration set up at
``/etc/salt/cloud.providers`` or ``/etc/salt/cloud.providers.d/vmware.conf``:
.. code-block:: yaml
vcenter01:
driver: vmware
user: 'DOMAIN\\user'
password: 'verybadpass'
url: 'vcenter01.domain.com'
# Required when adding a host system
esxi_host_user: 'root'
esxi_host_password: 'myhostpassword'
# Optional fields that can be specified when adding a host system
esxi_host_ssl_thumbprint: '12:A3:45:B6:CD:7E:F8:90:A1:BC:23:45:D6:78:9E:FA:01:2B:34:CD'
The SSL thumbprint of the host system can be optionally specified by setting
``esxi_host_ssl_thumbprint`` under your provider configuration. To get the SSL
thumbprint of the host system, execute the following command from a remote
server:
.. code-block:: bash
echo -n | openssl s_client -connect <YOUR-HOSTSYSTEM-DNS/IP>:443 2>/dev/null | openssl x509 -noout -fingerprint -sha1
CLI Example:
.. code-block:: bash
salt-cloud -f add_host my-vmware-config host="myHostSystemName" cluster="myClusterName"
salt-cloud -f add_host my-vmware-config host="myHostSystemName" datacenter="myDatacenterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The add_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
cluster_name = kwargs.get("cluster") if kwargs and "cluster" in kwargs else None
datacenter_name = (
kwargs.get("datacenter") if kwargs and "datacenter" in kwargs else None
)
host_user = config.get_cloud_config_value(
"esxi_host_user", get_configured_provider(), __opts__, search_global=False
)
host_password = config.get_cloud_config_value(
"esxi_host_password", get_configured_provider(), __opts__, search_global=False
)
host_ssl_thumbprint = config.get_cloud_config_value(
"esxi_host_ssl_thumbprint",
get_configured_provider(),
__opts__,
search_global=False,
)
if not host_user:
raise SaltCloudSystemExit(
"You must specify the ESXi host username in your providers config."
)
if not host_password:
raise SaltCloudSystemExit(
"You must specify the ESXi host password in your providers config."
)
if not host_name:
raise SaltCloudSystemExit(
"You must specify either the IP or DNS name of the host system."
)
if (cluster_name and datacenter_name) or not (cluster_name or datacenter_name):
raise SaltCloudSystemExit(
"You must specify either the cluster name or the datacenter name."
)
# Get the service instance
si = _get_si()
if cluster_name:
cluster_ref = salt.utils.vmware.get_mor_by_property(
si, vim.ClusterComputeResource, cluster_name
)
if not cluster_ref:
raise SaltCloudSystemExit("Specified cluster does not exist.")
if datacenter_name:
datacenter_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datacenter, datacenter_name
)
if not datacenter_ref:
raise SaltCloudSystemExit("Specified datacenter does not exist.")
spec = vim.host.ConnectSpec(
hostName=host_name,
userName=host_user,
password=host_password,
)
if host_ssl_thumbprint:
spec.sslThumbprint = host_ssl_thumbprint
else:
log.warning("SSL thumbprint has not been specified in provider configuration")
# This smells like a not-so-good idea. A plenty of VMWare VCenters
# do not listen to the default port 443.
try:
log.debug("Trying to get the SSL thumbprint directly from the host system")
p1 = subprocess.Popen(
("echo", "-n"), stdout=subprocess.PIPE, stderr=subprocess.PIPE
)
p2 = subprocess.Popen(
("openssl", "s_client", "-connect", f"{host_name}:443"),
stdin=p1.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
p3 = subprocess.Popen(
("openssl", "x509", "-noout", "-fingerprint", "-sha1"),
stdin=p2.stdout,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
out = salt.utils.stringutils.to_str(p3.stdout.read())
ssl_thumbprint = out.split("=")[-1].strip()
log.debug(
"SSL thumbprint received from the host system: %s", ssl_thumbprint
)
spec.sslThumbprint = ssl_thumbprint
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while trying to get SSL thumbprint of host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to add host"}
try:
if cluster_name:
task = cluster_ref.AddHost(spec=spec, asConnected=True)
ret = f"added host system to cluster {cluster_name}"
if datacenter_name:
task = datacenter_ref.hostFolder.AddStandaloneHost(
spec=spec, addConnected=True
)
ret = f"added host system to datacenter {datacenter_name}"
salt.utils.vmware.wait_for_task(task, host_name, "add host system", 5, "info")
except Exception as exc: # pylint: disable=broad-except
if isinstance(exc, vim.fault.SSLVerifyFault):
log.error("Authenticity of the host's SSL certificate is not verified")
log.info(
"Try again after setting the esxi_host_ssl_thumbprint "
"to %s in provider configuration",
spec.sslThumbprint,
)
log.error(
"Error while adding host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to add host"}
return {host_name: ret}
def remove_host(kwargs=None, call=None):
"""
Remove the specified host system from this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f remove_host my-vmware-config host="myHostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The remove_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
# Get the service instance
si = _get_si()
host_ref = salt.utils.vmware.get_mor_by_property(si, vim.HostSystem, host_name)
if not host_ref:
raise SaltCloudSystemExit("Specified host system does not exist.")
try:
if isinstance(host_ref.parent, vim.ClusterComputeResource):
# This is a host system that is part of a Cluster
task = host_ref.Destroy_Task()
else:
# This is a standalone host system
task = host_ref.parent.Destroy_Task()
salt.utils.vmware.wait_for_task(
task, host_name, "remove host", log_level="info"
)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while removing host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to remove host"}
return {host_name: "removed host from vcenter"}
def connect_host(kwargs=None, call=None):
"""
Connect the specified host system in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f connect_host my-vmware-config host="myHostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The connect_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
# Get the service instance
si = _get_si()
host_ref = salt.utils.vmware.get_mor_by_property(si, vim.HostSystem, host_name)
if not host_ref:
raise SaltCloudSystemExit("Specified host system does not exist.")
if host_ref.runtime.connectionState == "connected":
return {host_name: "host system already connected"}
try:
task = host_ref.ReconnectHost_Task()
salt.utils.vmware.wait_for_task(task, host_name, "connect host", 5, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while connecting host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to connect host"}
return {host_name: "connected host"}
def disconnect_host(kwargs=None, call=None):
"""
Disconnect the specified host system in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f disconnect_host my-vmware-config host="myHostSystemName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The disconnect_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
# Get the service instance
si = _get_si()
host_ref = salt.utils.vmware.get_mor_by_property(si, vim.HostSystem, host_name)
if not host_ref:
raise SaltCloudSystemExit("Specified host system does not exist.")
if host_ref.runtime.connectionState == "disconnected":
return {host_name: "host system already disconnected"}
try:
task = host_ref.DisconnectHost_Task()
salt.utils.vmware.wait_for_task(
task, host_name, "disconnect host", log_level="info"
)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while disconnecting host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to disconnect host"}
return {host_name: "disconnected host"}
def reboot_host(kwargs=None, call=None):
"""
Reboot the specified host system in this VMware environment
.. note::
If the host system is not in maintenance mode, it will not be rebooted. If you
want to reboot the host system regardless of whether it is in maintenance mode,
set ``force=True``. Default is ``force=False``.
CLI Example:
.. code-block:: bash
salt-cloud -f reboot_host my-vmware-config host="myHostSystemName" [force=True]
"""
if call != "function":
raise SaltCloudSystemExit(
"The reboot_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
force = _str_to_bool(kwargs.get("force")) if kwargs and "force" in kwargs else False
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
# Get the service instance
si = _get_si()
host_ref = salt.utils.vmware.get_mor_by_property(si, vim.HostSystem, host_name)
if not host_ref:
raise SaltCloudSystemExit("Specified host system does not exist.")
if host_ref.runtime.connectionState == "notResponding":
raise SaltCloudSystemExit(
"Specified host system cannot be rebooted in it's current state (not"
" responding)."
)
if not host_ref.capability.rebootSupported:
raise SaltCloudSystemExit("Specified host system does not support reboot.")
if not host_ref.runtime.inMaintenanceMode and not force:
raise SaltCloudSystemExit(
"Specified host system is not in maintenance mode. Specify force=True to"
" force reboot even if there are virtual machines running or other"
" operations in progress."
)
try:
host_ref.RebootHost_Task(force)
_wait_for_host(host_ref, "reboot", 10, "info")
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while rebooting host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to reboot host"}
return {host_name: "rebooted host"}
def create_datastore_cluster(kwargs=None, call=None):
"""
Create a new datastore cluster for the specified datacenter in this VMware environment
CLI Example:
.. code-block:: bash
salt-cloud -f create_datastore_cluster my-vmware-config name="datastoreClusterName" datacenter="datacenterName"
"""
if call != "function":
raise SaltCloudSystemExit(
"The create_datastore_cluster function must be called with "
"-f or --function."
)
datastore_cluster_name = kwargs.get("name") if kwargs and "name" in kwargs else None
datacenter_name = (
kwargs.get("datacenter") if kwargs and "datacenter" in kwargs else None
)
if not datastore_cluster_name:
raise SaltCloudSystemExit(
"You must specify name of the new datastore cluster to be created."
)
if not datastore_cluster_name or len(datastore_cluster_name) >= 80:
raise SaltCloudSystemExit(
"The datastore cluster name must be a non empty string of less than 80"
" characters."
)
if not datacenter_name:
raise SaltCloudSystemExit(
"You must specify name of the datacenter where the datastore cluster should"
" be created."
)
# Get the service instance
si = _get_si()
# Check if datastore cluster already exists
datastore_cluster_ref = salt.utils.vmware.get_mor_by_property(
si, vim.StoragePod, datastore_cluster_name
)
if datastore_cluster_ref:
return {datastore_cluster_name: "datastore cluster already exists"}
datacenter_ref = salt.utils.vmware.get_mor_by_property(
si, vim.Datacenter, datacenter_name
)
if not datacenter_ref:
raise SaltCloudSystemExit("The specified datacenter does not exist.")
try:
datacenter_ref.datastoreFolder.CreateStoragePod(name=datastore_cluster_name)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error creating datastore cluster %s: %s",
datastore_cluster_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return False
return {datastore_cluster_name: "created"}
def shutdown_host(kwargs=None, call=None):
"""
Shut down the specified host system in this VMware environment
.. note::
If the host system is not in maintenance mode, it will not be shut down. If you
want to shut down the host system regardless of whether it is in maintenance mode,
set ``force=True``. Default is ``force=False``.
CLI Example:
.. code-block:: bash
salt-cloud -f shutdown_host my-vmware-config host="myHostSystemName" [force=True]
"""
if call != "function":
raise SaltCloudSystemExit(
"The shutdown_host function must be called with -f or --function."
)
host_name = kwargs.get("host") if kwargs and "host" in kwargs else None
force = _str_to_bool(kwargs.get("force")) if kwargs and "force" in kwargs else False
if not host_name:
raise SaltCloudSystemExit("You must specify name of the host system.")
# Get the service instance
si = _get_si()
host_ref = salt.utils.vmware.get_mor_by_property(si, vim.HostSystem, host_name)
if not host_ref:
raise SaltCloudSystemExit("Specified host system does not exist.")
if host_ref.runtime.connectionState == "notResponding":
raise SaltCloudSystemExit(
"Specified host system cannot be shut down in it's current state (not"
" responding)."
)
if not host_ref.capability.rebootSupported:
raise SaltCloudSystemExit("Specified host system does not support shutdown.")
if not host_ref.runtime.inMaintenanceMode and not force:
raise SaltCloudSystemExit(
"Specified host system is not in maintenance mode. Specify force=True to"
" force reboot even if there are virtual machines running or other"
" operations in progress."
)
try:
host_ref.ShutdownHost_Task(force)
except Exception as exc: # pylint: disable=broad-except
log.error(
"Error while shutting down host %s: %s",
host_name,
exc,
# Show the traceback if the debug logging level is enabled
exc_info_on_loglevel=logging.DEBUG,
)
return {host_name: "failed to shut down host"}
return {host_name: "shut down host"}
Zerion Mini Shell 1.0