VMware: Support volumes backed by VStorageObject
vSphere 6.5 introduced APIs to manage virtual disks (volumes) as first class objects. The new managed disk entity is called VStorageObject aka First Class Disk (FCD). Adding support for volumes backed by VStorageObject. Change-Id: I4a5a9d3537dc175508f0a0fd82507c498737d1a5
This commit is contained in:
parent
bdfab8851f
commit
d5faf45e9d
@ -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,
|
||||
|
@ -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'
|
||||
|
@ -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)
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user