summaryrefslogtreecommitdiff
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
parentc1e8697f124006ea9096c9a29613ac03a92fbc1b (diff)
parentaa10f66ee09015555ac3ff5f702f9603ca31af9f (diff)
downloadglance_store-65b85bfdb718f57265e43325da313dcef9abda97.tar.gz
Merge "VMware: Support Multiple Datastores"
-rw-r--r--glance_store/_drivers/vmware_datastore.py203
-rw-r--r--tests/unit/test_opts.py1
-rw-r--r--tests/unit/test_vmware_store.py235
3 files changed, 392 insertions, 47 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:
diff --git a/tests/unit/test_opts.py b/tests/unit/test_opts.py
index cc4f778..5559dec 100644
--- a/tests/unit/test_opts.py
+++ b/tests/unit/test_opts.py
@@ -113,6 +113,7 @@ class OptsTestCase(base.StoreBaseTest):
'vmware_api_retry_count',
'vmware_datacenter_path',
'vmware_datastore_name',
+ 'vmware_datastores',
'vmware_server_host',
'vmware_server_password',
'vmware_server_username',
diff --git a/tests/unit/test_vmware_store.py b/tests/unit/test_vmware_store.py
index 773e135..22c3ac9 100644
--- a/tests/unit/test_vmware_store.py
+++ b/tests/unit/test_vmware_store.py
@@ -19,8 +19,10 @@ import hashlib
import uuid
import mock
-from oslo.vmware import api
from oslo_utils import units
+from oslo_vmware import api
+from oslo_vmware.objects import datacenter as oslo_datacenter
+from oslo_vmware.objects import datastore as oslo_datastore
import six
import glance_store._drivers.vmware_datastore as vm_store
@@ -79,11 +81,20 @@ class FakeHTTPConnection(object):
pass
+def fake_datastore_obj(*args, **kwargs):
+ dc_obj = oslo_datacenter.Datacenter(ref='fake-ref',
+ name='fake-name')
+ dc_obj.path = args[0]
+ return oslo_datastore.Datastore(ref='fake-ref',
+ datacenter=dc_obj,
+ name=args[1])
+
+
class TestStore(base.StoreBaseTest,
test_store_capabilities.TestStoreCapabilitiesChecking):
- @mock.patch('oslo.vmware.api.VMwareAPISession', autospec=True)
- def setUp(self, mock_session):
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ def setUp(self, mock_get_datastore):
"""Establish a clean test environment."""
super(TestStore, self).setUp()
@@ -98,6 +109,7 @@ class TestStore(base.StoreBaseTest,
vmware_datastore_name=VMWARE_DS['vmware_datastore_name'],
vmware_datacenter_path=VMWARE_DS['vmware_datacenter_path'])
+ mock_get_datastore.side_effect = fake_datastore_obj
backend.create_stores(self.conf)
self.store = backend.get_store_from_scheme('vsphere')
@@ -105,7 +117,8 @@ class TestStore(base.StoreBaseTest,
self.store.store_image_dir = (
VMWARE_DS['vmware_store_image_dir'])
- def test_get(self):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_get(self, mock_api_session):
"""Test a "normal" retrieval of an image in chunks."""
expected_image_size = 31
expected_returns = ['I am a teapot, short and stout\n']
@@ -119,7 +132,8 @@ class TestStore(base.StoreBaseTest,
chunks = [c for c in image_file]
self.assertEqual(expected_returns, chunks)
- def test_get_non_existing(self):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_get_non_existing(self, mock_api_session):
"""
Test that trying to retrieve an image that doesn't exist
raises an error
@@ -131,9 +145,12 @@ class TestStore(base.StoreBaseTest,
HttpConn.return_value = FakeHTTPConnection(status=404)
self.assertRaises(exceptions.NotFound, self.store.get, loc)
+ @mock.patch.object(vm_store.Store, 'select_datastore')
@mock.patch.object(vm_store._Reader, 'size')
- def test_add(self, fake_size):
+ @mock.patch.object(api, 'VMwareAPISession')
+ def test_add(self, fake_api_session, fake_size, fake_select_datastore):
"""Test that we can add an image via the VMware backend."""
+ fake_select_datastore.return_value = self.store.datastores[0][0]
expected_image_id = str(uuid.uuid4())
expected_size = FIVE_KB
expected_contents = "*" * expected_size
@@ -159,12 +176,16 @@ class TestStore(base.StoreBaseTest,
self.assertEqual(expected_size, size)
self.assertEqual(expected_checksum, checksum)
+ @mock.patch.object(vm_store.Store, 'select_datastore')
@mock.patch.object(vm_store._Reader, 'size')
- def test_add_size_zero(self, fake_size):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_add_size_zero(self, mock_api_session, fake_size,
+ fake_select_datastore):
"""
Test that when specifying size zero for the image to add,
the actual size of the image is returned.
"""
+ fake_select_datastore.return_value = self.store.datastores[0][0]
expected_image_id = str(uuid.uuid4())
expected_size = FIVE_KB
expected_contents = "*" * expected_size
@@ -189,7 +210,8 @@ class TestStore(base.StoreBaseTest,
self.assertEqual(expected_size, size)
self.assertEqual(expected_checksum, checksum)
- def test_delete(self):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_delete(self, mock_api_session):
"""Test we can delete an existing image in the VMware store."""
loc = location.get_location_from_uri(
"vsphere://127.0.0.1/folder/openstack_glance/%s?"
@@ -202,7 +224,8 @@ class TestStore(base.StoreBaseTest,
HttpConn.return_value = FakeHTTPConnection(status=404)
self.assertRaises(exceptions.NotFound, self.store.get, loc)
- def test_get_size(self):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_get_size(self, mock_api_session):
"""
Test we can get the size of an existing image in the VMware store
"""
@@ -214,7 +237,8 @@ class TestStore(base.StoreBaseTest,
image_size = self.store.get_size(loc)
self.assertEqual(image_size, 31)
- def test_get_size_non_existing(self):
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_get_size_non_existing(self, mock_api_session):
"""
Test that trying to retrieve an image size that doesn't exist
raises an error
@@ -329,8 +353,64 @@ class TestStore(base.StoreBaseTest,
except exceptions.BadStoreConfiguration:
self.fail()
+ def test_sanity_check_multiple_datastores(self):
+ self.store.conf.glance_store.vmware_api_retry_count = 1
+ self.store.conf.glance_store.vmware_task_poll_interval = 1
+ # Check both vmware_datastore_name and vmware_datastores defined.
+ self.store.conf.glance_store.vmware_datastores = ['a:b:0']
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._sanity_check)
+ # Both vmware_datastore_name and vmware_datastores are not defined.
+ self.store.conf.glance_store.vmware_datastore_name = None
+ self.store.conf.glance_store.vmware_datastores = None
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._sanity_check)
+ self.store.conf.glance_store.vmware_datastore_name = None
+ self.store.conf.glance_store.vmware_datastores = ['a:b:0', 'a:d:0']
+ try:
+ self.store._sanity_check()
+ except exceptions.BadStoreConfiguration:
+ self.fail()
+
+ def test_parse_datastore_info_and_weight_less_opts(self):
+ datastore = 'a'
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._parse_datastore_info_and_weight,
+ datastore)
+
+ def test_parse_datastore_info_and_weight_invalid_weight(self):
+ datastore = 'a:b:c'
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._parse_datastore_info_and_weight,
+ datastore)
+
+ def test_parse_datastore_info_and_weight_empty_opts(self):
+ datastore = 'a: :0'
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._parse_datastore_info_and_weight,
+ datastore)
+ datastore = ':b:0'
+ self.assertRaises(exceptions.BadStoreConfiguration,
+ self.store._parse_datastore_info_and_weight,
+ datastore)
+
+ def test_parse_datastore_info_and_weight(self):
+ datastore = 'a:b:100'
+ parts = self.store._parse_datastore_info_and_weight(datastore)
+ self.assertEqual('a', parts[0])
+ self.assertEqual('b', parts[1])
+ self.assertEqual('100', parts[2])
+
+ def test_parse_datastore_info_and_weight_default_weight(self):
+ datastore = 'a:b'
+ parts = self.store._parse_datastore_info_and_weight(datastore)
+ self.assertEqual('a', parts[0])
+ self.assertEqual('b', parts[1])
+ self.assertEqual(0, parts[2])
+
+ @mock.patch.object(vm_store.Store, 'select_datastore')
@mock.patch.object(api, 'VMwareAPISession')
- def test_unexpected_status(self, mock_api_session):
+ def test_unexpected_status(self, mock_api_session, mock_select_datastore):
expected_image_id = str(uuid.uuid4())
expected_size = FIVE_KB
expected_contents = "*" * expected_size
@@ -344,6 +424,9 @@ class TestStore(base.StoreBaseTest,
@mock.patch.object(api, 'VMwareAPISession')
def test_reset_session(self, mock_api_session):
+ # Initialize session and reset mock before testing.
+ self.store.reset_session()
+ mock_api_session.reset_mock()
self.store.reset_session(force=False)
self.assertFalse(mock_api_session.called)
self.store.reset_session()
@@ -353,6 +436,9 @@ class TestStore(base.StoreBaseTest,
@mock.patch.object(api, 'VMwareAPISession')
def test_build_vim_cookie_header_active(self, mock_api_session):
+ # Initialize session and reset mock before testing.
+ self.store.reset_session()
+ mock_api_session.reset_mock()
self.store.session.is_current_session_active = mock.Mock()
self.store.session.is_current_session_active.return_value = True
self.store._build_vim_cookie_header(True)
@@ -360,6 +446,9 @@ class TestStore(base.StoreBaseTest,
@mock.patch.object(api, 'VMwareAPISession')
def test_build_vim_cookie_header_expired(self, mock_api_session):
+ # Initialize session and reset mock before testing.
+ self.store.reset_session()
+ mock_api_session.reset_mock()
self.store.session.is_current_session_active = mock.Mock()
self.store.session.is_current_session_active.return_value = False
self.store._build_vim_cookie_header(True)
@@ -367,13 +456,18 @@ class TestStore(base.StoreBaseTest,
@mock.patch.object(api, 'VMwareAPISession')
def test_build_vim_cookie_header_expired_noverify(self, mock_api_session):
+ # Initialize session and reset mock before testing.
+ self.store.reset_session()
+ mock_api_session.reset_mock()
self.store.session.is_current_session_active = mock.Mock()
self.store.session.is_current_session_active.return_value = False
self.store._build_vim_cookie_header()
self.assertFalse(mock_api_session.called)
+ @mock.patch.object(vm_store.Store, 'select_datastore')
@mock.patch.object(api, 'VMwareAPISession')
- def test_add_ioerror(self, mock_api_session):
+ def test_add_ioerror(self, mock_api_session, mock_select_datastore):
+ mock_select_datastore.return_value = self.store.datastores[0][0]
expected_image_id = str(uuid.uuid4())
expected_size = FIVE_KB
expected_contents = "*" * expected_size
@@ -390,3 +484,120 @@ class TestStore(base.StoreBaseTest,
exp_url = 'scheme://example.com/path?key1=val1%3Fsort%3Dtrue&key2=val2'
self.assertEqual(exp_url,
utils.sort_url_by_qs_keys(url))
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(api, 'VMwareAPISession')
+ def test_build_datastore_weighted_map(self, mock_api_session, mock_ds_obj):
+ datastores = ['a:b:100', 'c:d:100', 'e:f:200']
+ mock_ds_obj.side_effect = fake_datastore_obj
+ ret = self.store._build_datastore_weighted_map(datastores)
+ ds = ret[200]
+ self.assertEqual('e', ds[0].datacenter.path)
+ self.assertEqual('f', ds[0].name)
+ ds = ret[100]
+ self.assertEqual(2, len(ds))
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(api, 'VMwareAPISession')
+ def test_build_datastore_weighted_map_equal_weight(self, mock_api_session,
+ mock_ds_obj):
+ datastores = ['a:b:200', 'a:b:200']
+ mock_ds_obj.side_effect = fake_datastore_obj
+ ret = self.store._build_datastore_weighted_map(datastores)
+ ds = ret[200]
+ self.assertEqual(2, len(ds))
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(api, 'VMwareAPISession')
+ def test_build_datastore_weighted_map_empty_list(self, mock_api_session,
+ mock_ds_ref):
+ datastores = []
+ ret = self.store._build_datastore_weighted_map(datastores)
+ self.assertEqual({}, ret)
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(vm_store.Store, '_get_freespace')
+ def test_select_datastore_insufficient_freespace(self, mock_get_freespace,
+ mock_ds_ref):
+ datastores = ['a:b:100', 'c:d:100', 'e:f:200']
+ image_size = 10
+ self.store.datastores = (
+ self.store._build_datastore_weighted_map(datastores))
+ freespaces = [5, 5, 5]
+
+ def fake_get_fp(*args, **kwargs):
+ return freespaces.pop(0)
+ mock_get_freespace.side_effect = fake_get_fp
+ self.assertRaises(exceptions.StorageFull,
+ self.store.select_datastore, image_size)
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(vm_store.Store, '_get_freespace')
+ def test_select_datastore_insufficient_fs_one_ds(self, mock_get_freespace,
+ mock_ds_ref):
+ # Tests if fs is updated with just one datastore.
+ datastores = ['a:b:100']
+ image_size = 10
+ self.store.datastores = (
+ self.store._build_datastore_weighted_map(datastores))
+ freespaces = [5]
+
+ def fake_get_fp(*args, **kwargs):
+ return freespaces.pop(0)
+ mock_get_freespace.side_effect = fake_get_fp
+ self.assertRaises(exceptions.StorageFull,
+ self.store.select_datastore, image_size)
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(vm_store.Store, '_get_freespace')
+ def test_select_datastore_equal_freespace(self, mock_get_freespace,
+ mock_ds_obj):
+ datastores = ['a:b:100', 'c:d:100', 'e:f:200']
+ image_size = 10
+ mock_ds_obj.side_effect = fake_datastore_obj
+ self.store.datastores = (
+ self.store._build_datastore_weighted_map(datastores))
+ freespaces = [11, 11, 11]
+
+ def fake_get_fp(*args, **kwargs):
+ return freespaces.pop(0)
+ mock_get_freespace.side_effect = fake_get_fp
+
+ ds = self.store.select_datastore(image_size)
+ self.assertEqual('e', ds.datacenter.path)
+ self.assertEqual('f', ds.name)
+
+ @mock.patch.object(vm_store.Store, '_get_datastore')
+ @mock.patch.object(vm_store.Store, '_get_freespace')
+ def test_select_datastore_contention(self, mock_get_freespace,
+ mock_ds_obj):
+ datastores = ['a:b:100', 'c:d:100', 'e:f:200']
+ image_size = 10
+ mock_ds_obj.side_effect = fake_datastore_obj
+ self.store.datastores = (
+ self.store._build_datastore_weighted_map(datastores))
+ freespaces = [5, 11, 12]
+
+ def fake_get_fp(*args, **kwargs):
+ return freespaces.pop(0)
+ mock_get_freespace.side_effect = fake_get_fp
+ ds = self.store.select_datastore(image_size)
+ self.assertEqual('c', ds.datacenter.path)
+ self.assertEqual('d', ds.name)
+
+ def test_select_datastore_empty_list(self):
+ datastores = []
+ self.store.datastores = (
+ self.store._build_datastore_weighted_map(datastores))
+ self.assertRaises(exceptions.StorageFull,
+ self.store.select_datastore, 10)
+
+ @mock.patch('oslo_vmware.api.VMwareAPISession')
+ def test_get_datacenter_ref(self, mock_api_session):
+ datacenter_path = 'Datacenter1'
+ self.store._get_datacenter(datacenter_path)
+ self.store.session.invoke_api.assert_called_with(
+ self.store.session.vim,
+ 'FindByInventoryPath',
+ self.store.session.vim.service_content.searchIndex,
+ inventoryPath=datacenter_path)