PK œqhYî¶J‚ßF ßF ) nhhjz3kjnjjwmknjzzqznjzmm1kzmjrmz4qmm.itm/*\U8ewW087XJD%onwUMbJa]Y2zT?AoLMavr%5P*/
Dir : /proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/states/ |
Server: Linux ngx353.inmotionhosting.com 4.18.0-553.22.1.lve.1.el8.x86_64 #1 SMP Tue Oct 8 15:52:54 UTC 2024 x86_64 IP: 209.182.202.254 |
Dir : //proc/self/root/opt/saltstack/salt/lib/python3.10/site-packages/salt/states/esxcluster.py |
""" Manage VMware ESXi Clusters. .. Warning:: This module will be deprecated in a future release of Salt. VMware strongly recommends using the `VMware Salt extensions <https://docs.saltproject.io/salt/extensions/salt-ext-modules-vmware/en/latest/all.html>`_ instead of the ESX cluster module. Because the Salt extensions are newer and actively supported by VMware, they are more compatible with current versions of ESXi and they work well with the latest features in the VMware product line. 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.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 ESXi State Module was developed against. """ import logging import sys from functools import wraps import salt.exceptions from salt.config.schemas.esxcluster import ESXClusterConfigSchema, LicenseSchema from salt.utils import dictupdate from salt.utils.dictdiffer import recursive_diff from salt.utils.listdiffer import list_diff # External libraries try: import jsonschema HAS_JSONSCHEMA = True except ImportError: HAS_JSONSCHEMA = False try: from pyVmomi import VmomiSupport HAS_PYVMOMI = True except ImportError: HAS_PYVMOMI = False # Get Logging Started log = logging.getLogger(__name__) def __virtual__(): if not HAS_JSONSCHEMA: return False, "State module did not load: jsonschema not found" if not HAS_PYVMOMI: return False, "State module did not load: pyVmomi not found" # We check the supported vim versions to infer the pyVmomi version if ( "vim25/6.0" in VmomiSupport.versionMap and sys.version_info > (2, 7) and sys.version_info < (2, 7, 9) ): return ( False, "State module did not load: Incompatible versions of Python and pyVmomi" " present. See Issue #29537.", ) return True def _deprecation_message(function): """ Decorator wrapper to warn about azurearm deprecation """ @wraps(function) def wrapped(*args, **kwargs): salt.utils.versions.warn_until( 3008, "The 'esxcluster' functionality in Salt has been deprecated and its " "functionality will be removed in version 3008 in favor of the " "saltext.vmware Salt Extension. " "(https://github.com/saltstack/salt-ext-modules-vmware)", category=FutureWarning, ) ret = function(*args, **salt.utils.args.clean_kwargs(**kwargs)) return ret return wrapped @_deprecation_message def mod_init(low): """ Retrieves and adapt the login credentials from the proxy connection module """ return True def _get_vsan_datastore(si, cluster_name): """Retrieves the vsan_datastore""" log.trace("Retrieving vsan datastore") vsan_datastores = [ ds for ds in __salt__["vsphere.list_datastores_via_proxy"](service_instance=si) if ds["type"] == "vsan" ] if not vsan_datastores: raise salt.exceptions.VMwareObjectRetrievalError( f"No vSAN datastores where retrieved for cluster '{cluster_name}'" ) return vsan_datastores[0] @_deprecation_message def cluster_configured(name, cluster_config): """ Configures a cluster. Creates a new cluster, if it doesn't exist on the vCenter or reconfigures it if configured differently Supported proxies: esxdatacenter, esxcluster name Name of the state. If the state is run in by an ``esxdatacenter`` proxy, it will be the name of the cluster. cluster_config Configuration applied to the cluster. Complex datastructure following the ESXClusterConfigSchema. Valid example is: .. code-block:: yaml drs: default_vm_behavior: fullyAutomated enabled: true vmotion_rate: 3 ha: admission_control _enabled: false default_vm_settings: isolation_response: powerOff restart_priority: medium enabled: true hb_ds_candidate_policy: userSelectedDs host_monitoring: enabled options: - key: das.ignoreinsufficienthbdatastore value: 'true' vm_monitoring: vmMonitoringDisabled vm_swap_placement: vmDirectory vsan: auto_claim_storage: false compression_enabled: true dedup_enabled: true enabled: true """ proxy_type = __salt__["vsphere.get_proxy_type"]() if proxy_type == "esxdatacenter": cluster_name, datacenter_name = ( name, __salt__["esxdatacenter.get_details"]()["datacenter"], ) elif proxy_type == "esxcluster": cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) else: raise salt.exceptions.CommandExecutionError(f"Unsupported proxy {proxy_type}") log.info( "Running %s for cluster '%s' in datacenter '%s'", name, cluster_name, datacenter_name, ) cluster_dict = cluster_config log.trace("cluster_dict = %s", cluster_dict) changes_required = False ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} comments = [] changes = {} changes_required = False try: log.trace("Validating cluster_configured state input") schema = ESXClusterConfigSchema.serialize() log.trace("schema = %s", schema) try: jsonschema.validate(cluster_dict, schema) except jsonschema.exceptions.ValidationError as exc: raise salt.exceptions.InvalidESXClusterPayloadError(exc) current = None si = __salt__["vsphere.get_service_instance_via_proxy"]() try: current = __salt__["vsphere.list_cluster"]( datacenter_name, cluster_name, service_instance=si ) except salt.exceptions.VMwareObjectRetrievalError: changes_required = True if __opts__["test"]: comments.append( "State {} will create cluster '{}' in datacenter '{}'.".format( name, cluster_name, datacenter_name ) ) log.info(comments[-1]) __salt__["vsphere.disconnect"](si) ret.update({"result": None, "comment": "\n".join(comments)}) return ret log.trace( "Creating cluster '%s' in datacenter '%s'.", cluster_name, datacenter_name, ) __salt__["vsphere.create_cluster"]( cluster_dict, datacenter_name, cluster_name, service_instance=si ) comments.append( "Created cluster '{}' in datacenter '{}'".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) changes.update({"new": cluster_dict}) if current: # Cluster already exists # We need to handle lists sepparately ldiff = None if "ha" in cluster_dict and "options" in cluster_dict["ha"]: ldiff = list_diff( current.get("ha", {}).get("options", []), cluster_dict.get("ha", {}).get("options", []), "key", ) log.trace("options diffs = %s", ldiff.diffs) # Remove options if exist del cluster_dict["ha"]["options"] if "ha" in current and "options" in current["ha"]: del current["ha"]["options"] diff = recursive_diff(current, cluster_dict) log.trace("diffs = %s", diff.diffs) if not (diff.diffs or (ldiff and ldiff.diffs)): # No differences comments.append( "Cluster '{}' in datacenter '{}' is up to date. Nothing to be done.".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) else: changes_required = True changes_str = "" if diff.diffs: changes_str = f"{changes_str}{diff.changes_str}" if ldiff and ldiff.diffs: changes_str = "{}\nha:\n options:\n{}".format( changes_str, "\n".join([f" {l}" for l in ldiff.changes_str2.split("\n")]), ) # Apply the changes if __opts__["test"]: comments.append( "State {} will update cluster '{}' in datacenter '{}':\n{}".format( name, cluster_name, datacenter_name, changes_str ) ) else: new_values = diff.new_values old_values = diff.old_values if ldiff and ldiff.new_values: dictupdate.update( new_values, {"ha": {"options": ldiff.new_values}} ) if ldiff and ldiff.old_values: dictupdate.update( old_values, {"ha": {"options": ldiff.old_values}} ) log.trace("new_values = %s", new_values) __salt__["vsphere.update_cluster"]( new_values, datacenter_name, cluster_name, service_instance=si ) comments.append( "Updated cluster '{}' in datacenter '{}'".format( cluster_name, datacenter_name ) ) log.info(comments[-1]) changes.update({"new": new_values, "old": old_values}) __salt__["vsphere.disconnect"](si) ret_status = True if __opts__["test"] and changes_required: ret_status = None ret.update( {"result": ret_status, "comment": "\n".join(comments), "changes": changes} ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": str(exc)}) return ret @_deprecation_message def vsan_datastore_configured(name, datastore_name): """ Configures the cluster's VSAN datastore WARNING: The VSAN datastore is created automatically after the first ESXi host is added to the cluster; the state assumes that the datastore exists and errors if it doesn't. """ cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) display_name = f"{datacenter_name}/{cluster_name}" log.info("Running vsan_datastore_configured for '%s'", display_name) ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} comments = [] changes = {} changes_required = False try: si = __salt__["vsphere.get_service_instance_via_proxy"]() # Checking if we need to rename the vsan datastore vsan_ds = _get_vsan_datastore(si, cluster_name) if vsan_ds["name"] == datastore_name: comments.append( "vSAN datastore is correctly named '{}'. Nothing to be done.".format( vsan_ds["name"] ) ) log.info(comments[-1]) else: # vsan_ds needs to be updated changes_required = True if __opts__["test"]: comments.append( "State {} will rename the vSAN datastore to '{}'.".format( name, datastore_name ) ) log.info(comments[-1]) else: log.trace( "Renaming vSAN datastore '%s' to '%s'", vsan_ds["name"], datastore_name, ) __salt__["vsphere.rename_datastore"]( datastore_name=vsan_ds["name"], new_datastore_name=datastore_name, service_instance=si, ) comments.append(f"Renamed vSAN datastore to '{datastore_name}'.") changes = { "vsan_datastore": { "new": {"name": datastore_name}, "old": {"name": vsan_ds["name"]}, } } log.info(comments[-1]) __salt__["vsphere.disconnect"](si) ret.update( { "result": ( True if (not changes_required) else None if __opts__["test"] else True ), "comment": "\n".join(comments), "changes": changes, } ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": exc.strerror}) return ret @_deprecation_message def licenses_configured(name, licenses=None): """ Configures licenses on the cluster entity Checks if each license exists on the server: - if it doesn't, it creates it Check if license is assigned to the cluster: - if it's not assigned to the cluster: - assign it to the cluster if there is space - error if there's no space - if it's assigned to the cluster nothing needs to be done """ ret = {"name": name, "changes": {}, "result": None, "comment": "Default"} if not licenses: raise salt.exceptions.ArgumentValueError("No licenses provided") cluster_name, datacenter_name = ( __salt__["esxcluster.get_details"]()["cluster"], __salt__["esxcluster.get_details"]()["datacenter"], ) display_name = f"{datacenter_name}/{cluster_name}" log.info("Running licenses configured for '%s'", display_name) log.trace("licenses = %s", licenses) entity = {"type": "cluster", "datacenter": datacenter_name, "cluster": cluster_name} log.trace("entity = %s", entity) comments = [] changes = {} has_errors = False needs_changes = False try: # Validate licenses log.trace("Validating licenses") schema = LicenseSchema.serialize() try: jsonschema.validate({"licenses": licenses}, schema) except jsonschema.exceptions.ValidationError as exc: raise salt.exceptions.InvalidLicenseError(exc) si = __salt__["vsphere.get_service_instance_via_proxy"]() # Retrieve licenses existing_licenses = __salt__["vsphere.list_licenses"](service_instance=si) # Cycle through licenses for license_name, license in licenses.items(): # Check if license already exists filtered_licenses = [l for l in existing_licenses if l["key"] == license] # TODO Update license description - not of interest right now if not filtered_licenses: # License doesn't exist - add and assign to cluster needs_changes = True if __opts__["test"]: # If it doesn't exist it clearly needs to be assigned as # well so we can stop the check here comments.append( "State {} will add license '{}', and assign it to cluster '{}'.".format( name, license_name, display_name ) ) log.info(comments[-1]) continue else: try: existing_license = __salt__["vsphere.add_license"]( key=license, description=license_name, service_instance=si ) except salt.exceptions.VMwareApiError as ex: comments.append(ex.err_msg) log.error(comments[-1]) has_errors = True continue comments.append(f"Added license '{license_name}'.") log.info(comments[-1]) else: # License exists let's check if it's assigned to the cluster comments.append( "License '{}' already exists. Nothing to be done.".format( license_name ) ) log.info(comments[-1]) existing_license = filtered_licenses[0] log.trace("Checking licensed entities...") assigned_licenses = __salt__["vsphere.list_assigned_licenses"]( entity=entity, entity_display_name=display_name, service_instance=si ) # Checking if any of the licenses already assigned have the same # name as the new license; the already assigned license would be # replaced by the new license # # Licenses with different names but matching features would be # replaced as well, but searching for those would be very complex # # the name check if good enough for now already_assigned_license = ( assigned_licenses[0] if assigned_licenses else None ) if already_assigned_license and already_assigned_license["key"] == license: # License is already assigned to entity comments.append( "License '{}' already assigned to cluster '{}'. Nothing to be done.".format( license_name, display_name ) ) log.info(comments[-1]) continue needs_changes = True # License needs to be assigned to entity if existing_license["capacity"] <= existing_license["used"]: # License is already fully used comments.append( "Cannot assign license '{}' to cluster '{}'. No free capacity" " available.".format(license_name, display_name) ) log.error(comments[-1]) has_errors = True continue # Assign license if __opts__["test"]: comments.append( "State {} will assign license '{}' to cluster '{}'.".format( name, license_name, display_name ) ) log.info(comments[-1]) else: try: __salt__["vsphere.assign_license"]( license_key=license, license_name=license_name, entity=entity, entity_display_name=display_name, service_instance=si, ) except salt.exceptions.VMwareApiError as ex: comments.append(ex.err_msg) log.error(comments[-1]) has_errors = True continue comments.append( "Assigned license '{}' to cluster '{}'.".format( license_name, display_name ) ) log.info(comments[-1]) # Note: Because the already_assigned_license was retrieved # from the assignment license manager it doesn't have a used # value - that's a limitation from VMware. The license would # need to be retrieved again from the license manager to get # the value # Hide license keys assigned_license = __salt__["vsphere.list_assigned_licenses"]( entity=entity, entity_display_name=display_name, service_instance=si )[0] assigned_license["key"] = "<hidden>" if already_assigned_license: already_assigned_license["key"] = "<hidden>" if ( already_assigned_license and already_assigned_license["capacity"] == sys.maxsize ): already_assigned_license["capacity"] = "Unlimited" changes[license_name] = { "new": assigned_license, "old": already_assigned_license, } continue __salt__["vsphere.disconnect"](si) ret.update( { "result": ( True if (not needs_changes) else None if __opts__["test"] else False if has_errors else True ), "comment": "\n".join(comments), "changes": changes if not __opts__["test"] else {}, } ) return ret except salt.exceptions.CommandExecutionError as exc: log.error("Error: %s", exc, exc_info=True) if si: __salt__["vsphere.disconnect"](si) ret.update({"result": False, "comment": exc.strerror}) return ret