Merge "Refactor how BDMs are handled when booting"
This commit is contained in:
commit
5422115924
|
@ -370,6 +370,21 @@ def is_swap_or_ephemeral(device_name):
|
||||||
(device_name == 'swap' or is_ephemeral(device_name)))
|
(device_name == 'swap' or is_ephemeral(device_name)))
|
||||||
|
|
||||||
|
|
||||||
|
def new_format_is_swap(bdm):
|
||||||
|
if (bdm.get('source_type') == 'blank' and
|
||||||
|
bdm.get('destination_type') == 'local' and
|
||||||
|
bdm.get('guest_format') == 'swap'):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def new_format_is_ephemeral(bdm):
|
||||||
|
if (bdm.get('source_type') == 'blank' and not
|
||||||
|
new_format_is_swap(bdm)):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
def mappings_prepend_dev(mappings):
|
def mappings_prepend_dev(mappings):
|
||||||
"""Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type."""
|
"""Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type."""
|
||||||
for m in mappings:
|
for m in mappings:
|
||||||
|
|
|
@ -40,7 +40,6 @@ import uuid
|
||||||
from eventlet import greenthread
|
from eventlet import greenthread
|
||||||
from oslo.config import cfg
|
from oslo.config import cfg
|
||||||
|
|
||||||
from nova import block_device
|
|
||||||
from nova.cells import rpcapi as cells_rpcapi
|
from nova.cells import rpcapi as cells_rpcapi
|
||||||
from nova.cloudpipe import pipelib
|
from nova.cloudpipe import pipelib
|
||||||
from nova import compute
|
from nova import compute
|
||||||
|
@ -76,6 +75,7 @@ from nova import paths
|
||||||
from nova import quota
|
from nova import quota
|
||||||
from nova import safe_utils
|
from nova import safe_utils
|
||||||
from nova import utils
|
from nova import utils
|
||||||
|
from nova.virt import block_device as driver_block_device
|
||||||
from nova.virt import driver
|
from nova.virt import driver
|
||||||
from nova.virt import event as virtevent
|
from nova.virt import event as virtevent
|
||||||
from nova.virt import storage_users
|
from nova.virt import storage_users
|
||||||
|
@ -906,70 +906,6 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||||
seconds=int(time.time() - start),
|
seconds=int(time.time() - start),
|
||||||
attempts=attempts)
|
attempts=attempts)
|
||||||
|
|
||||||
def _setup_block_device_mapping(self, context, instance, bdms):
|
|
||||||
"""setup volumes for block device mapping."""
|
|
||||||
block_device_mapping = []
|
|
||||||
swap = None
|
|
||||||
ephemerals = []
|
|
||||||
for bdm in bdms:
|
|
||||||
LOG.debug(_('Setting up bdm %s'), bdm, instance=instance)
|
|
||||||
|
|
||||||
if bdm['no_device']:
|
|
||||||
continue
|
|
||||||
if bdm['virtual_name']:
|
|
||||||
virtual_name = bdm['virtual_name']
|
|
||||||
device_name = bdm['device_name']
|
|
||||||
assert block_device.is_swap_or_ephemeral(virtual_name)
|
|
||||||
if virtual_name == 'swap':
|
|
||||||
swap = {'device_name': device_name,
|
|
||||||
'swap_size': bdm['volume_size']}
|
|
||||||
elif block_device.is_ephemeral(virtual_name):
|
|
||||||
eph = {'num': block_device.ephemeral_num(virtual_name),
|
|
||||||
'virtual_name': virtual_name,
|
|
||||||
'device_name': device_name,
|
|
||||||
'size': bdm['volume_size']}
|
|
||||||
ephemerals.append(eph)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if ((bdm['snapshot_id'] is not None) and
|
|
||||||
(bdm['volume_id'] is None)):
|
|
||||||
# TODO(yamahata): default name and description
|
|
||||||
snapshot = self.volume_api.get_snapshot(context,
|
|
||||||
bdm['snapshot_id'])
|
|
||||||
vol = self.volume_api.create(context, bdm['volume_size'],
|
|
||||||
'', '', snapshot)
|
|
||||||
self._await_block_device_map_created(context, vol['id'])
|
|
||||||
self.conductor_api.block_device_mapping_update(
|
|
||||||
context, bdm['id'], {'volume_id': vol['id']})
|
|
||||||
bdm['volume_id'] = vol['id']
|
|
||||||
|
|
||||||
if bdm['volume_id'] is not None:
|
|
||||||
volume = self.volume_api.get(context, bdm['volume_id'])
|
|
||||||
self.volume_api.check_attach(context, volume,
|
|
||||||
instance=instance)
|
|
||||||
cinfo = self._attach_volume_boot(context,
|
|
||||||
instance,
|
|
||||||
volume,
|
|
||||||
bdm['device_name'])
|
|
||||||
if 'serial' not in cinfo:
|
|
||||||
cinfo['serial'] = bdm['volume_id']
|
|
||||||
self.conductor_api.block_device_mapping_update(
|
|
||||||
context, bdm['id'],
|
|
||||||
{'connection_info': jsonutils.dumps(cinfo)})
|
|
||||||
bdmap = {'connection_info': cinfo,
|
|
||||||
'mount_device': bdm['device_name'],
|
|
||||||
'delete_on_termination': bdm['delete_on_termination']}
|
|
||||||
block_device_mapping.append(bdmap)
|
|
||||||
|
|
||||||
block_device_info = {
|
|
||||||
'root_device_name': instance['root_device_name'],
|
|
||||||
'swap': swap,
|
|
||||||
'ephemerals': ephemerals,
|
|
||||||
'block_device_mapping': block_device_mapping
|
|
||||||
}
|
|
||||||
|
|
||||||
return block_device_info
|
|
||||||
|
|
||||||
def _decode_files(self, injected_files):
|
def _decode_files(self, injected_files):
|
||||||
"""Base64 decode the list of files to inject."""
|
"""Base64 decode the list of files to inject."""
|
||||||
if not injected_files:
|
if not injected_files:
|
||||||
|
@ -1068,7 +1004,7 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||||
|
|
||||||
network_info = None
|
network_info = None
|
||||||
bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
|
bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
|
||||||
context, instance)
|
context, instance, legacy=False)
|
||||||
|
|
||||||
# b64 decode the files to inject:
|
# b64 decode the files to inject:
|
||||||
injected_files_orig = injected_files
|
injected_files_orig = injected_files
|
||||||
|
@ -1333,7 +1269,29 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||||
def _prep_block_device(self, context, instance, bdms):
|
def _prep_block_device(self, context, instance, bdms):
|
||||||
"""Set up the block device for an instance with error logging."""
|
"""Set up the block device for an instance with error logging."""
|
||||||
try:
|
try:
|
||||||
return self._setup_block_device_mapping(context, instance, bdms)
|
block_device_info = {
|
||||||
|
'root_device_name': instance['root_device_name'],
|
||||||
|
'swap': driver_block_device.get_swap(
|
||||||
|
driver_block_device.legacy_block_devices(
|
||||||
|
driver_block_device.convert_swap(bdms))),
|
||||||
|
'ephemerals': driver_block_device.legacy_block_devices(
|
||||||
|
driver_block_device.convert_ephemerals(bdms)),
|
||||||
|
'block_device_mapping':
|
||||||
|
(driver_block_device.legacy_block_devices(
|
||||||
|
driver_block_device.attach_block_devices(
|
||||||
|
driver_block_device.convert_volumes(bdms),
|
||||||
|
context, instance, self.volume_api,
|
||||||
|
self.driver, self.conductor_api)) +
|
||||||
|
driver_block_device.legacy_block_devices(
|
||||||
|
driver_block_device.attach_block_devices(
|
||||||
|
driver_block_device.convert_snapshots(bdms),
|
||||||
|
context, instance, self.volume_api,
|
||||||
|
self.driver, self.conductor_api,
|
||||||
|
self._await_block_device_map_created)))
|
||||||
|
}
|
||||||
|
|
||||||
|
return block_device_info
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
with excutils.save_and_reraise_exception():
|
with excutils.save_and_reraise_exception():
|
||||||
LOG.exception(_('Instance failed block device setup'),
|
LOG.exception(_('Instance failed block device setup'),
|
||||||
|
@ -3382,25 +3340,6 @@ class ComputeManager(manager.SchedulerDependentManager):
|
||||||
|
|
||||||
return console_info['port'] == port
|
return console_info['port'] == port
|
||||||
|
|
||||||
def _attach_volume_boot(self, context, instance, volume, mountpoint):
|
|
||||||
"""Attach a volume to an instance at boot time. So actual attach
|
|
||||||
is done by instance creation.
|
|
||||||
"""
|
|
||||||
|
|
||||||
instance_id = instance['id']
|
|
||||||
instance_uuid = instance['uuid']
|
|
||||||
volume_id = volume['id']
|
|
||||||
context = context.elevated()
|
|
||||||
LOG.audit(_('Booting with volume %(volume_id)s at %(mountpoint)s'),
|
|
||||||
{'volume_id': volume_id, 'mountpoint': mountpoint},
|
|
||||||
context=context, instance=instance)
|
|
||||||
connector = self.driver.get_volume_connector(instance)
|
|
||||||
connection_info = self.volume_api.initialize_connection(context,
|
|
||||||
volume_id,
|
|
||||||
connector)
|
|
||||||
self.volume_api.attach(context, volume_id, instance_uuid, mountpoint)
|
|
||||||
return connection_info
|
|
||||||
|
|
||||||
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
|
@exception.wrap_exception(notifier=notifier, publisher_id=publisher_id())
|
||||||
@reverts_task_state
|
@reverts_task_state
|
||||||
@wrap_instance_fault
|
@wrap_instance_fault
|
||||||
|
|
|
@ -419,17 +419,19 @@ class ComputeVolumeTestCase(BaseTestCase):
|
||||||
self.assertEqual(attempts, 3)
|
self.assertEqual(attempts, 3)
|
||||||
|
|
||||||
def test_boot_volume_serial(self):
|
def test_boot_volume_serial(self):
|
||||||
block_device_mapping = [{
|
block_device_mapping = [
|
||||||
|
block_device.BlockDeviceDict({
|
||||||
'id': 1,
|
'id': 1,
|
||||||
'no_device': None,
|
'no_device': None,
|
||||||
'virtual_name': None,
|
'source_type': 'volume',
|
||||||
|
'destination_type': 'volume',
|
||||||
'snapshot_id': None,
|
'snapshot_id': None,
|
||||||
'volume_id': self.volume_id,
|
'volume_id': self.volume_id,
|
||||||
'device_name': '/dev/vdb',
|
'device_name': '/dev/vdb',
|
||||||
'delete_on_termination': False,
|
'delete_on_termination': False,
|
||||||
}]
|
})]
|
||||||
self.compute._setup_block_device_mapping(self.context, self.instance,
|
self.compute._prep_block_device(self.context, self.instance,
|
||||||
block_device_mapping)
|
block_device_mapping)
|
||||||
self.assertEqual(self.cinfo.get('serial'), self.volume_id)
|
self.assertEqual(self.cinfo.get('serial'), self.volume_id)
|
||||||
|
|
||||||
def test_boot_volume_metadata(self):
|
def test_boot_volume_metadata(self):
|
||||||
|
@ -1204,7 +1206,7 @@ class ComputeTestCase(BaseTestCase):
|
||||||
def fake(*args, **kwargs):
|
def fake(*args, **kwargs):
|
||||||
raise test.TestingException()
|
raise test.TestingException()
|
||||||
self.stubs.Set(nova.compute.manager.ComputeManager,
|
self.stubs.Set(nova.compute.manager.ComputeManager,
|
||||||
'_setup_block_device_mapping', fake)
|
'_prep_block_device', fake)
|
||||||
instance = self._create_instance()
|
instance = self._create_instance()
|
||||||
self.assertRaises(test.TestingException, self.compute.run_instance,
|
self.assertRaises(test.TestingException, self.compute.run_instance,
|
||||||
self.context, instance=instance)
|
self.context, instance=instance)
|
||||||
|
@ -2800,8 +2802,8 @@ class ComputeTestCase(BaseTestCase):
|
||||||
# When a spawn fails the network must be deallocated.
|
# When a spawn fails the network must be deallocated.
|
||||||
instance = jsonutils.to_primitive(self._create_fake_instance())
|
instance = jsonutils.to_primitive(self._create_fake_instance())
|
||||||
|
|
||||||
self.mox.StubOutWithMock(self.compute, "_setup_block_device_mapping")
|
self.mox.StubOutWithMock(self.compute, "_prep_block_device")
|
||||||
self.compute._setup_block_device_mapping(
|
self.compute._prep_block_device(
|
||||||
mox.IgnoreArg(), mox.IgnoreArg(),
|
mox.IgnoreArg(), mox.IgnoreArg(),
|
||||||
mox.IgnoreArg()).AndRaise(rpc.common.RemoteError('', '', ''))
|
mox.IgnoreArg()).AndRaise(rpc.common.RemoteError('', '', ''))
|
||||||
|
|
||||||
|
@ -9010,6 +9012,7 @@ class EvacuateHostTestCase(BaseTestCase):
|
||||||
def test_rebuild_on_host_with_volumes(self):
|
def test_rebuild_on_host_with_volumes(self):
|
||||||
"""Confirm evacuate scenario reconnects volumes."""
|
"""Confirm evacuate scenario reconnects volumes."""
|
||||||
values = {'instance_uuid': self.inst_ref['uuid'],
|
values = {'instance_uuid': self.inst_ref['uuid'],
|
||||||
|
'source_type': 'volume',
|
||||||
'device_name': '/dev/vdc',
|
'device_name': '/dev/vdc',
|
||||||
'delete_on_termination': False,
|
'delete_on_termination': False,
|
||||||
'volume_id': 'fake_volume_id'}
|
'volume_id': 'fake_volume_id'}
|
||||||
|
@ -9036,10 +9039,10 @@ class EvacuateHostTestCase(BaseTestCase):
|
||||||
self.mox.StubOutWithMock(self.compute.volume_api, 'detach')
|
self.mox.StubOutWithMock(self.compute.volume_api, 'detach')
|
||||||
self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg())
|
self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg())
|
||||||
|
|
||||||
self.mox.StubOutWithMock(self.compute, '_setup_block_device_mapping')
|
self.mox.StubOutWithMock(self.compute, '_prep_block_device')
|
||||||
self.compute._setup_block_device_mapping(mox.IsA(self.context),
|
self.compute._prep_block_device(mox.IsA(self.context),
|
||||||
mox.IsA(self.inst_ref),
|
mox.IsA(self.inst_ref),
|
||||||
mox.IgnoreArg())
|
mox.IgnoreArg())
|
||||||
|
|
||||||
self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True)
|
self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True)
|
||||||
self.mox.ReplayAll()
|
self.mox.ReplayAll()
|
||||||
|
|
|
@ -0,0 +1,331 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
from nova import block_device
|
||||||
|
from nova.conductor import api as conductor_api
|
||||||
|
from nova import context
|
||||||
|
from nova.openstack.common import jsonutils
|
||||||
|
from nova import test
|
||||||
|
from nova.tests import matchers
|
||||||
|
from nova.virt import block_device as driver_block_device
|
||||||
|
from nova.virt import driver
|
||||||
|
from nova.volume import cinder
|
||||||
|
|
||||||
|
|
||||||
|
class TestDriverBlockDevice(test.TestCase):
|
||||||
|
driver_classes = {
|
||||||
|
'swap': driver_block_device.DriverSwapBlockDevice,
|
||||||
|
'ephemeral': driver_block_device.DriverEphemeralBlockDevice,
|
||||||
|
'volume': driver_block_device.DriverVolumeBlockDevice,
|
||||||
|
'snapshot': driver_block_device.DriverSnapshotBlockDevice
|
||||||
|
}
|
||||||
|
|
||||||
|
swap_bdm = block_device.BlockDeviceDict(
|
||||||
|
{'id': 1, 'instance_uuid': 'fake-instance',
|
||||||
|
'device_name': '/dev/sdb1',
|
||||||
|
'source_type': 'blank',
|
||||||
|
'destination_type': 'local',
|
||||||
|
'delete_on_termination': True,
|
||||||
|
'guest_format': 'swap',
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'volume_size': 2,
|
||||||
|
'boot_index': -1})
|
||||||
|
|
||||||
|
swap_driver_bdm = {
|
||||||
|
'device_name': '/dev/sdb1',
|
||||||
|
'swap_size': 2,
|
||||||
|
'disk_bus': 'scsi'}
|
||||||
|
|
||||||
|
swap_legacy_driver_bdm = {
|
||||||
|
'device_name': '/dev/sdb1',
|
||||||
|
'swap_size': 2}
|
||||||
|
|
||||||
|
ephemeral_bdm = block_device.BlockDeviceDict(
|
||||||
|
{'id': 2, 'instance_uuid': 'fake-instance',
|
||||||
|
'device_name': '/dev/sdc1',
|
||||||
|
'source_type': 'blank',
|
||||||
|
'destination_type': 'local',
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'device_type': 'disk',
|
||||||
|
'volume_size': 4,
|
||||||
|
'guest_format': 'ext4',
|
||||||
|
'delete_on_termination': True,
|
||||||
|
'boot_index': -1})
|
||||||
|
|
||||||
|
ephemeral_driver_bdm = {
|
||||||
|
'device_name': '/dev/sdc1',
|
||||||
|
'size': 4,
|
||||||
|
'device_type': 'disk',
|
||||||
|
'guest_format': 'ext4',
|
||||||
|
'disk_bus': 'scsi'}
|
||||||
|
|
||||||
|
ephemeral_legacy_driver_bdm = {
|
||||||
|
'device_name': '/dev/sdc1',
|
||||||
|
'size': 4,
|
||||||
|
'virtual_name': 'ephemeral0',
|
||||||
|
'num': 0}
|
||||||
|
|
||||||
|
volume_bdm = block_device.BlockDeviceDict(
|
||||||
|
{'id': 3, 'instance_uuid': 'fake-instance',
|
||||||
|
'device_name': '/dev/sda1',
|
||||||
|
'source_type': 'volume',
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'device_type': 'disk',
|
||||||
|
'volume_size': 8,
|
||||||
|
'destination_type': 'volume',
|
||||||
|
'volume_id': 'fake-volume-id-1',
|
||||||
|
'guest_format': 'ext4',
|
||||||
|
'connection_info': '{"fake": "connection_info"}',
|
||||||
|
'delete_on_termination': False,
|
||||||
|
'boot_index': 0})
|
||||||
|
|
||||||
|
volume_driver_bdm = {
|
||||||
|
'mount_device': '/dev/sda1',
|
||||||
|
'connection_info': {"fake": "connection_info"},
|
||||||
|
'delete_on_termination': False,
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'device_type': 'disk',
|
||||||
|
'guest_format': 'ext4',
|
||||||
|
'boot_index': 0}
|
||||||
|
|
||||||
|
volume_legacy_driver_bdm = {
|
||||||
|
'mount_device': '/dev/sda1',
|
||||||
|
'connection_info': {"fake": "connection_info"},
|
||||||
|
'delete_on_termination': False}
|
||||||
|
|
||||||
|
snapshot_bdm = block_device.BlockDeviceDict(
|
||||||
|
{'id': 4, 'instance_uuid': 'fake-instance',
|
||||||
|
'device_name': '/dev/sda2',
|
||||||
|
'delete_on_termination': True,
|
||||||
|
'volume_size': 3,
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'device_type': 'disk',
|
||||||
|
'source_type': 'snapshot',
|
||||||
|
'destination_type': 'volume',
|
||||||
|
'connection_info': '{"fake": "connection_info"}',
|
||||||
|
'snapshot_id': 'fake-snapshot-id-1',
|
||||||
|
'volume_id': 'fake-volume-id-2',
|
||||||
|
'boot_index': -1})
|
||||||
|
|
||||||
|
snapshot_driver_bdm = {
|
||||||
|
'mount_device': '/dev/sda2',
|
||||||
|
'connection_info': {"fake": "connection_info"},
|
||||||
|
'delete_on_termination': True,
|
||||||
|
'disk_bus': 'scsi',
|
||||||
|
'device_type': 'disk',
|
||||||
|
'guest_format': None,
|
||||||
|
'boot_index': -1}
|
||||||
|
|
||||||
|
snapshot_legacy_driver_bdm = {
|
||||||
|
'mount_device': '/dev/sda2',
|
||||||
|
'connection_info': {"fake": "connection_info"},
|
||||||
|
'delete_on_termination': True}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestDriverBlockDevice, self).setUp()
|
||||||
|
self.volume_api = self.mox.CreateMock(cinder.API)
|
||||||
|
self.virt_driver = self.mox.CreateMock(driver.ComputeDriver)
|
||||||
|
self.db_api = self.mox.CreateMock(conductor_api.API)
|
||||||
|
self.context = context.RequestContext('fake_user',
|
||||||
|
'fake_project')
|
||||||
|
|
||||||
|
def test_driver_block_device_base_class(self):
|
||||||
|
self.base_class_transform_called = False
|
||||||
|
|
||||||
|
class DummyBlockDevice(driver_block_device.DriverBlockDevice):
|
||||||
|
_fields = set(['foo', 'bar'])
|
||||||
|
_legacy_fields = set(['foo', 'baz'])
|
||||||
|
|
||||||
|
def _transform(inst, bdm):
|
||||||
|
self.base_class_transform_called = True
|
||||||
|
|
||||||
|
dummy_device = DummyBlockDevice({'foo': 'foo_val', 'id': 42})
|
||||||
|
self.assertTrue(self.base_class_transform_called)
|
||||||
|
self.assertThat(dummy_device, matchers.DictMatches(
|
||||||
|
{'foo': None, 'bar': None}))
|
||||||
|
self.assertEquals(dummy_device.id, 42)
|
||||||
|
self.assertThat(dummy_device.legacy(), matchers.DictMatches(
|
||||||
|
{'foo': None, 'baz': None}))
|
||||||
|
|
||||||
|
self.assertRaises(driver_block_device._NotTransformable,
|
||||||
|
DummyBlockDevice, {'no_device': True})
|
||||||
|
|
||||||
|
def _test_driver_device(self, name):
|
||||||
|
test_bdm = self.driver_classes[name](
|
||||||
|
getattr(self, "%s_bdm" % name))
|
||||||
|
self.assertThat(test_bdm, matchers.DictMatches(
|
||||||
|
getattr(self, "%s_driver_bdm" % name)))
|
||||||
|
self.assertThat(test_bdm.legacy(),
|
||||||
|
matchers.DictMatches(
|
||||||
|
getattr(self, "%s_legacy_driver_bdm" % name)))
|
||||||
|
# Make sure that all others raise _invalidType
|
||||||
|
for other_name, cls in self.driver_classes.iteritems():
|
||||||
|
if other_name == name:
|
||||||
|
continue
|
||||||
|
self.assertRaises(driver_block_device._InvalidType,
|
||||||
|
cls,
|
||||||
|
getattr(self, '%s_bdm' % name))
|
||||||
|
|
||||||
|
def test_driver_swap_block_device(self):
|
||||||
|
self._test_driver_device("swap")
|
||||||
|
|
||||||
|
def test_driver_ephemeral_block_device(self):
|
||||||
|
self._test_driver_device("ephemeral")
|
||||||
|
|
||||||
|
def test_driver_volume_block_device(self):
|
||||||
|
self._test_driver_device("volume")
|
||||||
|
|
||||||
|
test_bdm = self.driver_classes['volume'](
|
||||||
|
self.volume_bdm)
|
||||||
|
self.assertEquals(test_bdm.id, 3)
|
||||||
|
self.assertEquals(test_bdm.volume_id, 'fake-volume-id-1')
|
||||||
|
self.assertEquals(test_bdm.volume_size, 8)
|
||||||
|
|
||||||
|
def test_driver_snapshot_block_device(self):
|
||||||
|
self._test_driver_device("snapshot")
|
||||||
|
|
||||||
|
test_bdm = self.driver_classes['snapshot'](
|
||||||
|
self.snapshot_bdm)
|
||||||
|
self.assertEquals(test_bdm.id, 4)
|
||||||
|
self.assertEquals(test_bdm.snapshot_id, 'fake-snapshot-id-1')
|
||||||
|
self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2')
|
||||||
|
self.assertEquals(test_bdm.volume_size, 3)
|
||||||
|
|
||||||
|
def test_volume_attach(self):
|
||||||
|
test_bdm = self.driver_classes['volume'](
|
||||||
|
self.volume_bdm)
|
||||||
|
elevated_context = self.context.elevated()
|
||||||
|
self.stubs.Set(self.context, 'elevated',
|
||||||
|
lambda: elevated_context)
|
||||||
|
|
||||||
|
instance = {'id': 'fake_id', 'uuid': 'fake_uuid'}
|
||||||
|
volume = {'id': 'fake-volume-id-1'}
|
||||||
|
connector = {'ip': 'fake_ip', 'host': 'fake_host'}
|
||||||
|
connection_info = {'data': 'fake_data'}
|
||||||
|
expected_conn_info = {'data': 'fake_data',
|
||||||
|
'serial': 'fake-volume-id-1'}
|
||||||
|
|
||||||
|
self.volume_api.get(self.context,
|
||||||
|
'fake-volume-id-1').AndReturn(volume)
|
||||||
|
self.volume_api.check_attach(self.context, volume,
|
||||||
|
instance=instance).AndReturn(None)
|
||||||
|
self.virt_driver.get_volume_connector(instance).AndReturn(connector)
|
||||||
|
self.volume_api.initialize_connection(
|
||||||
|
elevated_context, volume['id'],
|
||||||
|
connector).AndReturn(connection_info)
|
||||||
|
self.volume_api.attach(elevated_context, 'fake-volume-id-1',
|
||||||
|
'fake_uuid', '/dev/sda1').AndReturn(None)
|
||||||
|
self.db_api.block_device_mapping_update(elevated_context, 3,
|
||||||
|
{'connection_info': jsonutils.dumps(expected_conn_info)})
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
test_bdm.attach(self.context, instance,
|
||||||
|
self.volume_api, self.virt_driver, self.db_api)
|
||||||
|
self.assertThat(test_bdm['connection_info'],
|
||||||
|
matchers.DictMatches(expected_conn_info))
|
||||||
|
|
||||||
|
def test_snapshot_attach_no_volume(self):
|
||||||
|
no_volume_snapshot = self.snapshot_bdm.copy()
|
||||||
|
no_volume_snapshot['volume_id'] = None
|
||||||
|
test_bdm = self.driver_classes['snapshot'](no_volume_snapshot)
|
||||||
|
|
||||||
|
instance = {'id': 'fake_id', 'uuid': 'fake_uuid'}
|
||||||
|
snapshot = {'id': 'fake-snapshot-id-1'}
|
||||||
|
volume = {'id': 'fake-volume-id-2'}
|
||||||
|
|
||||||
|
wait_func = self.mox.CreateMockAnything()
|
||||||
|
volume_class = self.driver_classes['volume']
|
||||||
|
self.mox.StubOutWithMock(volume_class, 'attach')
|
||||||
|
|
||||||
|
self.volume_api.get_snapshot(self.context,
|
||||||
|
'fake-snapshot-id-1').AndReturn(snapshot)
|
||||||
|
self.volume_api.create(self.context, 3,
|
||||||
|
'', '', snapshot).AndReturn(volume)
|
||||||
|
wait_func(self.context, 'fake-volume-id-2').AndReturn(None)
|
||||||
|
self.db_api.block_device_mapping_update(
|
||||||
|
self.context, 4, {'volume_id': 'fake-volume-id-2'}).AndReturn(None)
|
||||||
|
volume_class.attach(self.context, instance, self.volume_api,
|
||||||
|
self.virt_driver, self.db_api).AndReturn(None)
|
||||||
|
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
test_bdm.attach(self.context, instance, self.volume_api,
|
||||||
|
self.virt_driver, self.db_api, wait_func)
|
||||||
|
self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2')
|
||||||
|
|
||||||
|
def test_snapshot_attach_volume(self):
|
||||||
|
test_bdm = self.driver_classes['snapshot'](
|
||||||
|
self.snapshot_bdm)
|
||||||
|
|
||||||
|
instance = {'id': 'fake_id', 'uuid': 'fake_uuid'}
|
||||||
|
|
||||||
|
volume_class = self.driver_classes['volume']
|
||||||
|
self.mox.StubOutWithMock(volume_class, 'attach')
|
||||||
|
|
||||||
|
# Make sure theses are not called
|
||||||
|
self.mox.StubOutWithMock(self.volume_api, 'get_snapshot')
|
||||||
|
self.mox.StubOutWithMock(self.volume_api, 'create')
|
||||||
|
self.mox.StubOutWithMock(self.db_api,
|
||||||
|
'block_device_mapping_update')
|
||||||
|
|
||||||
|
volume_class.attach(self.context, instance, self.volume_api,
|
||||||
|
self.virt_driver, self.db_api).AndReturn(None)
|
||||||
|
self.mox.ReplayAll()
|
||||||
|
|
||||||
|
test_bdm.attach(self.context, instance, self.volume_api,
|
||||||
|
self.virt_driver, self.db_api)
|
||||||
|
self.assertEquals(test_bdm.volume_id, 'fake-volume-id-2')
|
||||||
|
|
||||||
|
def test_convert_block_devices(self):
|
||||||
|
converted = driver_block_device._convert_block_devices(
|
||||||
|
self.driver_classes['volume'],
|
||||||
|
[self.volume_bdm, self.ephemeral_bdm])
|
||||||
|
self.assertEquals(converted, [self.volume_driver_bdm])
|
||||||
|
|
||||||
|
def test_legacy_block_devices(self):
|
||||||
|
test_snapshot = self.driver_classes['snapshot'](
|
||||||
|
self.snapshot_bdm)
|
||||||
|
|
||||||
|
block_device_mapping = [test_snapshot, test_snapshot]
|
||||||
|
legacy_bdm = driver_block_device.legacy_block_devices(
|
||||||
|
block_device_mapping)
|
||||||
|
self.assertEquals(legacy_bdm, [self.snapshot_legacy_driver_bdm,
|
||||||
|
self.snapshot_legacy_driver_bdm])
|
||||||
|
|
||||||
|
# Test that the ephemerals work as expected
|
||||||
|
test_ephemerals = [self.driver_classes['ephemeral'](
|
||||||
|
self.ephemeral_bdm) for _ in xrange(2)]
|
||||||
|
expected = [self.ephemeral_legacy_driver_bdm.copy()
|
||||||
|
for _ in xrange(2)]
|
||||||
|
expected[0]['virtual_name'] = 'ephemeral0'
|
||||||
|
expected[0]['num'] = 0
|
||||||
|
expected[1]['virtual_name'] = 'ephemeral1'
|
||||||
|
expected[1]['num'] = 1
|
||||||
|
legacy_ephemerals = driver_block_device.legacy_block_devices(
|
||||||
|
test_ephemerals)
|
||||||
|
self.assertEquals(expected, legacy_ephemerals)
|
||||||
|
|
||||||
|
def test_get_swap(self):
|
||||||
|
swap = [self.swap_driver_bdm]
|
||||||
|
legacy_swap = [self.swap_legacy_driver_bdm]
|
||||||
|
no_swap = [self.volume_driver_bdm]
|
||||||
|
|
||||||
|
self.assertEquals(swap[0], driver_block_device.get_swap(swap))
|
||||||
|
self.assertEquals(legacy_swap[0],
|
||||||
|
driver_block_device.get_swap(legacy_swap))
|
||||||
|
self.assertEquals(no_swap, driver_block_device.get_swap(no_swap))
|
||||||
|
self.assertEquals(None, driver_block_device.get_swap([]))
|
|
@ -0,0 +1,284 @@
|
||||||
|
# vim: tabstop=4 shiftwidth=4 softtabstop=4
|
||||||
|
|
||||||
|
# All Rights Reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||||
|
# not use this file except in compliance with the License. You may obtain
|
||||||
|
# a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||||
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
|
# License for the specific language governing permissions and limitations
|
||||||
|
# under the License.
|
||||||
|
|
||||||
|
import functools
|
||||||
|
import operator
|
||||||
|
|
||||||
|
from nova import block_device
|
||||||
|
from nova.openstack.common.gettextutils import _
|
||||||
|
from nova.openstack.common import jsonutils
|
||||||
|
from nova.openstack.common import log as logging
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class _NotTransformable(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _InvalidType(_NotTransformable):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class _NoLegacy(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class DriverBlockDevice(dict):
|
||||||
|
def __init__(self, bdm):
|
||||||
|
if bdm.get('no_device'):
|
||||||
|
raise _NotTransformable()
|
||||||
|
|
||||||
|
# NOTE (ndipanov): Always save the id of the bdm
|
||||||
|
# so we can use it for db updates.
|
||||||
|
self.id = bdm.get('id')
|
||||||
|
self.update(dict((field, None)
|
||||||
|
for field in self._fields))
|
||||||
|
self._transform(bdm)
|
||||||
|
|
||||||
|
def _transform(self, bdm):
|
||||||
|
"""Transform bdm to the format that is passed to drivers."""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def legacy(self):
|
||||||
|
"""Basic legacy transformation.
|
||||||
|
|
||||||
|
Basic method will just drop the fields that are not in
|
||||||
|
_legacy_fields set. Override this in subclass if needed.
|
||||||
|
"""
|
||||||
|
return dict((key, self.get(key)) for key in self._legacy_fields)
|
||||||
|
|
||||||
|
def attach(self, **kwargs):
|
||||||
|
"""
|
||||||
|
Make the device available to be used by VMs.
|
||||||
|
|
||||||
|
To be overriden in subclasses with the connecting logic for
|
||||||
|
the type of device the subclass represents.
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
|
||||||
|
class DriverSwapBlockDevice(DriverBlockDevice):
|
||||||
|
_fields = set(['device_name', 'swap_size', 'disk_bus'])
|
||||||
|
_legacy_fields = _fields - set(['disk_bus'])
|
||||||
|
|
||||||
|
def _transform(self, bdm):
|
||||||
|
if not block_device.new_format_is_swap(bdm):
|
||||||
|
raise _InvalidType
|
||||||
|
self.update({
|
||||||
|
'device_name': bdm.get('device_name'),
|
||||||
|
'swap_size': bdm.get('volume_size', 0),
|
||||||
|
'disk_bus': bdm.get('disk_bus')
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
|
class DriverEphemeralBlockDevice(DriverBlockDevice):
|
||||||
|
_new_only_fields = set(['disk_bus', 'device_type', 'guest_format'])
|
||||||
|
_fields = set(['device_name', 'size']) | _new_only_fields
|
||||||
|
_legacy_fields = (_fields - _new_only_fields |
|
||||||
|
set(['num', 'virtual_name']))
|
||||||
|
|
||||||
|
def _transform(self, bdm):
|
||||||
|
if not block_device.new_format_is_ephemeral(bdm):
|
||||||
|
raise _InvalidType
|
||||||
|
self.update({
|
||||||
|
'device_name': bdm.get('device_name'),
|
||||||
|
'size': bdm.get('volume_size', 0),
|
||||||
|
'disk_bus': bdm.get('disk_bus'),
|
||||||
|
'device_type': bdm.get('device_type'),
|
||||||
|
'guest_format': bdm.get('guest_format')
|
||||||
|
})
|
||||||
|
|
||||||
|
def legacy(self, num=0):
|
||||||
|
legacy_bdm = super(DriverEphemeralBlockDevice, self).legacy()
|
||||||
|
legacy_bdm['num'] = num
|
||||||
|
legacy_bdm['virtual_name'] = 'ephemeral' + str(num)
|
||||||
|
return legacy_bdm
|
||||||
|
|
||||||
|
|
||||||
|
class DriverVolumeBlockDevice(DriverBlockDevice):
|
||||||
|
_legacy_fields = set(['connection_info', 'mount_device',
|
||||||
|
'delete_on_termination'])
|
||||||
|
_new_fields = set(['guest_format', 'device_type',
|
||||||
|
'disk_bus', 'boot_index'])
|
||||||
|
_fields = _legacy_fields | _new_fields
|
||||||
|
|
||||||
|
def _transform(self, bdm):
|
||||||
|
if not bdm.get('source_type') == 'volume':
|
||||||
|
raise _InvalidType
|
||||||
|
|
||||||
|
# NOTE (ndipanov): Save it as an attribute as we will
|
||||||
|
# need it for attach()
|
||||||
|
self.volume_size = bdm.get('volume_size')
|
||||||
|
self.volume_id = bdm.get('volume_id')
|
||||||
|
|
||||||
|
self.update(
|
||||||
|
dict((k, v) for k, v in bdm.iteritems()
|
||||||
|
if k in self._new_fields | set(['delete_on_termination']))
|
||||||
|
)
|
||||||
|
self['mount_device'] = bdm.get('device_name')
|
||||||
|
try:
|
||||||
|
self['connection_info'] = jsonutils.loads(
|
||||||
|
bdm.get('connection_info'))
|
||||||
|
except TypeError:
|
||||||
|
self['connection_info'] = None
|
||||||
|
|
||||||
|
def attach(self, context, instance, volume_api, virt_driver, db_api=None):
|
||||||
|
volume = volume_api.get(context, self.volume_id)
|
||||||
|
volume_api.check_attach(context, volume, instance=instance)
|
||||||
|
|
||||||
|
# Attach a volume to an instance at boot time. So actual attach
|
||||||
|
# is done by instance creation.
|
||||||
|
instance_id = instance['id']
|
||||||
|
instance_uuid = instance['uuid']
|
||||||
|
volume_id = volume['id']
|
||||||
|
context = context.elevated()
|
||||||
|
|
||||||
|
LOG.audit(_('Booting with volume %(volume_id)s at %(mountpoint)s'),
|
||||||
|
{'volume_id': volume_id,
|
||||||
|
'mountpoint': self['mount_device']},
|
||||||
|
context=context, instance=instance)
|
||||||
|
|
||||||
|
connector = virt_driver.get_volume_connector(instance)
|
||||||
|
connection_info = volume_api.initialize_connection(context,
|
||||||
|
volume_id,
|
||||||
|
connector)
|
||||||
|
volume_api.attach(context, volume_id,
|
||||||
|
instance_uuid, self['mount_device'])
|
||||||
|
|
||||||
|
if 'serial' not in connection_info:
|
||||||
|
connection_info['serial'] = self.volume_id
|
||||||
|
self['connection_info'] = connection_info
|
||||||
|
if db_api:
|
||||||
|
db_api.block_device_mapping_update(
|
||||||
|
context, self.id,
|
||||||
|
{'connection_info': jsonutils.dumps(connection_info)})
|
||||||
|
|
||||||
|
|
||||||
|
class DriverSnapshotBlockDevice(DriverVolumeBlockDevice):
|
||||||
|
def _transform(self, bdm):
|
||||||
|
if not bdm.get('source_type') == 'snapshot':
|
||||||
|
raise _InvalidType
|
||||||
|
|
||||||
|
# NOTE (ndipanov): Save these as attributes as we will
|
||||||
|
# need them for attach()
|
||||||
|
self.volume_size = bdm.get('volume_size')
|
||||||
|
self.snapshot_id = bdm.get('snapshot_id')
|
||||||
|
self.volume_id = bdm.get('volume_id')
|
||||||
|
|
||||||
|
self.update(
|
||||||
|
dict((k, v) for k, v in bdm.iteritems()
|
||||||
|
if k in self._new_fields | set(['delete_on_termination']))
|
||||||
|
)
|
||||||
|
self['mount_device'] = bdm.get('device_name')
|
||||||
|
try:
|
||||||
|
self['connection_info'] = jsonutils.loads(
|
||||||
|
bdm.get('connection_info'))
|
||||||
|
except TypeError:
|
||||||
|
self['connection_info'] = None
|
||||||
|
|
||||||
|
def attach(self, context, instance, volume_api, virt_driver,
|
||||||
|
db_api=None, wait_func=None):
|
||||||
|
|
||||||
|
if not self.volume_id:
|
||||||
|
snapshot = volume_api.get_snapshot(context,
|
||||||
|
self.snapshot_id)
|
||||||
|
vol = volume_api.create(context, self.volume_size,
|
||||||
|
'', '', snapshot)
|
||||||
|
if wait_func:
|
||||||
|
wait_func(context, vol['id'])
|
||||||
|
if db_api:
|
||||||
|
db_api.block_device_mapping_update(context, self.id,
|
||||||
|
{'volume_id': vol['id']})
|
||||||
|
self.volume_id = vol['id']
|
||||||
|
|
||||||
|
# Call the volume attach now
|
||||||
|
super(DriverSnapshotBlockDevice, self).attach(context, instance,
|
||||||
|
volume_api, virt_driver,
|
||||||
|
db_api)
|
||||||
|
|
||||||
|
|
||||||
|
def _convert_block_devices(device_type, block_device_mapping):
|
||||||
|
def _is_transformable(bdm):
|
||||||
|
try:
|
||||||
|
device_type(bdm)
|
||||||
|
except _NotTransformable:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
return [device_type(bdm)
|
||||||
|
for bdm in block_device_mapping
|
||||||
|
if _is_transformable(bdm)]
|
||||||
|
|
||||||
|
|
||||||
|
convert_swap = functools.partial(_convert_block_devices,
|
||||||
|
DriverSwapBlockDevice)
|
||||||
|
|
||||||
|
|
||||||
|
convert_ephemerals = functools.partial(_convert_block_devices,
|
||||||
|
DriverEphemeralBlockDevice)
|
||||||
|
|
||||||
|
|
||||||
|
convert_volumes = functools.partial(_convert_block_devices,
|
||||||
|
DriverVolumeBlockDevice)
|
||||||
|
|
||||||
|
|
||||||
|
convert_snapshots = functools.partial(_convert_block_devices,
|
||||||
|
DriverSnapshotBlockDevice)
|
||||||
|
|
||||||
|
|
||||||
|
def attach_block_devices(block_device_mapping, *attach_args, **attach_kwargs):
|
||||||
|
map(operator.methodcaller('attach', *attach_args, **attach_kwargs),
|
||||||
|
block_device_mapping)
|
||||||
|
return block_device_mapping
|
||||||
|
|
||||||
|
|
||||||
|
def legacy_block_devices(block_device_mapping):
|
||||||
|
def _has_legacy(bdm):
|
||||||
|
try:
|
||||||
|
bdm.legacy()
|
||||||
|
except _NoLegacy:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
bdms = [bdm.legacy()
|
||||||
|
for bdm in block_device_mapping
|
||||||
|
if _has_legacy(bdm)]
|
||||||
|
|
||||||
|
# Re-enumerate ephemeral devices
|
||||||
|
if all(isinstance(bdm, DriverEphemeralBlockDevice)
|
||||||
|
for bdm in block_device_mapping):
|
||||||
|
for i, dev in enumerate(bdms):
|
||||||
|
dev['virtual_name'] = dev['virtual_name'][:-1] + str(i)
|
||||||
|
dev['num'] = i
|
||||||
|
|
||||||
|
return bdms
|
||||||
|
|
||||||
|
|
||||||
|
def get_swap(transformed_list):
|
||||||
|
"""Get the swap device out of the list context.
|
||||||
|
|
||||||
|
The block_device_info needs swap to be a single device,
|
||||||
|
not a list - otherwise this is a no-op.
|
||||||
|
"""
|
||||||
|
if not all(isinstance(device, DriverSwapBlockDevice) or
|
||||||
|
'swap_size' in device
|
||||||
|
for device in transformed_list):
|
||||||
|
return transformed_list
|
||||||
|
try:
|
||||||
|
return transformed_list.pop()
|
||||||
|
except IndexError:
|
||||||
|
return None
|
Loading…
Reference in New Issue