|
|
07d81d7 |
From 6a3eabcd01981c6ccead47e2b610bd82b5d6be80 Mon Sep 17 00:00:00 2001
|
|
|
5689fb3 |
From: =?UTF-8?q?P=C3=A1draig=20Brady?= <pbrady@redhat.com>
|
|
|
5689fb3 |
Date: Fri, 16 Mar 2012 03:43:49 +0000
|
|
|
5689fb3 |
Subject: [PATCH] ensure atomic manipulation of libvirt disk images
|
|
|
5689fb3 |
|
|
|
5689fb3 |
This pattern could probably be used elsewhere,
|
|
|
5689fb3 |
but only libvirt disk images are considered for now.
|
|
|
5689fb3 |
This change ensures there are no stale files left
|
|
|
5689fb3 |
anywhere in the path from glance, through the libvirt image cache.
|
|
|
5689fb3 |
These could cause subsequent operational errors either
|
|
|
5689fb3 |
directly or indirectly through disk wastage.
|
|
|
5689fb3 |
|
|
|
dc6afc0 |
* nova/utils.py: Add a new remove_path_on_error() context manager
|
|
|
5689fb3 |
that is used to remove the passed PATH on a raised exception.
|
|
|
5689fb3 |
* nova/virt/images.py: Ensure temporary downloaded and
|
|
|
5689fb3 |
converted images are protected.
|
|
|
5689fb3 |
* nova/virt/libvirt/connection.py: Ensure all the images in
|
|
|
5689fb3 |
the image cache and instance dirs are protected.
|
|
|
5689fb3 |
|
|
|
5689fb3 |
Change-Id: I81a5407665a6998128c0dee41387ef00ebddeb4d
|
|
|
5689fb3 |
---
|
|
|
5689fb3 |
nova/utils.py | 21 +++++++++--
|
|
|
5689fb3 |
nova/virt/images.py | 69 +++++++++++++++++----------------------
|
|
|
5689fb3 |
nova/virt/libvirt/connection.py | 16 ++++++---
|
|
|
5689fb3 |
3 files changed, 57 insertions(+), 49 deletions(-)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
diff --git a/nova/utils.py b/nova/utils.py
|
|
|
dc6afc0 |
index 819929a..7188d98 100644
|
|
|
5689fb3 |
--- a/nova/utils.py
|
|
|
5689fb3 |
+++ b/nova/utils.py
|
|
|
5689fb3 |
@@ -21,6 +21,7 @@
|
|
|
5689fb3 |
|
|
|
5689fb3 |
import contextlib
|
|
|
5689fb3 |
import datetime
|
|
|
5689fb3 |
+import errno
|
|
|
5689fb3 |
import functools
|
|
|
5689fb3 |
import hashlib
|
|
|
5689fb3 |
import inspect
|
|
|
09bb944 |
@@ -1013,8 +1014,8 @@ def cleanup_file_locks():
|
|
|
5689fb3 |
continue
|
|
|
5689fb3 |
try:
|
|
|
5689fb3 |
stat_info = os.stat(os.path.join(FLAGS.lock_path, filename))
|
|
|
5689fb3 |
- except OSError as (errno, strerror):
|
|
|
5689fb3 |
- if errno == 2: # doesn't exist
|
|
|
5689fb3 |
+ except OSError as e:
|
|
|
5689fb3 |
+ if e.errno == errno.ENOENT:
|
|
|
5689fb3 |
continue
|
|
|
5689fb3 |
else:
|
|
|
5689fb3 |
raise
|
|
|
09bb944 |
@@ -1033,8 +1034,8 @@ def delete_if_exists(pathname):
|
|
|
5689fb3 |
|
|
|
5689fb3 |
try:
|
|
|
5689fb3 |
os.unlink(pathname)
|
|
|
5689fb3 |
- except OSError as (errno, strerror):
|
|
|
5689fb3 |
- if errno == 2: # doesn't exist
|
|
|
5689fb3 |
+ except OSError as e:
|
|
|
5689fb3 |
+ if e.errno == errno.ENOENT:
|
|
|
5689fb3 |
return
|
|
|
5689fb3 |
else:
|
|
|
5689fb3 |
raise
|
|
|
dc6afc0 |
@@ -1344,6 +1345,18 @@ def logging_error(message):
|
|
|
5689fb3 |
LOG.exception(message)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
|
|
|
5689fb3 |
+@contextlib.contextmanager
|
|
|
dc6afc0 |
+def remove_path_on_error(path):
|
|
|
5689fb3 |
+ """Protect code that wants to operate on PATH atomically.
|
|
|
5689fb3 |
+ Any exception will cause PATH to be removed.
|
|
|
5689fb3 |
+ """
|
|
|
5689fb3 |
+ try:
|
|
|
5689fb3 |
+ yield
|
|
|
5689fb3 |
+ except Exception:
|
|
|
5689fb3 |
+ with save_and_reraise_exception():
|
|
|
5689fb3 |
+ delete_if_exists(path)
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
def make_dev_path(dev, partition=None, base='/dev'):
|
|
|
5689fb3 |
"""Return a path to a particular device.
|
|
|
5689fb3 |
|
|
|
5689fb3 |
diff --git a/nova/virt/images.py b/nova/virt/images.py
|
|
|
dc6afc0 |
index 1e0ae0a..626f3ff 100644
|
|
|
5689fb3 |
--- a/nova/virt/images.py
|
|
|
5689fb3 |
+++ b/nova/virt/images.py
|
|
|
09bb944 |
@@ -51,18 +51,10 @@ def fetch(context, image_href, path, _user_id, _project_id):
|
|
|
5689fb3 |
# checked before we got here.
|
|
|
5689fb3 |
(image_service, image_id) = nova.image.get_image_service(context,
|
|
|
5689fb3 |
image_href)
|
|
|
5689fb3 |
- try:
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(path):
|
|
|
5689fb3 |
with open(path, "wb") as image_file:
|
|
|
5689fb3 |
metadata = image_service.get(context, image_id, image_file)
|
|
|
5689fb3 |
- except Exception:
|
|
|
5689fb3 |
- with utils.save_and_reraise_exception():
|
|
|
5689fb3 |
- try:
|
|
|
5689fb3 |
- os.unlink(path)
|
|
|
5689fb3 |
- except OSError, e:
|
|
|
5689fb3 |
- if e.errno != errno.ENOENT:
|
|
|
5689fb3 |
- LOG.warn("unable to remove stale image '%s': %s" %
|
|
|
5689fb3 |
- (path, e.strerror))
|
|
|
5689fb3 |
- return metadata
|
|
|
5689fb3 |
+ return metadata
|
|
|
5689fb3 |
|
|
|
5689fb3 |
|
|
|
5689fb3 |
def fetch_to_raw(context, image_href, path, user_id, project_id):
|
|
|
09bb944 |
@@ -85,37 +77,36 @@ def fetch_to_raw(context, image_href, path, user_id, project_id):
|
|
|
5689fb3 |
|
|
|
5689fb3 |
return(data)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
- data = _qemu_img_info(path_tmp)
|
|
|
5689fb3 |
-
|
|
|
5689fb3 |
- fmt = data.get("file format")
|
|
|
5689fb3 |
- if fmt is None:
|
|
|
5689fb3 |
- os.unlink(path_tmp)
|
|
|
5689fb3 |
- raise exception.ImageUnacceptable(
|
|
|
5689fb3 |
- reason=_("'qemu-img info' parsing failed."), image_id=image_href)
|
|
|
5689fb3 |
-
|
|
|
5689fb3 |
- if "backing file" in data:
|
|
|
5689fb3 |
- backing_file = data['backing file']
|
|
|
5689fb3 |
- os.unlink(path_tmp)
|
|
|
5689fb3 |
- raise exception.ImageUnacceptable(image_id=image_href,
|
|
|
5689fb3 |
- reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
|
|
|
5689fb3 |
-
|
|
|
5689fb3 |
- if fmt != "raw" and FLAGS.force_raw_images:
|
|
|
5689fb3 |
- staged = "%s.converted" % path
|
|
|
5689fb3 |
- LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
|
|
|
5689fb3 |
- out, err = utils.execute('qemu-img', 'convert', '-O', 'raw',
|
|
|
5689fb3 |
- path_tmp, staged)
|
|
|
5689fb3 |
- os.unlink(path_tmp)
|
|
|
5689fb3 |
-
|
|
|
5689fb3 |
- data = _qemu_img_info(staged)
|
|
|
5689fb3 |
- if data.get('file format', None) != "raw":
|
|
|
5689fb3 |
- os.unlink(staged)
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(path_tmp):
|
|
|
5689fb3 |
+ data = _qemu_img_info(path_tmp)
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
+ fmt = data.get("file format")
|
|
|
5689fb3 |
+ if fmt is None:
|
|
|
5689fb3 |
+ raise exception.ImageUnacceptable(
|
|
|
5689fb3 |
+ reason=_("'qemu-img info' parsing failed."),
|
|
|
5689fb3 |
+ image_id=image_href)
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
+ if "backing file" in data:
|
|
|
5689fb3 |
+ backing_file = data['backing file']
|
|
|
5689fb3 |
raise exception.ImageUnacceptable(image_id=image_href,
|
|
|
5689fb3 |
- reason=_("Converted to raw, but format is now %s") %
|
|
|
5689fb3 |
- data.get('file format', None))
|
|
|
5689fb3 |
+ reason=_("fmt=%(fmt)s backed by: %(backing_file)s") % locals())
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
+ if fmt != "raw" and FLAGS.force_raw_images:
|
|
|
5689fb3 |
+ staged = "%s.converted" % path
|
|
|
5689fb3 |
+ LOG.debug("%s was %s, converting to raw" % (image_href, fmt))
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(staged):
|
|
|
5689fb3 |
+ out, err = utils.execute('qemu-img', 'convert', '-O', 'raw',
|
|
|
5689fb3 |
+ path_tmp, staged)
|
|
|
5689fb3 |
+
|
|
|
5689fb3 |
+ data = _qemu_img_info(staged)
|
|
|
5689fb3 |
+ if data.get('file format', None) != "raw":
|
|
|
5689fb3 |
+ raise exception.ImageUnacceptable(image_id=image_href,
|
|
|
5689fb3 |
+ reason=_("Converted to raw, but format is now %s") %
|
|
|
5689fb3 |
+ data.get('file format', None))
|
|
|
5689fb3 |
|
|
|
5689fb3 |
- os.rename(staged, path)
|
|
|
5689fb3 |
+ os.rename(staged, path)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
- else:
|
|
|
5689fb3 |
- os.rename(path_tmp, path)
|
|
|
5689fb3 |
+ else:
|
|
|
5689fb3 |
+ os.rename(path_tmp, path)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
return metadata
|
|
|
5689fb3 |
diff --git a/nova/virt/libvirt/connection.py b/nova/virt/libvirt/connection.py
|
|
|
07d81d7 |
index 31e6511..dc16d05 100644
|
|
|
5689fb3 |
--- a/nova/virt/libvirt/connection.py
|
|
|
5689fb3 |
+++ b/nova/virt/libvirt/connection.py
|
|
|
07d81d7 |
@@ -1105,7 +1105,8 @@ class LibvirtConnection(driver.ComputeDriver):
|
|
|
5689fb3 |
@utils.synchronized(fname)
|
|
|
5689fb3 |
def call_if_not_exists(base, fn, *args, **kwargs):
|
|
|
5689fb3 |
if not os.path.exists(base):
|
|
|
5689fb3 |
- fn(target=base, *args, **kwargs)
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(base):
|
|
|
5689fb3 |
+ fn(target=base, *args, **kwargs)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
if cow or not generating:
|
|
|
5689fb3 |
call_if_not_exists(base, fn, *args, **kwargs)
|
|
|
07d81d7 |
@@ -1121,8 +1122,9 @@ class LibvirtConnection(driver.ComputeDriver):
|
|
|
5689fb3 |
size_gb = size / (1024 * 1024 * 1024)
|
|
|
5689fb3 |
cow_base += "_%d" % size_gb
|
|
|
5689fb3 |
if not os.path.exists(cow_base):
|
|
|
5689fb3 |
- libvirt_utils.copy_image(base, cow_base)
|
|
|
5689fb3 |
- disk.extend(cow_base, size)
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(cow_base):
|
|
|
5689fb3 |
+ libvirt_utils.copy_image(base, cow_base)
|
|
|
5689fb3 |
+ disk.extend(cow_base, size)
|
|
|
5689fb3 |
libvirt_utils.create_cow_image(cow_base, target)
|
|
|
5689fb3 |
elif not generating:
|
|
|
5689fb3 |
libvirt_utils.copy_image(base, target)
|
|
|
07d81d7 |
@@ -1132,7 +1134,8 @@ class LibvirtConnection(driver.ComputeDriver):
|
|
|
5689fb3 |
if size:
|
|
|
5689fb3 |
disk.extend(target, size)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
- copy_and_extend(cow, generating, base, target, size)
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(target):
|
|
|
5689fb3 |
+ copy_and_extend(cow, generating, base, target, size)
|
|
|
5689fb3 |
|
|
|
5689fb3 |
@staticmethod
|
|
|
5689fb3 |
def _create_local(target, local_size, unit='G',
|
|
|
07d81d7 |
@@ -1305,8 +1308,9 @@ class LibvirtConnection(driver.ComputeDriver):
|
|
|
5689fb3 |
project_id=instance['project_id'],)
|
|
|
5689fb3 |
elif config_drive:
|
|
|
5689fb3 |
label = 'config'
|
|
|
5689fb3 |
- self._create_local(basepath('disk.config'), 64, unit='M',
|
|
|
5689fb3 |
- fs_format='msdos', label=label) # 64MB
|
|
|
dc6afc0 |
+ with utils.remove_path_on_error(basepath('disk.config')):
|
|
|
5689fb3 |
+ self._create_local(basepath('disk.config'), 64, unit='M',
|
|
|
5689fb3 |
+ fs_format='msdos', label=label) # 64MB
|
|
|
5689fb3 |
|
|
|
07d81d7 |
if FLAGS.libvirt_inject_key and instance['key_data']:
|
|
|
5689fb3 |
key = str(instance['key_data'])
|