diff --git a/oslo_vmware/objects/datastore.py b/oslo_vmware/objects/datastore.py index f61a5895..2966352d 100644 --- a/oslo_vmware/objects/datastore.py +++ b/oslo_vmware/objects/datastore.py @@ -142,7 +142,19 @@ class Datastore(object): for host_mount in host_mounts.DatastoreHostMount: if self.is_datastore_mount_usable(host_mount.mountInfo): hosts.append(host_mount.key) - return hosts + connectables = [] + if hosts: + host_runtimes = session.invoke_api( + vim_util, + 'get_properties_for_a_collection_of_objects', + session.vim, 'HostSystem', hosts, ['runtime']) + for host_object in host_runtimes.objects: + host_props = vim_util.propset_dict(host_object.propSet) + host_runtime = host_props.get('runtime') + if hasattr(host_runtime, 'inMaintenanceMode') and ( + not host_runtime.inMaintenanceMode): + connectables.append(host_object.obj) + return connectables @staticmethod def is_datastore_mount_usable(mount_info): @@ -162,6 +174,9 @@ class Datastore(object): @staticmethod def choose_host(hosts): + if not hosts: + return None + i = random.SystemRandom().randrange(0, len(hosts)) return hosts[i] diff --git a/oslo_vmware/tests/objects/test_datastore.py b/oslo_vmware/tests/objects/test_datastore.py index 28bfacfc..9ee23bb5 100644 --- a/oslo_vmware/tests/objects/test_datastore.py +++ b/oslo_vmware/tests/objects/test_datastore.py @@ -95,7 +95,8 @@ class DatastoreTestCase(base.TestCase): session.vim, ds.ref, 'summary') - def test_get_connected_hosts(self): + def _test_get_connected_hosts(self, in_maintenance_mode, + m1_accessible=True): session = mock.Mock() ds_ref = vim_util.get_moref('ds-0', 'Datastore') ds = datastore.Datastore(ds_ref, 'ds-name') @@ -103,7 +104,7 @@ class DatastoreTestCase(base.TestCase): ds.get_summary.return_value.accessible = False self.assertEqual([], ds.get_connected_hosts(session)) ds.get_summary.return_value.accessible = True - m1 = HostMount("m1", MountInfo('readWrite', True, True)) + m1 = HostMount("m1", MountInfo('readWrite', True, m1_accessible)) m2 = HostMount("m2", MountInfo('read', True, True)) m3 = HostMount("m3", MountInfo('readWrite', False, True)) m4 = HostMount("m4", MountInfo('readWrite', True, False)) @@ -111,12 +112,46 @@ class DatastoreTestCase(base.TestCase): class Prop(object): DatastoreHostMount = [m1, m2, m3, m4] - session.invoke_api = mock.Mock() - session.invoke_api.return_value = Prop() + + class HostRuntime(object): + inMaintenanceMode = in_maintenance_mode + + class HostProp(object): + name = 'runtime' + val = HostRuntime() + + class Object(object): + obj = "m1" + propSet = [HostProp()] + + class Runtime(object): + objects = [Object()] + + session.invoke_api = mock.Mock(side_effect=[Prop(), Runtime()]) hosts = ds.get_connected_hosts(session) + calls = [mock.call(vim_util, 'get_object_property', + session.vim, ds_ref, 'host')] + if m1_accessible: + calls.append( + mock.call(vim_util, + 'get_properties_for_a_collection_of_objects', + session.vim, 'HostSystem', ["m1"], ['runtime'])) + self.assertEqual(calls, session.invoke_api.mock_calls) + return hosts + + def test_get_connected_hosts(self): + hosts = self._test_get_connected_hosts(False) self.assertEqual(1, len(hosts)) self.assertEqual("m1", hosts.pop()) + def test_get_connected_hosts_in_maintenance(self): + hosts = self._test_get_connected_hosts(True) + self.assertEqual(0, len(hosts)) + + def test_get_connected_hosts_ho_hosts(self): + hosts = self._test_get_connected_hosts(False, False) + self.assertEqual(0, len(hosts)) + def test_is_datastore_mount_usable(self): m = MountInfo('readWrite', True, True) self.assertTrue(datastore.Datastore.is_datastore_mount_usable(m)) diff --git a/oslo_vmware/tests/test_vim_util.py b/oslo_vmware/tests/test_vim_util.py index 8e43c955..1b7d4fd2 100644 --- a/oslo_vmware/tests/test_vim_util.py +++ b/oslo_vmware/tests/test_vim_util.py @@ -430,3 +430,105 @@ class VimUtilTest(base.TestCase): entity = mock.Mock() inv_path = vim_util.get_inventory_path(session.vim, entity, 100) self.assertEqual('dc-1', inv_path) + + def test_get_prop_spec(self): + client_factory = mock.Mock() + prop_spec = vim_util.get_prop_spec( + client_factory, "VirtualMachine", ["test_path"]) + self.assertEqual(["test_path"], prop_spec.pathSet) + self.assertEqual("VirtualMachine", prop_spec.type) + + def test_get_obj_spec(self): + client_factory = mock.Mock() + mock_obj = mock.Mock() + obj_spec = vim_util.get_obj_spec( + client_factory, mock_obj, select_set=["abc"]) + self.assertEqual(mock_obj, obj_spec.obj) + self.assertFalse(obj_spec.skip) + self.assertEqual(["abc"], obj_spec.selectSet) + + def test_get_prop_filter_spec(self): + client_factory = mock.Mock() + mock_obj = mock.Mock() + filter_spec = vim_util.get_prop_filter_spec( + client_factory, [mock_obj], ["test_prop"]) + self.assertEqual([mock_obj], filter_spec.objectSet) + self.assertEqual(["test_prop"], filter_spec.propSet) + + @mock.patch('oslo_vmware.vim_util.get_prop_spec') + @mock.patch('oslo_vmware.vim_util.get_obj_spec') + @mock.patch('oslo_vmware.vim_util.get_prop_filter_spec') + def _test_get_properties_for_a_collection_of_objects( + self, objs, max_objects, + mock_get_prop_filter_spec, + mock_get_obj_spec, + mock_get_prop_spec): + vim = mock.Mock() + if len(objs) == 0: + self.assertEqual( + [], vim_util.get_properties_for_a_collection_of_objects( + vim, 'VirtualMachine', [], {})) + return + + mock_prop_spec = mock.Mock() + mock_get_prop_spec.return_value = mock_prop_spec + + mock_get_obj_spec.side_effect = [mock.Mock() + for obj in objs] + get_obj_spec_calls = [mock.call(vim.client.factory, obj) + for obj in objs] + + mock_prop_spec = mock.Mock() + mock_get_prop_spec.return_value = mock_prop_spec + + mock_prop_filter_spec = mock.Mock() + mock_get_prop_filter_spec.return_value = mock_prop_filter_spec + mock_options = mock.Mock() + vim.client.factory.create.return_value = mock_options + + mock_return_value = mock.Mock() + vim.RetrievePropertiesEx.return_value = mock_return_value + res = vim_util.get_properties_for_a_collection_of_objects( + vim, 'VirtualMachine', objs, ['runtime'], max_objects) + self.assertEqual(mock_return_value, res) + + mock_get_prop_spec.assert_called_once_with(vim.client.factory, + 'VirtualMachine', + ['runtime']) + self.assertEqual(get_obj_spec_calls, mock_get_obj_spec.mock_calls) + vim.client.factory.create.assert_called_once_with( + 'ns0:RetrieveOptions') + self.assertEqual(max_objects if max_objects else len(objs), + mock_options.maxObjects) + vim.RetrievePropertiesEx.assert_called_once_with( + vim.service_content.propertyCollector, + specSet=[mock_prop_filter_spec], + options=mock_options) + + def test_get_properties_for_a_collection_of_objects( + self): + objects = ["m1", "m2"] + self._test_get_properties_for_a_collection_of_objects(objects, None) + + def test_get_properties_for_a_collection_of_objects_max_objects_1( + self): + objects = ["m1", "m2"] + self._test_get_properties_for_a_collection_of_objects(objects, 1) + + def test_get_properties_for_a_collection_of_objects_no_objects( + self): + self._test_get_properties_for_a_collection_of_objects([], None) + + def test_propset_dict(self): + self.assertEqual({}, vim_util.propset_dict(None)) + + mock_propset = [] + for i in range(2): + mock_obj = mock.Mock() + mock_obj.name = "test_name_%d" % i + mock_obj.val = "test_val_%d" % i + mock_propset.append(mock_obj) + + self.assertEqual({"test_name_0": "test_val_0", + "test_name_1": "test_val_1"}, + vim_util.propset_dict(mock_propset)) diff --git a/oslo_vmware/vim_util.py b/oslo_vmware/vim_util.py index 9332ae21..62690161 100644 --- a/oslo_vmware/vim_util.py +++ b/oslo_vmware/vim_util.py @@ -553,3 +553,71 @@ def get_http_service_request_spec(client_factory, method, uri): http_service_request_spec.method = method http_service_request_spec.url = uri return http_service_request_spec + + +def get_prop_spec(client_factory, spec_type, properties): + """Builds the Property Spec Object.""" + prop_spec = client_factory.create('ns0:PropertySpec') + prop_spec.type = spec_type + prop_spec.pathSet = properties + return prop_spec + + +def get_obj_spec(client_factory, obj, select_set=None): + """Builds the Object Spec object.""" + obj_spec = client_factory.create('ns0:ObjectSpec') + obj_spec.obj = obj + obj_spec.skip = False + if select_set is not None: + obj_spec.selectSet = select_set + return obj_spec + + +def get_prop_filter_spec(client_factory, obj_spec, prop_spec): + """Builds the Property Filter Spec Object.""" + prop_filter_spec = client_factory.create('ns0:PropertyFilterSpec') + prop_filter_spec.propSet = prop_spec + prop_filter_spec.objectSet = obj_spec + return prop_filter_spec + + +def get_properties_for_a_collection_of_objects(vim, type_, + obj_list, properties, + max_objects=None): + """Gets the list of properties for the collection of + objects of the type specified. + """ + client_factory = vim.client.factory + if len(obj_list) == 0: + return [] + prop_spec = get_prop_spec(client_factory, type_, properties) + lst_obj_specs = [] + for obj in obj_list: + lst_obj_specs.append(get_obj_spec(client_factory, obj)) + prop_filter_spec = get_prop_filter_spec(client_factory, + lst_obj_specs, [prop_spec]) + options = client_factory.create('ns0:RetrieveOptions') + options.maxObjects = max_objects if max_objects else len(obj_list) + return vim.RetrievePropertiesEx( + vim.service_content.propertyCollector, + specSet=[prop_filter_spec], options=options) + + +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 {} + + return {prop.name: prop.val for prop in propset}