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:
parent
017574ee82
commit
264425678f
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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':
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user