Source code for saltext.vmware.utils.common

# Copyright 2021 VMware, Inc.
# SPDX-License-Identifier: Apache-2.0
"""
Common functions used across modules
"""
import errno
import logging
import re
import time
from http.client import BadStatusLine

import salt.exceptions
import salt.modules.cmdmod
import salt.utils.path
import salt.utils.platform
import salt.utils.stringutils

try:
    from pyVmomi import vim, vmodl

    HAS_PYVMOMI = True
except ImportError:
    HAS_PYVMOMI = False

CAMELCASE_PATTERN = re.compile("((?<=[a-z0-9])[A-Z]|(?!^)[A-Z](?=[a-z]))")

log = logging.getLogger(__name__)


def camel_to_snake_case(attrib):
    return CAMELCASE_PATTERN.sub(r"_\1", attrib).lower()


[docs]def get_root_folder(service_instance): """ Returns the root folder of a vCenter. service_instance The Service Instance Object for which to obtain the root folder. """ try: log.trace("Retrieving root folder") return service_instance.RetrieveContent().rootFolder except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg)
[docs]def get_service_content(service_instance): """ Returns the service content for a Service Instance. service_instance The Service Instance from which to obtain service content. """ try: return service_instance.RetrieveServiceContent() except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( f"Not enough permissions. Required privilege: '{exc.privilegeId}'" ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg)
[docs]def get_content( service_instance, obj_type, property_list=None, container_ref=None, traversal_spec=None, local_properties=False, ): """ Returns the content of the specified type of object for a Service Instance. For more information, please see: http://pubs.vmware.com/vsphere-50/index.jsp?topic=%2Fcom.vmware.wssdk.pg.doc_50%2FPG_Ch5_PropertyCollector.7.6.html service_instance The Service Instance from which to obtain content. obj_type The type of content to obtain. property_list An optional list of object properties to used to return even more filtered content results. container_ref An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter, ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory rootFolder. traversal_spec An optional TraversalSpec to be used instead of the standard ``Traverse All`` spec. local_properties Flag specifying whether the properties to be retrieved are local to the container. If that is the case, the traversal spec needs to be None. """ # Start at the rootFolder if container starting point not specified if not container_ref: container_ref = get_root_folder(service_instance) # By default, the object reference used as the starting poing for the filter # is the container_ref passed in the function obj_ref = container_ref local_traversal_spec = False if not traversal_spec and not local_properties: local_traversal_spec = True # We don't have a specific traversal spec override so we are going to # get everything using a container view try: obj_ref = service_instance.content.viewManager.CreateContainerView( container_ref, [obj_type], True ) except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) # Create 'Traverse All' traversal spec to determine the path for # collection traversal_spec = vmodl.query.PropertyCollector.TraversalSpec( name="traverseEntities", path="view", skip=False, type=vim.view.ContainerView, ) # Create property spec to determine properties to be retrieved property_spec = vmodl.query.PropertyCollector.PropertySpec( type=obj_type, all=True if not property_list else False, pathSet=property_list ) # Create object spec to navigate content obj_spec = vmodl.query.PropertyCollector.ObjectSpec( obj=obj_ref, skip=True if not local_properties else False, selectSet=[traversal_spec] if not local_properties else None, ) # Create a filter spec and specify object, property spec in it filter_spec = vmodl.query.PropertyCollector.FilterSpec( objectSet=[obj_spec], propSet=[property_spec], reportMissingObjectsInResults=False, ) # Retrieve the contents try: content = service_instance.content.propertyCollector.RetrieveContents([filter_spec]) except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) # Destroy the object view if local_traversal_spec: try: obj_ref.Destroy() except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) return content
[docs]def get_mors_with_properties( service_instance, object_type, property_list=None, container_ref=None, traversal_spec=None, local_properties=False, ): """ Returns a list containing properties and managed object references for the managed object. service_instance The Service Instance from which to obtain managed object references. object_type The type of content for which to obtain managed object references. property_list An optional list of object properties used to return even more filtered managed object reference results. container_ref An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter, ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory rootFolder. traversal_spec An optional TraversalSpec to be used instead of the standard ``Traverse All`` spec local_properties Flag specifying whether the properties to be retrieved are local to the container. If that is the case, the traversal spec needs to be None. """ # Get all the content content_args = [service_instance, object_type] content_kwargs = { "property_list": property_list, "container_ref": container_ref, "traversal_spec": traversal_spec, "local_properties": local_properties, } try: content = get_content(*content_args, **content_kwargs) except BadStatusLine: content = get_content(*content_args, **content_kwargs) except OSError as exc: if exc.errno != errno.EPIPE: raise content = get_content(*content_args, **content_kwargs) object_list = [] for obj in content: properties = {} for prop in obj.propSet: properties[prop.name] = prop.val properties["object"] = obj.obj object_list.append(properties) log.trace("Retrieved %s objects", len(object_list)) return object_list
[docs]def get_mor_by_property( service_instance, object_type, property_value, property_name="name", container_ref=None, ): """ Returns the first managed object reference having the specified property value. service_instance The Service Instance from which to obtain managed object references. object_type The type of content for which to obtain managed object references. property_value The name of the property for which to obtain the managed object reference. property_name An object property used to return the specified object reference results. Defaults to ``name``. container_ref An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter, ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory rootFolder. """ # Get list of all managed object references with specified property object_list = get_mors_with_properties( service_instance, object_type, property_list=[property_name], container_ref=container_ref, ) for obj in object_list: obj_id = str(obj.get("object", "")).strip("'\"") if obj[property_name] == property_value or property_value == obj_id: return obj["object"] return None
[docs]def list_objects(service_instance, vim_object, properties=None): """ Returns a simple list of objects from a given service instance. service_instance The Service Instance for which to obtain a list of objects. object_type The type of content for which to obtain information. properties An optional list of object properties used to return reference results. If not provided, defaults to ``name``. """ if properties is None: properties = ["name"] items = [] item_list = get_mors_with_properties(service_instance, vim_object, properties) for item in item_list: items.append(item["name"]) return items
[docs]def get_service_instance_from_managed_object(mo_ref, name="<unnamed>"): """ Retrieves the service instance from a managed object. me_ref Reference to a managed object (of type vim.ManagedEntity). name Name of managed object. This field is optional. """ if not name: name = mo_ref.name log.trace("[%s] Retrieving service instance from managed object", name) si = vim.ServiceInstance("ServiceInstance") si._stub = mo_ref._stub return si
[docs]def get_properties_of_managed_object(mo_ref, properties): """ Returns specific properties of a managed object, retrieved in an optimally. mo_ref The managed object reference. properties List of properties of the managed object to retrieve. """ service_instance = get_service_instance_from_managed_object(mo_ref) log.trace("Retrieving name of %s", type(mo_ref).__name__) try: items = get_mors_with_properties( service_instance, type(mo_ref), container_ref=mo_ref, property_list=["name"], local_properties=True, ) mo_name = items[0]["name"] except vmodl.query.InvalidProperty: mo_name = "<unnamed>" log.trace( "Retrieving properties '%s' of %s '%s'", properties, type(mo_ref).__name__, mo_name, ) items = get_mors_with_properties( service_instance, type(mo_ref), container_ref=mo_ref, property_list=properties, local_properties=True, ) if not items: raise salt.exceptions.VMwareApiError( "Properties of managed object '{}' weren't " "retrieved".format(mo_name) ) return items[0]
[docs]def get_managed_object_name(mo_ref): """ Returns the name of a managed object. If the name wasn't found, it returns None. mo_ref The managed object reference. """ props = get_properties_of_managed_object(mo_ref, ["name"]) return props.get("name")
[docs]def get_resource_pools( service_instance, resource_pool_names, datacenter_name=None, get_all_resource_pools=False, ): """ Retrieves resource pool objects service_instance The service instance object to query the vCenter resource_pool_names Resource pool names datacenter_name Name of the datacenter where the resource pool is available get_all_resource_pools Boolean return Resourcepool managed object reference """ properties = ["name"] if not resource_pool_names: resource_pool_names = [] if datacenter_name: import saltext.vmware.utils.datacenter as utils_datacenter container_ref = utils_datacenter.get_datacenter(service_instance, datacenter_name) else: container_ref = get_root_folder(service_instance) resource_pools = get_mors_with_properties( service_instance, vim.ResourcePool, container_ref=container_ref, property_list=properties, ) selected_pools = [] for pool in resource_pools: if get_all_resource_pools or (pool["name"] in resource_pool_names): selected_pools.append(pool["object"]) if not selected_pools: raise salt.exceptions.VMwareObjectRetrievalError( "The resource pools with properties " "names={} get_all={} could not be found".format(selected_pools, get_all_resource_pools) ) return selected_pools
def _filter_kwargs(allowed_kwargs, default_dict=None, **kwargs): result = default_dict or {} for field in allowed_kwargs: val = kwargs.get(field) if val is not None: result[field] = val return result def _read_paginated(func, display_name, **kwargs): results = [] paginated = {"cursor": None} while "cursor" in paginated: paginated = func(**kwargs) if "error" in paginated: return paginated results.extend( result for result in paginated["results"] if result.get("display_name") == display_name ) kwargs["cursor"] = paginated.get("cursor") return results
[docs]def wait_for_task(task, instance_name, task_type, sleep_seconds=1, log_level="debug"): """ Waits for a task to be completed. task The task to wait for. instance_name The name of the ESXi host, vCenter Server, or Virtual Machine that the task is being run on. task_type The type of task being performed. Useful information for debugging purposes. sleep_seconds The number of seconds to wait before querying the task again. Defaults to ``1`` second. log_level The level at which to log task information. Default is ``debug``, but ``info`` is also supported. """ time_counter = 0 start_time = time.time() log.trace("task = %s, task_type = %s", task, task.__class__.__name__) try: task_info = task.info except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.FileNotFound as exc: log.exception(exc) raise salt.exceptions.VMwareFileNotFoundError(exc.msg) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) while task_info.state == "running" or task_info.state == "queued": if time_counter % sleep_seconds == 0: msg = "[ {} ] Waiting for {} task to finish [{} s]".format( instance_name, task_type, time_counter ) if log_level == "info": log.info(msg) else: log.debug(msg) time.sleep(1.0 - ((time.time() - start_time) % 1.0)) time_counter += 1 try: task_info = task.info except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.FileNotFound as exc: log.exception(exc) raise salt.exceptions.VMwareFileNotFoundError(exc.msg) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) if task_info.state == "success": msg = "[ {} ] Successfully completed {} task in {} seconds".format( instance_name, task_type, time_counter ) if log_level == "info": log.info(msg) else: log.debug(msg) # task is in a successful state return task_info.result else: # task is in an error state try: raise task_info.error except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.FileNotFound as exc: log.exception(exc) raise salt.exceptions.VMwareFileNotFoundError(exc.msg) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.fault.SystemError as exc: log.exception(exc) raise salt.exceptions.VMwareSystemError(exc.msg) except vmodl.fault.InvalidArgument as exc: log.exception(exc) exc_message = exc.msg if exc.faultMessage: exc_message = "{} ({})".format(exc_message, exc.faultMessage[0].message) raise salt.exceptions.VMwareApiError(exc_message)
[docs]def get_parent_type(node, parent_type): """ Return a parent of specified type from a node. node Object reference to start at. parent_type The vim type of the parent you are searching for. """ if isinstance(node, parent_type): return node try: node = node.parent except AttributeError: return None return get_parent_type(node, parent_type)
[docs]def get_path(node, service_instance, path=""): """ Return a path to root from a node. node Object reference to start at. service_instance The Service Instance from which to obtain managed object references. path Path to node, recursively passed. """ if node == service_instance.content.rootFolder: return path try: path = "/" + node.name + path node = node.parent except AttributeError: return path return get_path(node, service_instance, path)
[docs]def get_datacenters(service_instance, datacenter_names=None, get_all_datacenters=False): """ Returns all datacenters in a vCenter. service_instance The Service Instance Object from which to obtain cluster. datacenter_names List of datacenter names to filter by. Default value is None. get_all_datacenters Flag specifying whether to retrieve all datacenters. Default value is None. """ items = [ i["object"] for i in get_mors_with_properties(service_instance, vim.Datacenter, property_list=["name"]) if get_all_datacenters or (datacenter_names and i["name"] in datacenter_names) ] return items
[docs]def get_datacenter(service_instance, datacenter_name): """ Returns a vim.Datacenter managed object. service_instance The Service Instance Object from which to obtain datacenter. datacenter_name The datacenter name """ items = get_datacenters(service_instance, datacenter_names=[datacenter_name]) if not items: raise salt.exceptions.VMwareObjectRetrievalError( "Datacenter '{}' was not found".format(datacenter_name) ) return items[0]
[docs]def list_datacenters(service_instance): """ Returns a list of datacenters associated with a given service instance. service_instance The Service Instance Object from which to obtain datacenters. """ return list_objects(service_instance, vim.Datacenter)
[docs]def create_datacenter(service_instance, datacenter_name): """ Creates a datacenter. .. versionadded:: 2017.7.0 service_instance The Service Instance Object datacenter_name The datacenter name """ root_folder = get_root_folder(service_instance) log.trace("Creating datacenter '%s'", datacenter_name) try: dc_obj = root_folder.CreateDatacenter(datacenter_name) except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) return dc_obj
[docs]def delete_datacenter(service_instance, datacenter_name): """ Deletes a datacenter. service_instance The Service Instance Object datacenter_name The datacenter name """ root_folder = get_root_folder(service_instance) log.trace("Deleting datacenter '%s'", datacenter_name) try: dc_obj = get_datacenter(service_instance, datacenter_name) task = dc_obj.Destroy_Task() except vim.fault.NoPermission as exc: log.exception(exc) raise salt.exceptions.VMwareApiError( "Not enough permissions. Required privilege: " "{}".format(exc.privilegeId) ) except vim.fault.VimFault as exc: log.exception(exc) raise salt.exceptions.VMwareApiError(exc.msg) except vmodl.RuntimeFault as exc: log.exception(exc) raise salt.exceptions.VMwareRuntimeError(exc.msg) wait_for_task(task, datacenter_name, "DeleteDatacenterTask")
[docs]def get_parent_of_type(mors, type): """ Finds the first parent of a managed object that matches the type specified. `None` is returned if no object is found. """ while True: if isinstance(mors, type): return mors try: mors = mors.parent except AttributeError: return None
[docs]def find_filtered_object(service_instance, datacenter_name=None, cluster_name=None, host_name=None): """ Finds zero or one matching objects: plug in almost any combination of datacenter, cluster, and/or host name. If cluster_name is passed, datacenter_name must also be passed. At least one of the optional parameters must be set. The most specific object will be returned (if you pass host_name and datacenter_name, the host will be returned). service_instance The Service Instance Object from which to obtain cluster. datacenter_name (Optional) Datacenter name to filter by. cluster_name (Optional) Exact cluster name to filter by. If used, datacenter_name is required. host_name (Optional) Exact host name name to filter by. """ try: if host_name: import saltext.vmware.utils.esxi as utils_esxi hosts = utils_esxi.get_hosts( service_instance, datacenter_name=datacenter_name, cluster_name=cluster_name, host_names=[host_name], ) return hosts[0] if hosts else None elif cluster_name and datacenter_name: import saltext.vmware.utils.cluster as utils_cluster datacenter = get_datacenter(service_instance, datacenter_name) return utils_cluster.get_cluster(datacenter, cluster_name) elif datacenter_name: return get_datacenter(service_instance, datacenter_name=datacenter_name) else: raise salt.exceptions.ArgumentValueError( "find_filtered_object requires at least one of datacenter_name, host_name, or cluster_name with datacenter_name" ) except salt.exceptions.VMwareObjectRetrievalError: return None
[docs]def get_license_mgrs(service_instance, license_mgr_names=None, get_all_license_mgrs=False): """ Returns all license managers in a vCenter. service_instance The Service Instance Object from which to obtain cluster. license_mgr_names List of license manager names to filter by. Default value is None. get_all_license_mgrs Flag specifying whether to retrieve all license managers. Default value is None. """ log.debug("started get all License Managers") items = [ i["object"] for i in get_mors_with_properties( service_instance, vim.LicenseAssignmentManager, property_list=["name"] ) if get_all_license_mgrs or (license_mgr_names and i["name"] in license_mgr_names) ] log.debug("exited get all License Managers") return items
[docs]def get_license_mgr(service_instance, license_mgr_name): """ Returns a vim.LicenseAssignmentManager managed object. service_instance The Service Instance Object from which to obtain license manager. license_mgr_name The license manager name """ log.debug(f"started get License Manager '{license_mgr_name}'") items = get_license_mgrs(service_instance, license_mgr_names=[license_mgr_name]) if not items: raise salt.exceptions.VMwareObjectRetrievalError( f"license manager '{license_mgr_name}' was not found" ) log.debug(f"exit License Manager '{license_mgr_name}'") return items[0]
[docs]def list_license_mgrs(service_instance): """ Returns a list of license managers associated with a given service instance. service_instance The Service Instance Object from which to obtain license managers. """ log.debug("start list of License Managers") return list_objects(service_instance, vim.LicenseAssignmentManager)
[docs]def deployment_resources(host_name, service_instance): """ Returns the dict representation of deployment resources from given host name. host_name The name of the esxi host to obtain esxi reference. """ destination_host_ref = get_mor_by_property( service_instance, vim.HostSystem, host_name, ) datacenter_ref = get_parent_type(destination_host_ref, vim.Datacenter) cluster_ref = get_parent_type(destination_host_ref, vim.ClusterComputeResource) resource_pool = cluster_ref.resourcePool return { "destination_host": destination_host_ref, "datacenter": datacenter_ref, "cluster": cluster_ref, "resource_pool": resource_pool, }