summaryrefslogtreecommitdiff
path: root/glance_store/_drivers/vmware_datastore.py
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2015-03-09 12:50:25 +0000
committerGerrit Code Review <review@openstack.org>2015-03-09 12:50:25 +0000
commit65b85bfdb718f57265e43325da313dcef9abda97 (patch)
treece84198ee8a478653bcab7d39b4e9e5795cebef3 /glance_store/_drivers/vmware_datastore.py
parentc1e8697f124006ea9096c9a29613ac03a92fbc1b (diff)
parentaa10f66ee09015555ac3ff5f702f9603ca31af9f (diff)
downloadglance_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.py203
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: