Source code for saltext.vmware.states.esxi

# SPDX-License-Identifier: Apache-2.0
import functools
import logging

import salt
import saltext.vmware.utils.esxi as utils_esxi
from saltext.vmware.utils.connect import get_service_instance

log = logging.getLogger(__name__)

try:
    import pyVmomi
    from pyVmomi import vmodl, vim, VmomiSupport

    HAS_PYVMOMI = True
except ImportError:
    HAS_PYVMOMI = False


__virtualname__ = "vmware_esxi"
__proxyenabled__ = ["vmware_esxi"]


def __virtual__():
    if not HAS_PYVMOMI:
        return False, "Unable to import pyVmomi module."
    return __virtualname__


[docs]def role_present( name, privilege_ids, esxi_host_name=None, service_instance=None, ): """ Ensure role is present on service instance, which may be an ESXi host or vCenter instance. role_name Role to create/update on service instance. (required). privilege_ids List of privileges for the role. (required). Refer: https://docs.vmware.com/en/VMware-vSphere/7.0/com.vmware.vsphere.security.doc/GUID-ED56F3C4-77D0-49E3-88B6-B99B8B437B62.html Example: ['Folder.Create', 'Folder.Delete']. esxi_host_name Connect to this ESXi host using your pillar's service_instance credentials. (optional). service_instance Use this vCenter service connection instance instead of creating a new one. (optional). """ log.debug("Running vmware_esxi.role_present") ret = {"name": name, "result": None, "comment": "", "changes": {}} if not service_instance: service_instance = get_service_instance( opts=__opts__, pillar=__pillar__, esxi_host=esxi_host_name, ) role = __salt__["vmware_esxi.get_role"](role_name=name, service_instance=service_instance) sys_privs = {"System.Anonymous", "System.Read", "System.View"} del_privs = list(set(role.get("privilege_ids", [])) - sys_privs - set(privilege_ids)) new_privs = list(set(privilege_ids) - set(role.get("privilege_ids", [])) - sys_privs) changes = { "new": { "role_id": role.get("role_id"), "role_name": name, "privilege_ids": { "added": new_privs, "removed": del_privs, "current": sorted(list(set(privilege_ids) | sys_privs)), }, }, "old": role, } if not role: if __opts__["test"]: ret["comment"] = "Role {} will be created.".format(name) ret["result"] = None else: role = __salt__["vmware_esxi.add_role"]( role_name=name, privilege_ids=privilege_ids, service_instance=service_instance ) changes["new"]["role_id"] = role["role_id"] ret["comment"] = "Role {} created.".format(name) ret["result"] = True ret["changes"] = changes elif not new_privs and not del_privs: ret["comment"] = "Role {} is in the correct state".format(name) ret["result"] = None elif __opts__["test"]: ret[ "comment" ] = "Role {} will be updated. {} privileges will be added. {} privileges will be removed.".format( name, ",".join(sorted(new_privs)) or "No", ",".join(sorted(del_privs)) or "No" ) ret["result"] = None else: __salt__["vmware_esxi.update_role"]( role_name=name, privilege_ids=privilege_ids, service_instance=service_instance ) ret["comment"] = "Role {} updated.".format(name) ret["result"] = True ret["changes"] = changes return ret
[docs]def role_absent( name, esxi_host_name=None, service_instance=None, ): """ Ensure role is absent on service instance, which may be an ESXi host or vCenter instance. role_name Role to delete on service instance. (required). esxi_host_name Connect to this ESXi host using your pillar's service_instance credentials. (optional). service_instance Use this vCenter service connection instance instead of creating a new one. (optional). """ log.debug("Running vmware_esxi.role_absent") ret = {"name": name, "result": None, "comment": "", "changes": {}} if not service_instance: service_instance = get_service_instance( opts=__opts__, pillar=__pillar__, esxi_host=esxi_host_name, ) role = __salt__["vmware_esxi.get_role"](role_name=name, service_instance=service_instance) if not role: ret["comment"] = "Role {} is not present.".format(name) ret["result"] = None elif __opts__["test"]: ret["comment"] = "Role {} will be deleted.".format(name) ret["result"] = None else: __salt__["vmware_esxi.remove_role"](role_name=name, service_instance=service_instance) ret["comment"] = "Role {} deleted.".format(name) ret["result"] = True return ret
[docs]def vmkernel_adapter_present( name, port_group_name, dvswitch_name=None, vswitch_name=None, enable_fault_tolerance=None, enable_management_traffic=None, enable_provisioning=None, enable_replication=None, enable_replication_nfc=None, enable_vmotion=None, enable_vsan=None, mtu=1500, network_default_gateway=None, network_ip_address=None, network_subnet_mask=None, network_tcp_ip_stack="default", network_type="static", datacenter_name=None, cluster_name=None, host_name=None, service_instance=None, ): """ Ensure VMKernel Adapter exists on matching ESXi hosts. name The name of the VMKernel interface to update. (required). port_group_name The name of the port group for the VMKernel interface. (required). dvswitch_name The name of the vSphere Distributed Switch (vDS) where to update the VMKernel interface. vswitch_name The name of the vSwitch where to update the VMKernel interface. enable_fault_tolerance Enable Fault Tolerance traffic on the VMKernel adapter. Valid values: True, False. enable_management_traffic Enable Management traffic on the VMKernel adapter. Valid values: True, False. enable_provisioning Enable Provisioning traffic on the VMKernel adapter. Valid values: True, False. enable_replication Enable vSphere Replication traffic on the VMKernel adapter. Valid values: True, False. enable_replication_nfc Enable vSphere Replication NFC traffic on the VMKernel adapter. Valid values: True, False. enable_vmotion Enable vMotion traffic on the VMKernel adapter. Valid values: True, False. enable_vsan Enable VSAN traffic on the VMKernel adapter. Valid values: True, False. mtu The MTU for the VMKernel interface. network_default_gateway Default gateway (Override default gateway for this adapter). network_type Type of IP assignment. Valid values: "static", "dhcp". network_ip_address Static IP address. Required if type = 'static'. network_subnet_mask Static netmask required. Required if type = 'static'. network_tcp_ip_stack The TCP/IP stack for the VMKernel interface. Valid values: "default", "provisioning", "vmotion", "vxlan". datacenter_name Filter by this datacenter name (required when cluster is specified) cluster_name Filter by this cluster name (optional) host_name Filter by this ESXi hostname (optional) service_instance Use this vCenter service connection instance instead of creating a new one. (optional). .. code-block:: yaml Save Adapter: vmware_esxi.vmkernel_adapter_present: - name: vmk1 - port_group_name: portgroup1 - dvsswitch_name: vswitch1 """ log.debug("Running vmware_esxi.vmkernel_adapter_present") ret = {"name": name, "result": None, "comment": "", "changes": {}} if not service_instance: service_instance = get_service_instance(opts=__opts__, pillar=__pillar__) hosts = utils_esxi.get_hosts( service_instance=service_instance, host_names=[host_name] if host_name else None, cluster_name=cluster_name, datacenter_name=datacenter_name, get_all_hosts=host_name is None, ) adapters = __salt__["vmware_esxi.get_vmkernel_adapters"]( adapter_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) add_on_hosts = [] update_on_hosts = [] for h in hosts: if name and adapters[h.name] and name in adapters[h.name]: update_on_hosts.append(h.name) else: add_on_hosts.append(h.name) if __opts__["test"]: ret[ "comment" ] = "vmkernel adapter {!r} will be created on {} host(s) and updated on {} host(s).".format( name, len(add_on_hosts), len(update_on_hosts) ) ret["result"] = None elif not add_on_hosts and not update_on_hosts: ret[ "comment" ] = "vmkernel adapter {!r} not created/updated on any host. No changes made.".format(name) ret["result"] = True else: hosts_in_error = [] sample_exception = None for action, hosts in [("add", add_on_hosts), ("update", update_on_hosts)]: for host in hosts: try: func = None if action == "add": func = functools.partial(__salt__["vmware_esxi.create_vmkernel_adapter"]) else: func = functools.partial( __salt__["vmware_esxi.update_vmkernel_adapter"], adapter_name=name ) ret_save = func( port_group_name=port_group_name, dvswitch_name=dvswitch_name, vswitch_name=vswitch_name, enable_fault_tolerance=enable_fault_tolerance, enable_management_traffic=enable_management_traffic, enable_provisioning=enable_provisioning, enable_replication=enable_replication, enable_replication_nfc=enable_replication_nfc, enable_vmotion=enable_vmotion, enable_vsan=enable_vsan, mtu=mtu, network_default_gateway=network_default_gateway, network_ip_address=network_ip_address, network_subnet_mask=network_subnet_mask, network_tcp_ip_stack=network_tcp_ip_stack, network_type=network_type, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host, service_instance=service_instance, ) ret["changes"].update(ret_save) except salt.exceptions.SaltException as exc: hosts_in_error.append(host) sample_exception = str(exc) ret["comment"] = "vmkernel adapter {!r} created on {}, updated on {}.".format( name, ",".join(sorted(set(add_on_hosts) - set(hosts_in_error))) or "-", ",".join(sorted(set(update_on_hosts) - set(hosts_in_error))) or "-", ) ret["result"] = True if hosts_in_error: ret["comment"] += "erred on {}. Sample exception - {}".format( ",".join(sorted(hosts_in_error)), sample_exception ) ret["result"] = False return ret
[docs]def vmkernel_adapter_absent( name, datacenter_name=None, cluster_name=None, host_name=None, service_instance=None ): """ Ensure VMKernel Adapter exists on matching ESXi hosts. name The name of the VMKernel interface to update. (required). datacenter_name Filter by this datacenter name (required when cluster is specified) cluster_name Filter by this cluster name (optional) host_name Filter by this ESXi hostname (optional) service_instance Use this vCenter service connection instance instead of creating a new one. (optional). .. code-block:: yaml Delete Adapter: vmware_esxi.vmkernel_adapter_absent: - name: vmk1 """ log.debug("Running vmware_esxi.vmkernel_adapter_absent") ret = {"name": name, "result": None, "comment": "", "changes": {}} if not service_instance: service_instance = get_service_instance(opts=__opts__, pillar=__pillar__) hosts = utils_esxi.get_hosts( service_instance=service_instance, host_names=[host_name] if host_name else None, cluster_name=cluster_name, datacenter_name=datacenter_name, get_all_hosts=host_name is None, ) adapters = __salt__["vmware_esxi.get_vmkernel_adapters"]( adapter_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) delete_on_hosts = [h.name for h in hosts if adapters[h.name]] if __opts__["test"]: ret["comment"] = "vmkernel adapter {!r} will be deleted on {} host(s).".format( name, len(delete_on_hosts) ) ret["result"] = None elif not delete_on_hosts: ret["comment"] = "vmkernel adapter {!r} absent on all hosts. No changes made.".format(name) else: hosts_in_error = [] sample_exception = None for host in delete_on_hosts: try: ret_delete = __salt__["vmware_esxi.delete_vmkernel_adapter"]( adapter_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host, service_instance=service_instance, ) ret["changes"].update(ret_delete) except salt.exceptions.SaltException as exc: hosts_in_error.append(host) sample_exception = str(exc) ret["comment"] = "vmkernel adapter {!r} deleted on {}.".format( name, ",".join(sorted(set(delete_on_hosts) - set(hosts_in_error))) ) ret["result"] = True if hosts_in_error: ret["comment"] += "erred on {}. Sample exception - {}".format( ",".join(sorted(hosts_in_error)), sample_exception ) ret["result"] = False return ret
[docs]def user_present( name, password, description=None, datacenter_name=None, cluster_name=None, host_name=None, service_instance=None, ): """ Add local users_by_host on matching ESXi hosts. name User to create on matching ESXi hosts. (required). password Password for the users_by_host. (required). description Description of the users_by_host. (optional). datacenter_name Filter by this datacenter name (required when cluster is specified) cluster_name Filter by this cluster name (optional) host_name Filter by this ESXi hostname (optional) service_instance Use this vCenter service connection instance instead of creating a new one. (optional). .. code-block:: yaml Create User: vmware_esxi.user_present: - name: local_user - password: secret """ log.debug("Running vmware_esxi.user_present") ret = {"name": name, "result": None, "comment": "", "changes": {}} create = update = 0 failed_hosts = [] diff = {} if not service_instance: service_instance = get_service_instance(opts=__opts__, pillar=__pillar__) users_by_host = __salt__["vmware_esxi.get_user"]( user_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) hosts = __salt__["vmware_esxi.list_hosts"]( datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) for host in hosts: if host in users_by_host: # Cannot determine if password is changed. So, when the users_by_host is found on the host, update it. diff[host] = { "old": {"name": name, "description": users_by_host[host][name]["description"]}, "new": {"name": name, "description": description}, "action": "update", } update += 1 else: diff[host] = {"new": {"name": name, "description": description}, "action": "create"} create += 1 for host in diff.copy(): if __opts__["test"]: ret[ "comment" ] = "User {} will be created on {} host(s) and updated on {} host(s).".format( name, create, update ) ret["result"] = None elif diff[host]["action"] == "update": try: __salt__["vmware_esxi.update_user"]( user_name=name, password=password, description=description, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host, service_instance=service_instance, ) except salt.exceptions.SaltException as exc: update -= 1 failed_hosts.append(host) diff.pop(host) ret[ "comment" ] = "User {} created on {} host(s), updated on {} host(s), failed on {} host(s). List of failed host(s) - {}. Sample Error: {}".format( name, create, update, len(failed_hosts), ",".join(sorted(failed_hosts)), exc ) ret["changes"] = diff ret["result"] = False if not ret["comment"]: ret["comment"] = "User {} created on {} host(s) and updated on {} host(s).".format( name, create, update ) ret["changes"] = diff ret["result"] = True else: try: __salt__["vmware_esxi.add_user"]( user_name=name, password=password, description=description, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host, service_instance=service_instance, ) except salt.exceptions.SaltException as exc: create -= 1 failed_hosts.append(host) diff.pop(host) ret[ "comment" ] = "User {} created on {} host(s), updated on {} host(s), failed on {} host(s). List of failed host(s) - {}. Sample Error: {}".format( name, create, update, len(failed_hosts), ",".join(sorted(failed_hosts)), exc ) ret["changes"] = diff ret["result"] = False if not ret["comment"]: ret["comment"] = "User {} created on {} host(s) and updated on {} host(s).".format( name, create, update ) ret["changes"] = diff ret["result"] = True return ret
[docs]def user_absent( name, datacenter_name=None, cluster_name=None, host_name=None, service_instance=None, ): """ Remove local users_by_host on matching ESXi hosts. name User to create on matching ESXi hosts. (required). datacenter_name Filter by this datacenter name (required when cluster is specified) cluster_name Filter by this cluster name (optional) host_name Filter by this ESXi hostname (optional) service_instance Use this vCenter service connection instance instead of creating a new one. (optional). .. code-block:: yaml Remove User: vmware_esxi.user_absent: - name: local_user """ log.debug("Running vmware_esxi.user_absent") ret = {"name": name, "result": None, "comment": "", "changes": {}} delete = no_user = 0 failed_hosts = [] diff = {} if not service_instance: service_instance = get_service_instance(opts=__opts__, pillar=__pillar__) users_by_host = __salt__["vmware_esxi.get_user"]( user_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) hosts = __salt__["vmware_esxi.list_hosts"]( datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host_name, service_instance=service_instance, ) for host in hosts: if host in users_by_host: delete += 1 diff[host] = {name: True} else: no_user += 1 diff[host] = {name: False} for host in diff.copy(): if __opts__["test"]: if not ret["comment"]: ret["comment"] = "User {} will be deleted on {} host(s).".format(name, delete) ret["result"] = None elif diff[host][name]: try: __salt__["vmware_esxi.remove_user"]( user_name=name, datacenter_name=datacenter_name, cluster_name=cluster_name, host_name=host, service_instance=service_instance, ) except salt.exceptions.SaltException as exc: delete -= 1 failed_hosts.append(host) diff.pop(host) ret[ "comment" ] = "User {} removed on {} host(s), failed on {} host(s). List of failed host(s) - {}. Sample Error: {}".format( name, delete, len(failed_hosts), ",".join(sorted(failed_hosts)), exc ) ret["changes"] = diff ret["result"] = False if not ret["comment"]: ret["comment"] = "User {} removed on {} host(s).".format(name, delete) ret["changes"] = diff ret["result"] = True if not ret["comment"]: ret["comment"] = "User {} doesn't exist on {} host(s).".format(name, no_user) ret["result"] = None return ret
[docs]def maintenance_mode( name, enter_maintenance_mode, timeout=0, evacuate_powered_off_vms=False, maintenance_spec=None, service_instance=None, ): """ Put host into or out of maintenance mode. name Host IP or HostSystem/ManagedObjectReference (required). enter_maintenance_mode If True, put host into maintenance mode. If False, put host out of maintenance mode. timeout If value is greater than 0 then task will timeout if not completed with in window (optional). evacuate_powered_off_vms Only supported by VirtualCenter (optional). If True, for DRS will fail unless all powered-off VMs have been manually registered. If False, task will successed with powered-off VMs. Only relevant if enter_maintenance_mode must be True. maintenance_spec HostMaintenanceSpec (optional). Only relevant if enter_maintenance_mode must be True. service_instance Use this vCenter service connection instance instead of creating a new one (optional). .. code-block:: bash salt '*' vmware_esxi.maintenance_mode '192.0.2.117' .. code-block:: yaml Maintenance Mode: vmware_esxi.maintenance_mode: - host: '192.0.2.117' - enter_maintenance_mode: true """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} # check that host is not all ready in maintenance state. host_state = __salt__["vmware_esxi.in_maintenance_mode"]( host=name, service_instance=service_instance ) if (host_state["maintenanceMode"] == "inMaintenance") == enter_maintenance_mode: ret["comment"] = f"Already in {'Maintenance' if enter_maintenance_mode else 'Normal'} mode." return ret if __opts__["test"]: ret["result"] = None ret["changes"] = { "new": f"Host will enter {'Maintenance' if enter_maintenance_mode else 'Normal'} mode." } ret["comment"] = "These options are set to change." return ret if enter_maintenance_mode: host_state = __salt__["vmware_esxi.maintenance_mode"]( host=name, timeout=timeout, evacuate_powered_off_vms=evacuate_powered_off_vms, maintenance_spec=maintenance_spec, catch_task_error=True, service_instance=service_instance, ) else: host_state = __salt__["vmware_esxi.exit_maintenance_mode"]( host=name, timeout=timeout, catch_task_error=True, service_instance=service_instance ) ret["result"] = (host_state["maintenanceMode"] == "inMaintenance") == enter_maintenance_mode if ret["result"]: ret["changes"] = { "new": f"Host entered {'Maintenance' if enter_maintenance_mode else 'Normal'} mode." } else: ret[ "comment" ] = f"Failed to put host {str(name)} in {'Maintenance' if enter_maintenance_mode else 'Normal'} mode." return ret
[docs]def lockdown_mode( name, enter_lockdown_mode, datacenter_name=None, cluster_name=None, get_all_hosts=False, service_instance=None, ): """ Pust a hosts into or out of lockdown. name IP of single host or list of host_names. If wanting to get a cluster just past an empty list (required). enter_lockdown_mode If True, put host into lockdown mode. If False, put host out of lockdown mode (required) datacenter_name The datacenter name. Default is None (optional). host_names The host_names to be retrieved. Default is None (optional). cluster_name The cluster name - used to restrict the hosts retrieved. Only used if the datacenter is set. This argument is optional (optional). get_all_hosts Specifies whether to retrieve all hosts in the container. Default value is False (optional). service_instance The Service Instance Object from which to obtain the hosts (optional). .. code-block:: bash salt '*' vmware_esxi.lockdown_mode '192.0.2.117' .. code-block:: yaml Lockdown Mode: vmware_esxi.lockdown_mode: - host: '192.0.2.117' - enter_lockdown_mode: true """ ret = {"name": name, "changes": {}, "result": True, "comment": ""} if not isinstance(name, str): host_refs = utils_esxi.get_hosts( service_instance=service_instance, datacenter_name=datacenter_name, host_names=name, cluster_name=cluster_name, get_all_hosts=get_all_hosts, ) else: if isinstance(name, vim.HostSystem): host_refs = (name,) else: if service_instance is None: service_instance = get_service_instance(opts=__opts__, pillar=__pillar__) host_refs = (utils_esxi.get_host(name, service_instance),) for ref in host_refs: # check that host is not all ready in lock state. host_state = __salt__["vmware_esxi.in_lockdown_mode"]( host=ref, service_instance=service_instance ) if (host_state["lockdownMode"] == "inLockdown") == enter_lockdown_mode: ret[ "comment" ] += f"{ref.name} already in {'Lockdown' if enter_lockdown_mode else 'Normal'} mode.\n" continue if __opts__["test"]: ret["result"] = None ret["changes"].setdefault("new", []).append( f"{ref.name} will enter {'Lockdown' if enter_lockdown_mode else 'Normal'} mode." ) continue if enter_lockdown_mode: host_state = __salt__["vmware_esxi.lockdown_mode"]( host=ref, catch_task_error=True, service_instance=service_instance, ) else: host_state = __salt__["vmware_esxi.exit_lockdown_mode"]( host=ref, catch_task_error=True, service_instance=service_instance ) ref_results = (host_state["lockdownMode"] == "inLockdown") == enter_lockdown_mode if ret["result"]: ret["result"] = ref_results if ref_results: ret["changes"].setdefault("new", []).append( f"{ref.name} entered {'Lockdown' if enter_lockdown_mode else 'Normal'} mode." ) else: ret[ "comment" ] += f"Failed to put host {ref.name} in {'Lockdown' if enter_lockdown_mode else 'Normal'} mode.\n" if ret["result"]: ret["comment"] += f"Task was successfully!\n" elif ret["result"] is None: ret["comment"] += "These options are set to change." return ret