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'])