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:
hartsocks 2013-09-18 17:06:00 -07:00 committed by Gary Kotton
parent 081236d34e
commit 6bf7e78a6d
3 changed files with 204 additions and 10 deletions

26
nova/tests/virt/vmwareapi/test_vmwareapi_vm_util.py Normal file → Executable file
View 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'])

View File

@ -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!")

View File

@ -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,