diff --git a/nova/exception.py b/nova/exception.py index 7c178035411..770a7008eed 100644 --- a/nova/exception.py +++ b/nova/exception.py @@ -449,6 +449,14 @@ class InvalidDiskFormat(Invalid): msg_fmt = _("Disk format %(disk_format)s is not acceptable") +class InvalidDiskInfo(Invalid): + msg_fmt = _("Disk info file is invalid: %(reason)s") + + +class DiskInfoReadWriteFail(Invalid): + msg_fmt = _("Failed to read or write disk info file: %(reason)s") + + class ImageUnacceptable(Invalid): msg_fmt = _("Image %(image_id)s is unacceptable: %(reason)s") diff --git a/nova/tests/virt/libvirt/test_imagebackend.py b/nova/tests/virt/libvirt/test_imagebackend.py index 5bfa94d1489..5424f7b300b 100644 --- a/nova/tests/virt/libvirt/test_imagebackend.py +++ b/nova/tests/virt/libvirt/test_imagebackend.py @@ -16,6 +16,8 @@ # under the License. import os +import shutil +import tempfile import fixtures from oslo.config import cfg @@ -27,13 +29,13 @@ from nova import test from nova.tests import fake_processutils from nova.tests.virt.libvirt import fake_libvirt_utils +from nova import utils from nova.virt.libvirt import imagebackend CONF = cfg.CONF class _ImageTestCase(object): - INSTANCES_PATH = '/instances_path' def mock_create_image(self, image): def create_image(fn, base, size, *args, **kwargs): @@ -42,10 +44,13 @@ def create_image(fn, base, size, *args, **kwargs): def setUp(self): super(_ImageTestCase, self).setUp() + self.INSTANCES_PATH = tempfile.mkdtemp(suffix='instances') self.flags(disable_process_locking=True, instances_path=self.INSTANCES_PATH) self.INSTANCE = {'name': 'instance', 'uuid': uuidutils.generate_uuid()} + self.DISK_INFO_PATH = os.path.join(self.INSTANCES_PATH, + self.INSTANCE['uuid'], 'disk.info') self.NAME = 'fake.vm' self.TEMPLATE = 'template' @@ -63,6 +68,78 @@ def setUp(self): 'nova.virt.libvirt.imagebackend.libvirt_utils', fake_libvirt_utils)) + def fake_chown(path, owner_uid=None): + return None + self.stubs.Set(utils, 'chown', fake_chown) + + def tearDown(self): + super(_ImageTestCase, self).tearDown() + shutil.rmtree(self.INSTANCES_PATH) + + def test_prealloc_image(self): + CONF.set_override('preallocate_images', 'space') + + fake_processutils.fake_execute_clear_log() + fake_processutils.stub_out_processutils_execute(self.stubs) + image = self.image_class(self.INSTANCE, self.NAME) + + def fake_fetch(target, *args, **kwargs): + return + + self.stubs.Set(os.path, 'exists', lambda _: True) + self.stubs.Set(os, 'access', lambda p, w: True) + + # Call twice to verify testing fallocate is only called once. + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + + self.assertEqual(fake_processutils.fake_execute_get_log(), + ['fallocate -n -l 1 %s.fallocate_test' % self.PATH, + 'fallocate -n -l %s %s' % (self.SIZE, self.PATH), + 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)]) + + def test_prealloc_image_without_write_access(self): + CONF.set_override('preallocate_images', 'space') + + fake_processutils.fake_execute_clear_log() + fake_processutils.stub_out_processutils_execute(self.stubs) + image = self.image_class(self.INSTANCE, self.NAME) + + def fake_fetch(target, *args, **kwargs): + return + + self.stubs.Set(image, 'check_image_exists', lambda: True) + self.stubs.Set(image, '_can_fallocate', lambda: True) + self.stubs.Set(os.path, 'exists', lambda _: True) + self.stubs.Set(os, 'access', lambda p, w: False) + + # Testing fallocate is only called when user has write access. + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + + self.assertEqual(fake_processutils.fake_execute_get_log(), []) + + +class RawTestCase(_ImageTestCase, test.NoDBTestCase): + + SIZE = 1024 + + def setUp(self): + self.image_class = imagebackend.Raw + super(RawTestCase, self).setUp() + self.stubs.Set(imagebackend.Raw, 'correct_format', lambda _: None) + + def fake_chown(path, owner_uid=None): + return None + self.stubs.Set(utils, 'chown', fake_chown) + + def prepare_mocks(self): + fn = self.mox.CreateMockAnything() + self.mox.StubOutWithMock(imagebackend.utils.synchronized, + '__call__') + self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image') + self.mox.StubOutWithMock(imagebackend.disk, 'extend') + return fn + def test_cache(self): self.mox.StubOutWithMock(os.path, 'exists') if self.OLD_STYLE_INSTANCE_PATH: @@ -130,66 +207,6 @@ def test_cache_template_exists(self): self.mox.VerifyAll() - def test_prealloc_image(self): - CONF.set_override('preallocate_images', 'space') - - fake_processutils.fake_execute_clear_log() - fake_processutils.stub_out_processutils_execute(self.stubs) - image = self.image_class(self.INSTANCE, self.NAME) - - def fake_fetch(target, *args, **kwargs): - return - - self.stubs.Set(os.path, 'exists', lambda _: True) - self.stubs.Set(os, 'access', lambda p, w: True) - - # Call twice to verify testing fallocate is only called once. - image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) - image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) - - self.assertEqual(fake_processutils.fake_execute_get_log(), - ['fallocate -n -l 1 %s.fallocate_test' % self.PATH, - 'fallocate -n -l %s %s' % (self.SIZE, self.PATH), - 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)]) - - def test_prealloc_image_without_write_access(self): - CONF.set_override('preallocate_images', 'space') - - fake_processutils.fake_execute_clear_log() - fake_processutils.stub_out_processutils_execute(self.stubs) - image = self.image_class(self.INSTANCE, self.NAME) - - def fake_fetch(target, *args, **kwargs): - return - - self.stubs.Set(image, 'check_image_exists', lambda: True) - self.stubs.Set(image, '_can_fallocate', lambda: True) - self.stubs.Set(os.path, 'exists', lambda _: True) - self.stubs.Set(os, 'access', lambda p, w: False) - - # Testing fallocate is only called when user has write access. - image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) - - self.assertEqual(fake_processutils.fake_execute_get_log(), []) - - -class RawTestCase(_ImageTestCase, test.NoDBTestCase): - - SIZE = 1024 - - def setUp(self): - self.image_class = imagebackend.Raw - super(RawTestCase, self).setUp() - self.stubs.Set(imagebackend.Raw, 'correct_format', lambda _: None) - - def prepare_mocks(self): - fn = self.mox.CreateMockAnything() - self.mox.StubOutWithMock(imagebackend.utils.synchronized, - '__call__') - self.mox.StubOutWithMock(imagebackend.libvirt_utils, 'copy_image') - self.mox.StubOutWithMock(imagebackend.disk, 'extend') - return fn - def test_create_image(self): fn = self.prepare_mocks() fn(target=self.TEMPLATE_PATH, max_size=None, image_id=None) @@ -224,16 +241,21 @@ def test_create_image_extend(self): self.mox.VerifyAll() def test_correct_format(self): - info = self.mox.CreateMockAnything() self.stubs.UnsetAll() self.mox.StubOutWithMock(os.path, 'exists') self.mox.StubOutWithMock(imagebackend.images, 'qemu_img_info') + def fake_chown(path, owner_uid=None): + return None + self.stubs.Set(utils, 'chown', fake_chown) + os.path.exists(self.PATH).AndReturn(True) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) info = self.mox.CreateMockAnything() info.file_format = 'foo' imagebackend.images.qemu_img_info(self.PATH).AndReturn(info) + os.path.exists(CONF.instances_path).AndReturn(True) self.mox.ReplayAll() image = self.image_class(self.INSTANCE, self.NAME, path=self.PATH) @@ -241,6 +263,11 @@ def test_correct_format(self): self.mox.VerifyAll() + def test_resolve_driver_format(self): + image = self.image_class(self.INSTANCE, self.NAME) + driver_format = image.resolve_driver_format() + self.assertEqual(driver_format, 'raw') + class Qcow2TestCase(_ImageTestCase, test.NoDBTestCase): SIZE = 1024 * 1024 * 1024 @@ -251,6 +278,10 @@ def setUp(self): self.QCOW2_BASE = (self.TEMPLATE_PATH + '_%d' % (self.SIZE / (1024 * 1024 * 1024))) + def fake_chown(path, owner_uid=None): + return None + self.stubs.Set(utils, 'chown', fake_chown) + def prepare_mocks(self): fn = self.mox.CreateMockAnything() self.mox.StubOutWithMock(imagebackend.utils.synchronized, @@ -261,6 +292,80 @@ def prepare_mocks(self): self.mox.StubOutWithMock(imagebackend.disk, 'extend') return fn + def test_cache(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(CONF.instances_path).AndReturn(True) + os.path.exists(self.TEMPLATE_DIR).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) + os.path.exists(self.PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + + def test_cache_image_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_PATH).AndReturn(True) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + image.cache(None, self.TEMPLATE) + + self.mox.VerifyAll() + + def test_cache_base_dir_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + + def test_cache_template_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + def test_create_image(self): fn = self.prepare_mocks() fn(max_size=None, target=self.TEMPLATE_PATH) @@ -279,6 +384,8 @@ def test_create_image_with_size(self): self.mox.StubOutWithMock(os.path, 'exists') if self.OLD_STYLE_INSTANCE_PATH: os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) os.path.exists(self.TEMPLATE_PATH).AndReturn(False) os.path.exists(self.PATH).AndReturn(False) os.path.exists(self.PATH).AndReturn(False) @@ -298,6 +405,8 @@ def test_create_image_too_small(self): self.mox.StubOutWithMock(imagebackend.disk, 'get_disk_size') if self.OLD_STYLE_INSTANCE_PATH: os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) os.path.exists(self.TEMPLATE_PATH).AndReturn(True) imagebackend.disk.get_disk_size(self.TEMPLATE_PATH ).AndReturn(self.SIZE) @@ -316,6 +425,8 @@ def test_generate_resized_backing_files(self): 'get_disk_backing_file') if self.OLD_STYLE_INSTANCE_PATH: os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(CONF.instances_path).AndReturn(True) os.path.exists(self.TEMPLATE_PATH).AndReturn(False) os.path.exists(self.PATH).AndReturn(True) @@ -342,6 +453,9 @@ def test_qcow2_exists_and_has_no_backing_file(self): 'get_disk_backing_file') if self.OLD_STYLE_INSTANCE_PATH: os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.DISK_INFO_PATH).AndReturn(False) + os.path.exists(self.INSTANCES_PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) os.path.exists(self.PATH).AndReturn(True) @@ -355,6 +469,53 @@ def test_qcow2_exists_and_has_no_backing_file(self): self.mox.VerifyAll() + def test_resolve_driver_format(self): + image = self.image_class(self.INSTANCE, self.NAME) + driver_format = image.resolve_driver_format() + self.assertEqual(driver_format, 'qcow2') + + def test_prealloc_image(self): + CONF.set_override('preallocate_images', 'space') + + fake_processutils.fake_execute_clear_log() + fake_processutils.stub_out_processutils_execute(self.stubs) + image = self.image_class(self.INSTANCE, self.NAME) + + def fake_fetch(target, *args, **kwargs): + return + + self.stubs.Set(os.path, 'exists', lambda _: True) + self.stubs.Set(os, 'access', lambda p, w: True) + + # Call twice to verify testing fallocate is only called once. + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + + self.assertEqual(fake_processutils.fake_execute_get_log(), + ['fallocate -n -l 1 %s.fallocate_test' % self.PATH, + 'fallocate -n -l %s %s' % (self.SIZE, self.PATH), + 'fallocate -n -l %s %s' % (self.SIZE, self.PATH)]) + + def test_prealloc_image_without_write_access(self): + CONF.set_override('preallocate_images', 'space') + + fake_processutils.fake_execute_clear_log() + fake_processutils.stub_out_processutils_execute(self.stubs) + image = self.image_class(self.INSTANCE, self.NAME) + + def fake_fetch(target, *args, **kwargs): + return + + self.stubs.Set(image, 'check_image_exists', lambda: True) + self.stubs.Set(image, '_can_fallocate', lambda: True) + self.stubs.Set(os.path, 'exists', lambda _: True) + self.stubs.Set(os, 'access', lambda p, w: False) + + # Testing fallocate is only called when user has write access. + image.cache(fake_fetch, self.TEMPLATE_PATH, self.SIZE) + + self.assertEqual(fake_processutils.fake_execute_get_log(), []) + class LvmTestCase(_ImageTestCase, test.NoDBTestCase): VG = 'FakeVG' @@ -431,6 +592,58 @@ def _create_image_resize(self, sparse): self.mox.VerifyAll() + def test_cache(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_DIR).AndReturn(False) + os.path.exists(self.PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.StubOutWithMock(imagebackend.fileutils, 'ensure_tree') + imagebackend.fileutils.ensure_tree(self.TEMPLATE_DIR) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + + def test_cache_image_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.PATH).AndReturn(True) + os.path.exists(self.TEMPLATE_PATH).AndReturn(True) + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + image.cache(None, self.TEMPLATE) + + self.mox.VerifyAll() + + def test_cache_base_dir_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + if self.OLD_STYLE_INSTANCE_PATH: + os.path.exists(self.OLD_STYLE_INSTANCE_PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.PATH).AndReturn(False) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.StubOutWithMock(imagebackend.fileutils, 'ensure_tree') + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + def test_create_image(self): self._create_image(False) @@ -596,6 +809,21 @@ def test_cache_template_exists(self): self.mox.VerifyAll() + def test_cache_base_dir_exists(self): + self.mox.StubOutWithMock(os.path, 'exists') + os.path.exists(self.TEMPLATE_DIR).AndReturn(True) + os.path.exists(self.TEMPLATE_PATH).AndReturn(False) + fn = self.mox.CreateMockAnything() + fn(target=self.TEMPLATE_PATH) + self.mox.StubOutWithMock(imagebackend.fileutils, 'ensure_tree') + self.mox.ReplayAll() + + image = self.image_class(self.INSTANCE, self.NAME) + self.mock_create_image(image) + image.cache(fn, self.TEMPLATE) + + self.mox.VerifyAll() + def test_create_image(self): fn = self.prepare_mocks() fn(max_size=None, rbd=self.rbd, target=self.TEMPLATE_PATH) @@ -642,6 +870,13 @@ class BackendTestCase(test.NoDBTestCase): 'uuid': uuidutils.generate_uuid()} NAME = 'fake-name.suffix' + def setUp(self): + super(BackendTestCase, self).setUp() + + def fake_chown(path, owner_uid=None): + return None + self.stubs.Set(utils, 'chown', fake_chown) + def get_image(self, use_cow, image_type): return imagebackend.Backend(use_cow).image(self.INSTANCE, self.NAME, diff --git a/nova/tests/virt/libvirt/test_libvirt.py b/nova/tests/virt/libvirt/test_libvirt.py index ba842ad68a3..ea8ac13e671 100644 --- a/nova/tests/virt/libvirt/test_libvirt.py +++ b/nova/tests/virt/libvirt/test_libvirt.py @@ -390,6 +390,9 @@ def fake_extend(image, size, use_cow=False): self.stubs.Set(libvirt_driver.disk, 'extend', fake_extend) + self.stubs.Set(imagebackend.Image, 'resolve_driver_format', + imagebackend.Image._get_driver_format) + class FakeConn(): def getCapabilities(self): """Ensure standard capabilities being returned.""" diff --git a/nova/tests/virt/test_virt_drivers.py b/nova/tests/virt/test_virt_drivers.py index 832d9d6f25b..8f1bd236e19 100644 --- a/nova/tests/virt/test_virt_drivers.py +++ b/nova/tests/virt/test_virt_drivers.py @@ -30,6 +30,7 @@ from nova.tests.virt.libvirt import fake_libvirt_utils from nova.virt import event as virtevent from nova.virt import fake +from nova.virt.libvirt import imagebackend LOG = logging.getLogger(__name__) @@ -201,6 +202,12 @@ def setUp(self): fake.FakeVirtAPI()) self.ctxt = test_utils.get_test_admin_context() self.image_service = fake_image.FakeImageService() + # NOTE(dripton): resolve_driver_format does some file reading and + # writing and chowning that complicate testing too much by requiring + # using real directories with proper permissions. Just stub it out + # here; we test it in test_imagebackend.py + self.stubs.Set(imagebackend.Image, 'resolve_driver_format', + imagebackend.Image._get_driver_format) def _get_running_instance(self): instance_ref = test_utils.get_test_instance() diff --git a/nova/utils.py b/nova/utils.py index 599cb640dc9..4757f3a7233 100755 --- a/nova/utils.py +++ b/nova/utils.py @@ -924,6 +924,20 @@ def temporary_chown(path, owner_uid=None): execute('chown', orig_uid, path, run_as_root=True) +def chown(path, owner_uid=None): + """chown a path. + + :param owner_uid: UID of owner (defaults to current user) + """ + if owner_uid is None: + owner_uid = os.getuid() + + orig_uid = os.stat(path).st_uid + + if orig_uid != owner_uid: + execute('chown', owner_uid, path, run_as_root=True) + + @contextlib.contextmanager def tempdir(**kwargs): argdict = kwargs.copy() diff --git a/nova/virt/libvirt/imagebackend.py b/nova/virt/libvirt/imagebackend.py index 51872cfcc3f..ed11c90a029 100644 --- a/nova/virt/libvirt/imagebackend.py +++ b/nova/virt/libvirt/imagebackend.py @@ -88,6 +88,11 @@ def __init__(self, source_type, driver_format, is_block_dev=False): self.is_block_dev = is_block_dev self.preallocate = False + # NOTE(dripton): We store lines of json (path, disk_format) in this + # file, for some image types, to prevent attacks based on changing the + # disk_format. + self.disk_info_path = None + # NOTE(mikal): We need a lock directory which is shared along with # instance files, to cover the scenario where multiple compute nodes # are trying to create a base file at the same time @@ -232,6 +237,65 @@ def snapshot_extract(self, target, out_format): def snapshot_delete(self): raise NotImplementedError() + def _get_driver_format(self): + return self.driver_format + + def resolve_driver_format(self): + """Return the driver format for self.path. + + First checks self.disk_info_path for an entry. + If it's not there, calls self._get_driver_format(), and then + stores the result in self.disk_info_path + + See https://bugs.launchpad.net/nova/+bug/1221190 + """ + def _dict_from_line(line): + if not line: + return {} + try: + return jsonutils.loads(line) + except (TypeError, ValueError) as e: + msg = (_("Could not load line %(line)s, got error " + "%(error)s") % + {'line': line, 'error': unicode(e)}) + raise exception.InvalidDiskInfo(reason=msg) + + @utils.synchronized(self.disk_info_path, external=False, + lock_path=self.lock_path) + def write_to_disk_info_file(): + # Use os.open to create it without group or world write permission. + fd = os.open(self.disk_info_path, os.O_RDWR | os.O_CREAT, 0o644) + with os.fdopen(fd, "r+") as disk_info_file: + line = disk_info_file.read().rstrip() + dct = _dict_from_line(line) + if self.path in dct: + msg = _("Attempted overwrite of an existing value.") + raise exception.InvalidDiskInfo(reason=msg) + dct.update({self.path: driver_format}) + disk_info_file.seek(0) + disk_info_file.truncate() + disk_info_file.write('%s\n' % jsonutils.dumps(dct)) + # Ensure the file is always owned by the nova user so qemu can't + # write it. + utils.chown(self.disk_info_path, owner_uid=os.getuid()) + + try: + if (self.disk_info_path is not None and + os.path.exists(self.disk_info_path)): + with open(self.disk_info_path) as disk_info_file: + line = disk_info_file.read().rstrip() + dct = _dict_from_line(line) + for path, driver_format in dct.iteritems(): + if path == self.path: + return driver_format + driver_format = self._get_driver_format() + if self.disk_info_path is not None: + fileutils.ensure_tree(os.path.dirname(self.disk_info_path)) + write_to_disk_info_file() + except OSError as e: + raise exception.DiskInfoReadWriteFail(reason=unicode(e)) + return driver_format + class Raw(Image): def __init__(self, instance=None, disk_name=None, path=None, @@ -243,12 +307,17 @@ def __init__(self, instance=None, disk_name=None, path=None, disk_name)) self.snapshot_name = snapshot_name self.preallocate = CONF.preallocate_images != 'none' + self.disk_info_path = os.path.join(os.path.dirname(self.path), + 'disk.info') self.correct_format() + def _get_driver_format(self): + data = images.qemu_img_info(self.path) + return data.file_format or 'raw' + def correct_format(self): if os.path.exists(self.path): - data = images.qemu_img_info(self.path) - self.driver_format = data.file_format or 'raw' + self.driver_format = self.resolve_driver_format() def create_image(self, prepare_template, base, size, *args, **kwargs): @utils.synchronized(base, external=True, lock_path=self.lock_path) @@ -291,6 +360,9 @@ def __init__(self, instance=None, disk_name=None, path=None, disk_name)) self.snapshot_name = snapshot_name self.preallocate = CONF.preallocate_images != 'none' + self.disk_info_path = os.path.join(os.path.dirname(self.path), + 'disk.info') + self.resolve_driver_format() def create_image(self, prepare_template, base, size, *args, **kwargs): @utils.synchronized(base, external=True, lock_path=self.lock_path)