1"""
2Connection library for VMware
3
4.. versionadded:: 2015.8.2
5
6This is a base library used by a number of VMware services such as VMware
7ESX, ESXi, and vCenter servers.
8
9:codeauthor: Nitin Madhok <nmadhok@g.clemson.edu>
10:codeauthor: Alexandru Bleotu <alexandru.bleotu@morganstanley.com>
11
12Dependencies
13~~~~~~~~~~~~
14
15- pyVmomi Python Module
16- ESXCLI: This dependency is only needed to use the ``esxcli`` function. No other
17  functions in this module rely on ESXCLI.
18
19pyVmomi
20-------
21
22PyVmomi can be installed via pip:
23
24.. code-block:: bash
25
26    pip install pyVmomi
27
28.. note::
29
30    Version 6.0 of pyVmomi has some problems with SSL error handling on certain
31    versions of Python. If using version 6.0 of pyVmomi, Python 2.6,
32    Python 2.7.9, or newer must be present. This is due to an upstream dependency
33    in pyVmomi 6.0 that is not supported in Python versions 2.7 to 2.7.8. If the
34    version of Python is not in the supported range, you will need to install an
35    earlier version of pyVmomi. See `Issue #29537`_ for more information.
36
37.. _Issue #29537: https://github.com/saltstack/salt/issues/29537
38
39Based on the note above, to install an earlier version of pyVmomi than the
40version currently listed in PyPi, run the following:
41
42.. code-block:: bash
43
44    pip install pyVmomi==5.5.0.2014.1.1
45
46The 5.5.0.2014.1.1 is a known stable version that this original VMware utils file
47was developed against.
48
49ESXCLI
50------
51
52This dependency is only needed to use the ``esxcli`` function. At the time of this
53writing, no other functions in this module rely on ESXCLI.
54
55The ESXCLI package is also referred to as the VMware vSphere CLI, or vCLI. VMware
56provides vCLI package installation instructions for `vSphere 5.5`_ and
57`vSphere 6.0`_.
58
59.. _vSphere 5.5: http://pubs.vmware.com/vsphere-55/index.jsp#com.vmware.vcli.getstart.doc/cli_install.4.2.html
60.. _vSphere 6.0: http://pubs.vmware.com/vsphere-60/index.jsp#com.vmware.vcli.getstart.doc/cli_install.4.2.html
61
62Once all of the required dependencies are in place and the vCLI package is
63installed, you can check to see if you can connect to your ESXi host or vCenter
64server by running the following command:
65
66.. code-block:: bash
67
68    esxcli -s <host-location> -u <username> -p <password> system syslog config get
69
70If the connection was successful, ESXCLI was successfully installed on your system.
71You should see output related to the ESXi host's syslog configuration.
72
73"""
74
75import atexit
76import errno
77import logging
78import ssl
79import time
80from http.client import BadStatusLine
81
82import salt.exceptions
83import salt.modules.cmdmod
84import salt.utils.path
85import salt.utils.platform
86import salt.utils.stringutils
87
88# pylint: disable=no-name-in-module
89try:
90    from pyVim.connect import GetSi, SmartConnect, Disconnect, GetStub, SoapStubAdapter
91    from pyVmomi import vim, vmodl, VmomiSupport
92
93    HAS_PYVMOMI = True
94except ImportError:
95    HAS_PYVMOMI = False
96
97try:
98    from com.vmware.vapi.std.errors_client import Unauthenticated
99    from vmware.vapi.vsphere.client import create_vsphere_client
100
101    HAS_VSPHERE_SDK = True
102
103except ImportError:
104    HAS_VSPHERE_SDK = False
105# pylint: enable=no-name-in-module
106try:
107    import gssapi
108    import base64
109
110    HAS_GSSAPI = True
111except ImportError:
112    HAS_GSSAPI = False
113
114
115log = logging.getLogger(__name__)
116
117
118def __virtual__():
119    """
120    Only load if PyVmomi is installed.
121    """
122    if HAS_PYVMOMI:
123        return True
124
125    return False, "Missing dependency: The salt.utils.vmware module requires pyVmomi."
126
127
128def esxcli(
129    host, user, pwd, cmd, protocol=None, port=None, esxi_host=None, credstore=None
130):
131    """
132    Shell out and call the specified esxcli command, parse the result
133    and return something sane.
134
135    :param host: ESXi or vCenter host to connect to
136    :param user: User to connect as, usually root
137    :param pwd: Password to connect with
138    :param port: TCP port
139    :param cmd: esxcli command and arguments
140    :param esxi_host: If `host` is a vCenter host, then esxi_host is the
141                      ESXi machine on which to execute this command
142    :param credstore: Optional path to the credential store file
143
144    :return: Dictionary
145    """
146
147    esx_cmd = salt.utils.path.which("esxcli")
148    if not esx_cmd:
149        log.error(
150            "Missing dependency: The salt.utils.vmware.esxcli function requires ESXCLI."
151        )
152        return False
153
154    # Set default port and protocol if none are provided.
155    if port is None:
156        port = 443
157    if protocol is None:
158        protocol = "https"
159
160    if credstore:
161        esx_cmd += " --credstore '{}'".format(credstore)
162
163    if not esxi_host:
164        # Then we are connecting directly to an ESXi server,
165        # 'host' points at that server, and esxi_host is a reference to the
166        # ESXi instance we are manipulating
167        esx_cmd += " -s {} -u {} -p '{}' --protocol={} --portnumber={} {}".format(
168            host, user, pwd, protocol, port, cmd
169        )
170    else:
171        esx_cmd += " -s {} -h {} -u {} -p '{}' --protocol={} --portnumber={} {}".format(
172            host, esxi_host, user, pwd, protocol, port, cmd
173        )
174
175    ret = salt.modules.cmdmod.run_all(esx_cmd, output_loglevel="quiet")
176
177    return ret
178
179
180def get_vsphere_client(
181    server, username, password, session=None, verify_ssl=True, ca_bundle=None
182):
183    """
184    Internal helper method to create an instance of the vSphere API client.
185    Please provide username and password to authenticate.
186
187    :param basestring server:
188        vCenter host name or IP address
189    :param basestring username:
190        Name of the user
191    :param basestring password:
192        Password of the user
193    :param Session session:
194        Request HTTP session instance. If not specified, one
195        is automatically created and used
196    :param boolean verify_ssl:
197        Verify the SSL certificate. Default: True
198    :param basestring ca_bundle:
199        Path to the ca bundle to use when verifying SSL certificates.
200
201    :returns:
202        Vsphere Client instance
203    :rtype:
204        :class:`vmware.vapi.vmc.client.VsphereClient`
205    """
206    if not session:
207        # Create an https session to be used for a vSphere client
208        session = salt.utils.http.session(verify_ssl=verify_ssl, ca_bundle=ca_bundle)
209    client = None
210    try:
211        client = create_vsphere_client(
212            server=server, username=username, password=password, session=session
213        )
214    except Unauthenticated as err:
215        log.trace(err)
216    return client
217
218
219def _get_service_instance(
220    host,
221    username,
222    password,
223    protocol,
224    port,
225    mechanism,
226    principal,
227    domain,
228    verify_ssl=True,
229):
230    """
231    Internal method to authenticate with a vCenter server or ESX/ESXi host
232    and return the service instance object.
233    """
234    log.trace("Retrieving new service instance")
235    token = None
236    if mechanism == "userpass":
237        if username is None:
238            raise salt.exceptions.CommandExecutionError(
239                "Login mechanism userpass was specified but the mandatory "
240                "parameter 'username' is missing"
241            )
242        if password is None:
243            raise salt.exceptions.CommandExecutionError(
244                "Login mechanism userpass was specified but the mandatory "
245                "parameter 'password' is missing"
246            )
247    elif mechanism == "sspi":
248        if principal is not None and domain is not None:
249            try:
250                token = get_gssapi_token(principal, host, domain)
251            except Exception as exc:  # pylint: disable=broad-except
252                raise salt.exceptions.VMwareConnectionError(str(exc))
253        else:
254            err_msg = (
255                "Login mechanism '{}' was specified but the"
256                " mandatory parameters are missing".format(mechanism)
257            )
258            raise salt.exceptions.CommandExecutionError(err_msg)
259    else:
260        raise salt.exceptions.CommandExecutionError(
261            "Unsupported mechanism: '{}'".format(mechanism)
262        )
263
264    log.trace(
265        "Connecting using the '%s' mechanism, with username '%s'",
266        mechanism,
267        username,
268    )
269    default_msg = (
270        "Could not connect to host '{}'. "
271        "Please check the debug log for more information.".format(host)
272    )
273
274    try:
275        if verify_ssl:
276            service_instance = SmartConnect(
277                host=host,
278                user=username,
279                pwd=password,
280                protocol=protocol,
281                port=port,
282                b64token=token,
283                mechanism=mechanism,
284            )
285    except TypeError as exc:
286        if "unexpected keyword argument" in exc.message:
287            log.error(
288                "Initial connect to the VMware endpoint failed with %s", exc.message
289            )
290            log.error(
291                "This may mean that a version of PyVmomi EARLIER than 6.0.0.2016.6 is"
292                " installed."
293            )
294            log.error("We recommend updating to that version or later.")
295            raise
296    except Exception as exc:  # pylint: disable=broad-except
297        # pyVmomi's SmartConnect() actually raises Exception in some cases.
298        if (
299            isinstance(exc, vim.fault.HostConnectFault)
300            and "[SSL: CERTIFICATE_VERIFY_FAILED]" in exc.msg
301        ) or "[SSL: CERTIFICATE_VERIFY_FAILED]" in str(exc):
302            err_msg = (
303                "Could not verify the SSL certificate. You can use "
304                "verify_ssl: False if you do not want to verify the "
305                "SSL certificate. This is not recommended as it is "
306                "considered insecure."
307            )
308        else:
309            log.exception(exc)
310            err_msg = exc.msg if hasattr(exc, "msg") else default_msg
311        raise salt.exceptions.VMwareConnectionError(err_msg)
312
313    if not verify_ssl:
314        try:
315            service_instance = SmartConnect(
316                host=host,
317                user=username,
318                pwd=password,
319                protocol=protocol,
320                port=port,
321                sslContext=ssl._create_unverified_context(),
322                b64token=token,
323                mechanism=mechanism,
324            )
325        except Exception as exc:  # pylint: disable=broad-except
326            # pyVmomi's SmartConnect() actually raises Exception in some cases.
327            if "certificate verify failed" in str(exc):
328                context = ssl.SSLContext(ssl.PROTOCOL_TLSv1)
329                context.verify_mode = ssl.CERT_NONE
330                try:
331                    service_instance = SmartConnect(
332                        host=host,
333                        user=username,
334                        pwd=password,
335                        protocol=protocol,
336                        port=port,
337                        sslContext=context,
338                        b64token=token,
339                        mechanism=mechanism,
340                    )
341                except Exception as exc:  # pylint: disable=broad-except
342                    log.exception(exc)
343                    err_msg = exc.msg if hasattr(exc, "msg") else str(exc)
344                    raise salt.exceptions.VMwareConnectionError(
345                        "Could not connect to host '{}': {}".format(host, err_msg)
346                    )
347            else:
348                err_msg = exc.msg if hasattr(exc, "msg") else default_msg
349                log.trace(exc)
350                raise salt.exceptions.VMwareConnectionError(err_msg)
351
352    atexit.register(Disconnect, service_instance)
353    return service_instance
354
355
356def get_customizationspec_ref(si, customization_spec_name):
357    """
358    Get a reference to a VMware customization spec for the purposes of customizing a clone
359
360    si
361        ServiceInstance for the vSphere or ESXi server (see get_service_instance)
362
363    customization_spec_name
364        Name of the customization spec
365
366    """
367    customization_spec_name = si.content.customizationSpecManager.GetCustomizationSpec(
368        name=customization_spec_name
369    )
370    return customization_spec_name
371
372
373def get_mor_using_container_view(si, obj_type, obj_name):
374    """
375    Get reference to an object of specified object type and name
376
377    si
378        ServiceInstance for the vSphere or ESXi server (see get_service_instance)
379
380    obj_type
381        Type of the object (vim.StoragePod, vim.Datastore, etc)
382
383    obj_name
384        Name of the object
385
386    """
387    inventory = get_inventory(si)
388    container = inventory.viewManager.CreateContainerView(
389        inventory.rootFolder, [obj_type], True
390    )
391    for item in container.view:
392        if item.name == obj_name:
393            return item
394    return None
395
396
397def get_service_instance(
398    host,
399    username=None,
400    password=None,
401    protocol=None,
402    port=None,
403    mechanism="userpass",
404    principal=None,
405    domain=None,
406    verify_ssl=True,
407):
408    """
409    Authenticate with a vCenter server or ESX/ESXi host and return the service instance object.
410
411    host
412        The location of the vCenter server or ESX/ESXi host.
413
414    username
415        The username used to login to the vCenter server or ESX/ESXi host.
416        Required if mechanism is ``userpass``
417
418    password
419        The password used to login to the vCenter server or ESX/ESXi host.
420        Required if mechanism is ``userpass``
421
422    protocol
423        Optionally set to alternate protocol if the vCenter server or ESX/ESXi host is not
424        using the default protocol. Default protocol is ``https``.
425
426    port
427        Optionally set to alternate port if the vCenter server or ESX/ESXi host is not
428        using the default port. Default port is ``443``.
429
430    mechanism
431        pyVmomi connection mechanism. Can either be ``userpass`` or ``sspi``.
432        Default mechanism is ``userpass``.
433
434    principal
435        Kerberos service principal. Required if mechanism is ``sspi``
436
437    domain
438        Kerberos user domain. Required if mechanism is ``sspi``
439
440    verify_ssl
441        Verify the SSL certificate. Default: True
442    """
443
444    if protocol is None:
445        protocol = "https"
446    if port is None:
447        port = 443
448
449    service_instance = GetSi()
450    if service_instance:
451        stub = GetStub()
452        if salt.utils.platform.is_proxy() or (
453            hasattr(stub, "host") and stub.host != ":".join([host, str(port)])
454        ):
455            # Proxies will fork and mess up the cached service instance.
456            # If this is a proxy or we are connecting to a different host
457            # invalidate the service instance to avoid a potential memory leak
458            # and reconnect
459            Disconnect(service_instance)
460            service_instance = None
461
462    if not service_instance:
463        service_instance = _get_service_instance(
464            host,
465            username,
466            password,
467            protocol,
468            port,
469            mechanism,
470            principal,
471            domain,
472            verify_ssl=verify_ssl,
473        )
474
475    # Test if data can actually be retrieved or connection has gone stale
476    log.trace("Checking connection is still authenticated")
477    try:
478        service_instance.CurrentTime()
479    except vim.fault.NotAuthenticated:
480        log.trace("Session no longer authenticating. Reconnecting")
481        Disconnect(service_instance)
482        service_instance = _get_service_instance(
483            host,
484            username,
485            password,
486            protocol,
487            port,
488            mechanism,
489            principal,
490            domain,
491            verify_ssl=verify_ssl,
492        )
493    except vim.fault.NoPermission as exc:
494        log.exception(exc)
495        raise salt.exceptions.VMwareApiError(
496            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
497        )
498    except vim.fault.VimFault as exc:
499        log.exception(exc)
500        raise salt.exceptions.VMwareApiError(exc.msg)
501    except vmodl.RuntimeFault as exc:
502        log.exception(exc)
503        raise salt.exceptions.VMwareRuntimeError(exc.msg)
504
505    return service_instance
506
507
508def get_new_service_instance_stub(service_instance, path, ns=None, version=None):
509    """
510    Returns a stub that points to a different path,
511    created from an existing connection.
512
513    service_instance
514        The Service Instance.
515
516    path
517        Path of the new stub.
518
519    ns
520        Namespace of the new stub.
521        Default value is None
522
523    version
524        Version of the new stub.
525        Default value is None.
526    """
527    # For python 2.7.9 and later, the default SSL context has more strict
528    # connection handshaking rule. We may need turn off the hostname checking
529    # and the client side cert verification.
530    context = ssl.create_default_context()
531    context.check_hostname = False
532    context.verify_mode = ssl.CERT_NONE
533
534    stub = service_instance._stub
535    hostname = stub.host.split(":")[0]
536    session_cookie = stub.cookie.split('"')[1]
537    VmomiSupport.GetRequestContext()["vcSessionCookie"] = session_cookie
538    new_stub = SoapStubAdapter(
539        host=hostname, ns=ns, path=path, version=version, poolSize=0, sslContext=context
540    )
541    new_stub.cookie = stub.cookie
542    return new_stub
543
544
545def get_service_instance_from_managed_object(mo_ref, name="<unnamed>"):
546    """
547    Retrieves the service instance from a managed object.
548
549    me_ref
550        Reference to a managed object (of type vim.ManagedEntity).
551
552    name
553        Name of managed object. This field is optional.
554    """
555    if not name:
556        name = mo_ref.name
557    log.trace("[%s] Retrieving service instance from managed object", name)
558    si = vim.ServiceInstance("ServiceInstance")
559    si._stub = mo_ref._stub
560    return si
561
562
563def disconnect(service_instance):
564    """
565    Function that disconnects from the vCenter server or ESXi host
566
567    service_instance
568        The Service Instance from which to obtain managed object references.
569    """
570    log.trace("Disconnecting")
571    try:
572        Disconnect(service_instance)
573    except vim.fault.NoPermission as exc:
574        log.exception(exc)
575        raise salt.exceptions.VMwareApiError(
576            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
577        )
578    except vim.fault.VimFault as exc:
579        log.exception(exc)
580        raise salt.exceptions.VMwareApiError(exc.msg)
581    except vmodl.RuntimeFault as exc:
582        log.exception(exc)
583        raise salt.exceptions.VMwareRuntimeError(exc.msg)
584
585
586def is_connection_to_a_vcenter(service_instance):
587    """
588    Function that returns True if the connection is made to a vCenter Server and
589    False if the connection is made to an ESXi host
590
591    service_instance
592        The Service Instance from which to obtain managed object references.
593    """
594    try:
595        api_type = service_instance.content.about.apiType
596    except vim.fault.NoPermission as exc:
597        log.exception(exc)
598        raise salt.exceptions.VMwareApiError(
599            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
600        )
601    except vim.fault.VimFault as exc:
602        log.exception(exc)
603        raise salt.exceptions.VMwareApiError(exc.msg)
604    except vmodl.RuntimeFault as exc:
605        log.exception(exc)
606        raise salt.exceptions.VMwareRuntimeError(exc.msg)
607    log.trace("api_type = %s", api_type)
608    if api_type == "VirtualCenter":
609        return True
610    elif api_type == "HostAgent":
611        return False
612    else:
613        raise salt.exceptions.VMwareApiError(
614            "Unexpected api type '{}' . Supported types: "
615            "'VirtualCenter/HostAgent'".format(api_type)
616        )
617
618
619def get_service_info(service_instance):
620    """
621    Returns information of the vCenter or ESXi host
622
623    service_instance
624        The Service Instance from which to obtain managed object references.
625    """
626    try:
627        return service_instance.content.about
628    except vim.fault.NoPermission as exc:
629        log.exception(exc)
630        raise salt.exceptions.VMwareApiError(
631            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
632        )
633    except vim.fault.VimFault as exc:
634        log.exception(exc)
635        raise salt.exceptions.VMwareApiError(exc.msg)
636    except vmodl.RuntimeFault as exc:
637        log.exception(exc)
638        raise salt.exceptions.VMwareRuntimeError(exc.msg)
639
640
641def _get_dvs(service_instance, dvs_name):
642    """
643    Return a reference to a Distributed Virtual Switch object.
644
645    :param service_instance: PyVmomi service instance
646    :param dvs_name: Name of DVS to return
647    :return: A PyVmomi DVS object
648    """
649    switches = list_dvs(service_instance)
650    if dvs_name in switches:
651        inventory = get_inventory(service_instance)
652        container = inventory.viewManager.CreateContainerView(
653            inventory.rootFolder, [vim.DistributedVirtualSwitch], True
654        )
655        for item in container.view:
656            if item.name == dvs_name:
657                return item
658
659    return None
660
661
662def _get_pnics(host_reference):
663    """
664    Helper function that returns a list of PhysicalNics and their information.
665    """
666    return host_reference.config.network.pnic
667
668
669def _get_vnics(host_reference):
670    """
671    Helper function that returns a list of VirtualNics and their information.
672    """
673    return host_reference.config.network.vnic
674
675
676def _get_vnic_manager(host_reference):
677    """
678    Helper function that returns a list of Virtual NicManagers
679    and their information.
680    """
681    return host_reference.configManager.virtualNicManager
682
683
684def _get_dvs_portgroup(dvs, portgroup_name):
685    """
686    Return a portgroup object corresponding to the portgroup name on the dvs
687
688    :param dvs: DVS object
689    :param portgroup_name: Name of portgroup to return
690    :return: Portgroup object
691    """
692    for portgroup in dvs.portgroup:
693        if portgroup.name == portgroup_name:
694            return portgroup
695
696    return None
697
698
699def _get_dvs_uplink_portgroup(dvs, portgroup_name):
700    """
701    Return a portgroup object corresponding to the portgroup name on the dvs
702
703    :param dvs: DVS object
704    :param portgroup_name: Name of portgroup to return
705    :return: Portgroup object
706    """
707    for portgroup in dvs.portgroup:
708        if portgroup.name == portgroup_name:
709            return portgroup
710
711    return None
712
713
714def get_gssapi_token(principal, host, domain):
715    """
716    Get the gssapi token for Kerberos connection
717
718    principal
719       The service principal
720    host
721       Host url where we would like to authenticate
722    domain
723       Kerberos user domain
724    """
725
726    if not HAS_GSSAPI:
727        raise ImportError("The gssapi library is not imported.")
728
729    service = "{}/{}@{}".format(principal, host, domain)
730    log.debug("Retrieving gsspi token for service %s", service)
731    service_name = gssapi.Name(service, gssapi.C_NT_USER_NAME)
732    ctx = gssapi.InitContext(service_name)
733    in_token = None
734    while not ctx.established:
735        out_token = ctx.step(in_token)
736        if out_token:
737            return base64.b64encode(salt.utils.stringutils.to_bytes(out_token))
738        if ctx.established:
739            break
740        if not in_token:
741            raise salt.exceptions.CommandExecutionError(
742                "Can't receive token, no response from server"
743            )
744    raise salt.exceptions.CommandExecutionError(
745        "Context established, but didn't receive token"
746    )
747
748
749def get_hardware_grains(service_instance):
750    """
751    Return hardware info for standard minion grains if the service_instance is a HostAgent type
752
753    service_instance
754        The service instance object to get hardware info for
755
756    .. versionadded:: 2016.11.0
757    """
758    hw_grain_data = {}
759    if get_inventory(service_instance).about.apiType == "HostAgent":
760        view = service_instance.content.viewManager.CreateContainerView(
761            service_instance.RetrieveContent().rootFolder, [vim.HostSystem], True
762        )
763        if view and view.view:
764            hw_grain_data["manufacturer"] = view.view[0].hardware.systemInfo.vendor
765            hw_grain_data["productname"] = view.view[0].hardware.systemInfo.model
766
767            for _data in view.view[0].hardware.systemInfo.otherIdentifyingInfo:
768                if _data.identifierType.key == "ServiceTag":
769                    hw_grain_data["serialnumber"] = _data.identifierValue
770
771            hw_grain_data["osfullname"] = view.view[0].summary.config.product.fullName
772            hw_grain_data["osmanufacturer"] = view.view[0].summary.config.product.vendor
773            hw_grain_data["osrelease"] = view.view[0].summary.config.product.version
774            hw_grain_data["osbuild"] = view.view[0].summary.config.product.build
775            hw_grain_data["os_family"] = view.view[0].summary.config.product.name
776            hw_grain_data["os"] = view.view[0].summary.config.product.name
777            hw_grain_data["mem_total"] = view.view[0].hardware.memorySize / 1024 / 1024
778            hw_grain_data["biosversion"] = view.view[0].hardware.biosInfo.biosVersion
779            hw_grain_data["biosreleasedate"] = (
780                view.view[0].hardware.biosInfo.releaseDate.date().strftime("%m/%d/%Y")
781            )
782            hw_grain_data["cpu_model"] = view.view[0].hardware.cpuPkg[0].description
783            hw_grain_data["kernel"] = view.view[0].summary.config.product.productLineId
784            hw_grain_data["num_cpu_sockets"] = view.view[
785                0
786            ].hardware.cpuInfo.numCpuPackages
787            hw_grain_data["num_cpu_cores"] = view.view[0].hardware.cpuInfo.numCpuCores
788            hw_grain_data["num_cpus"] = (
789                hw_grain_data["num_cpu_sockets"] * hw_grain_data["num_cpu_cores"]
790            )
791            hw_grain_data["ip_interfaces"] = {}
792            hw_grain_data["ip4_interfaces"] = {}
793            hw_grain_data["ip6_interfaces"] = {}
794            hw_grain_data["hwaddr_interfaces"] = {}
795            for _vnic in view.view[0].configManager.networkSystem.networkConfig.vnic:
796                hw_grain_data["ip_interfaces"][_vnic.device] = []
797                hw_grain_data["ip4_interfaces"][_vnic.device] = []
798                hw_grain_data["ip6_interfaces"][_vnic.device] = []
799
800                hw_grain_data["ip_interfaces"][_vnic.device].append(
801                    _vnic.spec.ip.ipAddress
802                )
803                hw_grain_data["ip4_interfaces"][_vnic.device].append(
804                    _vnic.spec.ip.ipAddress
805                )
806                if _vnic.spec.ip.ipV6Config:
807                    hw_grain_data["ip6_interfaces"][_vnic.device].append(
808                        _vnic.spec.ip.ipV6Config.ipV6Address
809                    )
810                hw_grain_data["hwaddr_interfaces"][_vnic.device] = _vnic.spec.mac
811            hw_grain_data["host"] = view.view[
812                0
813            ].configManager.networkSystem.dnsConfig.hostName
814            hw_grain_data["domain"] = view.view[
815                0
816            ].configManager.networkSystem.dnsConfig.domainName
817            hw_grain_data["fqdn"] = "{}{}{}".format(
818                view.view[0].configManager.networkSystem.dnsConfig.hostName,
819                (
820                    "."
821                    if view.view[0].configManager.networkSystem.dnsConfig.domainName
822                    else ""
823                ),
824                view.view[0].configManager.networkSystem.dnsConfig.domainName,
825            )
826
827            for _pnic in view.view[0].configManager.networkSystem.networkInfo.pnic:
828                hw_grain_data["hwaddr_interfaces"][_pnic.device] = _pnic.mac
829
830            hw_grain_data["timezone"] = view.view[
831                0
832            ].configManager.dateTimeSystem.dateTimeInfo.timeZone.name
833        view = None
834    return hw_grain_data
835
836
837def get_inventory(service_instance):
838    """
839    Return the inventory of a Service Instance Object.
840
841    service_instance
842        The Service Instance Object for which to obtain inventory.
843    """
844    return service_instance.RetrieveContent()
845
846
847def get_root_folder(service_instance):
848    """
849    Returns the root folder of a vCenter.
850
851    service_instance
852        The Service Instance Object for which to obtain the root folder.
853    """
854    try:
855        log.trace("Retrieving root folder")
856        return service_instance.RetrieveContent().rootFolder
857    except vim.fault.NoPermission as exc:
858        log.exception(exc)
859        raise salt.exceptions.VMwareApiError(
860            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
861        )
862    except vim.fault.VimFault as exc:
863        log.exception(exc)
864        raise salt.exceptions.VMwareApiError(exc.msg)
865    except vmodl.RuntimeFault as exc:
866        log.exception(exc)
867        raise salt.exceptions.VMwareRuntimeError(exc.msg)
868
869
870def get_content(
871    service_instance,
872    obj_type,
873    property_list=None,
874    container_ref=None,
875    traversal_spec=None,
876    local_properties=False,
877):
878    """
879    Returns the content of the specified type of object for a Service Instance.
880
881    For more information, please see:
882    http://pubs.vmware.com/vsphere-50/index.jsp?topic=%2Fcom.vmware.wssdk.pg.doc_50%2FPG_Ch5_PropertyCollector.7.6.html
883
884    service_instance
885        The Service Instance from which to obtain content.
886
887    obj_type
888        The type of content to obtain.
889
890    property_list
891        An optional list of object properties to used to return even more filtered content results.
892
893    container_ref
894        An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter,
895        ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory
896        rootFolder.
897
898    traversal_spec
899        An optional TraversalSpec to be used instead of the standard
900        ``Traverse All`` spec.
901
902    local_properties
903        Flag specifying whether the properties to be retrieved are local to the
904        container. If that is the case, the traversal spec needs to be None.
905    """
906    # Start at the rootFolder if container starting point not specified
907    if not container_ref:
908        container_ref = get_root_folder(service_instance)
909
910    # By default, the object reference used as the starting poing for the filter
911    # is the container_ref passed in the function
912    obj_ref = container_ref
913    local_traversal_spec = False
914    if not traversal_spec and not local_properties:
915        local_traversal_spec = True
916        # We don't have a specific traversal spec override so we are going to
917        # get everything using a container view
918        try:
919            obj_ref = service_instance.content.viewManager.CreateContainerView(
920                container_ref, [obj_type], True
921            )
922        except vim.fault.NoPermission as exc:
923            log.exception(exc)
924            raise salt.exceptions.VMwareApiError(
925                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
926            )
927        except vim.fault.VimFault as exc:
928            log.exception(exc)
929            raise salt.exceptions.VMwareApiError(exc.msg)
930        except vmodl.RuntimeFault as exc:
931            log.exception(exc)
932            raise salt.exceptions.VMwareRuntimeError(exc.msg)
933
934        # Create 'Traverse All' traversal spec to determine the path for
935        # collection
936        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
937            name="traverseEntities",
938            path="view",
939            skip=False,
940            type=vim.view.ContainerView,
941        )
942
943    # Create property spec to determine properties to be retrieved
944    property_spec = vmodl.query.PropertyCollector.PropertySpec(
945        type=obj_type, all=True if not property_list else False, pathSet=property_list
946    )
947
948    # Create object spec to navigate content
949    obj_spec = vmodl.query.PropertyCollector.ObjectSpec(
950        obj=obj_ref,
951        skip=True if not local_properties else False,
952        selectSet=[traversal_spec] if not local_properties else None,
953    )
954
955    # Create a filter spec and specify object, property spec in it
956    filter_spec = vmodl.query.PropertyCollector.FilterSpec(
957        objectSet=[obj_spec],
958        propSet=[property_spec],
959        reportMissingObjectsInResults=False,
960    )
961
962    # Retrieve the contents
963    try:
964        content = service_instance.content.propertyCollector.RetrieveContents(
965            [filter_spec]
966        )
967    except vim.fault.NoPermission as exc:
968        log.exception(exc)
969        raise salt.exceptions.VMwareApiError(
970            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
971        )
972    except vim.fault.VimFault as exc:
973        log.exception(exc)
974        raise salt.exceptions.VMwareApiError(exc.msg)
975    except vmodl.RuntimeFault as exc:
976        log.exception(exc)
977        raise salt.exceptions.VMwareRuntimeError(exc.msg)
978
979    # Destroy the object view
980    if local_traversal_spec:
981        try:
982            obj_ref.Destroy()
983        except vim.fault.NoPermission as exc:
984            log.exception(exc)
985            raise salt.exceptions.VMwareApiError(
986                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
987            )
988        except vim.fault.VimFault as exc:
989            log.exception(exc)
990            raise salt.exceptions.VMwareApiError(exc.msg)
991        except vmodl.RuntimeFault as exc:
992            log.exception(exc)
993            raise salt.exceptions.VMwareRuntimeError(exc.msg)
994
995    return content
996
997
998def get_mor_by_property(
999    service_instance,
1000    object_type,
1001    property_value,
1002    property_name="name",
1003    container_ref=None,
1004):
1005    """
1006    Returns the first managed object reference having the specified property value.
1007
1008    service_instance
1009        The Service Instance from which to obtain managed object references.
1010
1011    object_type
1012        The type of content for which to obtain managed object references.
1013
1014    property_value
1015        The name of the property for which to obtain the managed object reference.
1016
1017    property_name
1018        An object property used to return the specified object reference results. Defaults to ``name``.
1019
1020    container_ref
1021        An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter,
1022        ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory
1023        rootFolder.
1024    """
1025    # Get list of all managed object references with specified property
1026    object_list = get_mors_with_properties(
1027        service_instance,
1028        object_type,
1029        property_list=[property_name],
1030        container_ref=container_ref,
1031    )
1032
1033    for obj in object_list:
1034        obj_id = str(obj.get("object", "")).strip("'\"")
1035        if obj[property_name] == property_value or property_value == obj_id:
1036            return obj["object"]
1037
1038    return None
1039
1040
1041def get_mors_with_properties(
1042    service_instance,
1043    object_type,
1044    property_list=None,
1045    container_ref=None,
1046    traversal_spec=None,
1047    local_properties=False,
1048):
1049    """
1050    Returns a list containing properties and managed object references for the managed object.
1051
1052    service_instance
1053        The Service Instance from which to obtain managed object references.
1054
1055    object_type
1056        The type of content for which to obtain managed object references.
1057
1058    property_list
1059        An optional list of object properties used to return even more filtered managed object reference results.
1060
1061    container_ref
1062        An optional reference to the managed object to search under. Can either be an object of type Folder, Datacenter,
1063        ComputeResource, Resource Pool or HostSystem. If not specified, default behaviour is to search under the inventory
1064        rootFolder.
1065
1066    traversal_spec
1067        An optional TraversalSpec to be used instead of the standard
1068        ``Traverse All`` spec
1069
1070    local_properties
1071        Flag specigying whether the properties to be retrieved are local to the
1072        container. If that is the case, the traversal spec needs to be None.
1073    """
1074    # Get all the content
1075    content_args = [service_instance, object_type]
1076    content_kwargs = {
1077        "property_list": property_list,
1078        "container_ref": container_ref,
1079        "traversal_spec": traversal_spec,
1080        "local_properties": local_properties,
1081    }
1082    try:
1083        content = get_content(*content_args, **content_kwargs)
1084    except BadStatusLine:
1085        content = get_content(*content_args, **content_kwargs)
1086    except OSError as exc:
1087        if exc.errno != errno.EPIPE:
1088            raise
1089        content = get_content(*content_args, **content_kwargs)
1090
1091    object_list = []
1092    for obj in content:
1093        properties = {}
1094        for prop in obj.propSet:
1095            properties[prop.name] = prop.val
1096        properties["object"] = obj.obj
1097        object_list.append(properties)
1098    log.trace("Retrieved %s objects", len(object_list))
1099    return object_list
1100
1101
1102def get_properties_of_managed_object(mo_ref, properties):
1103    """
1104    Returns specific properties of a managed object, retrieved in an
1105    optimally.
1106
1107    mo_ref
1108        The managed object reference.
1109
1110    properties
1111        List of properties of the managed object to retrieve.
1112    """
1113    service_instance = get_service_instance_from_managed_object(mo_ref)
1114    log.trace("Retrieving name of %s", type(mo_ref).__name__)
1115    try:
1116        items = get_mors_with_properties(
1117            service_instance,
1118            type(mo_ref),
1119            container_ref=mo_ref,
1120            property_list=["name"],
1121            local_properties=True,
1122        )
1123        mo_name = items[0]["name"]
1124    except vmodl.query.InvalidProperty:
1125        mo_name = "<unnamed>"
1126    log.trace(
1127        "Retrieving properties '%s' of %s '%s'",
1128        properties,
1129        type(mo_ref).__name__,
1130        mo_name,
1131    )
1132    items = get_mors_with_properties(
1133        service_instance,
1134        type(mo_ref),
1135        container_ref=mo_ref,
1136        property_list=properties,
1137        local_properties=True,
1138    )
1139    if not items:
1140        raise salt.exceptions.VMwareApiError(
1141            "Properties of managed object '{}' weren't retrieved".format(mo_name)
1142        )
1143    return items[0]
1144
1145
1146def get_managed_object_name(mo_ref):
1147    """
1148    Returns the name of a managed object.
1149    If the name wasn't found, it returns None.
1150
1151    mo_ref
1152        The managed object reference.
1153    """
1154    props = get_properties_of_managed_object(mo_ref, ["name"])
1155    return props.get("name")
1156
1157
1158def get_network_adapter_type(adapter_type):
1159    """
1160    Return the network adapter type.
1161
1162    adpater_type
1163        The adapter type from which to obtain the network adapter type.
1164    """
1165    if adapter_type == "vmxnet":
1166        return vim.vm.device.VirtualVmxnet()
1167    elif adapter_type == "vmxnet2":
1168        return vim.vm.device.VirtualVmxnet2()
1169    elif adapter_type == "vmxnet3":
1170        return vim.vm.device.VirtualVmxnet3()
1171    elif adapter_type == "e1000":
1172        return vim.vm.device.VirtualE1000()
1173    elif adapter_type == "e1000e":
1174        return vim.vm.device.VirtualE1000e()
1175
1176    raise ValueError("An unknown network adapter object type name.")
1177
1178
1179def get_network_adapter_object_type(adapter_object):
1180    """
1181    Returns the network adapter type.
1182
1183    adapter_object
1184        The adapter object from which to obtain the network adapter type.
1185    """
1186    if isinstance(adapter_object, vim.vm.device.VirtualVmxnet2):
1187        return "vmxnet2"
1188    if isinstance(adapter_object, vim.vm.device.VirtualVmxnet3):
1189        return "vmxnet3"
1190    if isinstance(adapter_object, vim.vm.device.VirtualVmxnet):
1191        return "vmxnet"
1192    if isinstance(adapter_object, vim.vm.device.VirtualE1000e):
1193        return "e1000e"
1194    if isinstance(adapter_object, vim.vm.device.VirtualE1000):
1195        return "e1000"
1196
1197    raise ValueError("An unknown network adapter object type.")
1198
1199
1200def get_dvss(dc_ref, dvs_names=None, get_all_dvss=False):
1201    """
1202    Returns distributed virtual switches (DVSs) in a datacenter.
1203
1204    dc_ref
1205        The parent datacenter reference.
1206
1207    dvs_names
1208        The names of the DVSs to return. Default is None.
1209
1210    get_all_dvss
1211        Return all DVSs in the datacenter. Default is False.
1212    """
1213    dc_name = get_managed_object_name(dc_ref)
1214    log.trace(
1215        "Retrieving DVSs in datacenter '%s', dvs_names='%s', get_all_dvss=%s",
1216        dc_name,
1217        ",".join(dvs_names) if dvs_names else None,
1218        get_all_dvss,
1219    )
1220    properties = ["name"]
1221    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1222        path="networkFolder",
1223        skip=True,
1224        type=vim.Datacenter,
1225        selectSet=[
1226            vmodl.query.PropertyCollector.TraversalSpec(
1227                path="childEntity", skip=False, type=vim.Folder
1228            )
1229        ],
1230    )
1231    service_instance = get_service_instance_from_managed_object(dc_ref)
1232    items = [
1233        i["object"]
1234        for i in get_mors_with_properties(
1235            service_instance,
1236            vim.DistributedVirtualSwitch,
1237            container_ref=dc_ref,
1238            property_list=properties,
1239            traversal_spec=traversal_spec,
1240        )
1241        if get_all_dvss or (dvs_names and i["name"] in dvs_names)
1242    ]
1243    return items
1244
1245
1246def get_network_folder(dc_ref):
1247    """
1248    Retrieves the network folder of a datacenter
1249    """
1250    dc_name = get_managed_object_name(dc_ref)
1251    log.trace("Retrieving network folder in datacenter '%s'", dc_name)
1252    service_instance = get_service_instance_from_managed_object(dc_ref)
1253    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1254        path="networkFolder", skip=False, type=vim.Datacenter
1255    )
1256    entries = get_mors_with_properties(
1257        service_instance,
1258        vim.Folder,
1259        container_ref=dc_ref,
1260        property_list=["name"],
1261        traversal_spec=traversal_spec,
1262    )
1263    if not entries:
1264        raise salt.exceptions.VMwareObjectRetrievalError(
1265            "Network folder in datacenter '{}' wasn't retrieved".format(dc_name)
1266        )
1267    return entries[0]["object"]
1268
1269
1270def create_dvs(dc_ref, dvs_name, dvs_create_spec=None):
1271    """
1272    Creates a distributed virtual switches (DVS) in a datacenter.
1273    Returns the reference to the newly created distributed virtual switch.
1274
1275    dc_ref
1276        The parent datacenter reference.
1277
1278    dvs_name
1279        The name of the DVS to create.
1280
1281    dvs_create_spec
1282        The DVS spec (vim.DVSCreateSpec) to use when creating the DVS.
1283        Default is None.
1284    """
1285    dc_name = get_managed_object_name(dc_ref)
1286    log.trace("Creating DVS '%s' in datacenter '%s'", dvs_name, dc_name)
1287    if not dvs_create_spec:
1288        dvs_create_spec = vim.DVSCreateSpec()
1289    if not dvs_create_spec.configSpec:
1290        dvs_create_spec.configSpec = vim.VMwareDVSConfigSpec()
1291        dvs_create_spec.configSpec.name = dvs_name
1292    netw_folder_ref = get_network_folder(dc_ref)
1293    try:
1294        task = netw_folder_ref.CreateDVS_Task(dvs_create_spec)
1295    except vim.fault.NoPermission as exc:
1296        log.exception(exc)
1297        raise salt.exceptions.VMwareApiError(
1298            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1299        )
1300    except vim.fault.VimFault as exc:
1301        log.exception(exc)
1302        raise salt.exceptions.VMwareApiError(exc.msg)
1303    except vmodl.RuntimeFault as exc:
1304        log.exception(exc)
1305        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1306    wait_for_task(task, dvs_name, str(task.__class__))
1307
1308
1309def update_dvs(dvs_ref, dvs_config_spec):
1310    """
1311    Updates a distributed virtual switch with the config_spec.
1312
1313    dvs_ref
1314        The DVS reference.
1315
1316    dvs_config_spec
1317        The updated config spec (vim.VMwareDVSConfigSpec) to be applied to
1318        the DVS.
1319    """
1320    dvs_name = get_managed_object_name(dvs_ref)
1321    log.trace("Updating dvs '%s'", dvs_name)
1322    try:
1323        task = dvs_ref.ReconfigureDvs_Task(dvs_config_spec)
1324    except vim.fault.NoPermission as exc:
1325        log.exception(exc)
1326        raise salt.exceptions.VMwareApiError(
1327            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1328        )
1329    except vim.fault.VimFault as exc:
1330        log.exception(exc)
1331        raise salt.exceptions.VMwareApiError(exc.msg)
1332    except vmodl.RuntimeFault as exc:
1333        log.exception(exc)
1334        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1335    wait_for_task(task, dvs_name, str(task.__class__))
1336
1337
1338def set_dvs_network_resource_management_enabled(dvs_ref, enabled):
1339    """
1340    Sets whether NIOC is enabled on a DVS.
1341
1342    dvs_ref
1343        The DVS reference.
1344
1345    enabled
1346        Flag specifying whether NIOC is enabled.
1347    """
1348    dvs_name = get_managed_object_name(dvs_ref)
1349    log.trace(
1350        "Setting network resource management enable to %s on dvs '%s'",
1351        enabled,
1352        dvs_name,
1353    )
1354    try:
1355        dvs_ref.EnableNetworkResourceManagement(enable=enabled)
1356    except vim.fault.NoPermission as exc:
1357        log.exception(exc)
1358        raise salt.exceptions.VMwareApiError(
1359            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1360        )
1361    except vim.fault.VimFault as exc:
1362        log.exception(exc)
1363        raise salt.exceptions.VMwareApiError(exc.msg)
1364    except vmodl.RuntimeFault as exc:
1365        log.exception(exc)
1366        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1367
1368
1369def get_dvportgroups(parent_ref, portgroup_names=None, get_all_portgroups=False):
1370    """
1371    Returns distributed virtual porgroups (dvportgroups).
1372    The parent object can be either a datacenter or a dvs.
1373
1374    parent_ref
1375        The parent object reference. Can be either a datacenter or a dvs.
1376
1377    portgroup_names
1378        The names of the dvss to return. Default is None.
1379
1380    get_all_portgroups
1381        Return all portgroups in the parent. Default is False.
1382    """
1383    if not isinstance(parent_ref, (vim.Datacenter, vim.DistributedVirtualSwitch)):
1384        raise salt.exceptions.ArgumentValueError(
1385            "Parent has to be either a datacenter, or a distributed virtual switch"
1386        )
1387    parent_name = get_managed_object_name(parent_ref)
1388    log.trace(
1389        "Retrieving portgroup in %s '%s', portgroups_names='%s', get_all_portgroups=%s",
1390        type(parent_ref).__name__,
1391        parent_name,
1392        ",".join(portgroup_names) if portgroup_names else None,
1393        get_all_portgroups,
1394    )
1395    properties = ["name"]
1396    if isinstance(parent_ref, vim.Datacenter):
1397        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1398            path="networkFolder",
1399            skip=True,
1400            type=vim.Datacenter,
1401            selectSet=[
1402                vmodl.query.PropertyCollector.TraversalSpec(
1403                    path="childEntity", skip=False, type=vim.Folder
1404                )
1405            ],
1406        )
1407    else:  # parent is distributed virtual switch
1408        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1409            path="portgroup", skip=False, type=vim.DistributedVirtualSwitch
1410        )
1411
1412    service_instance = get_service_instance_from_managed_object(parent_ref)
1413    items = [
1414        i["object"]
1415        for i in get_mors_with_properties(
1416            service_instance,
1417            vim.DistributedVirtualPortgroup,
1418            container_ref=parent_ref,
1419            property_list=properties,
1420            traversal_spec=traversal_spec,
1421        )
1422        if get_all_portgroups or (portgroup_names and i["name"] in portgroup_names)
1423    ]
1424    return items
1425
1426
1427def get_uplink_dvportgroup(dvs_ref):
1428    """
1429    Returns the uplink distributed virtual portgroup of a distributed virtual
1430    switch (dvs)
1431
1432    dvs_ref
1433        The dvs reference
1434    """
1435    dvs_name = get_managed_object_name(dvs_ref)
1436    log.trace("Retrieving uplink portgroup of dvs '%s'", dvs_name)
1437    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1438        path="portgroup", skip=False, type=vim.DistributedVirtualSwitch
1439    )
1440    service_instance = get_service_instance_from_managed_object(dvs_ref)
1441    items = [
1442        entry["object"]
1443        for entry in get_mors_with_properties(
1444            service_instance,
1445            vim.DistributedVirtualPortgroup,
1446            container_ref=dvs_ref,
1447            property_list=["tag"],
1448            traversal_spec=traversal_spec,
1449        )
1450        if entry["tag"] and [t for t in entry["tag"] if t.key == "SYSTEM/DVS.UPLINKPG"]
1451    ]
1452    if not items:
1453        raise salt.exceptions.VMwareObjectRetrievalError(
1454            "Uplink portgroup of DVS '{}' wasn't found".format(dvs_name)
1455        )
1456    return items[0]
1457
1458
1459def create_dvportgroup(dvs_ref, spec):
1460    """
1461    Creates a distributed virtual portgroup on a distributed virtual switch
1462    (dvs)
1463
1464    dvs_ref
1465        The dvs reference
1466
1467    spec
1468        Portgroup spec (vim.DVPortgroupConfigSpec)
1469    """
1470    dvs_name = get_managed_object_name(dvs_ref)
1471    log.trace("Adding portgroup %s to dvs '%s'", spec.name, dvs_name)
1472    log.trace("spec = %s", spec)
1473    try:
1474        task = dvs_ref.CreateDVPortgroup_Task(spec)
1475    except vim.fault.NoPermission as exc:
1476        log.exception(exc)
1477        raise salt.exceptions.VMwareApiError(
1478            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1479        )
1480    except vim.fault.VimFault as exc:
1481        log.exception(exc)
1482        raise salt.exceptions.VMwareApiError(exc.msg)
1483    except vmodl.RuntimeFault as exc:
1484        log.exception(exc)
1485        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1486    wait_for_task(task, dvs_name, str(task.__class__))
1487
1488
1489def update_dvportgroup(portgroup_ref, spec):
1490    """
1491    Updates a distributed virtual portgroup
1492
1493    portgroup_ref
1494        The portgroup reference
1495
1496    spec
1497        Portgroup spec (vim.DVPortgroupConfigSpec)
1498    """
1499    pg_name = get_managed_object_name(portgroup_ref)
1500    log.trace("Updating portgrouo %s", pg_name)
1501    try:
1502        task = portgroup_ref.ReconfigureDVPortgroup_Task(spec)
1503    except vim.fault.NoPermission as exc:
1504        log.exception(exc)
1505        raise salt.exceptions.VMwareApiError(
1506            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1507        )
1508    except vim.fault.VimFault as exc:
1509        log.exception(exc)
1510        raise salt.exceptions.VMwareApiError(exc.msg)
1511    except vmodl.RuntimeFault as exc:
1512        log.exception(exc)
1513        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1514    wait_for_task(task, pg_name, str(task.__class__))
1515
1516
1517def remove_dvportgroup(portgroup_ref):
1518    """
1519    Removes a distributed virtual portgroup
1520
1521    portgroup_ref
1522        The portgroup reference
1523    """
1524    pg_name = get_managed_object_name(portgroup_ref)
1525    log.trace("Removing portgroup %s", pg_name)
1526    try:
1527        task = portgroup_ref.Destroy_Task()
1528    except vim.fault.NoPermission as exc:
1529        log.exception(exc)
1530        raise salt.exceptions.VMwareApiError(
1531            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1532        )
1533    except vim.fault.VimFault as exc:
1534        log.exception(exc)
1535        raise salt.exceptions.VMwareApiError(exc.msg)
1536    except vmodl.RuntimeFault as exc:
1537        log.exception(exc)
1538        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1539    wait_for_task(task, pg_name, str(task.__class__))
1540
1541
1542def get_networks(parent_ref, network_names=None, get_all_networks=False):
1543    """
1544    Returns networks of standard switches.
1545    The parent object can be a datacenter.
1546
1547    parent_ref
1548        The parent object reference. A datacenter object.
1549
1550    network_names
1551        The name of the standard switch networks. Default is None.
1552
1553    get_all_networks
1554        Boolean indicates whether to return all networks in the parent.
1555        Default is False.
1556    """
1557
1558    if not isinstance(parent_ref, vim.Datacenter):
1559        raise salt.exceptions.ArgumentValueError("Parent has to be a datacenter.")
1560    parent_name = get_managed_object_name(parent_ref)
1561    log.trace(
1562        "Retrieving network from %s '%s', network_names='%s', get_all_networks=%s",
1563        type(parent_ref).__name__,
1564        parent_name,
1565        ",".join(network_names) if network_names else None,
1566        get_all_networks,
1567    )
1568    properties = ["name"]
1569    service_instance = get_service_instance_from_managed_object(parent_ref)
1570    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
1571        path="networkFolder",
1572        skip=True,
1573        type=vim.Datacenter,
1574        selectSet=[
1575            vmodl.query.PropertyCollector.TraversalSpec(
1576                path="childEntity", skip=False, type=vim.Folder
1577            )
1578        ],
1579    )
1580    items = [
1581        i["object"]
1582        for i in get_mors_with_properties(
1583            service_instance,
1584            vim.Network,
1585            container_ref=parent_ref,
1586            property_list=properties,
1587            traversal_spec=traversal_spec,
1588        )
1589        if get_all_networks or (network_names and i["name"] in network_names)
1590    ]
1591    return items
1592
1593
1594def list_objects(service_instance, vim_object, properties=None):
1595    """
1596    Returns a simple list of objects from a given service instance.
1597
1598    service_instance
1599        The Service Instance for which to obtain a list of objects.
1600
1601    object_type
1602        The type of content for which to obtain information.
1603
1604    properties
1605        An optional list of object properties used to return reference results.
1606        If not provided, defaults to ``name``.
1607    """
1608    if properties is None:
1609        properties = ["name"]
1610
1611    items = []
1612    item_list = get_mors_with_properties(service_instance, vim_object, properties)
1613    for item in item_list:
1614        items.append(item["name"])
1615    return items
1616
1617
1618def get_license_manager(service_instance):
1619    """
1620    Returns the license manager.
1621
1622    service_instance
1623        The Service Instance Object from which to obrain the license manager.
1624    """
1625
1626    log.debug("Retrieving license manager")
1627    try:
1628        lic_manager = service_instance.content.licenseManager
1629    except vim.fault.NoPermission as exc:
1630        log.exception(exc)
1631        raise salt.exceptions.VMwareApiError(
1632            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1633        )
1634    except vim.fault.VimFault as exc:
1635        log.exception(exc)
1636        raise salt.exceptions.VMwareApiError(exc.msg)
1637    except vmodl.RuntimeFault as exc:
1638        log.exception(exc)
1639        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1640    return lic_manager
1641
1642
1643def get_license_assignment_manager(service_instance):
1644    """
1645    Returns the license assignment manager.
1646
1647    service_instance
1648        The Service Instance Object from which to obrain the license manager.
1649    """
1650
1651    log.debug("Retrieving license assignment manager")
1652    try:
1653        lic_assignment_manager = (
1654            service_instance.content.licenseManager.licenseAssignmentManager
1655        )
1656    except vim.fault.NoPermission as exc:
1657        log.exception(exc)
1658        raise salt.exceptions.VMwareApiError(
1659            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1660        )
1661    except vim.fault.VimFault as exc:
1662        log.exception(exc)
1663        raise salt.exceptions.VMwareApiError(exc.msg)
1664    except vmodl.RuntimeFault as exc:
1665        log.exception(exc)
1666        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1667    if not lic_assignment_manager:
1668        raise salt.exceptions.VMwareObjectRetrievalError(
1669            "License assignment manager was not retrieved"
1670        )
1671    return lic_assignment_manager
1672
1673
1674def get_licenses(service_instance, license_manager=None):
1675    """
1676    Returns the licenses on a specific instance.
1677
1678    service_instance
1679        The Service Instance Object from which to obrain the licenses.
1680
1681    license_manager
1682        The License Manager object of the service instance. If not provided it
1683        will be retrieved.
1684    """
1685
1686    if not license_manager:
1687        license_manager = get_license_manager(service_instance)
1688    log.debug("Retrieving licenses")
1689    try:
1690        return license_manager.licenses
1691    except vim.fault.NoPermission as exc:
1692        log.exception(exc)
1693        raise salt.exceptions.VMwareApiError(
1694            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1695        )
1696    except vim.fault.VimFault as exc:
1697        log.exception(exc)
1698        raise salt.exceptions.VMwareApiError(exc.msg)
1699    except vmodl.RuntimeFault as exc:
1700        log.exception(exc)
1701        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1702
1703
1704def add_license(service_instance, key, description, license_manager=None):
1705    """
1706    Adds a license.
1707
1708    service_instance
1709        The Service Instance Object.
1710
1711    key
1712        The key of the license to add.
1713
1714    description
1715        The description of the license to add.
1716
1717    license_manager
1718        The License Manager object of the service instance. If not provided it
1719        will be retrieved.
1720    """
1721    if not license_manager:
1722        license_manager = get_license_manager(service_instance)
1723    label = vim.KeyValue()
1724    label.key = "VpxClientLicenseLabel"
1725    label.value = description
1726    log.debug("Adding license '%s'", description)
1727    try:
1728        vmware_license = license_manager.AddLicense(key, [label])
1729    except vim.fault.NoPermission as exc:
1730        log.exception(exc)
1731        raise salt.exceptions.VMwareApiError(
1732            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1733        )
1734    except vim.fault.VimFault as exc:
1735        log.exception(exc)
1736        raise salt.exceptions.VMwareApiError(exc.msg)
1737    except vmodl.RuntimeFault as exc:
1738        log.exception(exc)
1739        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1740    return vmware_license
1741
1742
1743def get_assigned_licenses(
1744    service_instance, entity_ref=None, entity_name=None, license_assignment_manager=None
1745):
1746    """
1747    Returns the licenses assigned to an entity. If entity ref is not provided,
1748    then entity_name is assumed to be the vcenter. This is later checked if
1749    the entity name is provided.
1750
1751    service_instance
1752        The Service Instance Object from which to obtain the licenses.
1753
1754    entity_ref
1755        VMware entity to get the assigned licenses for.
1756        If None, the entity is the vCenter itself.
1757        Default is None.
1758
1759    entity_name
1760        Entity name used in logging.
1761        Default is None.
1762
1763    license_assignment_manager
1764        The LicenseAssignmentManager object of the service instance.
1765        If not provided it will be retrieved.
1766        Default is None.
1767    """
1768    if not license_assignment_manager:
1769        license_assignment_manager = get_license_assignment_manager(service_instance)
1770    if not entity_name:
1771        raise salt.exceptions.ArgumentValueError("No entity_name passed")
1772    # If entity_ref is not defined, then interested in the vcenter
1773    entity_id = None
1774    entity_type = "moid"
1775    check_name = False
1776    if not entity_ref:
1777        if entity_name:
1778            check_name = True
1779        entity_type = "uuid"
1780        try:
1781            entity_id = service_instance.content.about.instanceUuid
1782        except vim.fault.NoPermission as exc:
1783            log.exception(exc)
1784            raise salt.exceptions.VMwareApiError(
1785                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1786            )
1787        except vim.fault.VimFault as exc:
1788            log.exception(exc)
1789            raise salt.exceptions.VMwareApiError(exc.msg)
1790        except vmodl.RuntimeFault as exc:
1791            log.exception(exc)
1792            raise salt.exceptions.VMwareRuntimeError(exc.msg)
1793    else:
1794        entity_id = entity_ref._moId
1795
1796    log.trace("Retrieving licenses assigned to '%s'", entity_name)
1797    try:
1798        assignments = license_assignment_manager.QueryAssignedLicenses(entity_id)
1799    except vim.fault.NoPermission as exc:
1800        log.exception(exc)
1801        raise salt.exceptions.VMwareApiError(
1802            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1803        )
1804    except vim.fault.VimFault as exc:
1805        log.exception(exc)
1806        raise salt.exceptions.VMwareApiError(exc.msg)
1807    except vmodl.RuntimeFault as exc:
1808        log.exception(exc)
1809        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1810
1811    if entity_type == "uuid" and len(assignments) > 1:
1812        log.trace("Unexpectectedly retrieved more than one VCenter license assignment.")
1813        raise salt.exceptions.VMwareObjectRetrievalError(
1814            "Unexpected return. Expect only a single assignment"
1815        )
1816
1817    if check_name:
1818        if entity_name != assignments[0].entityDisplayName:
1819            log.trace(
1820                "Getting license info for wrong vcenter: %s != %s",
1821                entity_name,
1822                assignments[0].entityDisplayName,
1823            )
1824            raise salt.exceptions.VMwareObjectRetrievalError(
1825                "Got license assignment info for a different vcenter"
1826            )
1827
1828    return [a.assignedLicense for a in assignments]
1829
1830
1831def assign_license(
1832    service_instance,
1833    license_key,
1834    license_name,
1835    entity_ref=None,
1836    entity_name=None,
1837    license_assignment_manager=None,
1838):
1839    """
1840    Assigns a license to an entity.
1841
1842    service_instance
1843        The Service Instance Object from which to obrain the licenses.
1844
1845    license_key
1846        The key of the license to add.
1847
1848    license_name
1849        The description of the license to add.
1850
1851    entity_ref
1852        VMware entity to assign the license to.
1853        If None, the entity is the vCenter itself.
1854        Default is None.
1855
1856    entity_name
1857        Entity name used in logging.
1858        Default is None.
1859
1860    license_assignment_manager
1861        The LicenseAssignmentManager object of the service instance.
1862        If not provided it will be retrieved
1863        Default is None.
1864    """
1865    if not license_assignment_manager:
1866        license_assignment_manager = get_license_assignment_manager(service_instance)
1867    entity_id = None
1868
1869    if not entity_ref:
1870        # vcenter
1871        try:
1872            entity_id = service_instance.content.about.instanceUuid
1873        except vim.fault.NoPermission as exc:
1874            log.exception(exc)
1875            raise salt.exceptions.VMwareApiError(
1876                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1877            )
1878        except vim.fault.VimFault as exc:
1879            raise salt.exceptions.VMwareApiError(exc.msg)
1880        except vmodl.RuntimeFault as exc:
1881            raise salt.exceptions.VMwareRuntimeError(exc.msg)
1882        if not entity_name:
1883            entity_name = "vCenter"
1884    else:
1885        # e.g. vsan cluster or host
1886        entity_id = entity_ref._moId
1887
1888    log.trace("Assigning license to '%s'", entity_name)
1889    try:
1890        vmware_license = license_assignment_manager.UpdateAssignedLicense(
1891            entity_id, license_key, license_name
1892        )
1893    except vim.fault.NoPermission as exc:
1894        log.exception(exc)
1895        raise salt.exceptions.VMwareApiError(
1896            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1897        )
1898    except vim.fault.VimFault as exc:
1899        log.exception(exc)
1900        raise salt.exceptions.VMwareApiError(exc.msg)
1901    except vmodl.RuntimeFault as exc:
1902        log.exception(exc)
1903        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1904    return vmware_license
1905
1906
1907def list_datacenters(service_instance):
1908    """
1909    Returns a list of datacenters associated with a given service instance.
1910
1911    service_instance
1912        The Service Instance Object from which to obtain datacenters.
1913    """
1914    return list_objects(service_instance, vim.Datacenter)
1915
1916
1917def get_datacenters(service_instance, datacenter_names=None, get_all_datacenters=False):
1918    """
1919    Returns all datacenters in a vCenter.
1920
1921    service_instance
1922        The Service Instance Object from which to obtain cluster.
1923
1924    datacenter_names
1925        List of datacenter names to filter by. Default value is None.
1926
1927    get_all_datacenters
1928        Flag specifying whether to retrieve all datacenters.
1929        Default value is None.
1930    """
1931    items = [
1932        i["object"]
1933        for i in get_mors_with_properties(
1934            service_instance, vim.Datacenter, property_list=["name"]
1935        )
1936        if get_all_datacenters or (datacenter_names and i["name"] in datacenter_names)
1937    ]
1938    return items
1939
1940
1941def get_datacenter(service_instance, datacenter_name):
1942    """
1943    Returns a vim.Datacenter managed object.
1944
1945    service_instance
1946        The Service Instance Object from which to obtain datacenter.
1947
1948    datacenter_name
1949        The datacenter name
1950    """
1951    items = get_datacenters(service_instance, datacenter_names=[datacenter_name])
1952    if not items:
1953        raise salt.exceptions.VMwareObjectRetrievalError(
1954            "Datacenter '{}' was not found".format(datacenter_name)
1955        )
1956    return items[0]
1957
1958
1959def create_datacenter(service_instance, datacenter_name):
1960    """
1961    Creates a datacenter.
1962
1963    .. versionadded:: 2017.7.0
1964
1965    service_instance
1966        The Service Instance Object
1967
1968    datacenter_name
1969        The datacenter name
1970    """
1971    root_folder = get_root_folder(service_instance)
1972    log.trace("Creating datacenter '%s'", datacenter_name)
1973    try:
1974        dc_obj = root_folder.CreateDatacenter(datacenter_name)
1975    except vim.fault.NoPermission as exc:
1976        log.exception(exc)
1977        raise salt.exceptions.VMwareApiError(
1978            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
1979        )
1980    except vim.fault.VimFault as exc:
1981        log.exception(exc)
1982        raise salt.exceptions.VMwareApiError(exc.msg)
1983    except vmodl.RuntimeFault as exc:
1984        log.exception(exc)
1985        raise salt.exceptions.VMwareRuntimeError(exc.msg)
1986    return dc_obj
1987
1988
1989def get_cluster(dc_ref, cluster):
1990    """
1991    Returns a cluster in a datacenter.
1992
1993    dc_ref
1994        The datacenter reference
1995
1996    cluster
1997        The cluster to be retrieved
1998    """
1999    dc_name = get_managed_object_name(dc_ref)
2000    log.trace("Retrieving cluster '%s' from datacenter '%s'", cluster, dc_name)
2001    si = get_service_instance_from_managed_object(dc_ref, name=dc_name)
2002    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2003        path="hostFolder",
2004        skip=True,
2005        type=vim.Datacenter,
2006        selectSet=[
2007            vmodl.query.PropertyCollector.TraversalSpec(
2008                path="childEntity", skip=False, type=vim.Folder
2009            )
2010        ],
2011    )
2012    items = [
2013        i["object"]
2014        for i in get_mors_with_properties(
2015            si,
2016            vim.ClusterComputeResource,
2017            container_ref=dc_ref,
2018            property_list=["name"],
2019            traversal_spec=traversal_spec,
2020        )
2021        if i["name"] == cluster
2022    ]
2023    if not items:
2024        raise salt.exceptions.VMwareObjectRetrievalError(
2025            "Cluster '{}' was not found in datacenter '{}'".format(cluster, dc_name)
2026        )
2027    return items[0]
2028
2029
2030def create_cluster(dc_ref, cluster_name, cluster_spec):
2031    """
2032    Creates a cluster in a datacenter.
2033
2034    dc_ref
2035        The parent datacenter reference.
2036
2037    cluster_name
2038        The cluster name.
2039
2040    cluster_spec
2041        The cluster spec (vim.ClusterConfigSpecEx).
2042        Defaults to None.
2043    """
2044    dc_name = get_managed_object_name(dc_ref)
2045    log.trace("Creating cluster '%s' in datacenter '%s'", cluster_name, dc_name)
2046    try:
2047        dc_ref.hostFolder.CreateClusterEx(cluster_name, cluster_spec)
2048    except vim.fault.NoPermission as exc:
2049        log.exception(exc)
2050        raise salt.exceptions.VMwareApiError(
2051            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2052        )
2053    except vim.fault.VimFault as exc:
2054        log.exception(exc)
2055        raise salt.exceptions.VMwareApiError(exc.msg)
2056    except vmodl.RuntimeFault as exc:
2057        log.exception(exc)
2058        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2059
2060
2061def update_cluster(cluster_ref, cluster_spec):
2062    """
2063    Updates a cluster in a datacenter.
2064
2065    cluster_ref
2066        The cluster reference.
2067
2068    cluster_spec
2069        The cluster spec (vim.ClusterConfigSpecEx).
2070        Defaults to None.
2071    """
2072    cluster_name = get_managed_object_name(cluster_ref)
2073    log.trace("Updating cluster '%s'", cluster_name)
2074    try:
2075        task = cluster_ref.ReconfigureComputeResource_Task(cluster_spec, modify=True)
2076    except vim.fault.NoPermission as exc:
2077        log.exception(exc)
2078        raise salt.exceptions.VMwareApiError(
2079            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2080        )
2081    except vim.fault.VimFault as exc:
2082        log.exception(exc)
2083        raise salt.exceptions.VMwareApiError(exc.msg)
2084    except vmodl.RuntimeFault as exc:
2085        log.exception(exc)
2086        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2087    wait_for_task(task, cluster_name, "ClusterUpdateTask")
2088
2089
2090def list_clusters(service_instance):
2091    """
2092    Returns a list of clusters associated with a given service instance.
2093
2094    service_instance
2095        The Service Instance Object from which to obtain clusters.
2096    """
2097    return list_objects(service_instance, vim.ClusterComputeResource)
2098
2099
2100def list_datastore_clusters(service_instance):
2101    """
2102    Returns a list of datastore clusters associated with a given service instance.
2103
2104    service_instance
2105        The Service Instance Object from which to obtain datastore clusters.
2106    """
2107    return list_objects(service_instance, vim.StoragePod)
2108
2109
2110def list_datastores(service_instance):
2111    """
2112    Returns a list of datastores associated with a given service instance.
2113
2114    service_instance
2115        The Service Instance Object from which to obtain datastores.
2116    """
2117    return list_objects(service_instance, vim.Datastore)
2118
2119
2120def get_datastore_files(
2121    service_instance, directory, datastores, container_object, browser_spec
2122):
2123    """
2124    Get the files with a given browser specification from the datastore.
2125
2126    service_instance
2127        The Service Instance Object from which to obtain datastores.
2128
2129    directory
2130        The name of the directory where we would like to search
2131
2132    datastores
2133        Name of the datastores
2134
2135    container_object
2136        The base object for searches
2137
2138    browser_spec
2139        BrowserSpec object which defines the search criteria
2140
2141    return
2142        list of vim.host.DatastoreBrowser.SearchResults objects
2143    """
2144
2145    files = []
2146    datastore_objects = get_datastores(
2147        service_instance, container_object, datastore_names=datastores
2148    )
2149    for datobj in datastore_objects:
2150        try:
2151            task = datobj.browser.SearchDatastore_Task(
2152                datastorePath="[{}] {}".format(datobj.name, directory),
2153                searchSpec=browser_spec,
2154            )
2155        except vim.fault.NoPermission as exc:
2156            log.exception(exc)
2157            raise salt.exceptions.VMwareApiError(
2158                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2159            )
2160        except vim.fault.VimFault as exc:
2161            log.exception(exc)
2162            raise salt.exceptions.VMwareApiError(exc.msg)
2163        except vmodl.RuntimeFault as exc:
2164            log.exception(exc)
2165            raise salt.exceptions.VMwareRuntimeError(exc.msg)
2166        try:
2167            files.append(
2168                salt.utils.vmware.wait_for_task(
2169                    task, directory, "query virtual machine files"
2170                )
2171            )
2172        except salt.exceptions.VMwareFileNotFoundError:
2173            pass
2174    return files
2175
2176
2177def get_datastores(
2178    service_instance,
2179    reference,
2180    datastore_names=None,
2181    backing_disk_ids=None,
2182    get_all_datastores=False,
2183):
2184    """
2185    Returns a list of vim.Datastore objects representing the datastores visible
2186    from a VMware object, filtered by their names, or the backing disk
2187    cannonical name or scsi_addresses
2188
2189    service_instance
2190        The Service Instance Object from which to obtain datastores.
2191
2192    reference
2193        The VMware object from which the datastores are visible.
2194
2195    datastore_names
2196        The list of datastore names to be retrieved. Default value is None.
2197
2198    backing_disk_ids
2199        The list of canonical names of the disks backing the datastores
2200        to be retrieved. Only supported if reference is a vim.HostSystem.
2201        Default value is None
2202
2203    get_all_datastores
2204        Specifies whether to retrieve all disks in the host.
2205        Default value is False.
2206    """
2207    obj_name = get_managed_object_name(reference)
2208    if get_all_datastores:
2209        log.trace("Retrieving all datastores visible to '%s'", obj_name)
2210    else:
2211        log.trace(
2212            "Retrieving datastores visible to '%s': names = (%s); "
2213            "backing disk ids = (%s)",
2214            obj_name,
2215            datastore_names,
2216            backing_disk_ids,
2217        )
2218        if backing_disk_ids and not isinstance(reference, vim.HostSystem):
2219
2220            raise salt.exceptions.ArgumentValueError(
2221                "Unsupported reference type '{}' when backing disk filter "
2222                "is set".format(reference.__class__.__name__)
2223            )
2224    if (not get_all_datastores) and backing_disk_ids:
2225        # At this point we know the reference is a vim.HostSystem
2226        log.trace("Filtering datastores with backing disk ids: %s", backing_disk_ids)
2227        storage_system = get_storage_system(service_instance, reference, obj_name)
2228        props = salt.utils.vmware.get_properties_of_managed_object(
2229            storage_system, ["fileSystemVolumeInfo.mountInfo"]
2230        )
2231        mount_infos = props.get("fileSystemVolumeInfo.mountInfo", [])
2232        disk_datastores = []
2233        # Non vmfs volumes aren't backed by a disk
2234        for vol in [
2235            i.volume for i in mount_infos if isinstance(i.volume, vim.HostVmfsVolume)
2236        ]:
2237
2238            if not [e for e in vol.extent if e.diskName in backing_disk_ids]:
2239                # Skip volume if it doesn't contain an extent with a
2240                # canonical name of interest
2241                continue
2242            log.trace(
2243                "Found datastore '%s' for disk id(s) '%s'",
2244                vol.name,
2245                [e.diskName for e in vol.extent],
2246            )
2247            disk_datastores.append(vol.name)
2248        log.trace("Datastore found for disk filter: %s", disk_datastores)
2249        if datastore_names:
2250            datastore_names.extend(disk_datastores)
2251        else:
2252            datastore_names = disk_datastores
2253
2254    if (not get_all_datastores) and (not datastore_names):
2255        log.trace(
2256            "No datastore to be filtered after retrieving the datastores "
2257            "backed by the disk id(s) '%s'",
2258            backing_disk_ids,
2259        )
2260        return []
2261
2262    log.trace("datastore_names = %s", datastore_names)
2263
2264    # Use the default traversal spec
2265    if isinstance(reference, vim.HostSystem):
2266        # Create a different traversal spec for hosts because it looks like the
2267        # default doesn't retrieve the datastores
2268        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2269            name="host_datastore_traversal",
2270            path="datastore",
2271            skip=False,
2272            type=vim.HostSystem,
2273        )
2274    elif isinstance(reference, vim.ClusterComputeResource):
2275        # Traversal spec for clusters
2276        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2277            name="cluster_datastore_traversal",
2278            path="datastore",
2279            skip=False,
2280            type=vim.ClusterComputeResource,
2281        )
2282    elif isinstance(reference, vim.Datacenter):
2283        # Traversal spec for datacenter
2284        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2285            name="datacenter_datastore_traversal",
2286            path="datastore",
2287            skip=False,
2288            type=vim.Datacenter,
2289        )
2290    elif isinstance(reference, vim.StoragePod):
2291        # Traversal spec for datastore clusters
2292        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2293            name="datastore_cluster_traversal",
2294            path="childEntity",
2295            skip=False,
2296            type=vim.StoragePod,
2297        )
2298    elif (
2299        isinstance(reference, vim.Folder)
2300        and get_managed_object_name(reference) == "Datacenters"
2301    ):
2302        # Traversal of root folder (doesn't support multiple levels of Folders)
2303        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2304            path="childEntity",
2305            selectSet=[
2306                vmodl.query.PropertyCollector.TraversalSpec(
2307                    path="datastore", skip=False, type=vim.Datacenter
2308                )
2309            ],
2310            skip=False,
2311            type=vim.Folder,
2312        )
2313    else:
2314        raise salt.exceptions.ArgumentValueError(
2315            "Unsupported reference type '{}'".format(reference.__class__.__name__)
2316        )
2317
2318    items = get_mors_with_properties(
2319        service_instance,
2320        object_type=vim.Datastore,
2321        property_list=["name"],
2322        container_ref=reference,
2323        traversal_spec=traversal_spec,
2324    )
2325    log.trace("Retrieved %s datastores", len(items))
2326    items = [i for i in items if get_all_datastores or i["name"] in datastore_names]
2327    log.trace("Filtered datastores: %s", [i["name"] for i in items])
2328    return [i["object"] for i in items]
2329
2330
2331def rename_datastore(datastore_ref, new_datastore_name):
2332    """
2333    Renames a datastore
2334
2335    datastore_ref
2336        vim.Datastore reference to the datastore object to be changed
2337
2338    new_datastore_name
2339        New datastore name
2340    """
2341    ds_name = get_managed_object_name(datastore_ref)
2342    log.trace("Renaming datastore '%s' to '%s'", ds_name, new_datastore_name)
2343    try:
2344        datastore_ref.RenameDatastore(new_datastore_name)
2345    except vim.fault.NoPermission as exc:
2346        log.exception(exc)
2347        raise salt.exceptions.VMwareApiError(
2348            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2349        )
2350    except vim.fault.VimFault as exc:
2351        log.exception(exc)
2352        raise salt.exceptions.VMwareApiError(exc.msg)
2353    except vmodl.RuntimeFault as exc:
2354        log.exception(exc)
2355        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2356
2357
2358def get_storage_system(service_instance, host_ref, hostname=None):
2359    """
2360    Returns a host's storage system
2361    """
2362
2363    if not hostname:
2364        hostname = get_managed_object_name(host_ref)
2365
2366    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2367        path="configManager.storageSystem", type=vim.HostSystem, skip=False
2368    )
2369    objs = get_mors_with_properties(
2370        service_instance,
2371        vim.HostStorageSystem,
2372        property_list=["systemFile"],
2373        container_ref=host_ref,
2374        traversal_spec=traversal_spec,
2375    )
2376    if not objs:
2377        raise salt.exceptions.VMwareObjectRetrievalError(
2378            "Host's '{}' storage system was not retrieved".format(hostname)
2379        )
2380    log.trace("[%s] Retrieved storage system", hostname)
2381    return objs[0]["object"]
2382
2383
2384def _get_partition_info(storage_system, device_path):
2385    """
2386    Returns partition information for a device path, of type
2387    vim.HostDiskPartitionInfo
2388    """
2389    try:
2390        partition_infos = storage_system.RetrieveDiskPartitionInfo(
2391            devicePath=[device_path]
2392        )
2393    except vim.fault.NoPermission as exc:
2394        log.exception(exc)
2395        raise salt.exceptions.VMwareApiError(
2396            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2397        )
2398    except vim.fault.VimFault as exc:
2399        log.exception(exc)
2400        raise salt.exceptions.VMwareApiError(exc.msg)
2401    except vmodl.RuntimeFault as exc:
2402        log.exception(exc)
2403        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2404    log.trace("partition_info = %s", partition_infos[0])
2405    return partition_infos[0]
2406
2407
2408def _get_new_computed_partition_spec(storage_system, device_path, partition_info):
2409    """
2410    Computes the new disk partition info when adding a new vmfs partition that
2411    uses up the remainder of the disk; returns a tuple
2412    (new_partition_number, vim.HostDiskPartitionSpec
2413    """
2414    log.trace(
2415        "Adding a partition at the end of the disk and getting the new "
2416        "computed partition spec"
2417    )
2418    # TODO implement support for multiple partitions
2419    # We support adding a partition add the end of the disk with partitions
2420    free_partitions = [p for p in partition_info.layout.partition if p.type == "none"]
2421    if not free_partitions:
2422        raise salt.exceptions.VMwareObjectNotFoundError(
2423            "Free partition was not found on device '{}'".format(
2424                partition_info.deviceName
2425            )
2426        )
2427    free_partition = free_partitions[0]
2428
2429    # Create a layout object that copies the existing one
2430    layout = vim.HostDiskPartitionLayout(
2431        total=partition_info.layout.total, partition=partition_info.layout.partition
2432    )
2433    # Create a partition with the free space on the disk
2434    # Change the free partition type to vmfs
2435    free_partition.type = "vmfs"
2436    try:
2437        computed_partition_info = storage_system.ComputeDiskPartitionInfo(
2438            devicePath=device_path,
2439            partitionFormat=vim.HostDiskPartitionInfoPartitionFormat.gpt,
2440            layout=layout,
2441        )
2442    except vim.fault.NoPermission as exc:
2443        log.exception(exc)
2444        raise salt.exceptions.VMwareApiError(
2445            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2446        )
2447    except vim.fault.VimFault as exc:
2448        log.exception(exc)
2449        raise salt.exceptions.VMwareApiError(exc.msg)
2450    except vmodl.RuntimeFault as exc:
2451        log.exception(exc)
2452        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2453    log.trace("computed partition info = {0}", computed_partition_info)
2454    log.trace("Retrieving new partition number")
2455    partition_numbers = [
2456        p.partition
2457        for p in computed_partition_info.layout.partition
2458        if (
2459            p.start.block == free_partition.start.block
2460            or
2461            # XXX If the entire disk is free (i.e. the free
2462            # disk partition starts at block 0) the newily
2463            # created partition is created from block 1
2464            (free_partition.start.block == 0 and p.start.block == 1)
2465        )
2466        and p.end.block == free_partition.end.block
2467        and p.type == "vmfs"
2468    ]
2469    if not partition_numbers:
2470        raise salt.exceptions.VMwareNotFoundError(
2471            "New partition was not found in computed partitions of device '{}'".format(
2472                partition_info.deviceName
2473            )
2474        )
2475    log.trace("new partition number = %s", partition_numbers[0])
2476    return (partition_numbers[0], computed_partition_info.spec)
2477
2478
2479def create_vmfs_datastore(
2480    host_ref, datastore_name, disk_ref, vmfs_major_version, storage_system=None
2481):
2482    """
2483    Creates a VMFS datastore from a disk_id
2484
2485    host_ref
2486        vim.HostSystem object referencing a host to create the datastore on
2487
2488    datastore_name
2489        Name of the datastore
2490
2491    disk_ref
2492        vim.HostScsiDislk on which the datastore is created
2493
2494    vmfs_major_version
2495        VMFS major version to use
2496    """
2497    # TODO Support variable sized partitions
2498    hostname = get_managed_object_name(host_ref)
2499    disk_id = disk_ref.canonicalName
2500    log.debug(
2501        "Creating datastore '%s' on host '%s', scsi disk '%s', vmfs v%s",
2502        datastore_name,
2503        hostname,
2504        disk_id,
2505        vmfs_major_version,
2506    )
2507    if not storage_system:
2508        si = get_service_instance_from_managed_object(host_ref, name=hostname)
2509        storage_system = get_storage_system(si, host_ref, hostname)
2510
2511    target_disk = disk_ref
2512    partition_info = _get_partition_info(storage_system, target_disk.devicePath)
2513    log.trace("partition_info = %s", partition_info)
2514    new_partition_number, partition_spec = _get_new_computed_partition_spec(
2515        storage_system, target_disk.devicePath, partition_info
2516    )
2517    spec = vim.VmfsDatastoreCreateSpec(
2518        vmfs=vim.HostVmfsSpec(
2519            majorVersion=vmfs_major_version,
2520            volumeName=datastore_name,
2521            extent=vim.HostScsiDiskPartition(
2522                diskName=disk_id, partition=new_partition_number
2523            ),
2524        ),
2525        diskUuid=target_disk.uuid,
2526        partition=partition_spec,
2527    )
2528    try:
2529        ds_ref = host_ref.configManager.datastoreSystem.CreateVmfsDatastore(spec)
2530    except vim.fault.NoPermission as exc:
2531        log.exception(exc)
2532        raise salt.exceptions.VMwareApiError(
2533            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2534        )
2535    except vim.fault.VimFault as exc:
2536        log.exception(exc)
2537        raise salt.exceptions.VMwareApiError(exc.msg)
2538    except vmodl.RuntimeFault as exc:
2539        log.exception(exc)
2540        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2541    log.debug("Created datastore '%s' on host '%s'", datastore_name, hostname)
2542    return ds_ref
2543
2544
2545def get_host_datastore_system(host_ref, hostname=None):
2546    """
2547    Returns a host's datastore system
2548
2549    host_ref
2550        Reference to the ESXi host
2551
2552    hostname
2553        Name of the host. This argument is optional.
2554    """
2555
2556    if not hostname:
2557        hostname = get_managed_object_name(host_ref)
2558    service_instance = get_service_instance_from_managed_object(host_ref)
2559    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2560        path="configManager.datastoreSystem", type=vim.HostSystem, skip=False
2561    )
2562    objs = get_mors_with_properties(
2563        service_instance,
2564        vim.HostDatastoreSystem,
2565        property_list=["datastore"],
2566        container_ref=host_ref,
2567        traversal_spec=traversal_spec,
2568    )
2569    if not objs:
2570        raise salt.exceptions.VMwareObjectRetrievalError(
2571            "Host's '{}' datastore system was not retrieved".format(hostname)
2572        )
2573    log.trace("[%s] Retrieved datastore system", hostname)
2574    return objs[0]["object"]
2575
2576
2577def remove_datastore(service_instance, datastore_ref):
2578    """
2579    Creates a VMFS datastore from a disk_id
2580
2581    service_instance
2582        The Service Instance Object containing the datastore
2583
2584    datastore_ref
2585        The reference to the datastore to remove
2586    """
2587    ds_props = get_properties_of_managed_object(datastore_ref, ["host", "info", "name"])
2588    ds_name = ds_props["name"]
2589    log.debug("Removing datastore '%s'", ds_name)
2590    ds_hosts = ds_props.get("host")
2591    if not ds_hosts:
2592        raise salt.exceptions.VMwareApiError(
2593            "Datastore '{}' can't be removed. No attached hosts found".format(ds_name)
2594        )
2595    hostname = get_managed_object_name(ds_hosts[0].key)
2596    host_ds_system = get_host_datastore_system(ds_hosts[0].key, hostname=hostname)
2597    try:
2598        host_ds_system.RemoveDatastore(datastore_ref)
2599    except vim.fault.NoPermission as exc:
2600        log.exception(exc)
2601        raise salt.exceptions.VMwareApiError(
2602            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2603        )
2604    except vim.fault.VimFault as exc:
2605        log.exception(exc)
2606        raise salt.exceptions.VMwareApiError(exc.msg)
2607    except vmodl.RuntimeFault as exc:
2608        log.exception(exc)
2609        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2610    log.trace("[%s] Removed datastore '%s'", hostname, ds_name)
2611
2612
2613def get_hosts(
2614    service_instance,
2615    datacenter_name=None,
2616    host_names=None,
2617    cluster_name=None,
2618    get_all_hosts=False,
2619):
2620    """
2621    Returns a list of vim.HostSystem objects representing ESXi hosts
2622    in a vcenter filtered by their names and/or datacenter, cluster membership.
2623
2624    service_instance
2625        The Service Instance Object from which to obtain the hosts.
2626
2627    datacenter_name
2628        The datacenter name. Default is None.
2629
2630    host_names
2631        The host_names to be retrieved. Default is None.
2632
2633    cluster_name
2634        The cluster name - used to restrict the hosts retrieved. Only used if
2635        the datacenter is set.  This argument is optional.
2636
2637    get_all_hosts
2638        Specifies whether to retrieve all hosts in the container.
2639        Default value is False.
2640    """
2641    properties = ["name"]
2642    if cluster_name and not datacenter_name:
2643        raise salt.exceptions.ArgumentValueError(
2644            "Must specify the datacenter when specifying the cluster"
2645        )
2646    if not host_names:
2647        host_names = []
2648    if not datacenter_name:
2649        # Assume the root folder is the starting point
2650        start_point = get_root_folder(service_instance)
2651    else:
2652        start_point = get_datacenter(service_instance, datacenter_name)
2653        if cluster_name:
2654            # Retrieval to test if cluster exists. Cluster existence only makes
2655            # sense if the datacenter has been specified
2656            properties.append("parent")
2657
2658    # Search for the objects
2659    hosts = get_mors_with_properties(
2660        service_instance,
2661        vim.HostSystem,
2662        container_ref=start_point,
2663        property_list=properties,
2664    )
2665    log.trace("Retrieved hosts: %s", [h["name"] for h in hosts])
2666    filtered_hosts = []
2667    for h in hosts:
2668        # Complex conditions checking if a host should be added to the
2669        # filtered list (either due to its name and/or cluster membership)
2670
2671        if cluster_name:
2672            if not isinstance(h["parent"], vim.ClusterComputeResource):
2673                continue
2674            parent_name = get_managed_object_name(h["parent"])
2675            if parent_name != cluster_name:
2676                continue
2677
2678        if get_all_hosts:
2679            filtered_hosts.append(h["object"])
2680            continue
2681
2682        if h["name"] in host_names:
2683            filtered_hosts.append(h["object"])
2684    return filtered_hosts
2685
2686
2687def _get_scsi_address_to_lun_key_map(
2688    service_instance, host_ref, storage_system=None, hostname=None
2689):
2690    """
2691    Returns a map between the scsi addresses and the keys of all luns on an ESXi
2692    host.
2693        map[<scsi_address>] = <lun key>
2694
2695    service_instance
2696        The Service Instance Object from which to obtain the hosts
2697
2698    host_ref
2699        The vim.HostSystem object representing the host that contains the
2700        requested disks.
2701
2702    storage_system
2703        The host's storage system. Default is None.
2704
2705    hostname
2706        Name of the host. Default is None.
2707    """
2708    if not hostname:
2709        hostname = get_managed_object_name(host_ref)
2710    if not storage_system:
2711        storage_system = get_storage_system(service_instance, host_ref, hostname)
2712    try:
2713        device_info = storage_system.storageDeviceInfo
2714    except vim.fault.NoPermission as exc:
2715        log.exception(exc)
2716        raise salt.exceptions.VMwareApiError(
2717            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2718        )
2719    except vim.fault.VimFault as exc:
2720        log.exception(exc)
2721        raise salt.exceptions.VMwareApiError(exc.msg)
2722    except vmodl.RuntimeFault as exc:
2723        log.exception(exc)
2724        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2725    if not device_info:
2726        raise salt.exceptions.VMwareObjectRetrievalError(
2727            "Host's '{}' storage device info was not retrieved".format(hostname)
2728        )
2729    multipath_info = device_info.multipathInfo
2730    if not multipath_info:
2731        raise salt.exceptions.VMwareObjectRetrievalError(
2732            "Host's '{}' multipath info was not retrieved".format(hostname)
2733        )
2734    if multipath_info.lun is None:
2735        raise salt.exceptions.VMwareObjectRetrievalError(
2736            "No luns were retrieved from host '{}'".format(hostname)
2737        )
2738    lun_key_by_scsi_addr = {}
2739    for l in multipath_info.lun:
2740        # The vmware scsi_address may have multiple comma separated values
2741        # The first one is the actual scsi address
2742        lun_key_by_scsi_addr.update({p.name.split(",")[0]: l.lun for p in l.path})
2743    log.trace(
2744        "Scsi address to lun id map on host '%s': %s", hostname, lun_key_by_scsi_addr
2745    )
2746    return lun_key_by_scsi_addr
2747
2748
2749def get_all_luns(host_ref, storage_system=None, hostname=None):
2750    """
2751    Returns a list of all vim.HostScsiDisk objects in a disk
2752
2753    host_ref
2754        The vim.HostSystem object representing the host that contains the
2755        requested disks.
2756
2757    storage_system
2758        The host's storage system. Default is None.
2759
2760    hostname
2761        Name of the host. This argument is optional.
2762    """
2763    if not hostname:
2764        hostname = get_managed_object_name(host_ref)
2765    if not storage_system:
2766        si = get_service_instance_from_managed_object(host_ref, name=hostname)
2767        storage_system = get_storage_system(si, host_ref, hostname)
2768        if not storage_system:
2769            raise salt.exceptions.VMwareObjectRetrievalError(
2770                "Host's '{}' storage system was not retrieved".format(hostname)
2771            )
2772    try:
2773        device_info = storage_system.storageDeviceInfo
2774    except vim.fault.NoPermission as exc:
2775        log.exception(exc)
2776        raise salt.exceptions.VMwareApiError(
2777            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
2778        )
2779    except vim.fault.VimFault as exc:
2780        log.exception(exc)
2781        raise salt.exceptions.VMwareApiError(exc.msg)
2782    except vmodl.RuntimeFault as exc:
2783        log.exception(exc)
2784        raise salt.exceptions.VMwareRuntimeError(exc.msg)
2785    if not device_info:
2786        raise salt.exceptions.VMwareObjectRetrievalError(
2787            "Host's '{}' storage device info was not retrieved".format(hostname)
2788        )
2789
2790    scsi_luns = device_info.scsiLun
2791    if scsi_luns:
2792        log.trace(
2793            "Retrieved scsi luns in host '%s': %s",
2794            hostname,
2795            [l.canonicalName for l in scsi_luns],
2796        )
2797        return scsi_luns
2798    log.trace("Retrieved no scsi_luns in host '%s'", hostname)
2799    return []
2800
2801
2802def get_scsi_address_to_lun_map(host_ref, storage_system=None, hostname=None):
2803    """
2804    Returns a map of all vim.ScsiLun objects on a ESXi host keyed by their
2805    scsi address
2806
2807    host_ref
2808        The vim.HostSystem object representing the host that contains the
2809        requested disks.
2810
2811    storage_system
2812        The host's storage system. Default is None.
2813
2814    hostname
2815        Name of the host. This argument is optional.
2816    """
2817    if not hostname:
2818        hostname = get_managed_object_name(host_ref)
2819    si = get_service_instance_from_managed_object(host_ref, name=hostname)
2820    if not storage_system:
2821        storage_system = get_storage_system(si, host_ref, hostname)
2822    lun_ids_to_scsi_addr_map = _get_scsi_address_to_lun_key_map(
2823        si, host_ref, storage_system, hostname
2824    )
2825    luns_to_key_map = {
2826        d.key: d for d in get_all_luns(host_ref, storage_system, hostname)
2827    }
2828    return {
2829        scsi_addr: luns_to_key_map[lun_key]
2830        for scsi_addr, lun_key in lun_ids_to_scsi_addr_map.items()
2831    }
2832
2833
2834def get_disks(host_ref, disk_ids=None, scsi_addresses=None, get_all_disks=False):
2835    """
2836    Returns a list of vim.HostScsiDisk objects representing disks
2837    in a ESXi host, filtered by their cannonical names and scsi_addresses
2838
2839    host_ref
2840        The vim.HostSystem object representing the host that contains the
2841        requested disks.
2842
2843    disk_ids
2844        The list of canonical names of the disks to be retrieved. Default value
2845        is None
2846
2847    scsi_addresses
2848        The list of scsi addresses of the disks to be retrieved. Default value
2849        is None
2850
2851    get_all_disks
2852        Specifies whether to retrieve all disks in the host.
2853        Default value is False.
2854    """
2855    hostname = get_managed_object_name(host_ref)
2856    if get_all_disks:
2857        log.trace("Retrieving all disks in host '%s'", hostname)
2858    else:
2859        log.trace(
2860            "Retrieving disks in host '%s': ids = (%s); scsi addresses = (%s)",
2861            hostname,
2862            disk_ids,
2863            scsi_addresses,
2864        )
2865        if not (disk_ids or scsi_addresses):
2866            return []
2867    si = get_service_instance_from_managed_object(host_ref, name=hostname)
2868    storage_system = get_storage_system(si, host_ref, hostname)
2869    disk_keys = []
2870    if scsi_addresses:
2871        # convert the scsi addresses to disk keys
2872        lun_key_by_scsi_addr = _get_scsi_address_to_lun_key_map(
2873            si, host_ref, storage_system, hostname
2874        )
2875        disk_keys = [
2876            key
2877            for scsi_addr, key in lun_key_by_scsi_addr.items()
2878            if scsi_addr in scsi_addresses
2879        ]
2880        log.trace("disk_keys based on scsi_addresses = %s", disk_keys)
2881
2882    scsi_luns = get_all_luns(host_ref, storage_system)
2883    scsi_disks = [
2884        disk
2885        for disk in scsi_luns
2886        if isinstance(disk, vim.HostScsiDisk)
2887        and (
2888            get_all_disks
2889            or
2890            # Filter by canonical name
2891            (disk_ids and (disk.canonicalName in disk_ids))
2892            or
2893            # Filter by disk keys from scsi addresses
2894            (disk.key in disk_keys)
2895        )
2896    ]
2897    log.trace(
2898        "Retrieved disks in host '%s': %s",
2899        hostname,
2900        [d.canonicalName for d in scsi_disks],
2901    )
2902    return scsi_disks
2903
2904
2905def get_disk_partition_info(host_ref, disk_id, storage_system=None):
2906    """
2907    Returns all partitions on a disk
2908
2909    host_ref
2910        The reference of the ESXi host containing the disk
2911
2912    disk_id
2913        The canonical name of the disk whose partitions are to be removed
2914
2915    storage_system
2916        The ESXi host's storage system. Default is None.
2917    """
2918    hostname = get_managed_object_name(host_ref)
2919    service_instance = get_service_instance_from_managed_object(host_ref)
2920    if not storage_system:
2921        storage_system = get_storage_system(service_instance, host_ref, hostname)
2922
2923    props = get_properties_of_managed_object(
2924        storage_system, ["storageDeviceInfo.scsiLun"]
2925    )
2926    if not props.get("storageDeviceInfo.scsiLun"):
2927        raise salt.exceptions.VMwareObjectRetrievalError(
2928            "No devices were retrieved in host '{}'".format(hostname)
2929        )
2930    log.trace(
2931        "[%s] Retrieved %s devices: %s",
2932        hostname,
2933        len(props["storageDeviceInfo.scsiLun"]),
2934        ", ".join([l.canonicalName for l in props["storageDeviceInfo.scsiLun"]]),
2935    )
2936    disks = [
2937        l
2938        for l in props["storageDeviceInfo.scsiLun"]
2939        if isinstance(l, vim.HostScsiDisk) and l.canonicalName == disk_id
2940    ]
2941    if not disks:
2942        raise salt.exceptions.VMwareObjectRetrievalError(
2943            "Disk '{}' was not found in host '{}'".format(disk_id, hostname)
2944        )
2945    log.trace("[%s] device_path = %s", hostname, disks[0].devicePath)
2946    partition_info = _get_partition_info(storage_system, disks[0].devicePath)
2947    log.trace(
2948        "[%s] Retrieved %s partition(s) on disk '%s'",
2949        hostname,
2950        len(partition_info.spec.partition),
2951        disk_id,
2952    )
2953    return partition_info
2954
2955
2956def erase_disk_partitions(
2957    service_instance, host_ref, disk_id, hostname=None, storage_system=None
2958):
2959    """
2960    Erases all partitions on a disk
2961
2962    in a vcenter filtered by their names and/or datacenter, cluster membership
2963
2964    service_instance
2965        The Service Instance Object from which to obtain all information
2966
2967    host_ref
2968        The reference of the ESXi host containing the disk
2969
2970    disk_id
2971        The canonical name of the disk whose partitions are to be removed
2972
2973    hostname
2974        The ESXi hostname. Default is None.
2975
2976    storage_system
2977        The ESXi host's storage system. Default is None.
2978    """
2979
2980    if not hostname:
2981        hostname = get_managed_object_name(host_ref)
2982    if not storage_system:
2983        storage_system = get_storage_system(service_instance, host_ref, hostname)
2984
2985    traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
2986        path="configManager.storageSystem", type=vim.HostSystem, skip=False
2987    )
2988    results = get_mors_with_properties(
2989        service_instance,
2990        vim.HostStorageSystem,
2991        ["storageDeviceInfo.scsiLun"],
2992        container_ref=host_ref,
2993        traversal_spec=traversal_spec,
2994    )
2995    if not results:
2996        raise salt.exceptions.VMwareObjectRetrievalError(
2997            "Host's '{}' devices were not retrieved".format(hostname)
2998        )
2999    log.trace(
3000        "[%s] Retrieved %s devices: %s",
3001        hostname,
3002        len(results[0].get("storageDeviceInfo.scsiLun", [])),
3003        ", ".join(
3004            [l.canonicalName for l in results[0].get("storageDeviceInfo.scsiLun", [])]
3005        ),
3006    )
3007    disks = [
3008        l
3009        for l in results[0].get("storageDeviceInfo.scsiLun", [])
3010        if isinstance(l, vim.HostScsiDisk) and l.canonicalName == disk_id
3011    ]
3012    if not disks:
3013        raise salt.exceptions.VMwareObjectRetrievalError(
3014            "Disk '{}' was not found in host '{}'".format(disk_id, hostname)
3015        )
3016    log.trace("[%s] device_path = %s", hostname, disks[0].devicePath)
3017    # Erase the partitions by setting an empty partition spec
3018    try:
3019        storage_system.UpdateDiskPartitions(
3020            disks[0].devicePath, vim.HostDiskPartitionSpec()
3021        )
3022    except vim.fault.NoPermission as exc:
3023        log.exception(exc)
3024        raise salt.exceptions.VMwareApiError(
3025            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3026        )
3027    except vim.fault.VimFault as exc:
3028        log.exception(exc)
3029        raise salt.exceptions.VMwareApiError(exc.msg)
3030    except vmodl.RuntimeFault as exc:
3031        log.exception(exc)
3032        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3033    log.trace("[%s] Erased partitions on disk '%s'", hostname, disk_id)
3034
3035
3036def get_diskgroups(host_ref, cache_disk_ids=None, get_all_disk_groups=False):
3037    """
3038    Returns a list of vim.VsanHostDiskMapping objects representing disks
3039    in a ESXi host, filtered by their cannonical names.
3040
3041    host_ref
3042        The vim.HostSystem object representing the host that contains the
3043        requested disks.
3044
3045    cache_disk_ids
3046        The list of cannonical names of the cache disks to be retrieved. The
3047        canonical name of the cache disk is enough to identify the disk group
3048        because it is guaranteed to have one and only one cache disk.
3049        Default is None.
3050
3051    get_all_disk_groups
3052        Specifies whether to retrieve all disks groups in the host.
3053        Default value is False.
3054    """
3055    hostname = get_managed_object_name(host_ref)
3056    if get_all_disk_groups:
3057        log.trace("Retrieving all disk groups on host '%s'", hostname)
3058    else:
3059        log.trace(
3060            "Retrieving disk groups from host '%s', with cache disk ids : (%s)",
3061            hostname,
3062            cache_disk_ids,
3063        )
3064        if not cache_disk_ids:
3065            return []
3066    try:
3067        vsan_host_config = host_ref.config.vsanHostConfig
3068    except vim.fault.NoPermission as exc:
3069        log.exception(exc)
3070        raise salt.exceptions.VMwareApiError(
3071            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3072        )
3073    except vim.fault.VimFault as exc:
3074        log.exception(exc)
3075        raise salt.exceptions.VMwareApiError(exc.msg)
3076    except vmodl.RuntimeFault as exc:
3077        log.exception(exc)
3078        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3079    if not vsan_host_config:
3080        raise salt.exceptions.VMwareObjectRetrievalError(
3081            "No host config found on host '{}'".format(hostname)
3082        )
3083    vsan_storage_info = vsan_host_config.storageInfo
3084    if not vsan_storage_info:
3085        raise salt.exceptions.VMwareObjectRetrievalError(
3086            "No vsan storage info found on host '{}'".format(hostname)
3087        )
3088    vsan_disk_mappings = vsan_storage_info.diskMapping
3089    if not vsan_disk_mappings:
3090        return []
3091    disk_groups = [
3092        dm
3093        for dm in vsan_disk_mappings
3094        if (get_all_disk_groups or (dm.ssd.canonicalName in cache_disk_ids))
3095    ]
3096    log.trace(
3097        "Retrieved disk groups on host '%s', with cache disk ids : %s",
3098        hostname,
3099        [d.ssd.canonicalName for d in disk_groups],
3100    )
3101    return disk_groups
3102
3103
3104def _check_disks_in_diskgroup(disk_group, cache_disk_id, capacity_disk_ids):
3105    """
3106    Checks that the disks in a disk group are as expected and raises
3107    CheckError exceptions if the check fails
3108    """
3109    if not disk_group.ssd.canonicalName == cache_disk_id:
3110        raise salt.exceptions.ArgumentValueError(
3111            "Incorrect diskgroup cache disk; got id: '{}'; expected id: '{}'".format(
3112                disk_group.ssd.canonicalName, cache_disk_id
3113            )
3114        )
3115    non_ssd_disks = [d.canonicalName for d in disk_group.nonSsd]
3116    if sorted(non_ssd_disks) != sorted(capacity_disk_ids):
3117        raise salt.exceptions.ArgumentValueError(
3118            "Incorrect capacity disks; got ids: '{}'; expected ids: '{}'".format(
3119                sorted(non_ssd_disks), sorted(capacity_disk_ids)
3120            )
3121        )
3122    log.trace("Checked disks in diskgroup with cache disk id '%s'", cache_disk_id)
3123    return True
3124
3125
3126# TODO Support host caches on multiple datastores
3127def get_host_cache(host_ref, host_cache_manager=None):
3128    """
3129    Returns a vim.HostScsiDisk if the host cache is configured on the specified
3130    host, other wise returns None
3131
3132    host_ref
3133        The vim.HostSystem object representing the host that contains the
3134        requested disks.
3135
3136    host_cache_manager
3137        The vim.HostCacheConfigurationManager object representing the cache
3138        configuration manager on the specified host. Default is None. If None,
3139        it will be retrieved in the method
3140    """
3141    hostname = get_managed_object_name(host_ref)
3142    service_instance = get_service_instance_from_managed_object(host_ref)
3143    log.trace("Retrieving the host cache on host '%s'", hostname)
3144    if not host_cache_manager:
3145        traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
3146            path="configManager.cacheConfigurationManager",
3147            type=vim.HostSystem,
3148            skip=False,
3149        )
3150        results = get_mors_with_properties(
3151            service_instance,
3152            vim.HostCacheConfigurationManager,
3153            ["cacheConfigurationInfo"],
3154            container_ref=host_ref,
3155            traversal_spec=traversal_spec,
3156        )
3157        if not results or not results[0].get("cacheConfigurationInfo"):
3158            log.trace("Host '%s' has no host cache", hostname)
3159            return None
3160        return results[0]["cacheConfigurationInfo"][0]
3161    else:
3162        results = get_properties_of_managed_object(
3163            host_cache_manager, ["cacheConfigurationInfo"]
3164        )
3165        if not results:
3166            log.trace("Host '%s' has no host cache", hostname)
3167            return None
3168        return results["cacheConfigurationInfo"][0]
3169
3170
3171# TODO Support host caches on multiple datastores
3172def configure_host_cache(
3173    host_ref, datastore_ref, swap_size_MiB, host_cache_manager=None
3174):
3175    """
3176    Configures the host cahe of the specified host
3177
3178    host_ref
3179        The vim.HostSystem object representing the host that contains the
3180        requested disks.
3181
3182    datastore_ref
3183        The vim.Datastore opject representing the datastore the host cache will
3184        be configured on.
3185
3186    swap_size_MiB
3187        The size in Mibibytes of the swap.
3188
3189    host_cache_manager
3190        The vim.HostCacheConfigurationManager object representing the cache
3191        configuration manager on the specified host. Default is None. If None,
3192        it will be retrieved in the method
3193    """
3194    hostname = get_managed_object_name(host_ref)
3195    if not host_cache_manager:
3196        props = get_properties_of_managed_object(
3197            host_ref, ["configManager.cacheConfigurationManager"]
3198        )
3199        if not props.get("configManager.cacheConfigurationManager"):
3200            raise salt.exceptions.VMwareObjectRetrievalError(
3201                "Host '{}' has no host cache".format(hostname)
3202            )
3203        host_cache_manager = props["configManager.cacheConfigurationManager"]
3204    log.trace(
3205        "Configuring the host cache on host '%s', datastore '%s', swap size=%s MiB",
3206        hostname,
3207        datastore_ref.name,
3208        swap_size_MiB,
3209    )
3210
3211    spec = vim.HostCacheConfigurationSpec(
3212        datastore=datastore_ref, swapSize=swap_size_MiB
3213    )
3214    log.trace("host_cache_spec=%s", spec)
3215    try:
3216        task = host_cache_manager.ConfigureHostCache_Task(spec)
3217    except vim.fault.NoPermission as exc:
3218        log.exception(exc)
3219        raise salt.exceptions.VMwareApiError(
3220            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3221        )
3222    except vim.fault.VimFault as exc:
3223        log.exception(exc)
3224        raise salt.exceptions.VMwareApiError(exc.msg)
3225    except vmodl.RuntimeFault as exc:
3226        log.exception(exc)
3227        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3228    wait_for_task(task, hostname, "HostCacheConfigurationTask")
3229    log.trace("Configured host cache on host '%s'", hostname)
3230    return True
3231
3232
3233def list_hosts(service_instance):
3234    """
3235    Returns a list of hosts associated with a given service instance.
3236
3237    service_instance
3238        The Service Instance Object from which to obtain hosts.
3239    """
3240    return list_objects(service_instance, vim.HostSystem)
3241
3242
3243def get_resource_pools(
3244    service_instance,
3245    resource_pool_names,
3246    datacenter_name=None,
3247    get_all_resource_pools=False,
3248):
3249    """
3250    Retrieves resource pool objects
3251
3252    service_instance
3253        The service instance object to query the vCenter
3254
3255    resource_pool_names
3256        Resource pool names
3257
3258    datacenter_name
3259        Name of the datacenter where the resource pool is available
3260
3261    get_all_resource_pools
3262        Boolean
3263
3264    return
3265        Resourcepool managed object reference
3266    """
3267
3268    properties = ["name"]
3269    if not resource_pool_names:
3270        resource_pool_names = []
3271    if datacenter_name:
3272        container_ref = get_datacenter(service_instance, datacenter_name)
3273    else:
3274        container_ref = get_root_folder(service_instance)
3275
3276    resource_pools = get_mors_with_properties(
3277        service_instance,
3278        vim.ResourcePool,
3279        container_ref=container_ref,
3280        property_list=properties,
3281    )
3282
3283    selected_pools = []
3284    for pool in resource_pools:
3285        if get_all_resource_pools or (pool["name"] in resource_pool_names):
3286            selected_pools.append(pool["object"])
3287    if not selected_pools:
3288        raise salt.exceptions.VMwareObjectRetrievalError(
3289            "The resource pools with properties "
3290            "names={} get_all={} could not be found".format(
3291                selected_pools, get_all_resource_pools
3292            )
3293        )
3294
3295    return selected_pools
3296
3297
3298def list_resourcepools(service_instance):
3299    """
3300    Returns a list of resource pools associated with a given service instance.
3301
3302    service_instance
3303        The Service Instance Object from which to obtain resource pools.
3304    """
3305    return list_objects(service_instance, vim.ResourcePool)
3306
3307
3308def list_networks(service_instance):
3309    """
3310    Returns a list of networks associated with a given service instance.
3311
3312    service_instance
3313        The Service Instance Object from which to obtain networks.
3314    """
3315    return list_objects(service_instance, vim.Network)
3316
3317
3318def list_vms(service_instance):
3319    """
3320    Returns a list of VMs associated with a given service instance.
3321
3322    service_instance
3323        The Service Instance Object from which to obtain VMs.
3324    """
3325    return list_objects(service_instance, vim.VirtualMachine)
3326
3327
3328def list_folders(service_instance):
3329    """
3330    Returns a list of folders associated with a given service instance.
3331
3332    service_instance
3333        The Service Instance Object from which to obtain folders.
3334    """
3335    return list_objects(service_instance, vim.Folder)
3336
3337
3338def list_dvs(service_instance):
3339    """
3340    Returns a list of distributed virtual switches associated with a given service instance.
3341
3342    service_instance
3343        The Service Instance Object from which to obtain distributed virtual switches.
3344    """
3345    return list_objects(service_instance, vim.DistributedVirtualSwitch)
3346
3347
3348def list_vapps(service_instance):
3349    """
3350    Returns a list of vApps associated with a given service instance.
3351
3352    service_instance
3353        The Service Instance Object from which to obtain vApps.
3354    """
3355    return list_objects(service_instance, vim.VirtualApp)
3356
3357
3358def list_portgroups(service_instance):
3359    """
3360    Returns a list of distributed virtual portgroups associated with a given service instance.
3361
3362    service_instance
3363        The Service Instance Object from which to obtain distributed virtual switches.
3364    """
3365    return list_objects(service_instance, vim.dvs.DistributedVirtualPortgroup)
3366
3367
3368def wait_for_task(task, instance_name, task_type, sleep_seconds=1, log_level="debug"):
3369    """
3370    Waits for a task to be completed.
3371
3372    task
3373        The task to wait for.
3374
3375    instance_name
3376        The name of the ESXi host, vCenter Server, or Virtual Machine that
3377        the task is being run on.
3378
3379    task_type
3380        The type of task being performed. Useful information for debugging purposes.
3381
3382    sleep_seconds
3383        The number of seconds to wait before querying the task again.
3384        Defaults to ``1`` second.
3385
3386    log_level
3387        The level at which to log task information. Default is ``debug``,
3388        but ``info`` is also supported.
3389    """
3390    time_counter = 0
3391    start_time = time.time()
3392    log.trace("task = %s, task_type = %s", task, task.__class__.__name__)
3393    try:
3394        task_info = task.info
3395    except vim.fault.NoPermission as exc:
3396        log.exception(exc)
3397        raise salt.exceptions.VMwareApiError(
3398            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3399        )
3400    except vim.fault.FileNotFound as exc:
3401        log.exception(exc)
3402        raise salt.exceptions.VMwareFileNotFoundError(exc.msg)
3403    except vim.fault.VimFault as exc:
3404        log.exception(exc)
3405        raise salt.exceptions.VMwareApiError(exc.msg)
3406    except vmodl.RuntimeFault as exc:
3407        log.exception(exc)
3408        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3409    while task_info.state == "running" or task_info.state == "queued":
3410        if time_counter % sleep_seconds == 0:
3411            msg = "[ {} ] Waiting for {} task to finish [{} s]".format(
3412                instance_name, task_type, time_counter
3413            )
3414            if log_level == "info":
3415                log.info(msg)
3416            else:
3417                log.debug(msg)
3418        time.sleep(1.0 - ((time.time() - start_time) % 1.0))
3419        time_counter += 1
3420        try:
3421            task_info = task.info
3422        except vim.fault.NoPermission as exc:
3423            log.exception(exc)
3424            raise salt.exceptions.VMwareApiError(
3425                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3426            )
3427        except vim.fault.FileNotFound as exc:
3428            log.exception(exc)
3429            raise salt.exceptions.VMwareFileNotFoundError(exc.msg)
3430        except vim.fault.VimFault as exc:
3431            log.exception(exc)
3432            raise salt.exceptions.VMwareApiError(exc.msg)
3433        except vmodl.RuntimeFault as exc:
3434            log.exception(exc)
3435            raise salt.exceptions.VMwareRuntimeError(exc.msg)
3436    if task_info.state == "success":
3437        msg = "[ {} ] Successfully completed {} task in {} seconds".format(
3438            instance_name, task_type, time_counter
3439        )
3440        if log_level == "info":
3441            log.info(msg)
3442        else:
3443            log.debug(msg)
3444        # task is in a successful state
3445        return task_info.result
3446    else:
3447        # task is in an error state
3448        try:
3449            raise task_info.error
3450        except vim.fault.NoPermission as exc:
3451            log.exception(exc)
3452            raise salt.exceptions.VMwareApiError(
3453                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3454            )
3455        except vim.fault.FileNotFound as exc:
3456            log.exception(exc)
3457            raise salt.exceptions.VMwareFileNotFoundError(exc.msg)
3458        except vim.fault.VimFault as exc:
3459            log.exception(exc)
3460            raise salt.exceptions.VMwareApiError(exc.msg)
3461        except vmodl.fault.SystemError as exc:
3462            log.exception(exc)
3463            raise salt.exceptions.VMwareSystemError(exc.msg)
3464        except vmodl.fault.InvalidArgument as exc:
3465            log.exception(exc)
3466            exc_message = exc.msg
3467            if exc.faultMessage:
3468                exc_message = "{} ({})".format(exc_message, exc.faultMessage[0].message)
3469            raise salt.exceptions.VMwareApiError(exc_message)
3470
3471
3472def get_vm_by_property(
3473    service_instance,
3474    name,
3475    datacenter=None,
3476    vm_properties=None,
3477    traversal_spec=None,
3478    parent_ref=None,
3479):
3480    """
3481    Get virtual machine properties based on the traversal specs and properties list,
3482    returns Virtual Machine object with properties.
3483
3484    service_instance
3485        Service instance object to access vCenter
3486
3487    name
3488        Name of the virtual machine.
3489
3490    datacenter
3491        Datacenter name
3492
3493    vm_properties
3494        List of vm properties.
3495
3496    traversal_spec
3497        Traversal Spec object(s) for searching.
3498
3499    parent_ref
3500        Container Reference object for searching under a given object.
3501    """
3502    if datacenter and not parent_ref:
3503        parent_ref = salt.utils.vmware.get_datacenter(service_instance, datacenter)
3504    if not vm_properties:
3505        vm_properties = [
3506            "name",
3507            "config.hardware.device",
3508            "summary.storage.committed",
3509            "summary.storage.uncommitted",
3510            "summary.storage.unshared",
3511            "layoutEx.file",
3512            "config.guestFullName",
3513            "config.guestId",
3514            "guest.net",
3515            "config.hardware.memoryMB",
3516            "config.hardware.numCPU",
3517            "config.files.vmPathName",
3518            "summary.runtime.powerState",
3519            "guest.toolsStatus",
3520        ]
3521    vm_list = salt.utils.vmware.get_mors_with_properties(
3522        service_instance,
3523        vim.VirtualMachine,
3524        vm_properties,
3525        container_ref=parent_ref,
3526        traversal_spec=traversal_spec,
3527    )
3528    vm_formatted = [vm for vm in vm_list if vm["name"] == name]
3529    if not vm_formatted:
3530        raise salt.exceptions.VMwareObjectRetrievalError(
3531            "The virtual machine was not found."
3532        )
3533    elif len(vm_formatted) > 1:
3534        raise salt.exceptions.VMwareMultipleObjectsError(
3535            " ".join(
3536                [
3537                    "Multiple virtual machines were found with the"
3538                    "same name, please specify a container."
3539                ]
3540            )
3541        )
3542    return vm_formatted[0]
3543
3544
3545def get_folder(service_instance, datacenter, placement, base_vm_name=None):
3546    """
3547    Returns a Folder Object
3548
3549    service_instance
3550        Service instance object
3551
3552    datacenter
3553        Name of the datacenter
3554
3555    placement
3556        Placement dictionary
3557
3558    base_vm_name
3559        Existing virtual machine name (for cloning)
3560    """
3561    log.trace("Retrieving folder information")
3562    if base_vm_name:
3563        vm_object = get_vm_by_property(
3564            service_instance, base_vm_name, vm_properties=["name"]
3565        )
3566        vm_props = salt.utils.vmware.get_properties_of_managed_object(
3567            vm_object, properties=["parent"]
3568        )
3569        if "parent" in vm_props:
3570            folder_object = vm_props["parent"]
3571        else:
3572            raise salt.exceptions.VMwareObjectRetrievalError(
3573                " ".join(["The virtual machine parent", "object is not defined"])
3574            )
3575    elif "folder" in placement:
3576        folder_objects = salt.utils.vmware.get_folders(
3577            service_instance, [placement["folder"]], datacenter
3578        )
3579        if len(folder_objects) > 1:
3580            raise salt.exceptions.VMwareMultipleObjectsError(
3581                " ".join(
3582                    [
3583                        "Multiple instances are available of the",
3584                        "specified folder {}".format(placement["folder"]),
3585                    ]
3586                )
3587            )
3588        folder_object = folder_objects[0]
3589    elif datacenter:
3590        datacenter_object = salt.utils.vmware.get_datacenter(
3591            service_instance, datacenter
3592        )
3593        dc_props = salt.utils.vmware.get_properties_of_managed_object(
3594            datacenter_object, properties=["vmFolder"]
3595        )
3596        if "vmFolder" in dc_props:
3597            folder_object = dc_props["vmFolder"]
3598        else:
3599            raise salt.exceptions.VMwareObjectRetrievalError(
3600                "The datacenter vm folder object is not defined"
3601            )
3602    return folder_object
3603
3604
3605def get_placement(service_instance, datacenter, placement=None):
3606    """
3607    To create a virtual machine a resource pool needs to be supplied, we would like to use the strictest as possible.
3608
3609    datacenter
3610        Name of the datacenter
3611
3612    placement
3613        Dictionary with the placement info, cluster, host resource pool name
3614
3615    return
3616        Resource pool, cluster and host object if any applies
3617    """
3618    log.trace("Retrieving placement information")
3619    resourcepool_object, placement_object = None, None
3620    if "host" in placement:
3621        host_objects = get_hosts(
3622            service_instance, datacenter_name=datacenter, host_names=[placement["host"]]
3623        )
3624        if not host_objects:
3625            raise salt.exceptions.VMwareObjectRetrievalError(
3626                " ".join(
3627                    [
3628                        "The specified host",
3629                        "{} cannot be found.".format(placement["host"]),
3630                    ]
3631                )
3632            )
3633        try:
3634            host_props = get_properties_of_managed_object(
3635                host_objects[0], properties=["resourcePool"]
3636            )
3637            resourcepool_object = host_props["resourcePool"]
3638        except vmodl.query.InvalidProperty:
3639            traversal_spec = vmodl.query.PropertyCollector.TraversalSpec(
3640                path="parent",
3641                skip=True,
3642                type=vim.HostSystem,
3643                selectSet=[
3644                    vmodl.query.PropertyCollector.TraversalSpec(
3645                        path="resourcePool", skip=False, type=vim.ClusterComputeResource
3646                    )
3647                ],
3648            )
3649            resourcepools = get_mors_with_properties(
3650                service_instance,
3651                vim.ResourcePool,
3652                container_ref=host_objects[0],
3653                property_list=["name"],
3654                traversal_spec=traversal_spec,
3655            )
3656            if resourcepools:
3657                resourcepool_object = resourcepools[0]["object"]
3658            else:
3659                raise salt.exceptions.VMwareObjectRetrievalError(
3660                    "The resource pool of host {} cannot be found.".format(
3661                        placement["host"]
3662                    )
3663                )
3664        placement_object = host_objects[0]
3665    elif "resourcepool" in placement:
3666        resourcepool_objects = get_resource_pools(
3667            service_instance, [placement["resourcepool"]], datacenter_name=datacenter
3668        )
3669        if len(resourcepool_objects) > 1:
3670            raise salt.exceptions.VMwareMultipleObjectsError(
3671                " ".join(
3672                    [
3673                        "Multiple instances are available of the",
3674                        "specified host {}.".format(placement["host"]),
3675                    ]
3676                )
3677            )
3678        resourcepool_object = resourcepool_objects[0]
3679        res_props = get_properties_of_managed_object(
3680            resourcepool_object, properties=["parent"]
3681        )
3682        if "parent" in res_props:
3683            placement_object = res_props["parent"]
3684        else:
3685            raise salt.exceptions.VMwareObjectRetrievalError(
3686                " ".join(["The resource pool's parent", "object is not defined"])
3687            )
3688    elif "cluster" in placement:
3689        datacenter_object = get_datacenter(service_instance, datacenter)
3690        cluster_object = get_cluster(datacenter_object, placement["cluster"])
3691        clus_props = get_properties_of_managed_object(
3692            cluster_object, properties=["resourcePool"]
3693        )
3694        if "resourcePool" in clus_props:
3695            resourcepool_object = clus_props["resourcePool"]
3696        else:
3697            raise salt.exceptions.VMwareObjectRetrievalError(
3698                " ".join(["The cluster's resource pool", "object is not defined"])
3699            )
3700        placement_object = cluster_object
3701    else:
3702        # We are checking the schema for this object, this exception should never be raised
3703        raise salt.exceptions.VMwareObjectRetrievalError(
3704            " ".join(["Placement is not defined."])
3705        )
3706    return (resourcepool_object, placement_object)
3707
3708
3709def convert_to_kb(unit, size):
3710    """
3711    Converts the given size to KB based on the unit, returns a long integer.
3712
3713    unit
3714        Unit of the size eg. GB; Note: to VMware a GB is the same as GiB = 1024MiB
3715    size
3716        Number which represents the size
3717    """
3718    if unit.lower() == "gb":
3719        # vCenter needs long value
3720        target_size = int(size * 1024 * 1024)
3721    elif unit.lower() == "mb":
3722        target_size = int(size * 1024)
3723    elif unit.lower() == "kb":
3724        target_size = int(size)
3725    else:
3726        raise salt.exceptions.ArgumentValueError("The unit is not specified")
3727    return {"size": target_size, "unit": "KB"}
3728
3729
3730def power_cycle_vm(virtual_machine, action="on"):
3731    """
3732    Powers on/off a virtual machine specified by its name.
3733
3734    virtual_machine
3735        vim.VirtualMachine object to power on/off virtual machine
3736
3737    action
3738        Operation option to power on/off the machine
3739    """
3740    if action == "on":
3741        try:
3742            task = virtual_machine.PowerOn()
3743            task_name = "power on"
3744        except vim.fault.NoPermission as exc:
3745            log.exception(exc)
3746            raise salt.exceptions.VMwareApiError(
3747                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3748            )
3749        except vim.fault.VimFault as exc:
3750            log.exception(exc)
3751            raise salt.exceptions.VMwareApiError(exc.msg)
3752        except vmodl.RuntimeFault as exc:
3753            log.exception(exc)
3754            raise salt.exceptions.VMwareRuntimeError(exc.msg)
3755    elif action == "off":
3756        try:
3757            task = virtual_machine.PowerOff()
3758            task_name = "power off"
3759        except vim.fault.NoPermission as exc:
3760            log.exception(exc)
3761            raise salt.exceptions.VMwareApiError(
3762                "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3763            )
3764        except vim.fault.VimFault as exc:
3765            log.exception(exc)
3766            raise salt.exceptions.VMwareApiError(exc.msg)
3767        except vmodl.RuntimeFault as exc:
3768            log.exception(exc)
3769            raise salt.exceptions.VMwareRuntimeError(exc.msg)
3770    else:
3771        raise salt.exceptions.ArgumentValueError("The given action is not supported")
3772    try:
3773        wait_for_task(task, get_managed_object_name(virtual_machine), task_name)
3774    except salt.exceptions.VMwareFileNotFoundError as exc:
3775        raise salt.exceptions.VMwarePowerOnError(
3776            " ".join(
3777                [
3778                    "An error occurred during power",
3779                    "operation, a file was not found: {}".format(exc),
3780                ]
3781            )
3782        )
3783    return virtual_machine
3784
3785
3786def create_vm(
3787    vm_name, vm_config_spec, folder_object, resourcepool_object, host_object=None
3788):
3789    """
3790    Creates virtual machine from config spec
3791
3792    vm_name
3793        Virtual machine name to be created
3794
3795    vm_config_spec
3796        Virtual Machine Config Spec object
3797
3798    folder_object
3799        vm Folder managed object reference
3800
3801    resourcepool_object
3802        Resource pool object where the machine will be created
3803
3804    host_object
3805        Host object where the machine will ne placed (optional)
3806
3807    return
3808        Virtual Machine managed object reference
3809    """
3810    try:
3811        if host_object and isinstance(host_object, vim.HostSystem):
3812            task = folder_object.CreateVM_Task(
3813                vm_config_spec, pool=resourcepool_object, host=host_object
3814            )
3815        else:
3816            task = folder_object.CreateVM_Task(vm_config_spec, pool=resourcepool_object)
3817    except vim.fault.NoPermission as exc:
3818        log.exception(exc)
3819        raise salt.exceptions.VMwareApiError(
3820            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3821        )
3822    except vim.fault.VimFault as exc:
3823        log.exception(exc)
3824        raise salt.exceptions.VMwareApiError(exc.msg)
3825    except vmodl.RuntimeFault as exc:
3826        log.exception(exc)
3827        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3828    vm_object = wait_for_task(task, vm_name, "CreateVM Task", 10, "info")
3829    return vm_object
3830
3831
3832def register_vm(datacenter, name, vmx_path, resourcepool_object, host_object=None):
3833    """
3834    Registers a virtual machine to the inventory with the given vmx file, on success
3835    it returns the vim.VirtualMachine managed object reference
3836
3837    datacenter
3838        Datacenter object of the virtual machine, vim.Datacenter object
3839
3840    name
3841        Name of the virtual machine
3842
3843    vmx_path:
3844        Full path to the vmx file, datastore name should be included
3845
3846    resourcepool
3847        Placement resource pool of the virtual machine, vim.ResourcePool object
3848
3849    host
3850        Placement host of the virtual machine, vim.HostSystem object
3851    """
3852    try:
3853        if host_object:
3854            task = datacenter.vmFolder.RegisterVM_Task(
3855                path=vmx_path,
3856                name=name,
3857                asTemplate=False,
3858                host=host_object,
3859                pool=resourcepool_object,
3860            )
3861        else:
3862            task = datacenter.vmFolder.RegisterVM_Task(
3863                path=vmx_path, name=name, asTemplate=False, pool=resourcepool_object
3864            )
3865    except vim.fault.NoPermission as exc:
3866        log.exception(exc)
3867        raise salt.exceptions.VMwareApiError(
3868            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3869        )
3870    except vim.fault.VimFault as exc:
3871        log.exception(exc)
3872        raise salt.exceptions.VMwareApiError(exc.msg)
3873    except vmodl.RuntimeFault as exc:
3874        log.exception(exc)
3875        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3876    try:
3877        vm_ref = wait_for_task(task, name, "RegisterVM Task")
3878    except salt.exceptions.VMwareFileNotFoundError as exc:
3879        raise salt.exceptions.VMwareVmRegisterError(
3880            "An error occurred during registration operation, the "
3881            "configuration file was not found: {}".format(exc)
3882        )
3883    return vm_ref
3884
3885
3886def update_vm(vm_ref, vm_config_spec):
3887    """
3888    Updates the virtual machine configuration with the given object
3889
3890    vm_ref
3891        Virtual machine managed object reference
3892
3893    vm_config_spec
3894        Virtual machine config spec object to update
3895    """
3896    vm_name = get_managed_object_name(vm_ref)
3897    log.trace("Updating vm '%s'", vm_name)
3898    try:
3899        task = vm_ref.ReconfigVM_Task(vm_config_spec)
3900    except vim.fault.NoPermission as exc:
3901        log.exception(exc)
3902        raise salt.exceptions.VMwareApiError(
3903            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3904        )
3905    except vim.fault.VimFault as exc:
3906        log.exception(exc)
3907        raise salt.exceptions.VMwareApiError(exc.msg)
3908    except vmodl.RuntimeFault as exc:
3909        log.exception(exc)
3910        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3911    vm_ref = wait_for_task(task, vm_name, "ReconfigureVM Task")
3912    return vm_ref
3913
3914
3915def delete_vm(vm_ref):
3916    """
3917    Destroys the virtual machine
3918
3919    vm_ref
3920        Managed object reference of a virtual machine object
3921    """
3922    vm_name = get_managed_object_name(vm_ref)
3923    log.trace("Destroying vm '%s'", vm_name)
3924    try:
3925        task = vm_ref.Destroy_Task()
3926    except vim.fault.NoPermission as exc:
3927        log.exception(exc)
3928        raise salt.exceptions.VMwareApiError(
3929            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3930        )
3931    except vim.fault.VimFault as exc:
3932        log.exception(exc)
3933        raise salt.exceptions.VMwareApiError(exc.msg)
3934    except vmodl.RuntimeFault as exc:
3935        log.exception(exc)
3936        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3937    wait_for_task(task, vm_name, "Destroy Task")
3938
3939
3940def unregister_vm(vm_ref):
3941    """
3942    Destroys the virtual machine
3943
3944    vm_ref
3945        Managed object reference of a virtual machine object
3946    """
3947    vm_name = get_managed_object_name(vm_ref)
3948    log.trace("Destroying vm '%s'", vm_name)
3949    try:
3950        vm_ref.UnregisterVM()
3951    except vim.fault.NoPermission as exc:
3952        log.exception(exc)
3953        raise salt.exceptions.VMwareApiError(
3954            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
3955        )
3956    except vim.fault.VimFault as exc:
3957        raise salt.exceptions.VMwareApiError(exc.msg)
3958    except vmodl.RuntimeFault as exc:
3959        raise salt.exceptions.VMwareRuntimeError(exc.msg)
3960