Merge "VMware: StableMoRefProxy for moref recovery"

This commit is contained in:
Zuul 2022-04-29 16:33:54 +00:00 committed by Gerrit Code Review
commit 3ac9948f98
10 changed files with 417 additions and 162 deletions

View File

@ -23,11 +23,11 @@ import collections
import sys
from oslo_log import log as logging
from oslo_serialization import jsonutils
from oslo_utils import units
from oslo_utils import uuidutils
from oslo_vmware import exceptions as vexc
from oslo_vmware.objects import datastore as ds_obj
from oslo_vmware import vim_util
from nova import exception
from nova.virt.vmwareapi import constants
@ -76,23 +76,34 @@ def cleanup():
_db_content[c] = {}
def _create_object(table, table_obj):
def _create_object(table_obj):
"""Create an object in the db."""
_db_content.setdefault(table, {})
_db_content[table][table_obj.obj] = table_obj
_db_content.setdefault(table_obj.obj._type, {})
update_object(table_obj)
def _get_object(obj_ref):
def get_object(obj_ref):
"""Get object for the give reference."""
return _db_content[obj_ref.type][obj_ref]
return _db_content[obj_ref.type][obj_ref.value]
def _get_objects(obj_type):
def get_objects(obj_type):
"""Get objects of the type."""
lst_objs = FakeRetrieveResult()
for key in _db_content[obj_type]:
lst_objs.add_object(_db_content[obj_type][key])
return lst_objs
return _db_content[obj_type].values()
def get_first_object(obj_type):
"""Get the first object of an object type"""
return next(iter(_db_content[obj_type].values()))
def get_first_object_ref(obj_type):
"""Get the first reference of an object type"""
return get_first_object(obj_type).obj
def _no_objects_of_type(obj_type):
return not _db_content.get(obj_type)
def _convert_to_array_of_mor(mors):
@ -135,21 +146,19 @@ class FakeRetrieveResult(object):
if token is not None:
self.token = token
def add_object(self, object):
self.objects.append(object)
def add_object(self, obj):
self.objects.append(obj)
def _get_object_refs(obj_type):
"""Get object References of the type."""
lst_objs = []
for key in _db_content[obj_type]:
lst_objs.append(key)
return lst_objs
def get_object_refs(obj_type):
"""Get iterator over object References of the type."""
for obj in _db_content[obj_type].values():
yield obj.obj
def _update_object(table, table_obj):
def update_object(table_obj):
"""Update objects of the type."""
_db_content[table][table_obj.obj] = table_obj
_db_content[table_obj.obj._type][table_obj.obj.value] = table_obj
class Prop(object):
@ -177,6 +186,14 @@ class ManagedObjectReference(object):
self.type = name
self._type = name
def __repr__(self):
return f'{self._type}:{self.value}'
def __eq__(self, other):
return (other is not None and
vim_util.get_moref_value(other) == self.value and
vim_util.get_moref_type(other) == self.type)
class ObjectContent(object):
"""ObjectContent array holds dynamic properties."""
@ -262,8 +279,11 @@ class ManagedObject(object):
return prefix + "-" + str(self.__class__._counter)
def __repr__(self):
return jsonutils.dumps({elem.name: elem.val
for elem in self.propSet})
# We can't just dump the managed-object, because it may be circular
return "{}:{}({})".format(self.obj._type, self.obj.value,
", ".join(
"{}={}".format(p.name, p.val if p.name == "name" else "<>")
for p in self.propSet))
class DataObject(object):
@ -593,8 +613,7 @@ class ResourcePool(ManagedObject):
class DatastoreHostMount(DataObject):
def __init__(self, value='host-100'):
super(DatastoreHostMount, self).__init__()
host_ref = (_db_content["HostSystem"]
[list(_db_content["HostSystem"].keys())[0]].obj)
host_ref = get_first_object_ref("HostSystem")
host_system = DataObject()
host_system.ManagedObjectReference = [host_ref]
host_system.value = value
@ -621,9 +640,15 @@ class ClusterComputeResource(ManagedObject):
summary.effectiveCpu = 10000
self.set("summary", summary)
vm_list = DataObject()
vm_list.ManagedObjectReference = []
self.set("vm", vm_list)
def _add_root_resource_pool(self, r_pool):
if r_pool:
self.set("resourcePool", r_pool)
pool = get_object(r_pool)
self.set("vm", pool.get("vm"))
def _add_host(self, host_sys):
if host_sys:
@ -659,7 +684,7 @@ class ClusterComputeResource(ManagedObject):
# Compute the aggregate stats
summary.numHosts = len(hosts.ManagedObjectReference)
for host_ref in hosts.ManagedObjectReference:
host_sys = _get_object(host_ref)
host_sys = get_object(host_ref)
connected = host_sys.get("connected")
host_summary = host_sys.get("summary")
summary.numCpuCores += host_summary.hardware.numCpuCores
@ -717,14 +742,17 @@ class HostSystem(ManagedObject):
maintenance_mode=False):
super(HostSystem, self).__init__("host")
self.set("name", name)
if _db_content.get("HostNetworkSystem", None) is None:
if _no_objects_of_type("HostNetworkSystem"):
create_host_network_system()
if not _get_object_refs('HostStorageSystem'):
if _no_objects_of_type("HostStorageSystem"):
create_host_storage_system()
host_net_key = list(_db_content["HostNetworkSystem"].keys())[0]
host_net_sys = _db_content["HostNetworkSystem"][host_net_key].obj
self.set("configManager.networkSystem", host_net_sys)
host_storage_sys_key = _get_object_refs('HostStorageSystem')[0]
host_net_obj = get_first_object("HostNetworkSystem")
host_net_ref = host_net_obj.obj
self.set("configManager.networkSystem", host_net_ref)
host_storage_sys_key = get_first_object_ref('HostStorageSystem')
self.set("configManager.storageSystem", host_storage_sys_key)
if not ds_ref:
@ -779,10 +807,9 @@ class HostSystem(ManagedObject):
self.set("config.network.pnic", net_info_pnic)
self.set("connected", connected)
if _db_content.get("Network", None) is None:
if _no_objects_of_type("Network"):
create_network()
net_ref = _db_content["Network"][
list(_db_content["Network"].keys())[0]].obj
net_ref = get_first_object_ref("Network")
network_do = DataObject()
network_do.ManagedObjectReference = [net_ref]
self.set("network", network_do)
@ -821,7 +848,7 @@ class HostSystem(ManagedObject):
self.set("config.storageDevice.hostBusAdapter", host_bus_adapter_array)
# Set the same on the storage system managed object
host_storage_sys = _get_object(host_storage_sys_key)
host_storage_sys = get_object(host_storage_sys_key)
host_storage_sys.set('storageDeviceInfo.hostBusAdapter',
host_bus_adapter_array)
@ -882,17 +909,15 @@ class Datacenter(ManagedObject):
def __init__(self, name="ha-datacenter", ds_ref=None):
super(Datacenter, self).__init__("dc")
self.set("name", name)
if _db_content.get("Folder", None) is None:
if _no_objects_of_type("Folder"):
create_folder()
folder_ref = _db_content["Folder"][
list(_db_content["Folder"].keys())[0]].obj
folder_ref = get_first_object_ref("Folder")
folder_do = DataObject()
folder_do.ManagedObjectReference = [folder_ref]
self.set("vmFolder", folder_ref)
if _db_content.get("Network", None) is None:
if _no_objects_of_type("Network"):
create_network()
net_ref = _db_content["Network"][
list(_db_content["Network"].keys())[0]].obj
net_ref = get_first_object_ref("Network")
network_do = DataObject()
network_do.ManagedObjectReference = [net_ref]
self.set("network", network_do)
@ -927,54 +952,56 @@ class Task(ManagedObject):
def create_host_network_system():
host_net_system = HostNetworkSystem()
_create_object("HostNetworkSystem", host_net_system)
_create_object(host_net_system)
def create_host_storage_system():
host_storage_system = HostStorageSystem()
_create_object("HostStorageSystem", host_storage_system)
_create_object(host_storage_system)
def create_host(ds_ref=None):
host_system = HostSystem(ds_ref=ds_ref)
_create_object('HostSystem', host_system)
_create_object(host_system)
def create_datacenter(name, ds_ref=None):
data_center = Datacenter(name, ds_ref)
_create_object('Datacenter', data_center)
_create_object(data_center)
def create_datastore(name, capacity, free):
data_store = Datastore(name, capacity, free)
_create_object('Datastore', data_store)
_create_object(data_store)
return data_store.obj
def create_res_pool():
res_pool = ResourcePool()
_create_object('ResourcePool', res_pool)
_create_object(res_pool)
return res_pool.obj
def create_folder():
folder = Folder()
_create_object('Folder', folder)
_create_object(folder)
return folder.obj
def create_network():
network = Network()
_create_object('Network', network)
_create_object(network)
def create_cluster(name, ds_ref):
cluster = ClusterComputeResource(name=name)
cluster._add_host(_get_object_refs("HostSystem")[0])
cluster._add_host(_get_object_refs("HostSystem")[1])
for i, host in enumerate(get_object_refs("HostSystem")):
cluster._add_host(host)
if i >= 1:
break
cluster._add_datastore(ds_ref)
cluster._add_root_resource_pool(create_res_pool())
_create_object('ClusterComputeResource', cluster)
_create_object(cluster)
return cluster
@ -993,16 +1020,15 @@ def create_vm(uuid=None, name=None,
devices = []
if vmPathName is None:
vm_path = ds_obj.DatastorePath(
list(_db_content['Datastore'].values())[0])
vm_path = ds_obj.DatastorePath(get_first_object("Datastore"))
else:
vm_path = ds_obj.DatastorePath.parse(vmPathName)
if res_pool_ref is None:
res_pool_ref = list(_db_content['ResourcePool'].keys())[0]
res_pool_ref = get_first_object_ref("ResourcePool")
if host_ref is None:
host_ref = list(_db_content["HostSystem"].keys())[0]
host_ref = get_first_object_ref("HostSystem")
# Fill in the default path to the vmx file if we were only given a
# datastore. Note that if you create a VM with vmPathName '[foo]', when you
@ -1011,9 +1037,9 @@ def create_vm(uuid=None, name=None,
if vm_path.rel_path == '':
vm_path = vm_path.join(name, name + '.vmx')
for key, value in _db_content["Datastore"].items():
for value in get_objects("Datastore"):
if value.get('summary.name') == vm_path.datastore:
ds = key
ds = value.obj
break
else:
ds = create_datastore(vm_path.datastore, 1024, 500)
@ -1030,9 +1056,9 @@ def create_vm(uuid=None, name=None,
"instanceUuid": uuid,
"version": version}
vm = VirtualMachine(**vm_dict)
_create_object("VirtualMachine", vm)
_create_object(vm)
res_pool = _get_object(res_pool_ref)
res_pool = get_object(res_pool_ref)
res_pool.vm.ManagedObjectReference.append(vm.obj)
return vm.obj
@ -1040,7 +1066,7 @@ def create_vm(uuid=None, name=None,
def create_task(task_name, state="running", result=None, error_fault=None):
task = Task(task_name, state, result, error_fault)
_create_object("Task", task)
_create_object(task)
return task
@ -1103,12 +1129,14 @@ def fake_fetch_image(context, instance, host, port, dc_name, ds_name,
def _get_vm_mdo(vm_ref):
"""Gets the Virtual Machine with the ref from the db."""
if _db_content.get("VirtualMachine", None) is None:
vms = _db_content.get("VirtualMachine")
if not vms:
raise exception.NotFound("There is no VM registered")
if vm_ref not in _db_content.get("VirtualMachine"):
try:
return vms[vm_ref.value]
except KeyError:
raise exception.NotFound("Virtual Machine with ref %s is not "
"there" % vm_ref)
return _db_content.get("VirtualMachine")[vm_ref]
"there" % vm_ref.value)
def _merge_extraconfig(existing, changes):
@ -1354,11 +1382,10 @@ class FakeVim(object):
def _find_all_by_uuid(self, *args, **kwargs):
uuid = kwargs.get('uuid')
vm_refs = []
for vm_ref in _db_content.get("VirtualMachine"):
vm = _get_object(vm_ref)
for vm in get_objects("VirtualMachine"):
vm_uuid = vm.get("summary.config.instanceUuid")
if vm_uuid == uuid:
vm_refs.append(vm_ref)
vm_refs.append(vm.obj)
return vm_refs
def _delete_snapshot(self, method, *args, **kwargs):
@ -1412,7 +1439,7 @@ class FakeVim(object):
vm_dict["extra_config"] = extraConfigs
virtual_machine = VirtualMachine(**vm_dict)
_create_object("VirtualMachine", virtual_machine)
_create_object(virtual_machine)
task_mdo = create_task(method, "success")
return task_mdo.obj
@ -1420,7 +1447,7 @@ class FakeVim(object):
"""Unregisters a VM from the Host System."""
vm_ref = args[0]
_get_vm_mdo(vm_ref)
del _db_content["VirtualMachine"][vm_ref]
del _db_content["VirtualMachine"][vm_ref.value]
task_mdo = create_task(method, "success")
return task_mdo.obj
@ -1491,13 +1518,7 @@ class FakeVim(object):
def _set_power_state(self, method, vm_ref, pwr_state="poweredOn"):
"""Sets power state for the VM."""
if _db_content.get("VirtualMachine", None) is None:
raise exception.NotFound("No Virtual Machine has been "
"registered yet")
if vm_ref not in _db_content.get("VirtualMachine"):
raise exception.NotFound("Virtual Machine with ref %s is not "
"there" % vm_ref)
vm_mdo = _db_content.get("VirtualMachine").get(vm_ref)
vm_mdo = _get_vm_mdo(vm_ref)
vm_mdo.set("runtime.powerState", pwr_state)
task_mdo = create_task(method, "success")
return task_mdo.obj
@ -1526,7 +1547,7 @@ class FakeVim(object):
# This means that we are retrieving props for all managed
# data objects of the specified 'type' in the entire
# inventory. This gets invoked by vim_util.get_objects.
mdo_refs = _db_content[spec_type]
mdo_refs = list(get_object_refs(spec_type))
elif obj_ref.type != spec_type:
# This means that we are retrieving props for the managed
# data objects in the parent object's 'path' property.
@ -1536,7 +1557,7 @@ class FakeVim(object):
# path = 'datastore'
# the above will retrieve all datastores in the given
# cluster.
parent_mdo = _db_content[obj_ref.type][obj_ref]
parent_mdo = get_object(obj_ref)
path = obj.selectSet[0].path
mdo_refs = parent_mdo.get(path).ManagedObjectReference
else:
@ -1545,12 +1566,13 @@ class FakeVim(object):
# vim_util.get_properties_for_a_collection_of_objects.
mdo_refs = [obj_ref]
mdo_list = _db_content[spec_type]
for mdo_ref in mdo_refs:
mdo = _db_content[spec_type][mdo_ref]
prop_list = []
for prop_name in properties:
prop = Prop(prop_name, mdo.get(prop_name))
prop_list.append(prop)
mdo = mdo_list[mdo_ref.value]
prop_list = [
Prop(prop_name, mdo.get(prop_name))
for prop_name in properties
]
obj_content = ObjectContent(mdo.obj, prop_list)
lst_ret_objs.add_object(obj_content)
except Exception:
@ -1560,14 +1582,13 @@ class FakeVim(object):
def _add_port_group(self, method, *args, **kwargs):
"""Adds a port group to the host system."""
_host_sk = list(_db_content["HostSystem"].keys())[0]
host_mdo = _db_content["HostSystem"][_host_sk]
host_mdo = get_first_object("HostSystem")
host_mdo._add_port_group(kwargs.get("portgrp"))
def _add_iscsi_send_tgt(self, method, *args, **kwargs):
"""Adds a iscsi send target to the hba."""
send_targets = kwargs.get('targets')
host_storage_sys = _get_objects('HostStorageSystem').objects[0]
host_storage_sys = get_first_object('HostStorageSystem')
iscsi_hba_array = host_storage_sys.get('storageDeviceInfo'
'.hostBusAdapter')
iscsi_hba = iscsi_hba_array.HostHostBusAdapter[0]

View File

@ -373,8 +373,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase,
def _get_vm_record(self):
# Get record for VM
vms = vmwareapi_fake._get_objects("VirtualMachine")
for vm in vms.objects:
vms = vmwareapi_fake.get_objects("VirtualMachine")
for vm in vms:
if vm.get('name') == vm_util._get_vm_name(self._display_name,
self.uuid):
return vm
@ -1269,7 +1269,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase,
def _snapshot_delete_vm_snapshot_exception(self, exception, call_count=1):
self._create_vm()
fake_vm = vmwareapi_fake._get_objects("VirtualMachine").objects[0].obj
fake_vm = vmwareapi_fake.get_first_object_ref("VirtualMachine")
snapshot_ref = vmwareapi_fake.ManagedObjectReference(
value="Snapshot-123",
name="VirtualMachineSnapshot")
@ -1763,8 +1763,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase,
get_vm_ref.assert_called_once_with(self.conn._session,
self.instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
get_volume_ref.assert_called_once_with(connection_info['data'])
self.assertTrue(get_vmdk_info.called)
attach_disk_to_vm.assert_called_once_with(mock.sentinel.vm_ref,
self.instance, adapter_type, disk_type, vmdk_path='fake-path')
@ -1840,8 +1839,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase,
def test_iscsi_rescan_hba(self):
fake_target_portal = 'fake_target_host:port'
host_storage_sys = vmwareapi_fake._get_objects(
"HostStorageSystem").objects[0]
host_storage_sys = vmwareapi_fake.get_first_object(
"HostStorageSystem")
iscsi_hba_array = host_storage_sys.get('storageDeviceInfo'
'.hostBusAdapter')
iscsi_hba = iscsi_hba_array.HostHostBusAdapter[0]
@ -1861,7 +1860,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase,
def test_iscsi_get_target(self):
data = {'target_portal': 'fake_target_host:port',
'target_iqn': 'fake_target_iqn'}
host = vmwareapi_fake._get_objects('HostSystem').objects[0]
host = vmwareapi_fake.get_first_object('HostSystem')
host._add_iscsi_target(data)
vops = volumeops.VMwareVolumeOps(self.conn._session)
result = vops._iscsi_get_target(data)

View File

@ -22,6 +22,9 @@ Test suite for VMwareAPI Session
"""
import mock
from oslo_vmware import exceptions as vexec
from nova import test
from nova.tests.unit.virt.vmwareapi import fake as vmwareapi_fake
from nova.virt.vmwareapi import session
@ -36,17 +39,47 @@ def _fake_create_session(inst):
inst._session = _session
def _fake_fetch_moref_impl(inst, _):
inst.moref = vmwareapi_fake.ManagedObjectReference(
value=mock.sentinel.moref2)
class FakeStableMoRefProxy(session.StableMoRefProxy):
def __init__(self, ref=None):
super(FakeStableMoRefProxy, self).__init__(
ref or vmwareapi_fake.ManagedObjectReference(
value=mock.sentinel.moref))
def fetch_moref(self, session):
pass
def __repr__(self):
return "FakeStableMoRefProxy({!r})".format(self.moref)
class StableMoRefProxyTestCase(test.NoDBTestCase):
def test_proxy(self):
ref = FakeStableMoRefProxy()
self.assertEqual(mock.sentinel.moref, ref.value)
self.assertEqual("ManagedObject", ref._type)
def test_proxy_classes(self):
# Necessary for suds serialisation
ref = FakeStableMoRefProxy()
self.assertEqual("ManagedObjectReference", ref.__class__.__name__)
class VMwareSessionTestCase(test.NoDBTestCase):
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=False)
def test_call_method(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession,
'_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession,
'invoke_api'),
mock.patch.object(session.VMwareAPISession,
'_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession,
'invoke_api'),
) as (fake_create, fake_invoke):
_session = session.VMwareAPISession()
_session._vim = mock.Mock()
@ -58,13 +91,118 @@ class VMwareSessionTestCase(test.NoDBTestCase):
return_value=True)
def test_call_method_vim(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession,
'_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession,
'invoke_api'),
mock.patch.object(session.VMwareAPISession,
'_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession,
'invoke_api'),
) as (fake_create, fake_invoke):
_session = session.VMwareAPISession()
module = mock.Mock()
_session._call_method(module, 'fira')
fake_invoke.assert_called_once_with(module, 'fira')
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=True)
def test_call_method_no_recovery(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession, '_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession, 'invoke_api'),
mock.patch.object(FakeStableMoRefProxy, 'fetch_moref'),
) as (fake_create, fake_invoke, fake_fetch_moref):
_session = session.VMwareAPISession()
module = mock.Mock()
ref = FakeStableMoRefProxy()
_session._call_method(
module, mock.sentinel.method_arg, ref, ref=ref)
fake_invoke.assert_called_once_with(
module, mock.sentinel.method_arg, ref, ref=ref)
fake_fetch_moref.assert_not_called()
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=True)
def test_call_method_recovery_arg_failed(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession, '_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession, 'invoke_api'),
mock.patch.object(FakeStableMoRefProxy, 'fetch_moref'),
) as (fake_create, fake_invoke, fake_fetch_moref):
_session = session.VMwareAPISession()
module = mock.Mock()
ref = FakeStableMoRefProxy()
fake_invoke.side_effect = [vexec.ManagedObjectNotFoundException]
self.assertRaises(vexec.ManagedObjectNotFoundException,
_session._call_method, module, mock.sentinel.method_arg, ref)
fake_invoke.assert_called_once_with(
module, mock.sentinel.method_arg, ref)
fake_fetch_moref.assert_not_called()
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=True)
def test_call_method_recovery_kwarg_failed(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession, '_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession, 'invoke_api'),
mock.patch.object(FakeStableMoRefProxy, 'fetch_moref'),
) as (fake_create, fake_invoke, fake_fetch_moref):
_session = session.VMwareAPISession()
module = mock.Mock()
ref = FakeStableMoRefProxy()
fake_invoke.side_effect = [vexec.ManagedObjectNotFoundException]
self.assertRaises(vexec.ManagedObjectNotFoundException,
_session._call_method, module,
mock.sentinel.method_arg, ref=ref)
fake_invoke.assert_called_once_with(
module, mock.sentinel.method_arg, ref=ref)
fake_fetch_moref.assert_not_called()
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=True)
def test_call_method_recovery_arg_success(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession, '_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession, 'invoke_api'),
mock.patch.object(FakeStableMoRefProxy,
'fetch_moref', _fake_fetch_moref_impl),
) as (fake_create, fake_invoke, fake_fetch_moref):
_session = session.VMwareAPISession()
module = mock.Mock()
ref = FakeStableMoRefProxy()
fake_invoke.side_effect = [vexec.ManagedObjectNotFoundException(
details=dict(obj=mock.sentinel.moref),
), None]
_session._call_method(module, mock.sentinel.method_arg, ref)
fake_invoke.assert_called_with(
module, mock.sentinel.method_arg, ref)
@mock.patch.object(session.VMwareAPISession, '_is_vim_object',
return_value=True)
def test_call_method_recovery_kwarg_success(self, mock_is_vim):
with test.nested(
mock.patch.object(session.VMwareAPISession, '_create_session',
_fake_create_session),
mock.patch.object(session.VMwareAPISession, 'invoke_api'),
mock.patch.object(FakeStableMoRefProxy,
'fetch_moref', _fake_fetch_moref_impl),
) as (fake_create, fake_invoke, fake_fetch_moref):
_session = session.VMwareAPISession()
module = mock.Mock()
ref = FakeStableMoRefProxy()
fake_invoke.side_effect = [vexec.ManagedObjectNotFoundException(
details=dict(obj=mock.sentinel.moref),
), None]
_session._call_method(module, mock.sentinel.method_arg, ref=ref)
fake_invoke.assert_called_with(
module, mock.sentinel.method_arg, ref=ref)

View File

@ -28,11 +28,11 @@ class VMwareVIMUtilTestCase(test.NoDBTestCase):
def test_get_inner_objects(self):
property = ['summary.name']
# Get the fake datastores directly from the cluster
cluster_refs = fake._get_object_refs('ClusterComputeResource')
cluster = fake._get_object(cluster_refs[0])
cluster = fake.get_first_object('ClusterComputeResource')
cluster_ref = cluster.obj
expected_ds = cluster.datastore.ManagedObjectReference
# Get the fake datastores using inner objects utility method
result = vim_util.get_inner_objects(
self.vim, cluster_refs[0], 'datastore', 'Datastore', property)
self.vim, cluster_ref, 'datastore', 'Datastore', property)
datastores = [oc.obj for oc in result.objects]
self.assertEqual(expected_ds, datastores)

View File

@ -376,7 +376,7 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
ide_controller = fake.VirtualIDEController()
devices.append(scsi_controller)
devices.append(ide_controller)
fake._update_object("VirtualMachine", vm)
fake.update_object(vm)
# return the scsi type, not ide
self.assertEqual(constants.DEFAULT_ADAPTER_TYPE,
vm_util.get_scsi_adapter_type(devices))
@ -388,7 +388,7 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
ide_controller = fake.VirtualIDEController()
devices.append(scsi_controller)
devices.append(ide_controller)
fake._update_object("VirtualMachine", vm)
fake.update_object(vm)
# the controller is not suitable since the device under this controller
# has exceeded SCSI_MAX_CONNECT_NUMBER
for i in range(0, constants.SCSI_MAX_CONNECT_NUMBER):
@ -2066,7 +2066,7 @@ class VMwareVMUtilGetHostRefTestCase(test.NoDBTestCase):
self.session = vmware_session.VMwareAPISession()
# Create a fake VirtualMachine running on a known host
self.host_ref = list(fake._db_content['HostSystem'].keys())[0]
self.host_ref = fake.get_first_object_ref("HostSystem")
self.vm_ref = fake.create_vm(host_ref=self.host_ref)
@mock.patch.object(vm_util, 'get_vm_ref')
@ -2082,7 +2082,7 @@ class VMwareVMUtilGetHostRefTestCase(test.NoDBTestCase):
def test_get_host_name_for_vm(self, mock_get_vm_ref):
mock_get_vm_ref.return_value = self.vm_ref
host = fake._get_object(self.host_ref)
host = fake.get_object(self.host_ref)
ret = vm_util.get_host_name_for_vm(self.session, 'fake-instance')

View File

@ -69,14 +69,16 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
self._virtapi = mock.Mock()
self._image_id = uuids.image
fake_ds_ref = vmwareapi_fake.ManagedObjectReference(value='fake-ds')
fake_ds_ref = vmwareapi_fake.ManagedObjectReference(
name='Datastore', value='fake-ds')
self._ds = ds_obj.Datastore(
ref=fake_ds_ref, name='fake_ds',
capacity=10 * units.Gi,
freespace=10 * units.Gi)
self._dc_info = ds_util.DcInfo(
ref='fake_dc_ref', name='fake_dc',
vmFolder='fake_vm_folder')
vmFolder=vmwareapi_fake.ManagedObjectReference(
name='Folder', value='fake_vm_folder'))
cluster = vmwareapi_fake.create_cluster('fake_cluster', fake_ds_ref)
self._uuid = uuids.foo
fake_info_cache = {
@ -297,7 +299,8 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
mock_save.assert_called_once_with()
self.assertEqual(50, self._instance.progress)
@mock.patch.object(vm_util, 'get_vm_ref', return_value='fake_ref')
@mock.patch.object(vm_util, 'get_vm_ref',
return_value=vmwareapi_fake.ManagedObjectReference())
def test_get_info(self, mock_get_vm_ref):
result = {
'summary.config.numCpu': 4,
@ -2059,7 +2062,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
extra_specs,
self._metadata)
vm = vmwareapi_fake._get_object(vm_ref)
vm = vmwareapi_fake.get_object(vm_ref)
# Test basic VM parameters
self.assertEqual(self._instance.uuid, vm.name)
@ -2082,7 +2085,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
datastores = vm.datastore.ManagedObjectReference
self.assertEqual(1, len(datastores))
datastore = vmwareapi_fake._get_object(datastores[0])
datastore = vmwareapi_fake.get_object(datastores[0])
self.assertEqual(self._ds.name, datastore.get('summary.name'))
# Test that the VM's network is configured as specified

View File

@ -143,8 +143,7 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_vm_ref.assert_called_once_with(self._volumeops._session,
instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
get_volume_ref.assert_called_once_with(connection_info['data'])
self.assertTrue(get_vmdk_info.called)
get_vm_state.assert_called_once_with(self._volumeops._session,
instance)
@ -267,8 +266,7 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_vm_ref.assert_called_once_with(self._volumeops._session,
instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
get_volume_ref.assert_called_once_with(connection_info['data'])
get_vmdk_backed_disk_device.assert_called_once_with(
mock.sentinel.vm_ref, connection_info['data'])
adapter_type = vm_util.CONTROLLER_TO_ADAPTER_TYPE.get(
@ -317,8 +315,7 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_vm_ref.assert_called_once_with(self._volumeops._session,
instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
get_volume_ref.assert_called_once_with(connection_info['data'])
get_vmdk_backed_disk_device.assert_called_once_with(
mock.sentinel.vm_ref, connection_info['data'])
get_vm_state.assert_called_once_with(self._volumeops._session,
@ -497,8 +494,7 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_vm_ref.assert_called_once_with(self._volumeops._session,
self._instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
get_volume_ref.assert_called_once_with(connection_info['data'])
self.assertTrue(get_vmdk_info.called)
attach_disk_to_vm.assert_called_once_with(
vm_ref, self._instance, adapter_type,

View File

@ -15,12 +15,71 @@
# License for the specific language governing permissions and limitations
# under the License.
import abc
import itertools
from oslo_log import log as logging
from oslo_utils import excutils
from oslo_vmware import api
from oslo_vmware import exceptions as vexc
from oslo_vmware import vim
from oslo_vmware.vim_util import get_moref_value
import nova.conf
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
class StableMoRefProxy(metaclass=abc.ABCMeta):
"""Abstract base class which acts as a proxy
for Managed-Object-References (MoRef).
Those references are usually "stable", meaning
they don't change over the life-time of the object.
But usually doesn't mean always. In that case, we
need to fetch the reference again via some search method,
which uses a guaranteed stable identifier (names, uuids, ...)
"""
def __init__(self, ref):
self.moref = ref
@property
def __class__(self):
# Suds accesses the __class__.__name__ attribute
# of the object to determine the xml-tag of the object
# so we have to fake it
return self.moref.__class__
@abc.abstractmethod
def fetch_moref(self, session):
"""Updates the moref field or raises
same exception the initial search would have
"""
def __getattr__(self, name):
return getattr(self.moref, name)
def __repr__(self):
return "StableMoRefProxy({!r})".format(self.moref)
class MoRef(StableMoRefProxy):
"""MoRef takes a closure to resolve the reference of a managed object
That closure is called again, in case we get a ManagedObjectNotFound
exception on said reference.
"""
def __init__(self, closure, ref=None):
self._closure = closure
ref = ref or self._closure()
super().__init__(ref)
def fetch_moref(self, _):
self.moref = self._closure()
def __repr__(self):
return "MoRef({!r})".format(self.moref)
class VMwareAPISession(api.VMwareAPISession):
@ -59,10 +118,37 @@ class VMwareAPISession(api.VMwareAPISession):
"""Calls a method within the module specified with
args provided.
"""
if not self._is_vim_object(module):
return self.invoke_api(module, method, self.vim, *args, **kwargs)
else:
try:
if not self._is_vim_object(module):
return self.invoke_api(module, method, self.vim, *args,
**kwargs)
return self.invoke_api(module, method, *args, **kwargs)
except vexc.ManagedObjectNotFoundException as monfe:
with excutils.save_and_reraise_exception() as ctxt:
moref = monfe.details.get("obj") if monfe.details else None
for arg in itertools.chain(args, kwargs.values()):
if not isinstance(arg, StableMoRefProxy):
continue
moref_arg = get_moref_value(arg.moref)
if moref != moref_arg:
continue
# We have found the argument with the moref
# causing the exception and we can try to recover it
arg.fetch_moref(self)
if not arg.moref:
# We didn't recover the reference
ctxt.reraise = True
break
moref_arg = get_moref_value(arg.moref)
if moref != moref_arg:
# We actually recovered, so do not raise `monfe`
LOG.info("Replaced moref %s with %s",
moref, moref_arg)
ctxt.reraise = False
# We only end up here when we have recovered a moref by changing
# the stored value of an argument to a different value,
# so let's try again (and recover again if it happens more than once)
return self._call_method(module, method, *args, **kwargs)
def _wait_for_task(self, task_ref):
"""Return a Deferred that will give the result of the given task.

View File

@ -20,7 +20,6 @@ The VMware API VM utility module to build SOAP object specs.
import collections
import copy
import functools
from oslo_log import log as logging
from oslo_service import loopingcall
@ -37,6 +36,7 @@ from nova import exception
from nova.i18n import _
from nova.network import model as network_model
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import session
from nova.virt.vmwareapi import vim_util
LOG = logging.getLogger(__name__)
@ -131,22 +131,6 @@ def vm_ref_cache_get(id_):
return _VM_REFS_CACHE.get(id_)
def _vm_ref_cache(id_, func, session, data):
vm_ref = vm_ref_cache_get(id_)
if not vm_ref:
vm_ref = func(session, data)
vm_ref_cache_update(id_, vm_ref)
return vm_ref
def vm_ref_cache_from_instance(func):
@functools.wraps(func)
def wrapper(session, instance):
id_ = instance.uuid
return _vm_ref_cache(id_, func, session, instance)
return wrapper
# the config key which stores the VNC port
VNC_CONFIG_KEY = 'config.extraConfig["RemoteDisplay.vnc.port"]'
@ -1131,15 +1115,25 @@ def _get_vm_ref_from_extraconfig(session, instance_uuid):
_get_object_for_optionvalue)
@vm_ref_cache_from_instance
class VmMoRefProxy(session.StableMoRefProxy):
def __init__(self, ref, uuid):
super(VmMoRefProxy, self).__init__(ref)
self._uuid = uuid
def fetch_moref(self, session):
self.moref = search_vm_ref_by_identifier(session, self._uuid)
if not self.moref:
raise exception.InstanceNotFound(instance_id=self._uuid)
vm_ref_cache_update(self._uuid, self.moref)
def get_vm_ref(session, instance):
"""Get reference to the VM through uuid or vm name."""
uuid = instance.uuid
vm_ref = (search_vm_ref_by_identifier(session, uuid) or
_get_vm_ref_from_name(session, instance.name))
if vm_ref is None:
raise exception.InstanceNotFound(instance_id=uuid)
return vm_ref
"""Get reference to the VM through uuid."""
moref = vm_ref_cache_get(instance.uuid)
stable_ref = VmMoRefProxy(moref, instance.uuid)
if not moref:
stable_ref.fetch_moref(session)
return stable_ref
def search_vm_ref_by_identifier(session, identifier):
@ -1151,8 +1145,7 @@ def search_vm_ref_by_identifier(session, identifier):
use get_vm_ref instead.
"""
vm_ref = (_get_vm_ref_from_vm_uuid(session, identifier) or
_get_vm_ref_from_extraconfig(session, identifier) or
_get_vm_ref_from_name(session, identifier))
_get_vm_ref_from_extraconfig(session, identifier))
return vm_ref

View File

@ -26,12 +26,30 @@ import nova.conf
from nova import exception
from nova.i18n import _
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import session
from nova.virt.vmwareapi import vm_util
CONF = nova.conf.CONF
LOG = logging.getLogger(__name__)
class VolumeMoRefProxy(session.StableMoRefProxy):
def __init__(self, connection_info_data):
volume_ref_value = connection_info_data.get('volume')
ref = None
if volume_ref_value:
ref = vutil.get_moref(volume_ref_value, 'VirtualMachine')
super(VolumeMoRefProxy, self).__init__(ref)
self._connection_info_data = connection_info_data
def fetch_moref(self, session):
volume_id = self._connection_info_data.get('volume_id')
if not volume_id:
volume_id = self._connection_info_data.get('name')
if volume_id:
self.moref = vm_util._get_vm_ref_from_vm_uuid(session, volume_id)
class VMwareVolumeOps(object):
"""Management class for Volume-related tasks."""
@ -300,9 +318,10 @@ class VMwareVolumeOps(object):
connector['instance'] = vutil.get_moref_value(vm_ref)
return connector
def _get_volume_ref(self, volume_ref_name):
"""Get the volume moref from the ref name."""
return vutil.get_moref(volume_ref_name, 'VirtualMachine')
@staticmethod
def _get_volume_ref(connection_info_data):
"""Get the volume moref from the "data" field in connection_info ."""
return VolumeMoRefProxy(connection_info_data)
def _get_vmdk_base_volume_device(self, volume_ref):
# Get the vmdk file name that the VM is pointing to
@ -317,7 +336,7 @@ class VMwareVolumeOps(object):
LOG.debug("_attach_volume_vmdk: %s", connection_info,
instance=instance)
data = connection_info['data']
volume_ref = self._get_volume_ref(data['volume'])
volume_ref = self._get_volume_ref(data)
# Get details required for adding disk device such as
# adapter_type, disk_type
@ -552,7 +571,7 @@ class VMwareVolumeOps(object):
LOG.debug("_detach_volume_vmdk: %s", connection_info,
instance=instance)
data = connection_info['data']
volume_ref = self._get_volume_ref(data['volume'])
volume_ref = self._get_volume_ref(data)
device = self._get_vmdk_backed_disk_device(vm_ref, data)
@ -645,7 +664,7 @@ class VMwareVolumeOps(object):
vm_ref = vm_util.get_vm_ref(self._session, instance)
data = connection_info['data']
# Get the volume ref
volume_ref = self._get_volume_ref(data['volume'])
volume_ref = self._get_volume_ref(data)
# Pick the resource pool on which the instance resides. Move the
# volume to the datastore of the instance.
res_pool = self._get_res_pool_of_vm(vm_ref)