Fixes datastore selection bug
Currently the vmwareapi drivers will only select the first
available datastore it finds, even when there are other
datastores with capacity, it will fill one datastore completely
and then die.
Closes-bug: 1227825
Change-Id: I24f85114ea3f014950c27c0c91a6be352990d263
(cherry picked from commit 6c4b89a2ad
)
This commit is contained in:
parent
081236d34e
commit
6bf7e78a6d
26
nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
Normal file → Executable file
26
nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py
Normal file → Executable file
@ -344,3 +344,29 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
|
||||
refs = vm_util.get_all_cluster_refs_by_name(fake_session(fake_objects),
|
||||
['cluster'])
|
||||
self.assertTrue(not refs)
|
||||
|
||||
def test_propset_dict_simple(self):
|
||||
ObjectContent = collections.namedtuple('ObjectContent', ['propSet'])
|
||||
DynamicProperty = collections.namedtuple('Property', ['name', 'val'])
|
||||
|
||||
object = ObjectContent(propSet=[
|
||||
DynamicProperty(name='foo', val="bar")])
|
||||
propdict = vm_util.propset_dict(object.propSet)
|
||||
self.assertEqual("bar", propdict['foo'])
|
||||
|
||||
def test_propset_dict_complex(self):
|
||||
ObjectContent = collections.namedtuple('ObjectContent', ['propSet'])
|
||||
DynamicProperty = collections.namedtuple('Property', ['name', 'val'])
|
||||
MoRef = collections.namedtuple('Val', ['value'])
|
||||
|
||||
object = ObjectContent(propSet=[
|
||||
DynamicProperty(name='foo', val="bar"),
|
||||
DynamicProperty(name='some.thing',
|
||||
val=MoRef(value='else')),
|
||||
DynamicProperty(name='another.thing', val='value')])
|
||||
|
||||
propdict = vm_util.propset_dict(object.propSet)
|
||||
self.assertEqual("bar", propdict['foo'])
|
||||
self.assertTrue(hasattr(propdict['some.thing'], 'value'))
|
||||
self.assertEqual("else", propdict['some.thing'].value)
|
||||
self.assertEqual("value", propdict['another.thing'])
|
||||
|
@ -0,0 +1,114 @@
|
||||
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import collections
|
||||
import re
|
||||
|
||||
from nova import test
|
||||
from nova.virt.vmwareapi import vm_util
|
||||
|
||||
ResultSet = collections.namedtuple('ResultSet', ['objects'])
|
||||
ResultSetToken = collections.namedtuple('ResultSet', ['objects', 'token'])
|
||||
ObjectContent = collections.namedtuple('ObjectContent', ['obj', 'propSet'])
|
||||
DynamicProperty = collections.namedtuple('Property', ['name', 'val'])
|
||||
MoRef = collections.namedtuple('ManagedObjectReference', ['value'])
|
||||
|
||||
|
||||
class VMwareVMUtilDatastoreSelectionTestCase(test.NoDBTestCase):
|
||||
|
||||
def setUp(self):
|
||||
super(VMwareVMUtilDatastoreSelectionTestCase, self).setUp()
|
||||
self.data = [
|
||||
['VMFS', 'os-some-name', True, 987654321, 12346789],
|
||||
['NFS', 'another-name', True, 9876543210, 123467890],
|
||||
['BAD', 'some-name-bad', True, 98765432100, 1234678900],
|
||||
['VMFS', 'some-name-good', False, 987654321, 12346789],
|
||||
]
|
||||
|
||||
def build_result_set(self, mock_data, name_list=None):
|
||||
# datastores will have a moref_id of ds-000 and
|
||||
# so on based on their index in the mock_data list
|
||||
if name_list is None:
|
||||
name_list = self.propset_name_list
|
||||
|
||||
objects = []
|
||||
for id, row in enumerate(mock_data):
|
||||
obj = ObjectContent(
|
||||
obj=MoRef(value="ds-%03d" % id),
|
||||
propSet=[])
|
||||
for index, value in enumerate(row):
|
||||
obj.propSet.append(
|
||||
DynamicProperty(name=name_list[index], val=row[index]))
|
||||
objects.append(obj)
|
||||
return ResultSet(objects=objects)
|
||||
|
||||
@property
|
||||
def propset_name_list(self):
|
||||
return ['summary.type', 'summary.name', 'summary.accessible',
|
||||
'summary.capacity', 'summary.freeSpace']
|
||||
|
||||
def test_filter_datastores_simple(self):
|
||||
datastores = self.build_result_set(self.data)
|
||||
rec = vm_util._get_datastore_ref_and_name(datastores)
|
||||
|
||||
self.assertIsNotNone(rec[0], "could not find datastore!")
|
||||
self.assertEqual('ds-001', rec[0].value,
|
||||
"didn't find the right datastore!")
|
||||
self.assertEqual(123467890, rec[3],
|
||||
"did not obtain correct freespace!")
|
||||
|
||||
def test_filter_datastores_empty(self):
|
||||
data = []
|
||||
datastores = self.build_result_set(data)
|
||||
|
||||
rec = vm_util._get_datastore_ref_and_name(datastores)
|
||||
|
||||
self.assertIsNone(rec)
|
||||
|
||||
def test_filter_datastores_no_match(self):
|
||||
datastores = self.build_result_set(self.data)
|
||||
datastore_regex = re.compile('no_match.*')
|
||||
|
||||
rec = vm_util._get_datastore_ref_and_name(datastores,
|
||||
datastore_regex)
|
||||
|
||||
self.assertIsNone(rec, "did not fail to match datastore properly")
|
||||
|
||||
def test_filter_datastores_specific_match(self):
|
||||
|
||||
data = [
|
||||
['VMFS', 'os-some-name', True, 987654321, 1234678],
|
||||
['NFS', 'another-name', True, 9876543210, 123467890],
|
||||
['BAD', 'some-name-bad', True, 98765432100, 1234678900],
|
||||
['VMFS', 'some-name-good', True, 987654321, 12346789],
|
||||
['VMFS', 'some-other-good', False, 987654321000, 12346789000],
|
||||
]
|
||||
# only the DS some-name-good is accessible and matches the regex
|
||||
datastores = self.build_result_set(data)
|
||||
datastore_regex = re.compile('.*-good$')
|
||||
|
||||
rec = vm_util._get_datastore_ref_and_name(datastores,
|
||||
datastore_regex)
|
||||
|
||||
self.assertIsNotNone(rec, "could not find datastore!")
|
||||
self.assertEqual('ds-003', rec[0].value,
|
||||
"didn't find the right datastore!")
|
||||
self.assertNotEqual('ds-004', rec[0].value,
|
||||
"accepted an unreachable datastore!")
|
||||
self.assertEqual('some-name-good', rec[1])
|
||||
self.assertEqual(12346789, rec[3],
|
||||
"did not obtain correct freespace!")
|
||||
self.assertEqual(987654321, rec[2],
|
||||
"did not obtain correct capacity!")
|
@ -20,6 +20,7 @@
|
||||
The VMware API VM utility module to build SOAP object specs.
|
||||
"""
|
||||
|
||||
import collections
|
||||
import copy
|
||||
|
||||
from nova import exception
|
||||
@ -835,20 +836,73 @@ def get_host_ref(session, cluster=None):
|
||||
return host_mor
|
||||
|
||||
|
||||
def propset_dict(propset):
|
||||
"""Turn a propset list into a dictionary
|
||||
|
||||
PropSet is an optional attribute on ObjectContent objects
|
||||
that are returned by the VMware API.
|
||||
|
||||
You can read more about these at:
|
||||
http://pubs.vmware.com/vsphere-51/index.jsp
|
||||
#com.vmware.wssdk.apiref.doc/
|
||||
vmodl.query.PropertyCollector.ObjectContent.html
|
||||
|
||||
:param propset: a property "set" from ObjectContent
|
||||
:return: dictionary representing property set
|
||||
"""
|
||||
if propset is None:
|
||||
return {}
|
||||
|
||||
#TODO(hartsocks): once support for Python 2.6 is dropped
|
||||
# change to {[(prop.name, prop.val) for prop in propset]}
|
||||
return dict([(prop.name, prop.val) for prop in propset])
|
||||
|
||||
|
||||
def _get_datastore_ref_and_name(data_stores, datastore_regex=None):
|
||||
for elem in data_stores.objects:
|
||||
propset_dict = dict([(prop.name, prop.val) for prop in elem.propSet])
|
||||
# selects the datastore with the most freespace
|
||||
"""Find a usable datastore in a given RetrieveResult object.
|
||||
|
||||
:param data_stores: a RetrieveResult object from vSphere API call
|
||||
:param datastore_regex: an optional regular expression to match names
|
||||
:return: datastore_ref, datastore_name, capacity, freespace
|
||||
"""
|
||||
DSRecord = collections.namedtuple(
|
||||
'DSRecord', ['datastore', 'name', 'capacity', 'freespace'])
|
||||
|
||||
# we lean on checks performed in caller methods to validate the
|
||||
# datastore reference is not None. If it is, the caller handles
|
||||
# a None reference as appropriate in its context.
|
||||
found_ds = DSRecord(datastore=None, name=None, capacity=None, freespace=0)
|
||||
|
||||
# datastores is actually a RetrieveResult object from vSphere API call
|
||||
for obj_content in data_stores.objects:
|
||||
# the propset attribute "need not be set" by returning API
|
||||
if not hasattr(obj_content, 'propSet'):
|
||||
continue
|
||||
|
||||
propdict = propset_dict(obj_content.propSet)
|
||||
# Local storage identifier vSphere doesn't support CIFS or
|
||||
# vfat for datastores, therefore filtered
|
||||
ds_type = propset_dict['summary.type']
|
||||
ds_name = propset_dict['summary.name']
|
||||
ds_type = propdict['summary.type']
|
||||
ds_name = propdict['summary.name']
|
||||
if ((ds_type == 'VMFS' or ds_type == 'NFS') and
|
||||
propset_dict['summary.accessible']):
|
||||
if not datastore_regex or datastore_regex.match(ds_name):
|
||||
return (elem.obj,
|
||||
ds_name,
|
||||
propset_dict['summary.capacity'],
|
||||
propset_dict['summary.freeSpace'])
|
||||
propdict['summary.accessible']):
|
||||
if datastore_regex is None or datastore_regex.match(ds_name):
|
||||
new_ds = DSRecord(
|
||||
datastore=obj_content.obj,
|
||||
name=ds_name,
|
||||
capacity=propdict['summary.capacity'],
|
||||
freespace=propdict['summary.freeSpace'])
|
||||
# find the largest freespace to return
|
||||
if new_ds.freespace > found_ds.freespace:
|
||||
found_ds = new_ds
|
||||
|
||||
#TODO(hartsocks): refactor driver to use DSRecord namedtuple
|
||||
# using DSRecord through out will help keep related information
|
||||
# together and improve readability and organisation of the code.
|
||||
if found_ds.datastore is not None:
|
||||
return (found_ds.datastore, found_ds.name,
|
||||
found_ds.capacity, found_ds.freespace)
|
||||
|
||||
|
||||
def get_datastore_ref_and_name(session, cluster=None, host=None,
|
||||
|
Loading…
Reference in New Issue
Block a user