Merge "Refactor how BDMs are handled when booting"

This commit is contained in:
Jenkins 2013-08-21 10:27:22 +00:00 committed by Gerrit Code Review
commit 5422115924
5 changed files with 670 additions and 98 deletions

View File

@ -370,6 +370,21 @@ def is_swap_or_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):
"""Prepend '/dev/' to 'device' entry of swap/ephemeral virtual type."""
for m in mappings:

View File

@ -40,7 +40,6 @@ import uuid
from eventlet import greenthread
from oslo.config import cfg
from nova import block_device
from nova.cells import rpcapi as cells_rpcapi
from nova.cloudpipe import pipelib
from nova import compute
@ -76,6 +75,7 @@ from nova import paths
from nova import quota
from nova import safe_utils
from nova import utils
from nova.virt import block_device as driver_block_device
from nova.virt import driver
from nova.virt import event as virtevent
from nova.virt import storage_users
@ -906,70 +906,6 @@ class ComputeManager(manager.SchedulerDependentManager):
seconds=int(time.time() - start),
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):
"""Base64 decode the list of files to inject."""
if not injected_files:
@ -1068,7 +1004,7 @@ class ComputeManager(manager.SchedulerDependentManager):
network_info = None
bdms = self.conductor_api.block_device_mapping_get_all_by_instance(
context, instance)
context, instance, legacy=False)
# b64 decode the files to inject:
injected_files_orig = injected_files
@ -1333,7 +1269,29 @@ class ComputeManager(manager.SchedulerDependentManager):
def _prep_block_device(self, context, instance, bdms):
"""Set up the block device for an instance with error logging."""
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:
with excutils.save_and_reraise_exception():
LOG.exception(_('Instance failed block device setup'),
@ -3382,25 +3340,6 @@ class ComputeManager(manager.SchedulerDependentManager):
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())
@reverts_task_state
@wrap_instance_fault

View File

@ -419,17 +419,19 @@ class ComputeVolumeTestCase(BaseTestCase):
self.assertEqual(attempts, 3)
def test_boot_volume_serial(self):
block_device_mapping = [{
block_device_mapping = [
block_device.BlockDeviceDict({
'id': 1,
'no_device': None,
'virtual_name': None,
'source_type': 'volume',
'destination_type': 'volume',
'snapshot_id': None,
'volume_id': self.volume_id,
'device_name': '/dev/vdb',
'delete_on_termination': False,
}]
self.compute._setup_block_device_mapping(self.context, self.instance,
block_device_mapping)
})]
self.compute._prep_block_device(self.context, self.instance,
block_device_mapping)
self.assertEqual(self.cinfo.get('serial'), self.volume_id)
def test_boot_volume_metadata(self):
@ -1204,7 +1206,7 @@ class ComputeTestCase(BaseTestCase):
def fake(*args, **kwargs):
raise test.TestingException()
self.stubs.Set(nova.compute.manager.ComputeManager,
'_setup_block_device_mapping', fake)
'_prep_block_device', fake)
instance = self._create_instance()
self.assertRaises(test.TestingException, self.compute.run_instance,
self.context, instance=instance)
@ -2800,8 +2802,8 @@ class ComputeTestCase(BaseTestCase):
# When a spawn fails the network must be deallocated.
instance = jsonutils.to_primitive(self._create_fake_instance())
self.mox.StubOutWithMock(self.compute, "_setup_block_device_mapping")
self.compute._setup_block_device_mapping(
self.mox.StubOutWithMock(self.compute, "_prep_block_device")
self.compute._prep_block_device(
mox.IgnoreArg(), mox.IgnoreArg(),
mox.IgnoreArg()).AndRaise(rpc.common.RemoteError('', '', ''))
@ -9010,6 +9012,7 @@ class EvacuateHostTestCase(BaseTestCase):
def test_rebuild_on_host_with_volumes(self):
"""Confirm evacuate scenario reconnects volumes."""
values = {'instance_uuid': self.inst_ref['uuid'],
'source_type': 'volume',
'device_name': '/dev/vdc',
'delete_on_termination': False,
'volume_id': 'fake_volume_id'}
@ -9036,10 +9039,10 @@ class EvacuateHostTestCase(BaseTestCase):
self.mox.StubOutWithMock(self.compute.volume_api, 'detach')
self.compute.volume_api.detach(mox.IsA(self.context), mox.IgnoreArg())
self.mox.StubOutWithMock(self.compute, '_setup_block_device_mapping')
self.compute._setup_block_device_mapping(mox.IsA(self.context),
mox.IsA(self.inst_ref),
mox.IgnoreArg())
self.mox.StubOutWithMock(self.compute, '_prep_block_device')
self.compute._prep_block_device(mox.IsA(self.context),
mox.IsA(self.inst_ref),
mox.IgnoreArg())
self.stubs.Set(self.compute.driver, 'instance_on_disk', lambda x: True)
self.mox.ReplayAll()

View File

@ -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([]))

284
nova/virt/block_device.py Normal file
View File

@ -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