VMware: Accept image and block device mappings

The logic in spawn ignores the specified image if there
are block device mappings in "nova boot".  This is incorrect
and is a bug in the VMware driver, since the block devices
can be blank volumes.  We should be able to accept an image
and block device mappings in "nova boot".

DocImpact: VMware supports nova boot with the --block-device
option.  End users can specify the block device's bus to be
either lsiLogic, busLogic, ide, lsiLogicsas, or paraVirtual.
For example, nova boot --flavor m1.tiny --block-device
source=image,dest=volume,size=1,id=<image_id>,bus=lsiLogicsas,
bootindex=0 test

Change-Id: Ibf59906b95bda560d2427c88a78b65c098825959
Closes-Bug: #1350224
Closes-Bug: #1271966
This commit is contained in:
Thang Pham 2014-10-14 23:24:54 -04:00
parent 017574ee82
commit 264425678f
5 changed files with 426 additions and 96 deletions

View File

@ -1100,7 +1100,8 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(block_device, 'volume_in_mapping')
self.mox.StubOutWithMock(v_driver, 'block_device_info_get_mapping')
connection_info = self._test_vmdk_connection_info('vmdk')
root_disk = [{'connection_info': connection_info}]
root_disk = [{'connection_info': connection_info,
'boot_index': 0}]
v_driver.block_device_info_get_mapping(
mox.IgnoreArg()).AndReturn(root_disk)
self.mox.StubOutWithMock(volumeops.VMwareVolumeOps,
@ -1114,9 +1115,9 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(volumeops.VMwareVolumeOps,
'attach_volume')
volumeops.VMwareVolumeOps.attach_volume(connection_info,
self.instance)
self.instance, constants.DEFAULT_ADAPTER_TYPE)
self.mox.ReplayAll()
block_device_info = {'mount_device': 'vda'}
block_device_info = {'block_device_mapping': root_disk}
self.conn.spawn(self.context, self.instance, self.image,
injected_files=[], admin_password=None,
network_info=self.network_info,
@ -1127,13 +1128,14 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(block_device, 'volume_in_mapping')
self.mox.StubOutWithMock(v_driver, 'block_device_info_get_mapping')
connection_info = self._test_vmdk_connection_info('iscsi')
root_disk = [{'connection_info': connection_info}]
root_disk = [{'connection_info': connection_info,
'boot_index': 0}]
v_driver.block_device_info_get_mapping(
mox.IgnoreArg()).AndReturn(root_disk)
self.mox.StubOutWithMock(volumeops.VMwareVolumeOps,
'attach_volume')
volumeops.VMwareVolumeOps.attach_volume(connection_info,
self.instance)
self.instance, constants.DEFAULT_ADAPTER_TYPE)
self.mox.ReplayAll()
block_device_info = {'mount_device': 'vda'}
self.conn.spawn(self.context, self.instance, self.image,
@ -1723,7 +1725,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(volumeops.VMwareVolumeOps,
'_attach_volume_vmdk')
volumeops.VMwareVolumeOps._attach_volume_vmdk(connection_info,
self.instance)
self.instance, None)
self.mox.ReplayAll()
self.conn.attach_volume(None, connection_info, self.instance,
mount_point)
@ -1821,7 +1823,7 @@ class VMwareAPIVMTestCase(test.NoDBTestCase):
self.mox.StubOutWithMock(volumeops.VMwareVolumeOps,
'_attach_volume_iscsi')
volumeops.VMwareVolumeOps._attach_volume_iscsi(connection_info,
self.instance)
self.instance, None)
self.mox.ReplayAll()
self.conn.attach_volume(None, connection_info, self.instance,
mount_point)

View File

@ -83,6 +83,9 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
}
self._instance = fake_instance.fake_instance_obj(
self._context, **self._instance_values)
self._flavor = objects.Flavor(name='m1.small', memory_mb=512, vcpus=1,
root_gb=10, ephemeral_gb=0, swap=0,
extra_specs={})
self._vmops = vmops.VMwareVMOps(self._session, self._virtapi, None,
cluster=cluster.obj)
@ -713,16 +716,18 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
vm_ref, self._instance, self._ds.ref, str(upload_iso_path))
@mock.patch.object(vmops.LOG, 'debug')
@mock.patch.object(vmops.VMwareVMOps, '_fetch_image_if_missing')
@mock.patch.object(vmops.VMwareVMOps, '_get_vm_config_info')
@mock.patch.object(vmops.VMwareVMOps, 'build_virtual_machine')
def test_spawn_mask_block_device_info_password(self,
mock_build_virtual_machine,
mock_get_vm_config_info,
mock_debug):
@mock.patch.object(vmops.lockutils, 'lock')
def test_spawn_mask_block_device_info_password(self, mock_lock,
mock_build_virtual_machine, mock_get_vm_config_info,
mock_fetch_image_if_missing, mock_debug):
# Very simple test that just ensures block_device_info auth_password
# is masked when logged; the rest of the test just fails out early.
data = {'auth_password': 'scrubme'}
bdm = [{'connection_info': {'data': data}}]
bdm = [{'boot_index': 0, 'disk_bus': constants.DEFAULT_ADAPTER_TYPE,
'connection_info': {'data': data}}]
bdi = {'block_device_mapping': bdm}
self.password_logged = False
@ -754,6 +759,189 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
# that we checked it was scrubbed
self.assertTrue(self.password_logged)
@mock.patch('nova.virt.vmwareapi.vm_util.power_on_instance')
@mock.patch.object(vmops.VMwareVMOps, '_use_disk_image_as_linked_clone')
@mock.patch.object(vmops.VMwareVMOps, '_fetch_image_if_missing')
@mock.patch(
'nova.virt.vmwareapi.imagecache.ImageCacheManager.enlist_image')
@mock.patch.object(vmops.VMwareVMOps, 'build_virtual_machine')
@mock.patch.object(objects.Flavor, 'get_by_id')
@mock.patch.object(vmops.VMwareVMOps, '_get_vm_config_info')
@mock.patch.object(vmops.VMwareVMOps, '_get_extra_specs')
@mock.patch.object(nova.virt.vmwareapi.images.VMwareImage,
'from_image')
def test_spawn_non_root_block_device(self, from_image,
get_extra_specs,
get_vm_config_info,
get_by_id,
build_virtual_machine,
enlist_image, fetch_image,
use_disk_image,
power_on_instance):
extra_specs = get_extra_specs.return_value
get_by_id.return_value = self._flavor
connection_info1 = {'data': 'fake-data1', 'serial': 'volume-fake-id1'}
connection_info2 = {'data': 'fake-data2', 'serial': 'volume-fake-id2'}
bdm = [{'connection_info': connection_info1,
'disk_bus': constants.ADAPTER_TYPE_IDE,
'mount_device': '/dev/sdb'},
{'connection_info': connection_info2,
'disk_bus': constants.DEFAULT_ADAPTER_TYPE,
'mount_device': '/dev/sdc'}]
bdi = {'block_device_mapping': bdm, 'root_device_name': '/dev/sda'}
self.flags(flat_injected=False, vnc_enabled=False)
image_size = (self._instance.root_gb) * units.Gi / 2
image_info = images.VMwareImage(
image_id=self._image_id,
file_size=image_size)
vi = get_vm_config_info.return_value
from_image.return_value = image_info
build_virtual_machine.return_value = 'fake-vm-ref'
with mock.patch.object(self._vmops, '_volumeops') as volumeops:
self._vmops.spawn(self._context, self._instance, {},
injected_files=None, admin_password=None,
network_info=[], block_device_info=bdi)
from_image.assert_called_once_with(self._instance.image_ref, {})
get_vm_config_info.assert_called_once_with(self._instance,
image_info, None, extra_specs.storage_policy)
build_virtual_machine.assert_called_once_with(self._instance,
vi.instance_name, image_info, vi.dc_info, vi.datastore, [],
extra_specs)
enlist_image.assert_called_once_with(image_info.image_id,
vi.datastore, vi.dc_info.ref)
fetch_image.assert_called_once_with(self._context, vi)
use_disk_image.assert_called_once_with('fake-vm-ref', vi)
volumeops.attach_volume.assert_any_call(
connection_info1, self._instance, constants.ADAPTER_TYPE_IDE)
volumeops.attach_volume.assert_any_call(
connection_info2, self._instance,
constants.DEFAULT_ADAPTER_TYPE)
@mock.patch('nova.virt.vmwareapi.vm_util.power_on_instance')
@mock.patch.object(vmops.VMwareVMOps, 'build_virtual_machine')
@mock.patch.object(objects.Flavor, 'get_by_id')
@mock.patch.object(vmops.VMwareVMOps, '_get_vm_config_info')
@mock.patch.object(vmops.VMwareVMOps, '_get_extra_specs')
@mock.patch.object(nova.virt.vmwareapi.images.VMwareImage,
'from_image')
def test_spawn_with_no_image_and_block_devices(self, from_image,
get_extra_specs,
get_vm_config_info,
get_by_id,
build_virtual_machine,
power_on_instance):
instance_values = {
'name': 'fake_name',
'uuid': 'fake_uuid',
'vcpus': 1,
'memory_mb': 512,
'image_ref': None,
'root_gb': 10,
'node': 'respool-1001(MyResPoolName)',
'expected_attrs': ['system_metadata'],
}
instance = fake_instance.fake_instance_obj(
self._context, **instance_values)
extra_specs = get_extra_specs.return_value
get_by_id.return_value = self._flavor
connection_info1 = {'data': 'fake-data1', 'serial': 'volume-fake-id1'}
connection_info2 = {'data': 'fake-data2', 'serial': 'volume-fake-id2'}
connection_info3 = {'data': 'fake-data3', 'serial': 'volume-fake-id3'}
bdm = [{'boot_index': 0,
'connection_info': connection_info1,
'disk_bus': constants.ADAPTER_TYPE_IDE},
{'boot_index': 1,
'connection_info': connection_info2,
'disk_bus': constants.DEFAULT_ADAPTER_TYPE},
{'boot_index': 2,
'connection_info': connection_info3,
'disk_bus': constants.ADAPTER_TYPE_LSILOGICSAS}]
bdi = {'block_device_mapping': bdm}
self.flags(flat_injected=False, vnc_enabled=False)
image_info = mock.sentinel.image_info
vi = get_vm_config_info.return_value
from_image.return_value = image_info
build_virtual_machine.return_value = 'fake-vm-ref'
with mock.patch.object(self._vmops, '_volumeops') as volumeops:
self._vmops.spawn(self._context, instance, {},
injected_files=None, admin_password=None,
network_info=[], block_device_info=bdi)
from_image.assert_called_once_with(instance.image_ref, {})
get_vm_config_info.assert_called_once_with(instance, image_info,
None, extra_specs.storage_policy)
build_virtual_machine.assert_called_once_with(instance,
vi.instance_name, image_info, vi.dc_info, vi.datastore, [],
extra_specs)
volumeops.attach_root_volume.assert_called_once_with(
connection_info1, instance, vi.datastore.ref,
constants.ADAPTER_TYPE_IDE)
volumeops.attach_volume.assert_any_call(
connection_info2, instance,
constants.DEFAULT_ADAPTER_TYPE)
volumeops.attach_volume.assert_any_call(
connection_info3, instance,
constants.ADAPTER_TYPE_LSILOGICSAS)
@mock.patch('nova.virt.vmwareapi.vm_util.power_on_instance')
@mock.patch.object(vmops.VMwareVMOps, 'build_virtual_machine')
@mock.patch.object(objects.Flavor, 'get_by_id')
@mock.patch.object(vmops.VMwareVMOps, '_get_vm_config_info')
@mock.patch.object(vmops.VMwareVMOps, '_get_extra_specs')
@mock.patch.object(nova.virt.vmwareapi.images.VMwareImage,
'from_image')
def test_spawn_unsupported_hardware(self, from_image,
get_extra_specs,
get_vm_config_info,
get_by_id,
build_virtual_machine,
power_on_instance):
instance_values = {
'name': 'fake_name',
'uuid': 'fake_uuid',
'vcpus': 1,
'memory_mb': 512,
'image_ref': None,
'root_gb': 10,
'node': 'respool-1001(MyResPoolName)',
'expected_attrs': ['system_metadata'],
}
instance = fake_instance.fake_instance_obj(
self._context, **instance_values)
extra_specs = get_extra_specs.return_value
get_by_id.return_value = self._flavor
connection_info = {'data': 'fake-data', 'serial': 'volume-fake-id'}
bdm = [{'boot_index': 0,
'connection_info': connection_info,
'disk_bus': 'invalid_adapter_type'}]
bdi = {'block_device_mapping': bdm}
self.flags(flat_injected=False, vnc_enabled=False)
image_info = mock.sentinel.image_info
vi = get_vm_config_info.return_value
from_image.return_value = image_info
build_virtual_machine.return_value = 'fake-vm-ref'
self.assertRaises(exception.UnsupportedHardware, self._vmops.spawn,
self._context, instance, {}, injected_files=None,
admin_password=None, network_info=[],
block_device_info=bdi)
from_image.assert_called_once_with(instance.image_ref, {})
get_vm_config_info.assert_called_once_with(instance, image_info, None,
extra_specs.storage_policy)
build_virtual_machine.assert_called_once_with(instance,
vi.instance_name, image_info, vi.dc_info, vi.datastore, [],
extra_specs)
def test_get_ds_browser(self):
cache = self._vmops._datastore_browser_mapping
ds_browser = mock.Mock()
@ -973,12 +1161,19 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
extra_specs=None,
config_drive=False):
self._vmops._volumeops = mock.Mock()
image_size = (self._instance.root_gb) * units.Gi / 2
image = {
'id': 'fake-image-d',
'id': self._image_id,
'disk_format': 'vmdk',
'size': 1 * units.Gi,
'size': image_size,
}
image_info = images.VMwareImage(
image_id=self._image_id,
file_size=image_size)
vi = self._vmops._get_vm_config_info(
self._instance, image_info)
self._vmops._volumeops = mock.Mock()
network_info = mock.Mock()
mock_get_datastore.return_value = self._ds
mock_get_datacenter_ref_and_name.return_value = self._dc_info
@ -1007,15 +1202,7 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
mock_is_neutron.assert_called_once_with()
expected_mkdir_calls = 2
if block_device_info and len(block_device_info.get(
'block_device_mapping', [])) > 0:
# if block_device_info contains key 'block_device_mapping'
# with any information, method mkdir wouldn't be called in
# method self._vmops.spawn()
expected_mkdir_calls = 0
self.assertEqual(expected_mkdir_calls, len(mock_mkdir.mock_calls))
self.assertEqual(2, mock_mkdir.call_count)
mock_get_vif_info.assert_called_once_with(
self._session, self._cluster.obj, False,
@ -1052,47 +1239,54 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
if (block_device_info and
'block_device_mapping' in block_device_info):
root_disk = block_device_info['block_device_mapping'][0]
mock_attach = self._vmops._volumeops.attach_root_volume
mock_attach.assert_called_once_with(
root_disk['connection_info'], self._instance,
self._ds.ref)
self.assertFalse(_wait_for_task.called)
self.assertFalse(_fetch_image.called)
self.assertFalse(_call_method.called)
else:
mock_enlist_image.assert_called_once_with(
bdms = block_device_info['block_device_mapping']
for bdm in bdms:
mock_attach_root = (
self._vmops._volumeops.attach_root_volume)
mock_attach = self._vmops._volumeops.attach_volume
adapter_type = bdm.get('disk_bus') or vi.ii.adapter_type
if bdm.get('boot_index') == 0:
mock_attach_root.assert_any_call(
bdm['connection_info'], self._instance,
self._ds.ref, adapter_type)
else:
mock_attach.assert_any_call(
bdm['connection_info'], self._instance,
self._ds.ref, adapter_type)
mock_enlist_image.assert_called_once_with(
self._image_id, self._ds, self._dc_info.ref)
upload_file_name = 'vmware_temp/tmp-uuid/%s/%s-flat.vmdk' % (
self._image_id, self._image_id)
_fetch_image.assert_called_once_with(
self._context,
self._instance,
self._session._host,
self._session._port,
self._dc_info.name,
self._ds.name,
upload_file_name,
cookies='Fake-CookieJar')
self.assertTrue(len(_wait_for_task.mock_calls) > 0)
extras = None
if block_device_info and 'ephemerals' in block_device_info:
extras = ['CreateVirtualDisk_Task']
self._verify_spawn_method_calls(_call_method, extras)
upload_file_name = 'vmware_temp/tmp-uuid/%s/%s-flat.vmdk' % (
self._image_id, self._image_id)
_fetch_image.assert_called_once_with(
self._context,
self._instance,
self._session._host,
self._session._port,
self._dc_info.name,
self._ds.name,
upload_file_name,
cookies='Fake-CookieJar')
self.assertTrue(len(_wait_for_task.mock_calls) > 0)
extras = None
if block_device_info and 'ephemerals' in block_device_info:
extras = ['CreateVirtualDisk_Task']
self._verify_spawn_method_calls(_call_method, extras)
dc_ref = 'fake_dc_ref'
source_file = unicode('[fake_ds] vmware_base/%s/%s.vmdk' %
(self._image_id, self._image_id))
dest_file = unicode('[fake_ds] vmware_base/%s/%s.%d.vmdk' %
(self._image_id, self._image_id,
self._instance['root_gb']))
# TODO(dims): add more tests for copy_virtual_disk after
# the disk/image code in spawn gets refactored
mock_copy_virtual_disk.assert_called_with(self._session,
dc_ref,
source_file,
dest_file)
dc_ref = 'fake_dc_ref'
source_file = unicode('[fake_ds] vmware_base/%s/%s.vmdk' %
(self._image_id, self._image_id))
dest_file = unicode('[fake_ds] vmware_base/%s/%s.%d.vmdk' %
(self._image_id, self._image_id,
self._instance['root_gb']))
# TODO(dims): add more tests for copy_virtual_disk after
# the disk/image code in spawn gets refactored
mock_copy_virtual_disk.assert_called_with(self._session,
dc_ref,
source_file,
dest_file)
if config_drive:
mock_configure_config_drive.assert_called_once_with(
self._instance, 'fake_vm_ref', self._dc_info,
@ -1160,14 +1354,18 @@ class VMwareVMOpsTestCase(test.NoDBTestCase):
def test_spawn_with_block_device_info(self):
block_device_info = {
'block_device_mapping': [{'connection_info': 'fake'}]
'block_device_mapping': [{'boot_index': 0,
'connection_info': 'fake',
'mount_device': '/dev/vda'}]
}
self._test_spawn(block_device_info=block_device_info)
def test_spawn_with_block_device_info_with_config_drive(self):
self.flags(force_config_drive=True)
block_device_info = {
'block_device_mapping': [{'connection_info': 'fake'}]
'block_device_mapping': [{'boot_index': 0,
'connection_info': 'fake',
'mount_device': '/dev/vda'}]
}
self._test_spawn(block_device_info=block_device_info,
config_drive=True)

View File

@ -17,10 +17,14 @@ import contextlib
import mock
from nova.compute import vm_states
from nova import context
from nova import exception
from nova import test
from nova.tests.unit import fake_instance
from nova.tests.unit.image import fake as image_fake
from nova.tests.unit.virt.vmwareapi import fake as vmwareapi_fake
from nova.tests.unit.virt.vmwareapi import stubs
from nova.virt.vmwareapi import constants
from nova.virt.vmwareapi import driver
from nova.virt.vmwareapi import vm_util
from nova.virt.vmwareapi import volumeops
@ -34,9 +38,22 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
vmwareapi_fake.reset()
stubs.set_stubs(self.stubs)
self._session = driver.VMwareAPISession()
self._context = context.RequestContext('fake_user', 'fake_project')
self._volumeops = volumeops.VMwareVolumeOps(self._session)
self.instance = {'name': 'fake_name', 'uuid': 'fake_uuid'}
self._image_id = image_fake.get_valid_image_id()
self._instance_values = {
'name': 'fake_name',
'uuid': 'fake_uuid',
'vcpus': 1,
'memory_mb': 512,
'image_ref': self._image_id,
'root_gb': 10,
'node': 'respool-1001(MyResPoolName)',
'expected_attrs': ['system_metadata'],
}
self._instance = fake_instance.fake_instance_obj(self._context,
**self._instance_values)
def _test_detach_disk_from_vm(self, destroy_disk=False):
def fake_call_method(module, method, *args, **kwargs):
@ -61,7 +78,7 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
fake_device.backing = vmwareapi_fake.DataObject()
fake_device.backing.fileName = 'fake_path'
fake_device.key = 'fake_key'
self._volumeops.detach_disk_from_vm('fake_vm_ref', self.instance,
self._volumeops.detach_disk_from_vm('fake_vm_ref', self._instance,
fake_device, destroy_disk)
_wait_for_task.assert_has_calls([
mock.call('fake_configure_task')])
@ -150,3 +167,91 @@ class VMwareVolumeOpsTestCase(test.NoDBTestCase):
get_vmdk_backed_disk_device.assert_called_once_with(
mock.sentinel.vm_ref, connection_info['data'])
self.assertTrue(get_vmdk_info.called)
def _test_attach_volume_vmdk(self, adapter_type=None):
connection_info = {'driver_volume_type': constants.DISK_FORMAT_VMDK,
'serial': 'volume-fake-id',
'data': {'volume': 'vm-10',
'volume_id': 'volume-fake-id'}}
vm_ref = 'fake-vm-ref'
volume_device = mock.MagicMock()
volume_device.backing.fileName = 'fake-path'
default_adapter_type = constants.DEFAULT_ADAPTER_TYPE
vmdk_info = vm_util.VmdkInfo('fake-path', default_adapter_type,
constants.DISK_TYPE_PREALLOCATED, 1024,
'fake-device')
adapter_type = adapter_type or default_adapter_type
with contextlib.nested(
mock.patch.object(vm_util, 'get_vm_ref', return_value=vm_ref),
mock.patch.object(self._volumeops, '_get_volume_ref'),
mock.patch.object(self._volumeops, '_get_vmdk_base_volume_device',
return_value=volume_device),
mock.patch.object(vm_util, 'get_vmdk_info',
return_value=vmdk_info),
mock.patch.object(self._volumeops, 'attach_disk_to_vm'),
mock.patch.object(self._volumeops, '_update_volume_details')
) as (get_vm_ref, get_volume_ref, get_vmdk_base_volume_device,
get_vmdk_info, attach_disk_to_vm, update_volume_details):
self._volumeops.attach_volume(connection_info, self._instance,
adapter_type)
get_vm_ref.assert_called_once_with(self._volumeops._session,
self._instance)
get_volume_ref.assert_called_once_with(
connection_info['data']['volume'])
self.assertTrue(get_vmdk_info.called)
attach_disk_to_vm.assert_called_once_with(vm_ref,
self._instance, adapter_type,
constants.DISK_TYPE_PREALLOCATED, vmdk_path='fake-path')
update_volume_details.assert_called_once_with(vm_ref,
self._instance, connection_info['data']['volume_id'])
def _test_attach_volume_iscsi(self, adapter_type=None):
connection_info = {'driver_volume_type': 'iscsi',
'serial': 'volume-fake-id',
'data': {'volume': 'vm-10',
'volume_id': 'volume-fake-id'}}
vm_ref = 'fake-vm-ref'
default_adapter_type = constants.DEFAULT_ADAPTER_TYPE
vmdk_info = vm_util.VmdkInfo('fake-path', default_adapter_type,
constants.DISK_TYPE_PREALLOCATED, 1024,
'fake-device')
adapter_type = adapter_type or default_adapter_type
with contextlib.nested(
mock.patch.object(vm_util, 'get_vm_ref', return_value=vm_ref),
mock.patch.object(self._volumeops, '_iscsi_discover_target',
return_value=(mock.sentinel.device_name,
mock.sentinel.uuid)),
mock.patch.object(vm_util, 'get_vmdk_info',
return_value=vmdk_info),
mock.patch.object(self._volumeops, 'attach_disk_to_vm')
) as (get_vm_ref, iscsi_discover_target, get_vmdk_info,
attach_disk_to_vm):
self._volumeops.attach_volume(connection_info, self._instance,
adapter_type)
get_vm_ref.assert_called_once_with(self._volumeops._session,
self._instance)
iscsi_discover_target.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, 'rdmp',
device_name=mock.sentinel.device_name)
def test_attach_volume_vmdk(self):
for adapter_type in (None, constants.DEFAULT_ADAPTER_TYPE,
constants.ADAPTER_TYPE_BUSLOGIC,
constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS,
constants.ADAPTER_TYPE_PARAVIRTUAL):
self._test_attach_volume_vmdk(adapter_type)
def test_attach_volume_iscsi(self):
for adapter_type in (None, constants.DEFAULT_ADAPTER_TYPE,
constants.ADAPTER_TYPE_BUSLOGIC,
constants.ADAPTER_TYPE_LSILOGICSAS,
constants.ADAPTER_TYPE_PARAVIRTUAL):
self._test_attach_volume_iscsi(adapter_type)

View File

@ -606,24 +606,7 @@ class VMwareVMOps(object):
block_device_mapping = driver.block_device_info_get_mapping(
block_device_info)
# NOTE(mdbooth): the logic here is that we ignore the image if there
# are block device mappings. This behaviour is incorrect, and a bug in
# the driver. We should be able to accept an image and block device
# mappings.
if len(block_device_mapping) > 0:
msg = "Block device information present: %s" % block_device_info
# NOTE(mriedem): block_device_info can contain an auth_password
# so we have to scrub the message before logging it.
LOG.debug(strutils.mask_password(msg), instance=instance)
for root_disk in block_device_mapping:
connection_info = root_disk['connection_info']
# TODO(hartsocks): instance is unnecessary, remove it
# we still use instance in many locations for no other purpose
# than logging, can we simplify this?
self._volumeops.attach_root_volume(connection_info, instance,
vi.datastore.ref)
else:
if instance.image_ref:
self._imagecache.enlist_image(
image_info.image_id, vi.datastore, vi.dc_info.ref)
self._fetch_image_if_missing(context, vi)
@ -635,6 +618,30 @@ class VMwareVMOps(object):
else:
self._use_disk_image_as_full_clone(vm_ref, vi)
if len(block_device_mapping) > 0:
msg = "Block device information present: %s" % block_device_info
# NOTE(mriedem): block_device_info can contain an auth_password
# so we have to scrub the message before logging it.
LOG.debug(strutils.mask_password(msg), instance=instance)
# Before attempting to attach any volume, make sure the
# block_device_mapping (i.e. disk_bus) is valid
self._is_bdm_valid(block_device_mapping)
for disk in block_device_mapping:
connection_info = disk['connection_info']
adapter_type = disk.get('disk_bus') or vi.ii.adapter_type
# TODO(hartsocks): instance is unnecessary, remove it
# we still use instance in many locations for no other purpose
# than logging, can we simplify this?
if disk.get('boot_index') == 0:
self._volumeops.attach_root_volume(connection_info,
instance, vi.datastore.ref, adapter_type)
else:
self._volumeops.attach_volume(connection_info,
instance, adapter_type)
# Create ephemeral disks
self._create_ephemeral(block_device_info, instance, vm_ref,
vi.dc_info, vi.datastore, instance.uuid,
@ -648,6 +655,20 @@ class VMwareVMOps(object):
if power_on:
vm_util.power_on_instance(self._session, instance, vm_ref=vm_ref)
def _is_bdm_valid(self, block_device_mapping):
"""Checks if the block device mapping is valid."""
valid_bus = (constants.DEFAULT_ADAPTER_TYPE,
constants.ADAPTER_TYPE_BUSLOGIC,
constants.ADAPTER_TYPE_IDE,
constants.ADAPTER_TYPE_LSILOGICSAS,
constants.ADAPTER_TYPE_PARAVIRTUAL)
for disk in block_device_mapping:
adapter_type = disk.get('disk_bus')
if (adapter_type is not None and adapter_type not in valid_bus):
raise exception.UnsupportedHardware(model=adapter_type,
virt="vmware")
def _create_config_drive(self, instance, injected_files, admin_password,
data_store_name, dc_name, upload_folder, cookies):
if CONF.config_drive_format != 'iso9660':

View File

@ -311,7 +311,8 @@ class VMwareVolumeOps(object):
"VirtualMachine", "config.hardware.device")
return vm_util.get_vmdk_volume_disk(hardware_devices)
def _attach_volume_vmdk(self, connection_info, instance):
def _attach_volume_vmdk(self, connection_info, instance,
adapter_type=None):
"""Attach vmdk volume storage to VM instance."""
vm_ref = vm_util.get_vm_ref(self._session, instance)
LOG.debug("_attach_volume_vmdk: %s", connection_info,
@ -322,23 +323,25 @@ class VMwareVolumeOps(object):
# Get details required for adding disk device such as
# adapter_type, disk_type
vmdk = vm_util.get_vmdk_info(self._session, volume_ref)
adapter_type = adapter_type or vmdk.adapter_type
# IDE does not support disk hotplug
if (instance.vm_state == vm_states.ACTIVE and
vmdk.adapter_type == constants.ADAPTER_TYPE_IDE):
msg = _('%s does not support disk hotplug.') % vmdk.adapter_type
adapter_type == constants.ADAPTER_TYPE_IDE):
msg = _('%s does not support disk hotplug.') % adapter_type
raise exception.Invalid(msg)
# Attach the disk to virtual machine instance
self.attach_disk_to_vm(vm_ref, instance, vmdk.adapter_type,
vmdk.disk_type, vmdk_path=vmdk.path)
self.attach_disk_to_vm(vm_ref, instance, adapter_type, vmdk.disk_type,
vmdk_path=vmdk.path)
# Store the uuid of the volume_device
self._update_volume_details(vm_ref, instance, data['volume_id'])
LOG.debug("Attached VMDK: %s", connection_info, instance=instance)
def _attach_volume_iscsi(self, connection_info, instance):
def _attach_volume_iscsi(self, connection_info, instance,
adapter_type=None):
"""Attach iscsi volume storage to VM instance."""
vm_ref = vm_util.get_vm_ref(self._session, instance)
# Attach Volume to VM
@ -354,21 +357,22 @@ class VMwareVolumeOps(object):
reason=_("Unable to find iSCSI Target"))
vmdk = vm_util.get_vmdk_info(self._session, vm_ref)
adapter_type = adapter_type or vmdk.adapter_type
self.attach_disk_to_vm(vm_ref, instance,
vmdk.adapter_type, 'rdmp',
adapter_type, 'rdmp',
device_name=device_name)
LOG.debug("Attached ISCSI: %s", connection_info, instance=instance)
def attach_volume(self, connection_info, instance):
def attach_volume(self, connection_info, instance, adapter_type=None):
"""Attach volume storage to VM instance."""
driver_type = connection_info['driver_volume_type']
LOG.debug("Volume attach. Driver type: %s", driver_type,
instance=instance)
if driver_type == 'vmdk':
self._attach_volume_vmdk(connection_info, instance)
self._attach_volume_vmdk(connection_info, instance, adapter_type)
elif driver_type == 'iscsi':
self._attach_volume_iscsi(connection_info, instance)
self._attach_volume_iscsi(connection_info, instance, adapter_type)
else:
raise exception.VolumeDriverNotFound(driver_type=driver_type)
@ -536,7 +540,7 @@ class VMwareVolumeOps(object):
raise exception.VolumeDriverNotFound(driver_type=driver_type)
def attach_root_volume(self, connection_info, instance,
datastore):
datastore, adapter_type=None):
"""Attach a root volume to the VM instance."""
driver_type = connection_info['driver_volume_type']
LOG.debug("Root volume attach. Driver type: %s", driver_type,
@ -551,4 +555,4 @@ class VMwareVolumeOps(object):
res_pool = self._get_res_pool_of_vm(vm_ref)
self._relocate_vmdk_volume(volume_ref, res_pool, datastore)
self.attach_volume(connection_info, instance)
self.attach_volume(connection_info, instance, adapter_type)