"Fossies" - the Fresh Open Source Software Archive

Member "cinder-14.0.2/cinder/volume/drivers/vmware/datastore.py" (4 Oct 2019, 12568 Bytes) of package /linux/misc/openstack/cinder-14.0.2.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file. For more information about "datastore.py" see the Fossies "Dox" file reference documentation and the last Fossies "Diffs" side-by-side code changes report: 14.0.2_vs_15.0.0.

    1 # Copyright (c) 2014 VMware, Inc.
    2 # All Rights Reserved.
    3 #
    4 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
    5 #    not use this file except in compliance with the License. You may obtain
    6 #    a copy of the License at
    7 #
    8 #         http://www.apache.org/licenses/LICENSE-2.0
    9 #
   10 #    Unless required by applicable law or agreed to in writing, software
   11 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
   12 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
   13 #    License for the specific language governing permissions and limitations
   14 #    under the License.
   15 
   16 """
   17 Classes and utility methods for datastore selection.
   18 """
   19 
   20 import random
   21 
   22 from oslo_log import log as logging
   23 from oslo_vmware import pbm
   24 from oslo_vmware import vim_util
   25 
   26 from cinder import coordination
   27 from cinder.volume.drivers.vmware import exceptions as vmdk_exceptions
   28 
   29 
   30 LOG = logging.getLogger(__name__)
   31 
   32 
   33 class DatastoreType(object):
   34     """Supported datastore types."""
   35 
   36     NFS = "nfs"
   37     VMFS = "vmfs"
   38     VSAN = "vsan"
   39     VVOL = "vvol"
   40     NFS41 = "nfs41"
   41 
   42     _ALL_TYPES = {NFS, VMFS, VSAN, VVOL, NFS41}
   43 
   44     @staticmethod
   45     def get_all_types():
   46         return DatastoreType._ALL_TYPES
   47 
   48 
   49 class DatastoreSelector(object):
   50     """Class for selecting datastores which satisfy input requirements."""
   51 
   52     HARD_AFFINITY_DS_TYPE = "hardAffinityDatastoreTypes"
   53     HARD_ANTI_AFFINITY_DS = "hardAntiAffinityDatastores"
   54     SIZE_BYTES = "sizeBytes"
   55     PROFILE_NAME = "storageProfileName"
   56 
   57     # TODO(vbala) Remove dependency on volumeops.
   58     def __init__(self, vops, session, max_objects, ds_regex=None):
   59         self._vops = vops
   60         self._session = session
   61         self._max_objects = max_objects
   62         self._ds_regex = ds_regex
   63         self._profile_id_cache = {}
   64 
   65     @coordination.synchronized('vmware-datastore-profile-{profile_name}')
   66     def get_profile_id(self, profile_name):
   67         """Get vCenter profile ID for the given profile name.
   68 
   69         :param profile_name: profile name
   70         :return: vCenter profile ID
   71         :raises ProfileNotFoundException:
   72         """
   73         if profile_name in self._profile_id_cache:
   74             LOG.debug("Returning cached ID for profile: %s.", profile_name)
   75             return self._profile_id_cache[profile_name]
   76 
   77         profile_id = pbm.get_profile_id_by_name(self._session, profile_name)
   78         if profile_id is None:
   79             LOG.error("Storage profile: %s cannot be found in vCenter.",
   80                       profile_name)
   81             raise vmdk_exceptions.ProfileNotFoundException(
   82                 storage_profile=profile_name)
   83 
   84         self._profile_id_cache[profile_name] = profile_id
   85         LOG.debug("Storage profile: %(name)s resolved to vCenter profile ID: "
   86                   "%(id)s.",
   87                   {'name': profile_name,
   88                    'id': profile_id})
   89         return profile_id
   90 
   91     def _filter_by_profile(self, datastores, profile_id):
   92         """Filter out input datastores that do not match the given profile."""
   93         cf = self._session.pbm.client.factory
   94         hubs = pbm.convert_datastores_to_hubs(cf, datastores)
   95         hubs = pbm.filter_hubs_by_profile(self._session, hubs, profile_id)
   96         hub_ids = [hub.hubId for hub in hubs]
   97         return {k: v for k, v in datastores.items() if k.value in hub_ids}
   98 
   99     def _filter_datastores(self,
  100                            datastores,
  101                            size_bytes,
  102                            profile_id,
  103                            hard_anti_affinity_ds,
  104                            hard_affinity_ds_types,
  105                            valid_host_refs=None):
  106 
  107         if not datastores:
  108             return
  109 
  110         def _is_valid_ds_type(summary):
  111             ds_type = summary.type.lower()
  112             return (ds_type in DatastoreType.get_all_types() and
  113                     (hard_affinity_ds_types is None or
  114                      ds_type in hard_affinity_ds_types))
  115 
  116         def _is_ds_usable(summary):
  117             return summary.accessible and not self._vops._in_maintenance(
  118                 summary)
  119 
  120         valid_host_refs = valid_host_refs or []
  121         valid_hosts = [host_ref.value for host_ref in valid_host_refs]
  122 
  123         def _is_ds_accessible_to_valid_host(host_mounts):
  124             for host_mount in host_mounts:
  125                 if host_mount.key.value in valid_hosts:
  126                     return True
  127 
  128         def _is_ds_valid(ds_ref, ds_props):
  129             summary = ds_props.get('summary')
  130             host_mounts = ds_props.get('host')
  131             if (summary is None or host_mounts is None):
  132                 return False
  133 
  134             if self._ds_regex and not self._ds_regex.match(summary.name):
  135                 return False
  136 
  137             if (hard_anti_affinity_ds and
  138                     ds_ref.value in hard_anti_affinity_ds):
  139                 return False
  140 
  141             if summary.freeSpace < size_bytes:
  142                 return False
  143 
  144             if (valid_hosts and
  145                     not _is_ds_accessible_to_valid_host(host_mounts)):
  146                 return False
  147 
  148             return _is_valid_ds_type(summary) and _is_ds_usable(summary)
  149 
  150         datastores = {k: v for k, v in datastores.items()
  151                       if _is_ds_valid(k, v)}
  152 
  153         if datastores and profile_id:
  154             datastores = self._filter_by_profile(datastores, profile_id)
  155 
  156         return datastores
  157 
  158     def _get_object_properties(self, obj_content):
  159         props = {}
  160         if hasattr(obj_content, 'propSet'):
  161             prop_set = obj_content.propSet
  162             if prop_set:
  163                 props = {prop.name: prop.val for prop in prop_set}
  164         return props
  165 
  166     def _get_datastores(self):
  167         datastores = {}
  168         retrieve_result = self._session.invoke_api(
  169             vim_util,
  170             'get_objects',
  171             self._session.vim,
  172             'Datastore',
  173             self._max_objects,
  174             properties_to_collect=['host', 'summary'])
  175 
  176         while retrieve_result:
  177             if retrieve_result.objects:
  178                 for obj_content in retrieve_result.objects:
  179                     props = self._get_object_properties(obj_content)
  180                     if ('host' in props and
  181                             hasattr(props['host'], 'DatastoreHostMount')):
  182                         props['host'] = props['host'].DatastoreHostMount
  183                     datastores[obj_content.obj] = props
  184             retrieve_result = self._session.invoke_api(vim_util,
  185                                                        'continue_retrieval',
  186                                                        self._session.vim,
  187                                                        retrieve_result)
  188 
  189         return datastores
  190 
  191     def _get_host_properties(self, host_ref):
  192         retrieve_result = self._session.invoke_api(vim_util,
  193                                                    'get_object_properties',
  194                                                    self._session.vim,
  195                                                    host_ref,
  196                                                    ['runtime', 'parent'])
  197 
  198         if retrieve_result:
  199             return self._get_object_properties(retrieve_result[0])
  200 
  201     def _get_resource_pool(self, cluster_ref):
  202         return self._session.invoke_api(vim_util,
  203                                         'get_object_property',
  204                                         self._session.vim,
  205                                         cluster_ref,
  206                                         'resourcePool')
  207 
  208     def _select_best_datastore(self, datastores, valid_host_refs=None):
  209 
  210         if not datastores:
  211             return
  212 
  213         def _sort_key(ds_props):
  214             host = ds_props.get('host')
  215             summary = ds_props.get('summary')
  216             space_utilization = (1.0 -
  217                                  (summary.freeSpace / float(summary.capacity)))
  218             return (-len(host), space_utilization)
  219 
  220         host_prop_map = {}
  221 
  222         def _is_host_usable(host_ref):
  223             props = host_prop_map.get(host_ref.value)
  224             if props is None:
  225                 props = self._get_host_properties(host_ref)
  226                 host_prop_map[host_ref.value] = props
  227 
  228             runtime = props.get('runtime')
  229             parent = props.get('parent')
  230             if runtime and parent:
  231                 return (runtime.connectionState == 'connected' and
  232                         not runtime.inMaintenanceMode)
  233             else:
  234                 return False
  235 
  236         valid_host_refs = valid_host_refs or []
  237         valid_hosts = [host_ref.value for host_ref in valid_host_refs]
  238 
  239         def _select_host(host_mounts):
  240             random.shuffle(host_mounts)
  241             for host_mount in host_mounts:
  242                 if valid_hosts and host_mount.key.value not in valid_hosts:
  243                     continue
  244                 if (self._vops._is_usable(host_mount.mountInfo) and
  245                         _is_host_usable(host_mount.key)):
  246                     return host_mount.key
  247 
  248         sorted_ds_props = sorted(datastores.values(), key=_sort_key)
  249         for ds_props in sorted_ds_props:
  250             host_ref = _select_host(ds_props['host'])
  251             if host_ref:
  252                 rp = self._get_resource_pool(
  253                     host_prop_map[host_ref.value]['parent'])
  254                 return (host_ref, rp, ds_props['summary'])
  255 
  256     def select_datastore(self, req, hosts=None):
  257         """Selects a datastore satisfying the given requirements.
  258 
  259         A datastore which is connected to maximum number of hosts is
  260         selected. Ties if any are broken based on space utilization--
  261         datastore with least space utilization is preferred. It returns
  262         the selected datastore's summary along with a host and resource
  263         pool where the volume can be created.
  264 
  265         :param req: selection requirements
  266         :param hosts: list of hosts to consider
  267         :return: (host, resourcePool, summary)
  268         """
  269         LOG.debug("Using requirements: %s for datastore selection.", req)
  270 
  271         hard_affinity_ds_types = req.get(
  272             DatastoreSelector.HARD_AFFINITY_DS_TYPE)
  273         hard_anti_affinity_datastores = req.get(
  274             DatastoreSelector.HARD_ANTI_AFFINITY_DS)
  275         size_bytes = req[DatastoreSelector.SIZE_BYTES]
  276         profile_name = req.get(DatastoreSelector.PROFILE_NAME)
  277 
  278         profile_id = None
  279         if profile_name is not None:
  280             profile_id = self.get_profile_id(profile_name)
  281 
  282         datastores = self._get_datastores()
  283         datastores = self._filter_datastores(datastores,
  284                                              size_bytes,
  285                                              profile_id,
  286                                              hard_anti_affinity_datastores,
  287                                              hard_affinity_ds_types,
  288                                              valid_host_refs=hosts)
  289         res = self._select_best_datastore(datastores, valid_host_refs=hosts)
  290         LOG.debug("Selected (host, resourcepool, datastore): %s", res)
  291         return res
  292 
  293     def is_datastore_compliant(self, datastore, profile_name):
  294         """Check if the datastore is compliant with given profile.
  295 
  296         :param datastore: datastore to check the compliance
  297         :param profile_name: profile to check the compliance against
  298         :return: True if the datastore is compliant; False otherwise
  299         :raises ProfileNotFoundException:
  300         """
  301         LOG.debug("Checking datastore: %(datastore)s compliance against "
  302                   "profile: %(profile)s.",
  303                   {'datastore': datastore,
  304                    'profile': profile_name})
  305         if profile_name is None:
  306             # Any datastore is trivially compliant with a None profile.
  307             return True
  308 
  309         profile_id = self.get_profile_id(profile_name)
  310         # _filter_by_profile expects a map of datastore references to its
  311         # properties. It only uses the properties to construct a map of
  312         # filtered datastores to its properties. Here we don't care about
  313         # the datastore property, so pass it as None.
  314         is_compliant = bool(self._filter_by_profile({datastore: None},
  315                                                     profile_id))
  316         LOG.debug("Compliance is %(is_compliant)s for datastore: "
  317                   "%(datastore)s against profile: %(profile)s.",
  318                   {'is_compliant': is_compliant,
  319                    'datastore': datastore,
  320                    'profile': profile_name})
  321         return is_compliant