diff options
author | Jenkins <jenkins@review.openstack.org> | 2015-03-09 12:50:25 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2015-03-09 12:50:25 +0000 |
commit | 65b85bfdb718f57265e43325da313dcef9abda97 (patch) | |
tree | ce84198ee8a478653bcab7d39b4e9e5795cebef3 /glance_store/_drivers/vmware_datastore.py | |
parent | c1e8697f124006ea9096c9a29613ac03a92fbc1b (diff) | |
parent | aa10f66ee09015555ac3ff5f702f9603ca31af9f (diff) | |
download | glance_store-65b85bfdb718f57265e43325da313dcef9abda97.tar.gz |
Merge "VMware: Support Multiple Datastores"
Diffstat (limited to 'glance_store/_drivers/vmware_datastore.py')
-rw-r--r-- | glance_store/_drivers/vmware_datastore.py | 203 |
1 files changed, 168 insertions, 35 deletions
diff --git a/glance_store/_drivers/vmware_datastore.py b/glance_store/_drivers/vmware_datastore.py index 8f992f2..22e7093 100644 --- a/glance_store/_drivers/vmware_datastore.py +++ b/glance_store/_drivers/vmware_datastore.py @@ -21,11 +21,16 @@ import logging import os import socket -from oslo.vmware import api -from oslo.vmware import constants from oslo_config import cfg from oslo_utils import excutils from oslo_utils import units +from oslo_vmware import api +from oslo_vmware import constants +from oslo_vmware.objects import datacenter as oslo_datacenter +from oslo_vmware.objects import datastore as oslo_datastore +from oslo_vmware import vim_util + +import six # NOTE(jokke): simplified transition to py3, behaves like py2 xrange from six.moves import range import six.moves.urllib.parse as urlparse @@ -82,7 +87,14 @@ _VMWARE_OPTS = [ cfg.BoolOpt('vmware_api_insecure', default=False, help=_('Allow to perform insecure SSL requests to ESX/VC.')), -] + cfg.MultiStrOpt('vmware_datastores', + help=_('The datastores where the images are stored inside ' + 'vCenter. The expected format is ' + 'datacenter_path:datastore_name:weight. The weight ' + 'will be used unless there is not enough free ' + 'space to store the image. If the weights are ' + 'equal, the datastore with most free space ' + 'is chosen.'))] def is_valid_ipv6(address): @@ -170,18 +182,22 @@ class StoreLocation(location.StoreLocation): vsphere://server_host/folder/file_path?dcPath=dc_path&dsName=ds_name """ + def __init__(self, store_specs, conf): + super(StoreLocation, self).__init__(store_specs, conf) + self.datacenter_path = None + self.datastore_name = None + def process_specs(self): self.scheme = self.specs.get('scheme', STORE_SCHEME) self.server_host = self.specs.get('server_host') self.path = os.path.join(DS_URL_PREFIX, self.specs.get('image_dir').strip('/'), self.specs.get('image_id')) - dc_path = self.specs.get('datacenter_path') - if dc_path is not None: - param_list = {'dcPath': self.specs.get('datacenter_path'), - 'dsName': self.specs.get('datastore_name')} - else: - param_list = {'dsName': self.specs.get('datastore_name')} + self.datacenter_path = self.specs.get('datacenter_path') + self.datstore_name = self.specs.get('datastore_name') + param_list = {'dsName': self.datstore_name} + if self.datacenter_path: + param_list['dcPath'] = self.datacenter_path self.query = urlparse.urlencode(param_list) def get_uri(self): @@ -218,6 +234,13 @@ class StoreLocation(location.StoreLocation): # reason = 'Badly formed VMware datastore URI %(uri)s.' % {'uri': uri} # LOG.debug(reason) # raise exceptions.BadStoreUri(reason) + parts = urlparse.parse_qs(self.query) + dc_path = parts.get('dcPath') + if dc_path: + self.datacenter_path = dc_path[0] + ds_name = parts.get('dsName') + if ds_name: + self.datastore_name = ds_name[0] class Store(glance_store.Store): @@ -230,6 +253,10 @@ class Store(glance_store.Store): # FIXME(arnaud): re-visit this code once the store API is cleaned up. _VMW_SESSION = None + def __init__(self, conf): + super(Store, self).__init__(conf) + self.datastores = {} + def reset_session(self, force=False): if Store._VMW_SESSION is None or force: Store._VMW_SESSION = api.VMwareAPISession( @@ -248,12 +275,29 @@ class Store(glance_store.Store): LOG.error(msg) raise exceptions.BadStoreConfiguration( store_name='vmware_datastore', reason=msg) + if self.conf.glance_store.vmware_task_poll_interval <= 0: msg = _('vmware_task_poll_interval should be greater than zero') LOG.error(msg) raise exceptions.BadStoreConfiguration( store_name='vmware_datastore', reason=msg) + if not (self.conf.glance_store.vmware_datastore_name + or self.conf.glance_store.vmware_datastores): + msg = (_("Specify at least 'vmware_datastore_name' or " + "'vmware_datastores' option")) + LOG.error(msg) + raise exceptions.BadStoreConfiguration( + store_name='vmware_datastore', reason=msg) + + if (self.conf.glance_store.vmware_datastore_name and + self.conf.glance_store.vmware_datastores): + msg = (_("Specify either 'vmware_datastore_name' or " + "'vmware_datastores' option")) + LOG.error(msg) + raise exceptions.BadStoreConfiguration( + store_name='vmware_datastore', reason=msg) + def configure(self): self._sanity_check() self.scheme = STORE_SCHEME @@ -265,31 +309,122 @@ class Store(glance_store.Store): self.api_insecure = self.conf.glance_store.vmware_api_insecure super(Store, self).configure() - def configure_add(self): - self.datacenter_path = self.conf.glance_store.vmware_datacenter_path - self.datastore_name = self._option_get('vmware_datastore_name') - global _datastore_info_valid - if not _datastore_info_valid: - search_index_moref = ( - self.session.vim.service_content.searchIndex) - - inventory_path = ('%s/datastore/%s' - % (self.datacenter_path, self.datastore_name)) - ds_moref = self.session.invoke_api( - self.session.vim, 'FindByInventoryPath', - search_index_moref, inventoryPath=inventory_path) - if ds_moref is None: + def _get_datacenter(self, datacenter_path): + search_index_moref = self.session.vim.service_content.searchIndex + dc_moref = self.session.invoke_api( + self.session.vim, + 'FindByInventoryPath', + search_index_moref, + inventoryPath=datacenter_path) + dc_name = datacenter_path.rsplit('/', 1)[-1] + # TODO(sabari): Add datacenter_path attribute in oslo.vmware + dc_obj = oslo_datacenter.Datacenter(ref=dc_moref, name=dc_name) + dc_obj.path = datacenter_path + return dc_obj + + def _get_datastore(self, datacenter_path, datastore_name): + dc_obj = self._get_datacenter(datacenter_path) + datastore_ret = self.session.invoke_api( + vim_util, 'get_object_property', self.session.vim, dc_obj.ref, + 'datastore') + if datastore_ret: + datastore_refs = datastore_ret.ManagedObjectReference + for ds_ref in datastore_refs: + ds_obj = oslo_datastore.get_datastore_by_ref(self.session, + ds_ref) + if ds_obj.name == datastore_name: + ds_obj.datacenter = dc_obj + return ds_obj + + def _get_freespace(self, ds_obj): + # TODO(sabari): Move this function into oslo_vmware's datastore object. + return self.session.invoke_api( + vim_util, 'get_object_property', self.session.vim, ds_obj.ref, + 'summary.freeSpace') + + def _parse_datastore_info_and_weight(self, datastore): + weight = 0 + parts = map(lambda x: x.strip(), datastore.rsplit(":", 2)) + if len(parts) < 2: + msg = _('vmware_datastores format must be ' + 'datacenter_path:datastore_name:weight or ' + 'datacenter_path:datastore_name') + LOG.error(msg) + raise exceptions.BadStoreConfiguration( + store_name='vmware_datastore', reason=msg) + if len(parts) == 3 and parts[2]: + weight = parts[2] + if not weight.isdigit(): + msg = (_('Invalid weight value %(weight)s in ' + 'vmware_datastores configuration') % + {'weight': weight}) + LOG.exception(msg) + raise exceptions.BadStoreConfiguration( + store_name="vmware_datastore", reason=msg) + datacenter_path, datastore_name = parts[0], parts[1] + if not datacenter_path or not datastore_name: + msg = _('Invalid datacenter_path or datastore_name specified ' + 'in vmware_datastores configuration') + LOG.exception(msg) + raise exceptions.BadStoreConfiguration( + store_name="vmware_datastore", reason=msg) + return datacenter_path, datastore_name, weight + + def _build_datastore_weighted_map(self, datastores): + """Build an ordered map where the key is a weight and the value is a + Datastore object. + + :param: a list of datastores in the format + datacenter_path:datastore_name:weight + :return: a map with key-value <weight>:<Datastore> + """ + ds_map = {} + for ds in datastores: + dc_path, name, weight = self._parse_datastore_info_and_weight(ds) + # Fetch the server side reference. + ds_obj = self._get_datastore(dc_path, name) + if not ds_obj: msg = (_("Could not find datastore %(ds_name)s " "in datacenter %(dc_path)s") - % {'ds_name': self.datastore_name, - 'dc_path': self.datacenter_path}) + % {'ds_name': name, + 'dc_path': dc_path}) LOG.error(msg) raise exceptions.BadStoreConfiguration( store_name='vmware_datastore', reason=msg) - else: - _datastore_info_valid = True + ds_map.setdefault(int(weight), []).append(ds_obj) + return ds_map + + def configure_add(self): + if self.conf.glance_store.vmware_datastores: + datastores = self.conf.glance_store.vmware_datastores + else: + # Backwards compatibility for vmware_datastore_name and + # vmware_datacenter_path. + datacenter_path = self.conf.glance_store.vmware_datacenter_path + datastore_name = self._option_get('vmware_datastore_name') + datastores = ['%s:%s:%s' % (datacenter_path, datastore_name, 0)] + + self.datastores = self._build_datastore_weighted_map(datastores) self.store_image_dir = self.conf.glance_store.vmware_store_image_dir + def select_datastore(self, image_size): + """Select a datastore with free space larger than image size.""" + for k, v in sorted(six.iteritems(self.datastores), reverse=True): + max_ds = None + max_fs = 0 + for ds in v: + # Update with current freespace + ds.freespace = self._get_freespace(ds) + if ds.freespace > max_fs: + max_ds = ds + max_fs = ds.freespace + if max_ds and max_ds.freespace >= image_size: + return max_ds + msg = _LE("No datastore found with enough free space to contain an " + "image of size %d") % image_size + LOG.error(msg) + raise exceptions.StorageFull() + def _option_get(self, param): result = getattr(self.conf.glance_store, param) if not result: @@ -325,6 +460,7 @@ class Store(glance_store.Store): request returned an unexpected status. The expected responses are 201 Created and 200 OK. """ + ds = self.select_datastore(image_size) if image_size > 0: headers = {'Content-Length': image_size} image_file = _Reader(image_file) @@ -337,8 +473,8 @@ class Store(glance_store.Store): loc = StoreLocation({'scheme': self.scheme, 'server_host': self.server_host, 'image_dir': self.store_image_dir, - 'datacenter_path': self.datacenter_path, - 'datastore_name': self.datastore_name, + 'datacenter_path': ds.datacenter.path, + 'datastore_name': ds.name, 'image_id': image_id}, self.conf) # NOTE(arnaud): use a decorator when the config is not tied to self cookie = self._build_vim_cookie_header(True) @@ -425,18 +561,15 @@ class Store(glance_store.Store): :raises NotFound if image does not exist """ file_path = '[%s] %s' % ( - self.datastore_name, + location.store_location.datastore_name, location.store_location.path[len(DS_URL_PREFIX):]) - search_index_moref = self.session.vim.service_content.searchIndex - dc_moref = self.session.invoke_api( - self.session.vim, 'FindByInventoryPath', search_index_moref, - inventoryPath=self.datacenter_path) + dc_obj = self._get_datacenter(location.store_location.datacenter_path) delete_task = self.session.invoke_api( self.session.vim, 'DeleteDatastoreFile_Task', self.session.vim.service_content.fileManager, name=file_path, - datacenter=dc_moref) + datacenter=dc_obj.ref) try: self.session.wait_for_task(delete_task) except Exception: |