Merge "VMware: Support volumes backed by VStorageObject"

This commit is contained in:
Zuul
2022-02-23 12:50:37 +00:00
committed by Gerrit Code Review
6 changed files with 339 additions and 1 deletions
@@ -23,6 +23,7 @@ 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 pbm
from oslo_vmware import vim_util as vutil
from nova import exception
from nova.network import model as network_model
@@ -1987,6 +1988,66 @@ class VMwareVMUtilTestCase(test.NoDBTestCase):
mock_get_name.assert_called_once_with(self._instance.display_name,
self._instance.uuid)
def test_create_fcd_id_obj(self):
fcd_id_obj = mock.Mock()
client_factory = mock.Mock()
client_factory.create.return_value = fcd_id_obj
fcd_id = mock.sentinel.fcd_id
ret = vm_util._create_fcd_id_obj(client_factory, fcd_id)
self.assertEqual(fcd_id_obj, ret)
self.assertEqual(fcd_id, ret.id)
client_factory.create.assert_called_once_with('ns0:ID')
@mock.patch.object(vm_util, '_create_fcd_id_obj')
@mock.patch.object(vutil, 'get_moref')
def test_attach_fcd(self, get_moref, create_fcd_id_obj):
disk_id = mock.sentinel.disk_id
create_fcd_id_obj.return_value = disk_id
ds_ref = mock.sentinel.ds_ref
get_moref.return_value = ds_ref
task = mock.sentinel.task
session = mock.Mock()
session._call_method.return_value = task
vm_ref = mock.sentinel.vm_ref
fcd_id = mock.sentinel.fcd_id
ds_ref_val = mock.sentinel.ds_ref_val
controller_key = mock.sentinel.controller_key
unit_number = mock.sentinel.unit_number
vm_util.attach_fcd(
session, vm_ref, fcd_id, ds_ref_val, controller_key, unit_number)
create_fcd_id_obj.assert_called_once_with(
session.vim.client.factory, fcd_id)
get_moref.assert_called_once_with(ds_ref_val, 'Datastore')
session._call_method.assert_called_once_with(
session.vim, "AttachDisk_Task", vm_ref, diskId=disk_id,
datastore=ds_ref, controllerKey=controller_key,
unitNumber=unit_number)
session._wait_for_task.assert_called_once_with(task)
@mock.patch.object(vm_util, '_create_fcd_id_obj')
def test_detach_fcd(self, create_fcd_id_obj):
disk_id = mock.sentinel.disk_id
create_fcd_id_obj.return_value = disk_id
task = mock.sentinel.task
session = mock.Mock()
session._call_method.return_value = task
vm_ref = mock.sentinel.vm_ref
fcd_id = mock.sentinel.fcd_id
vm_util.detach_fcd(session, vm_ref, fcd_id)
create_fcd_id_obj.assert_called_once_with(
session.vim.client.factory, fcd_id)
session._call_method.assert_called_once_with(
session.vim, "DetachDisk_Task", vm_ref, diskId=disk_id)
session._wait_for_task.assert_called_once_with(task)
@mock.patch.object(driver.VMwareAPISession, 'vim', stubs.fake_vim_prop)
class VMwareVMUtilGetHostRefTestCase(test.NoDBTestCase):
@@ -12,6 +12,7 @@
# License for the specific language governing permissions and limitations
# under the License.
import ddt
import mock
from oslo_utils.fixture import uuidsentinel as uuids
from oslo_vmware import exceptions as oslo_vmw_exceptions
@@ -31,6 +32,7 @@ from nova.virt.vmwareapi import vm_util
from nova.virt.vmwareapi import volumeops
@ddt.ddt
class VMwareVolumeOpsTestCase(test.NoDBTestCase):
def setUp(self):
@@ -406,6 +408,57 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_rdm_disk.assert_called_once_with(hardware_devices, disk_uuid)
self.assertFalse(detach_disk_from_vm.called)
@mock.patch.object(vm_util, 'get_vm_ref')
@mock.patch.object(vm_util, 'get_vm_state')
@mock.patch.object(vm_util, 'detach_fcd')
def _test__detach_volume_fcd(
self, detach_fcd, get_vm_state, get_vm_ref,
adapter_type=constants.ADAPTER_TYPE_IDE, powered_off=True):
vm_ref = mock.sentinel.vm_ref
get_vm_ref.return_value = vm_ref
if adapter_type == constants.ADAPTER_TYPE_IDE:
get_vm_state.return_value = (
power_state.SHUTDOWN if powered_off else power_state.RUNNING)
fcd_id = mock.sentinel.fcd_id
ds_ref_val = mock.sentinel.ds_ref_val
connection_info = {'data': {'id': fcd_id,
'ds_ref_val': ds_ref_val,
'adapter_type': adapter_type}}
instance = mock.sentinel.instance
if adapter_type == constants.ADAPTER_TYPE_IDE and not powered_off:
self.assertRaises(exception.Invalid,
self._volumeops._detach_volume_fcd,
connection_info,
instance)
detach_fcd.assert_not_called()
else:
self._volumeops._detach_volume_fcd(connection_info, instance)
detach_fcd.assert_called_once_with(
self._volumeops._session, vm_ref, fcd_id)
@ddt.data(
constants.ADAPTER_TYPE_BUSLOGIC, constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS, constants.ADAPTER_TYPE_PARAVIRTUAL)
def test_detach_volume_fcd_powered_off_instance(self, adapter_type):
self._test__detach_volume_fcd(adapter_type=adapter_type)
@ddt.data(
constants.ADAPTER_TYPE_BUSLOGIC, constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS, constants.ADAPTER_TYPE_PARAVIRTUAL)
def test_detach_volume_fcd_powered_on_instance(self, adapter_type):
self._test__detach_volume_fcd(adapter_type=adapter_type,
powered_off=False)
@mock.patch.object(volumeops.VMwareVolumeOps, '_detach_volume_fcd')
def test_detach_volume_fcd(self, detach_volume_fcd):
connection_info = {'driver_volume_type': constants.DISK_FORMAT_FCD}
instance = mock.sentinel.instance
self._volumeops.detach_volume(connection_info, instance)
detach_volume_fcd.assert_called_once_with(connection_info, instance)
def _test_attach_volume_vmdk(self, adapter_type=None):
connection_info = {'driver_volume_type': constants.DISK_FORMAT_VMDK,
'serial': 'volume-fake-id',
@@ -498,6 +551,126 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
constants.ADAPTER_TYPE_PARAVIRTUAL):
self._test_attach_volume_vmdk(adapter_type)
@mock.patch.object(vm_util, 'allocate_controller_key_and_unit_number')
def test_get_controller_key_and_unit(
self, allocate_controller_key_and_unit_number):
key = mock.sentinel.key
unit = mock.sentinel.unit
allocate_controller_key_and_unit_number.return_value = (
key, unit, None)
with mock.patch.object(self._volumeops, '_session') as session:
devices = mock.sentinel.devices
session._call_method.return_value = devices
vm_ref = mock.sentinel.vm_ref
adapter_type = mock.sentinel.adapter_type
ret = self._volumeops._get_controller_key_and_unit(
vm_ref, adapter_type)
self.assertEqual((key, unit, None), ret)
session._call_method.assert_called_once_with(
vutil, 'get_object_property', vm_ref, 'config.hardware.device')
allocate_controller_key_and_unit_number.assert_called_once_with(
session.vim.client.factory, devices, adapter_type)
@mock.patch.object(volumeops.VMwareVolumeOps,
'_get_controller_key_and_unit')
@mock.patch.object(vm_util, 'reconfigure_vm')
@mock.patch.object(vm_util, 'attach_fcd')
def _test_attach_fcd(
self, attach_fcd, reconfigure_vm, get_controller_key_and_unit,
existing_controller=True):
key = mock.sentinel.key
unit = mock.sentinel.unit
spec = mock.sentinel.spec
if existing_controller:
get_controller_key_and_unit.return_value = (key, unit, None)
else:
get_controller_key_and_unit.side_effect = [(None, None, spec),
(key, unit, None)]
with mock.patch.object(self._volumeops, '_session') as session:
config_spec = mock.Mock()
session.vim.client.factory.create.return_value = config_spec
vm_ref = mock.sentinel.vm_ref
adapter_type = mock.sentinel.adapter_type
fcd_id = mock.sentinel.fcd_id
ds_ref_val = mock.sentinel.ds_ref_val
self._volumeops._attach_fcd(
vm_ref, adapter_type, fcd_id, ds_ref_val)
attach_fcd.assert_called_once_with(
session, vm_ref, fcd_id, ds_ref_val, key, unit)
if existing_controller:
get_controller_key_and_unit.assert_called_once_with(
vm_ref, adapter_type)
reconfigure_vm.assert_not_called()
else:
exp_calls = [mock.call(vm_ref, adapter_type),
mock.call(vm_ref, adapter_type)]
get_controller_key_and_unit.assert_has_calls(exp_calls)
self.assertEqual([spec], config_spec.deviceChange)
reconfigure_vm.assert_called_once_with(
session, vm_ref, config_spec)
def test_attach_fcd_using_existing_controller(self):
self._test_attach_fcd()
def test_attach_fcd_using_new_controller(self):
self._test_attach_fcd(existing_controller=False)
@mock.patch.object(vm_util, 'get_vm_ref')
@mock.patch.object(vm_util, 'get_vm_state')
@mock.patch.object(volumeops.VMwareVolumeOps, '_attach_fcd')
def _test__attach_volume_fcd(
self, attach_fcd, get_vm_state, get_vm_ref,
adapter_type=constants.ADAPTER_TYPE_IDE, powered_off=True):
vm_ref = mock.sentinel.vm_ref
get_vm_ref.return_value = vm_ref
if adapter_type == constants.ADAPTER_TYPE_IDE:
get_vm_state.return_value = (
power_state.SHUTDOWN if powered_off else power_state.RUNNING)
fcd_id = mock.sentinel.fcd_id
ds_ref_val = mock.sentinel.ds_ref_val
connection_info = {'data': {'id': fcd_id,
'ds_ref_val': ds_ref_val,
'adapter_type': adapter_type}}
instance = mock.sentinel.instance
if adapter_type == constants.ADAPTER_TYPE_IDE and not powered_off:
self.assertRaises(exception.Invalid,
self._volumeops._attach_volume_fcd,
connection_info,
instance)
attach_fcd.assert_not_called()
else:
self._volumeops._attach_volume_fcd(connection_info, instance)
attach_fcd.assert_called_once_with(
vm_ref, adapter_type, fcd_id, ds_ref_val)
@ddt.data(
constants.ADAPTER_TYPE_BUSLOGIC, constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS, constants.ADAPTER_TYPE_PARAVIRTUAL)
def test_attach_volume_fcd_powered_off_instance(self, adapter_type):
self._test__attach_volume_fcd(adapter_type=adapter_type)
@ddt.data(
constants.ADAPTER_TYPE_BUSLOGIC, constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS, constants.ADAPTER_TYPE_PARAVIRTUAL)
def test_attach_volume_fcd_powered_on_instance(self, adapter_type):
self._test__attach_volume_fcd(adapter_type=adapter_type,
powered_off=False)
@mock.patch.object(volumeops.VMwareVolumeOps, '_attach_volume_fcd')
def test_attach_volume_fcd(self, attach_volume_fcd):
connection_info = {'driver_volume_type': constants.DISK_FORMAT_FCD}
instance = mock.sentinel.instance
self._volumeops.attach_volume(connection_info, instance)
attach_volume_fcd.assert_called_once_with(connection_info, instance)
def test_attach_volume_iscsi(self):
for adapter_type in (None, constants.DEFAULT_ADAPTER_TYPE,
constants.ADAPTER_TYPE_BUSLOGIC,
+2 -1
View File
@@ -27,7 +27,8 @@ MIN_VC_OVS_VERSION = '5.5.0'
DISK_FORMAT_ISO = 'iso'
DISK_FORMAT_VMDK = 'vmdk'
DISK_FORMAT_ISCSI = 'iscsi'
DISK_FORMATS_ALL = [DISK_FORMAT_ISO, DISK_FORMAT_VMDK]
DISK_FORMAT_FCD = 'vstorageobject'
DISK_FORMATS_ALL = [DISK_FORMAT_ISO, DISK_FORMAT_VMDK, DISK_FORMAT_FCD]
DISK_TYPE_THIN = 'thin'
CONTAINER_FORMAT_BARE = 'bare'
+33
View File
@@ -1631,3 +1631,36 @@ def rename_vm(session, vm_ref, instance):
rename_task = session._call_method(session.vim, "Rename_Task", vm_ref,
newName=vm_name)
session._wait_for_task(rename_task)
def _create_fcd_id_obj(client_factory, fcd_id):
id_obj = client_factory.create('ns0:ID')
id_obj.id = fcd_id
return id_obj
def attach_fcd(
session, vm_ref, fcd_id, ds_ref_val, controller_key, unit_number
):
client_factory = session.vim.client.factory
disk_id = _create_fcd_id_obj(client_factory, fcd_id)
ds_ref = vutil.get_moref(ds_ref_val, 'Datastore')
LOG.debug("Attaching fcd (id: %(fcd_id)s, datastore: %(ds_ref_val)s) to "
"vm: %(vm_ref)s.",
{'fcd_id': fcd_id,
'ds_ref_val': ds_ref_val,
'vm_ref': vm_ref})
task = session._call_method(
session.vim, "AttachDisk_Task", vm_ref, diskId=disk_id,
datastore=ds_ref, controllerKey=controller_key, unitNumber=unit_number)
session._wait_for_task(task)
def detach_fcd(session, vm_ref, fcd_id):
client_factory = session.vim.client.factory
disk_id = _create_fcd_id_obj(client_factory, fcd_id)
LOG.debug("Detaching fcd (id: %(fcd_id)s) from vm: %(vm_ref)s.",
{'fcd_id': fcd_id, 'vm_ref': vm_ref})
task = session._call_method(
session.vim, "DetachDisk_Task", vm_ref, diskId=disk_id)
session._wait_for_task(task)
+65
View File
@@ -367,6 +367,53 @@ class VMwareVolumeOps(object):
device_name=device_name)
LOG.debug("Attached ISCSI: %s", connection_info, instance=instance)
def _get_controller_key_and_unit(self, vm_ref, adapter_type):
LOG.debug("_get_controller_key_and_unit vm: %(vm_ref)s, adapter: "
"%(adapter)s.",
{'vm_ref': vm_ref, 'adapter': adapter_type})
client_factory = self._session.vim.client.factory
devices = self._session._call_method(vutil,
"get_object_property",
vm_ref,
"config.hardware.device")
return vm_util.allocate_controller_key_and_unit_number(
client_factory, devices, adapter_type)
def _attach_fcd(self, vm_ref, adapter_type, fcd_id, ds_ref_val):
(controller_key, unit_number,
controller_spec) = self._get_controller_key_and_unit(
vm_ref, adapter_type)
if controller_spec:
# No controller available to attach, create one first.
config_spec = self._session.vim.client.factory.create(
'ns0:VirtualMachineConfigSpec')
config_spec.deviceChange = [controller_spec]
vm_util.reconfigure_vm(self._session, vm_ref, config_spec)
(controller_key, unit_number,
controller_spec) = self._get_controller_key_and_unit(
vm_ref, adapter_type)
vm_util.attach_fcd(
self._session, vm_ref, fcd_id, ds_ref_val, controller_key,
unit_number)
def _attach_volume_fcd(self, connection_info, instance):
"""Attach fcd volume storage to VM instance."""
LOG.debug("_attach_volume_fcd: %s", connection_info, instance=instance)
vm_ref = vm_util.get_vm_ref(self._session, instance)
data = connection_info['data']
adapter_type = data['adapter_type']
if adapter_type == constants.ADAPTER_TYPE_IDE:
state = vm_util.get_vm_state(self._session, instance)
if state != power_state.SHUTDOWN:
raise exception.Invalid(_('%s does not support disk '
'hotplug.') % adapter_type)
self._attach_fcd(vm_ref, adapter_type, data['id'], data['ds_ref_val'])
LOG.debug("Attached fcd: %s", connection_info, instance=instance)
def attach_volume(self, connection_info, instance, adapter_type=None):
"""Attach volume storage to VM instance."""
driver_type = connection_info['driver_volume_type']
@@ -376,6 +423,8 @@ class VMwareVolumeOps(object):
self._attach_volume_vmdk(connection_info, instance, adapter_type)
elif driver_type == constants.DISK_FORMAT_ISCSI:
self._attach_volume_iscsi(connection_info, instance, adapter_type)
elif driver_type == constants.DISK_FORMAT_FCD:
self._attach_volume_fcd(connection_info, instance)
else:
raise exception.VolumeDriverNotFound(driver_type=driver_type)
@@ -558,6 +607,20 @@ class VMwareVolumeOps(object):
self.detach_disk_from_vm(vm_ref, instance, device, destroy_disk=True)
LOG.debug("Detached ISCSI: %s", connection_info, instance=instance)
def _detach_volume_fcd(self, connection_info, instance):
"""Detach fcd volume storage to VM instance."""
vm_ref = vm_util.get_vm_ref(self._session, instance)
data = connection_info['data']
adapter_type = data['adapter_type']
if adapter_type == constants.ADAPTER_TYPE_IDE:
state = vm_util.get_vm_state(self._session, instance)
if state != power_state.SHUTDOWN:
raise exception.Invalid(_('%s does not support disk '
'hotplug.') % adapter_type)
vm_util.detach_fcd(self._session, vm_ref, data['id'])
def detach_volume(self, connection_info, instance):
"""Detach volume storage to VM instance."""
driver_type = connection_info['driver_volume_type']
@@ -567,6 +630,8 @@ class VMwareVolumeOps(object):
self._detach_volume_vmdk(connection_info, instance)
elif driver_type == constants.DISK_FORMAT_ISCSI:
self._detach_volume_iscsi(connection_info, instance)
elif driver_type == constants.DISK_FORMAT_FCD:
self._detach_volume_fcd(connection_info, instance)
else:
raise exception.VolumeDriverNotFound(driver_type=driver_type)
@@ -0,0 +1,5 @@
---
features:
- |
Added support for VMware VStorageObject based volumes in
VMware vCenter driver. vSphere version 6.5 is required.