Merge "VMware: Support volumes backed by VStorageObject"
This commit is contained in:
@@ -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.
|
||||
Reference in New Issue
Block a user