Blame SOURCES/BZ-1335250-fssnapshot-handle-lvm-errors.patch

5e9bef
diff -up yum-3.4.3/yumcommands.py.orig yum-3.4.3/yumcommands.py
5e9bef
--- yum-3.4.3/yumcommands.py.orig	2016-07-21 11:39:40.422379800 +0200
5e9bef
+++ yum-3.4.3/yumcommands.py	2016-07-21 11:40:42.144992126 +0200
5e9bef
@@ -42,6 +42,7 @@ import errno
5e9bef
 import yum.config
5e9bef
 from yum import updateinfo
5e9bef
 from yum.packages import parsePackages
5e9bef
+from yum.fssnapshots import LibLVMError, lvmerr2str
5e9bef
 
5e9bef
 def _err_mini_usage(base, basecmd):
5e9bef
     if basecmd not in base.yum_cli_commands:
5e9bef
@@ -4266,12 +4267,19 @@ class FSSnapshotCommand(YumCommand):
5e9bef
             return 1, [basecmd + ' ' + subcommand + ' done']
5e9bef
 
5e9bef
         if subcommand == 'list':
5e9bef
-            snaps = base.fssnap.old_snapshots()
5e9bef
+            try:
5e9bef
+                snaps = base.fssnap.old_snapshots()
5e9bef
+            except LibLVMError as e:
5e9bef
+                return 1, [_("Failed to list snapshots: ") + lvmerr2str(e)]
5e9bef
             print _("List of %u snapshosts:") % len(snaps)
5e9bef
             self._li_snaps(base, snaps)
5e9bef
 
5e9bef
         if subcommand == 'delete':
5e9bef
-            snaps = base.fssnap.old_snapshots()
5e9bef
+            msg = _("Failed to delete snapshots: ")
5e9bef
+            try:
5e9bef
+                snaps = base.fssnap.old_snapshots()
5e9bef
+            except LibLVMError as e:
5e9bef
+                return 1, [msg + lvmerr2str(e)]
5e9bef
             devs = [x['dev'] for x in snaps]
5e9bef
             snaps = set()
5e9bef
             for dev in devs:
5e9bef
@@ -4282,13 +4290,20 @@ class FSSnapshotCommand(YumCommand):
5e9bef
                     if dev == extcmd or fnmatch.fnmatch(dev, extcmd):
5e9bef
                         snaps.add(dev)
5e9bef
                         break
5e9bef
-            snaps = base.fssnap.del_snapshots(devices=snaps)
5e9bef
+            try:
5e9bef
+                snaps = base.fssnap.del_snapshots(devices=snaps)
5e9bef
+            except LibLVMError as e:
5e9bef
+                return 1, [msg + lvmerr2str(e)]
5e9bef
             print _("Deleted %u snapshosts:") % len(snaps)
5e9bef
             self._li_snaps(base, snaps)
5e9bef
 
5e9bef
         if subcommand in ('have-space', 'has-space'):
5e9bef
             pc = base.conf.fssnap_percentage
5e9bef
-            if base.fssnap.has_space(pc):
5e9bef
+            try:
5e9bef
+                has_space = base.fssnap.has_space(pc)
5e9bef
+            except LibLVMError as e:
5e9bef
+                return 1, [_("Could not determine free space on logical volumes: ") + lvmerr2str(e)]
5e9bef
+            if has_space:
5e9bef
                 print _("Space available to take a snapshot.")
5e9bef
             else:
5e9bef
                 print _("Not enough space available on logical volumes to take a snapshot.")
5e9bef
@@ -4296,14 +4311,22 @@ class FSSnapshotCommand(YumCommand):
5e9bef
         if subcommand == 'create':
5e9bef
             tags = {'*': ['reason=manual']}
5e9bef
             pc = base.conf.fssnap_percentage
5e9bef
-            snaps = base.fssnap.snapshot(pc, tags=tags)
5e9bef
+            msg = _("Failed to create snapshots")
5e9bef
+            try:
5e9bef
+                snaps = base.fssnap.snapshot(pc, tags=tags)
5e9bef
+            except LibLVMError as e:
5e9bef
+                msg += ": " + lvmerr2str(e)
5e9bef
+                snaps = []
5e9bef
             if not snaps:
5e9bef
-                print _("Failed to create snapshots")
5e9bef
+                print msg
5e9bef
             for (odev, ndev) in snaps:
5e9bef
                 print _("Created snapshot from %s, results is: %s") %(odev,ndev)
5e9bef
 
5e9bef
         if subcommand == 'summary':
5e9bef
-            snaps = base.fssnap.old_snapshots()
5e9bef
+            try:
5e9bef
+                snaps = base.fssnap.old_snapshots()
5e9bef
+            except LibLVMError as e:
5e9bef
+                return 1, [_("Failed to list snapshots: ") + lvmerr2str(e)]
5e9bef
             if not snaps:
5e9bef
                 print _("No snapshots, LVM version:"), base.fssnap.version
5e9bef
                 return 0, [basecmd + ' ' + subcommand + ' done']
5e9bef
diff -up yum-3.4.3/yum/fssnapshots.py.orig yum-3.4.3/yum/fssnapshots.py
5e9bef
--- yum-3.4.3/yum/fssnapshots.py.orig	2016-07-21 11:39:40.351380246 +0200
5e9bef
+++ yum-3.4.3/yum/fssnapshots.py	2016-07-21 11:40:02.211242946 +0200
5e9bef
@@ -6,6 +6,7 @@ import time
5e9bef
 from datetime import datetime
5e9bef
 
5e9bef
 import subprocess
5e9bef
+from yum import _
5e9bef
 
5e9bef
 try:
5e9bef
     import lvm
5e9bef
@@ -24,6 +25,14 @@ except:
5e9bef
     lvm = None
5e9bef
     _ver = None
5e9bef
 
5e9bef
+if lvm is not None:
5e9bef
+    from lvm import LibLVMError
5e9bef
+    class _ResultError(LibLVMError):
5e9bef
+        """Exception raised for LVM calls resulting in bad return values."""
5e9bef
+        pass
5e9bef
+else:
5e9bef
+    LibLVMError = None
5e9bef
+
5e9bef
 
5e9bef
 def _is_origin(lv):
5e9bef
     snap = lv.getAttr()
5e9bef
@@ -53,14 +62,18 @@ def _vg_name2lv(vg, lvname):
5e9bef
         return None
5e9bef
 
5e9bef
 def _list_vg_names():
5e9bef
-    names = lvm.listVgNames()
5e9bef
+    try:
5e9bef
+        names = lvm.listVgNames()
5e9bef
+    except LibLVMError:
5e9bef
+        # Try to use the lvm binary instead
5e9bef
+        names = []
5e9bef
 
5e9bef
     if not names: # Could be just broken...
5e9bef
         p = subprocess.Popen(["/sbin/lvm", "vgs", "-o", "vg_name"],
5e9bef
                              stdout=subprocess.PIPE, stderr=subprocess.PIPE)
5e9bef
         err = p.wait()
5e9bef
         if err:
5e9bef
-            return [] # Meh.
5e9bef
+            raise _ResultError(_("Failed to obtain volume group names"))
5e9bef
 
5e9bef
         output = p.communicate()[0]
5e9bef
         output = output.split('\n')
5e9bef
@@ -132,6 +145,25 @@ def _lv_data(vg, lv):
5e9bef
 
5e9bef
     return data
5e9bef
 
5e9bef
+def _log_traceback(func):
5e9bef
+    """Decorator for _FSSnap methods that logs LVM tracebacks."""
5e9bef
+    def wrap(self, *args, **kwargs):
5e9bef
+        try:
5e9bef
+            return func(self, *args, **kwargs)
5e9bef
+        except LibLVMError as e:
5e9bef
+            if self._logger is not None:
5e9bef
+                self._logger.exception(e)
5e9bef
+            raise
5e9bef
+    return wrap
5e9bef
+
5e9bef
+def lvmerr2str(exc):
5e9bef
+    """Convert a LibLVMError instance to a readable error message."""
5e9bef
+    if type(exc) == LibLVMError and len(exc.args) == 2:
5e9bef
+        # args[0] is the error number so ignore that
5e9bef
+        return exc.args[1]
5e9bef
+    else:
5e9bef
+        return str(exc)
5e9bef
+
5e9bef
 
5e9bef
 class _FSSnap(object):
5e9bef
 
5e9bef
@@ -139,7 +171,7 @@ class _FSSnap(object):
5e9bef
     # New style is: fedora/root fedora/swap
5e9bef
     # New style is: redhat/root redhat/swap
5e9bef
     def __init__(self, root="/", lookup_mounts=True,
5e9bef
-                 devices=('!*/swap', '!*/lv_swap')):
5e9bef
+                 devices=('!*/swap', '!*/lv_swap'), logger=None):
5e9bef
         if not lvm or os.geteuid():
5e9bef
             devices = []
5e9bef
 
5e9bef
@@ -150,12 +182,18 @@ class _FSSnap(object):
5e9bef
         self._postfix = None
5e9bef
         self._root = root
5e9bef
         self._devs = devices
5e9bef
-        self._vgnames = []
5e9bef
+        self._vgname_list = None
5e9bef
+        # Logger object to be used for LVM traceback logging
5e9bef
+        self._logger = logger
5e9bef
 
5e9bef
         if not self._devs:
5e9bef
             return
5e9bef
 
5e9bef
-        self._vgnames = _list_vg_names() if self.available else []
5e9bef
+    @property
5e9bef
+    def _vgnames(self):
5e9bef
+        if self._vgname_list is None:
5e9bef
+            self._vgname_list = _list_vg_names() if self.available else []
5e9bef
+        return self._vgname_list
5e9bef
 
5e9bef
     def _use_dev(self, vgname, lv=None):
5e9bef
 
5e9bef
@@ -196,6 +234,7 @@ class _FSSnap(object):
5e9bef
 
5e9bef
         return found_neg
5e9bef
 
5e9bef
+    @_log_traceback
5e9bef
     def has_space(self, percentage=100):
5e9bef
         """ See if we have enough space to try a snapshot. """
5e9bef
 
5e9bef
@@ -207,7 +246,8 @@ class _FSSnap(object):
5e9bef
 
5e9bef
             vg = lvm.vgOpen(vgname, 'r')
5e9bef
             if not vg:
5e9bef
-                return False
5e9bef
+                raise _ResultError(
5e9bef
+                    _("Unknown error when opening volume group ") + vgname)
5e9bef
 
5e9bef
             vgfsize = vg.getFreeSize()
5e9bef
             lvssize = 0
5e9bef
@@ -230,6 +270,7 @@ class _FSSnap(object):
5e9bef
         return ret
5e9bef
 
5e9bef
 
5e9bef
+    @_log_traceback
5e9bef
     def snapshot(self, percentage=100, prefix='', postfix=None, tags={}):
5e9bef
         """ Attempt to take a snapshot, note that errors can happen after
5e9bef
             this function succeeds. """
5e9bef
@@ -245,7 +286,8 @@ class _FSSnap(object):
5e9bef
 
5e9bef
             vg = lvm.vgOpen(vgname, 'w')
5e9bef
             if not vg:
5e9bef
-                return False
5e9bef
+                raise _ResultError(
5e9bef
+                    _("Unknown error when opening volume group ") + vgname)
5e9bef
 
5e9bef
             for lv in vg.listLVs():
5e9bef
                 lvname = lv.getName()
5e9bef
@@ -257,7 +299,8 @@ class _FSSnap(object):
5e9bef
                 nlv = lv.snapshot(nlvname, (lv.getSize() * percentage) / 100)
5e9bef
                 if not nlv: # Failed here ... continuing seems bad.
5e9bef
                     vg.close()
5e9bef
-                    return None
5e9bef
+                    raise _ResultError(
5e9bef
+                        _("Unknown error when creating snapshot ") + nlvname)
5e9bef
 
5e9bef
                 odev = "%s/%s" % (vgname,  lvname)
5e9bef
                 ndev = "%s/%s" % (vgname, nlvname)
5e9bef
@@ -280,6 +323,7 @@ class _FSSnap(object):
5e9bef
 
5e9bef
         return ret
5e9bef
 
5e9bef
+    @_log_traceback
5e9bef
     def old_snapshots(self):
5e9bef
         """ List data for old snapshots. """
5e9bef
 
5e9bef
@@ -289,6 +333,9 @@ class _FSSnap(object):
5e9bef
             # see stuff after changing config. options.
5e9bef
 
5e9bef
             vg = lvm.vgOpen(vgname, 'w')
5e9bef
+            if not vg:
5e9bef
+                raise _ResultError(
5e9bef
+                    _("Unknown error when opening volume group ") + vgname)
5e9bef
 
5e9bef
             for lv in vg.listLVs():
5e9bef
 
5e9bef
@@ -300,6 +347,7 @@ class _FSSnap(object):
5e9bef
 
5e9bef
         return ret
5e9bef
 
5e9bef
+    @_log_traceback
5e9bef
     def del_snapshots(self, devices=[]):
5e9bef
         """ Remove snapshots. """
5e9bef
 
5e9bef
@@ -318,6 +366,9 @@ class _FSSnap(object):
5e9bef
 
5e9bef
         for vgname in togo:
5e9bef
             vg = lvm.vgOpen(vgname, 'w')
5e9bef
+            if not vg:
5e9bef
+                raise _ResultError(
5e9bef
+                    _("Unknown error when opening volume group ") + vgname)
5e9bef
 
5e9bef
             for lvname in togo[vgname]:
5e9bef
                 lv = _vg_name2lv(vg, lvname)
5e9bef
diff -up yum-3.4.3/yum/__init__.py.orig yum-3.4.3/yum/__init__.py
5e9bef
--- yum-3.4.3/yum/__init__.py.orig	2016-07-21 11:39:40.425379782 +0200
5e9bef
+++ yum-3.4.3/yum/__init__.py	2016-07-21 11:40:02.211242946 +0200
5e9bef
@@ -81,6 +81,7 @@ import yumRepo
5e9bef
 import callbacks
5e9bef
 import yum.history
5e9bef
 import yum.fssnapshots
5e9bef
+from yum.fssnapshots import LibLVMError, lvmerr2str
5e9bef
 import yum.igroups
5e9bef
 import update_md
5e9bef
 
5e9bef
@@ -204,6 +205,7 @@ class YumBase(depsolve.Depsolve):
5e9bef
         self._not_found_i = {}
5e9bef
         self.logger = logging.getLogger("yum.YumBase")
5e9bef
         self.verbose_logger = logging.getLogger("yum.verbose.YumBase")
5e9bef
+        self.file_logger = logging.getLogger("yum.filelogging.YumBase")
5e9bef
         self._override_sigchecks = False
5e9bef
         self._repos = RepoStorage(self)
5e9bef
         self.repo_setopts = {} # since we have to use repo_setopts in base and 
5e9bef
@@ -1048,7 +1050,8 @@ class YumBase(depsolve.Depsolve):
5e9bef
         if self._fssnap is None:
5e9bef
             devices = self.conf.fssnap_devices
5e9bef
             self._fssnap = yum.fssnapshots._FSSnap(root=self.conf.installroot,
5e9bef
-                                                   devices=devices)
5e9bef
+                                                   devices=devices,
5e9bef
+                                                   logger=self.file_logger)
5e9bef
 
5e9bef
         return self._fssnap
5e9bef
 
5e9bef
@@ -1726,6 +1729,37 @@ much more problems).
5e9bef
         :raises: :class:`yum.Errors.YumRPMTransError` if there is a
5e9bef
            transaction cannot be completed
5e9bef
         """
5e9bef
+
5e9bef
+        def create_snapshot(post=False):
5e9bef
+            """Create the pre or post trans snapshot if we have free space."""
5e9bef
+            msg = _("Not enough space on logical volumes to create %s FS snapshot." %
5e9bef
+                    ("post trans" if post else "pre."))
5e9bef
+            try:
5e9bef
+                has_space = self.fssnap.has_space(self.conf.fssnap_percentage)
5e9bef
+            except LibLVMError as e:
5e9bef
+                msg = _("Could not determine free space on logical volumes: ") + lvmerr2str(e)
5e9bef
+                has_space = False
5e9bef
+            if not has_space:
5e9bef
+                if not post and self.conf.fssnap_abort_on_errors in ('snapshot-failure', 'any'):
5e9bef
+                    raise Errors.YumRPMTransError(msg="Aborting transaction", errors=msg)
5e9bef
+                else:
5e9bef
+                    self.verbose_logger.critical(msg)
5e9bef
+            else:
5e9bef
+                tags = {'*': ['reason=automatic']} # FIXME: pre. and post tags
5e9bef
+                msg = _("Failed to create snapshot")
5e9bef
+                try:
5e9bef
+                    snaps = self.fssnap.snapshot(self.conf.fssnap_percentage, tags=tags)
5e9bef
+                except LibLVMError as e:
5e9bef
+                    msg += ": " + lvmerr2str(e)
5e9bef
+                    snaps = []
5e9bef
+                if not snaps:
5e9bef
+                    if not post and self.conf.fssnap_abort_on_errors in ('snapshot-failure', 'any'):
5e9bef
+                        raise Errors.YumRPMTransError(msg="Aborting transaction", errors=msg)
5e9bef
+                    else:
5e9bef
+                        self.verbose_logger.critical(msg)
5e9bef
+                for (odev, ndev) in snaps:
5e9bef
+                    self.verbose_logger.info(_("Created snapshot from %s, results is: %s") % (odev, ndev))
5e9bef
+
5e9bef
         if (self.conf.fssnap_automatic_pre or self.conf.fssnap_automatic_post) and not self.fssnap.available:
5e9bef
             msg = _("Snapshot support not available.")
5e9bef
             if self.conf.fssnap_abort_on_errors in ('broken-setup', 'any'):
5e9bef
@@ -1737,7 +1771,13 @@ much more problems).
5e9bef
                                        self.conf.fssnap_automatic_post) and
5e9bef
                                       self.conf.fssnap_automatic_keep):
5e9bef
             # Automatically kill old snapshots...
5e9bef
-            snaps = self.fssnap.old_snapshots()
5e9bef
+            cleanup_fail = False
5e9bef
+            try:
5e9bef
+                snaps = self.fssnap.old_snapshots()
5e9bef
+            except LibLVMError as e:
5e9bef
+                self.verbose_logger.debug(lvmerr2str(e))
5e9bef
+                cleanup_fail = True
5e9bef
+                snaps = []
5e9bef
             snaps = sorted(snaps, key=lambda x: (x['ctime'], x['origin_dev']),
5e9bef
                            reverse=True)
5e9bef
             last = '<n/a>'
5e9bef
@@ -1754,30 +1794,22 @@ much more problems).
5e9bef
                 if num > self.conf.fssnap_automatic_keep:
5e9bef
                     todel.append(snap['dev'])
5e9bef
             # Display something to the user?
5e9bef
-            snaps = self.fssnap.del_snapshots(devices=todel)
5e9bef
+            try:
5e9bef
+                snaps = self.fssnap.del_snapshots(devices=todel)
5e9bef
+            except LibLVMError as e:
5e9bef
+                self.verbose_logger.debug(lvmerr2str(e))
5e9bef
+                cleanup_fail = True
5e9bef
+                snaps = []
5e9bef
             if len(snaps):
5e9bef
                 self.verbose_logger.info(_("Deleted %u snapshots.") % len(snaps))
5e9bef
+            elif cleanup_fail:
5e9bef
+                self.verbose_logger.warning(_("Skipping the cleanup of old "
5e9bef
+                                              "snapshots due to errors"))
5e9bef
 
5e9bef
         if (self.fssnap.available and
5e9bef
             (not self.ts.isTsFlagSet(rpm.RPMTRANS_FLAG_TEST) and
5e9bef
             self.conf.fssnap_automatic_pre)):
5e9bef
-            if not self.fssnap.has_space(self.conf.fssnap_percentage):
5e9bef
-                msg = _("Not enough space on logical volumes to create pre. FS snapshot.")
5e9bef
-                if self.conf.fssnap_abort_on_errors in ('snapshot-failure', 'any'):
5e9bef
-                    raise Errors.YumRPMTransError(msg="Aborting transaction", errors=msg)
5e9bef
-                else:
5e9bef
-                    self.verbose_logger.critical(msg)
5e9bef
-            else:
5e9bef
-                tags = {'*': ['reason=automatic']} # FIXME: pre. tags
5e9bef
-                snaps = self.fssnap.snapshot(self.conf.fssnap_percentage, tags=tags)
5e9bef
-                if not snaps:
5e9bef
-                    msg = _("Failed to create snapshot")
5e9bef
-                    if self.conf.fssnap_abort_on_errors in ('snapshot-failure', 'any'):
5e9bef
-                        raise Errors.YumRPMTransError(msg="Aborting transaction", errors=msg)
5e9bef
-                    else:
5e9bef
-                        self.verbose_logger.critical(msg)
5e9bef
-                for (odev, ndev) in snaps:
5e9bef
-                    self.verbose_logger.info(_("Created snapshot from %s, results is: %s") % (odev, ndev))
5e9bef
+            create_snapshot()
5e9bef
 
5e9bef
         self.plugins.run('pretrans')
5e9bef
 
5e9bef
@@ -1912,16 +1944,7 @@ much more problems).
5e9bef
         if (self.fssnap.available and
5e9bef
             (not self.ts.isTsFlagSet(rpm.RPMTRANS_FLAG_TEST) and
5e9bef
             self.conf.fssnap_automatic_post)):
5e9bef
-            if not self.fssnap.has_space(self.conf.fssnap_percentage):
5e9bef
-                msg = _("Not enough space on logical volumes to create post trans FS snapshot.")
5e9bef
-                self.verbose_logger.critical(msg)
5e9bef
-            else:
5e9bef
-                tags = {'*': ['reason=automatic']} # FIXME: post tags
5e9bef
-                snaps = self.fssnap.snapshot(self.conf.fssnap_percentage, tags=tags)
5e9bef
-                if not snaps:
5e9bef
-                    self.verbose_logger.critical(_("Failed to create snapshot"))
5e9bef
-                for (odev, ndev) in snaps:
5e9bef
-                    self.verbose_logger.info(_("Created snapshot from %s, results is: %s") % (odev, ndev))
5e9bef
+            create_snapshot(post=True)
5e9bef
         return resultobject
5e9bef
 
5e9bef
     def verifyTransaction(self, resultobject=None, txmbr_cb=None):