1"""
2Library for VMware Storage Policy management (via the pbm endpoint)
3
4This library is used to manage the various policies available in VMware
5
6:codeauthor: Alexandru Bleotu <alexandru.bleotu@morganstaley.com>
7
8Dependencies
9~~~~~~~~~~~~
10
11- pyVmomi Python Module
12
13pyVmomi
14-------
15
16PyVmomi can be installed via pip:
17
18.. code-block:: bash
19
20    pip install pyVmomi
21
22.. note::
23
24    versions of Python. If using version 6.0 of pyVmomi, Python 2.6,
25    Python 2.7.9, or newer must be present. This is due to an upstream dependency
26    in pyVmomi 6.0 that is not supported in Python versions 2.7 to 2.7.8. If the
27    version of Python is not in the supported range, you will need to install an
28    earlier version of pyVmomi. See `Issue #29537`_ for more information.
29
30.. _Issue #29537: https://github.com/saltstack/salt/issues/29537
31
32Based on the note above, to install an earlier version of pyVmomi than the
33version currently listed in PyPi, run the following:
34
35.. code-block:: bash
36
37    pip install pyVmomi==5.5.0.2014.1.1
38"""
39
40
41import logging
42
43import salt.utils.vmware
44from salt.exceptions import (
45    VMwareApiError,
46    VMwareObjectRetrievalError,
47    VMwareRuntimeError,
48)
49
50try:
51    from pyVmomi import pbm, vim, vmodl  # pylint: disable=no-name-in-module
52
53    HAS_PYVMOMI = True
54except ImportError:
55    HAS_PYVMOMI = False
56
57
58# Get Logging Started
59log = logging.getLogger(__name__)
60
61
62def __virtual__():
63    """
64    Only load if PyVmomi is installed.
65    """
66    if HAS_PYVMOMI:
67        return True
68    else:
69        return (
70            False,
71            "Missing dependency: The salt.utils.pbm module "
72            "requires the pyvmomi library",
73        )
74
75
76def get_profile_manager(service_instance):
77    """
78    Returns a profile manager
79
80    service_instance
81        Service instance to the host or vCenter
82    """
83    stub = salt.utils.vmware.get_new_service_instance_stub(
84        service_instance, ns="pbm/2.0", path="/pbm/sdk"
85    )
86    pbm_si = pbm.ServiceInstance("ServiceInstance", stub)
87    try:
88        profile_manager = pbm_si.RetrieveContent().profileManager
89    except vim.fault.NoPermission as exc:
90        log.exception(exc)
91        raise VMwareApiError(
92            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
93        )
94    except vim.fault.VimFault as exc:
95        log.exception(exc)
96        raise VMwareApiError(exc.msg)
97    except vmodl.RuntimeFault as exc:
98        log.exception(exc)
99        raise VMwareRuntimeError(exc.msg)
100    return profile_manager
101
102
103def get_placement_solver(service_instance):
104    """
105    Returns a placement solver
106
107    service_instance
108        Service instance to the host or vCenter
109    """
110    stub = salt.utils.vmware.get_new_service_instance_stub(
111        service_instance, ns="pbm/2.0", path="/pbm/sdk"
112    )
113    pbm_si = pbm.ServiceInstance("ServiceInstance", stub)
114    try:
115        profile_manager = pbm_si.RetrieveContent().placementSolver
116    except vim.fault.NoPermission as exc:
117        log.exception(exc)
118        raise VMwareApiError(
119            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
120        )
121    except vim.fault.VimFault as exc:
122        log.exception(exc)
123        raise VMwareApiError(exc.msg)
124    except vmodl.RuntimeFault as exc:
125        log.exception(exc)
126        raise VMwareRuntimeError(exc.msg)
127    return profile_manager
128
129
130def get_capability_definitions(profile_manager):
131    """
132    Returns a list of all capability definitions.
133
134    profile_manager
135        Reference to the profile manager.
136    """
137    res_type = pbm.profile.ResourceType(
138        resourceType=pbm.profile.ResourceTypeEnum.STORAGE
139    )
140    try:
141        cap_categories = profile_manager.FetchCapabilityMetadata(res_type)
142    except vim.fault.NoPermission as exc:
143        log.exception(exc)
144        raise VMwareApiError(
145            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
146        )
147    except vim.fault.VimFault as exc:
148        log.exception(exc)
149        raise VMwareApiError(exc.msg)
150    except vmodl.RuntimeFault as exc:
151        log.exception(exc)
152        raise VMwareRuntimeError(exc.msg)
153    cap_definitions = []
154    for cat in cap_categories:
155        cap_definitions.extend(cat.capabilityMetadata)
156    return cap_definitions
157
158
159def get_policies_by_id(profile_manager, policy_ids):
160    """
161    Returns a list of policies with the specified ids.
162
163    profile_manager
164        Reference to the profile manager.
165
166    policy_ids
167        List of policy ids to retrieve.
168    """
169    try:
170        return profile_manager.RetrieveContent(policy_ids)
171    except vim.fault.NoPermission as exc:
172        log.exception(exc)
173        raise VMwareApiError(
174            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
175        )
176    except vim.fault.VimFault as exc:
177        log.exception(exc)
178        raise VMwareApiError(exc.msg)
179    except vmodl.RuntimeFault as exc:
180        log.exception(exc)
181        raise VMwareRuntimeError(exc.msg)
182
183
184def get_storage_policies(profile_manager, policy_names=None, get_all_policies=False):
185    """
186    Returns a list of the storage policies, filtered by name.
187
188    profile_manager
189        Reference to the profile manager.
190
191    policy_names
192        List of policy names to filter by.
193        Default is None.
194
195    get_all_policies
196        Flag specifying to return all policies, regardless of the specified
197        filter.
198    """
199    res_type = pbm.profile.ResourceType(
200        resourceType=pbm.profile.ResourceTypeEnum.STORAGE
201    )
202    try:
203        policy_ids = profile_manager.QueryProfile(res_type)
204    except vim.fault.NoPermission as exc:
205        log.exception(exc)
206        raise VMwareApiError(
207            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
208        )
209    except vim.fault.VimFault as exc:
210        log.exception(exc)
211        raise VMwareApiError(exc.msg)
212    except vmodl.RuntimeFault as exc:
213        log.exception(exc)
214        raise VMwareRuntimeError(exc.msg)
215    log.trace("policy_ids = %s", policy_ids)
216    # More policies are returned so we need to filter again
217    policies = [
218        p
219        for p in get_policies_by_id(profile_manager, policy_ids)
220        if p.resourceType.resourceType == pbm.profile.ResourceTypeEnum.STORAGE
221    ]
222    if get_all_policies:
223        return policies
224    if not policy_names:
225        policy_names = []
226    return [p for p in policies if p.name in policy_names]
227
228
229def create_storage_policy(profile_manager, policy_spec):
230    """
231    Creates a storage policy.
232
233    profile_manager
234        Reference to the profile manager.
235
236    policy_spec
237        Policy update spec.
238    """
239    try:
240        profile_manager.Create(policy_spec)
241    except vim.fault.NoPermission as exc:
242        log.exception(exc)
243        raise VMwareApiError(
244            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
245        )
246    except vim.fault.VimFault as exc:
247        log.exception(exc)
248        raise VMwareApiError(exc.msg)
249    except vmodl.RuntimeFault as exc:
250        log.exception(exc)
251        raise VMwareRuntimeError(exc.msg)
252
253
254def update_storage_policy(profile_manager, policy, policy_spec):
255    """
256    Updates a storage policy.
257
258    profile_manager
259        Reference to the profile manager.
260
261    policy
262        Reference to the policy to be updated.
263
264    policy_spec
265        Policy update spec.
266    """
267    try:
268        profile_manager.Update(policy.profileId, policy_spec)
269    except vim.fault.NoPermission as exc:
270        log.exception(exc)
271        raise VMwareApiError(
272            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
273        )
274    except vim.fault.VimFault as exc:
275        log.exception(exc)
276        raise VMwareApiError(exc.msg)
277    except vmodl.RuntimeFault as exc:
278        log.exception(exc)
279        raise VMwareRuntimeError(exc.msg)
280
281
282def get_default_storage_policy_of_datastore(profile_manager, datastore):
283    """
284    Returns the default storage policy reference assigned to a datastore.
285
286    profile_manager
287        Reference to the profile manager.
288
289    datastore
290        Reference to the datastore.
291    """
292    # Retrieve all datastores visible
293    hub = pbm.placement.PlacementHub(hubId=datastore._moId, hubType="Datastore")
294    log.trace("placement_hub = %s", hub)
295    try:
296        policy_id = profile_manager.QueryDefaultRequirementProfile(hub)
297    except vim.fault.NoPermission as exc:
298        log.exception(exc)
299        raise VMwareApiError(
300            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
301        )
302    except vim.fault.VimFault as exc:
303        log.exception(exc)
304        raise VMwareApiError(exc.msg)
305    except vmodl.RuntimeFault as exc:
306        log.exception(exc)
307        raise VMwareRuntimeError(exc.msg)
308    policy_refs = get_policies_by_id(profile_manager, [policy_id])
309    if not policy_refs:
310        raise VMwareObjectRetrievalError(
311            "Storage policy with id '{}' was not found".format(policy_id)
312        )
313    return policy_refs[0]
314
315
316def assign_default_storage_policy_to_datastore(profile_manager, policy, datastore):
317    """
318    Assigns a storage policy as the default policy to a datastore.
319
320    profile_manager
321        Reference to the profile manager.
322
323    policy
324        Reference to the policy to assigned.
325
326    datastore
327        Reference to the datastore.
328    """
329    placement_hub = pbm.placement.PlacementHub(
330        hubId=datastore._moId, hubType="Datastore"
331    )
332    log.trace("placement_hub = %s", placement_hub)
333    try:
334        profile_manager.AssignDefaultRequirementProfile(
335            policy.profileId, [placement_hub]
336        )
337    except vim.fault.NoPermission as exc:
338        log.exception(exc)
339        raise VMwareApiError(
340            "Not enough permissions. Required privilege: {}".format(exc.privilegeId)
341        )
342    except vim.fault.VimFault as exc:
343        log.exception(exc)
344        raise VMwareApiError(exc.msg)
345    except vmodl.RuntimeFault as exc:
346        log.exception(exc)
347        raise VMwareRuntimeError(exc.msg)
348