From: Mike Snitzer <msnit...@fedoraproject.org>

LVM snapshot support is provided for the purpose of system rollback.  As
such LVM snapshots will only be created if the kernel supports the
"snapshot-merge" DM target (upstream in 2.6.33).  LVM cannot be used for
/boot.  /boot must be manually restored in the event of a system
rollback that uninstalls a kernel package.

In addition to the user needing to specify 'enabled = 1' in the '[lvm]'
section of fs-snapshot.conf: the LVM snapshot support also depends on
the user specifying the 'lvcreate_size_args' option in the
fs-snapshot.conf.

Changes have been made to upstream LVM2 (>= 2.02.61) to allow a user to
specify the size of the snapshot LV as a percentage of the origin LV,
for more details please see the %ORIGIN changes here:
http://sources.redhat.com/git/?p=lvm2.git;a=commit;h=6610f9bdbc

Signed-off-by: Mike Snitzer <msnit...@fedoraproject.org>
---
 docs/yum-fs-snapshot.1               |   10 ++-
 docs/yum-fs-snapshot.conf.5          |   15 +++-
 plugins/fs-snapshot/fs-snapshot.conf |    5 +
 plugins/fs-snapshot/fs-snapshot.py   |  163 ++++++++++++++++++++++++++++++++--
 4 files changed, 181 insertions(+), 12 deletions(-)

diff --git a/docs/yum-fs-snapshot.1 b/docs/yum-fs-snapshot.1
index 0fc1397..1c3c834 100644
--- a/docs/yum-fs-snapshot.1
+++ b/docs/yum-fs-snapshot.1
@@ -1,5 +1,5 @@
 .\" yum-fs-snapshot
-.TH YUM-FS-SNAPSHOT 1 "14 December 2009" "" "User Manuals"
+.TH YUM-FS-SNAPSHOT 1 "3 February 2010" "" "User Manuals"
 .SH NAME
 .B yum-fs-snapshot
 .SH SYNOPSIS
@@ -10,7 +10,11 @@ package
 .BR yum-fs-snapshot(1)
 is a Yum plugin for taking snapshots of your filesystems before running a yum
 transaction.  By default it will take a snapshot of any filesystem that can be
-snapshotted, which currently is limited to BTRFS filesystems. 
+snapshotted, which currently is limited to BTRFS filesystems.  However,
+all filesystems built on LVM logical volumes may be snapshotted at the
+block level using LVM snapshots.  LVM snapshot support is provided for
+the purpose of system rollback.  As such LVM snapshots will only be
+created if the kernel supports the "snapshot-merge" DM target.
 .SH FILES
 .B yum-fs-snapshot
 uses a configuration file for its specific actions: 
@@ -24,6 +28,8 @@ uses a configuration file for its specific actions:
 .SH AUTHORS
 .nf
 Josef Bacik <jo...@toxicpanda.com>
+.br
+Mike Snitzer <msnit...@fedoraproject.org>
 .fi
 .SH "SEE ALSO"
 .BR yum (1)
diff --git a/docs/yum-fs-snapshot.conf.5 b/docs/yum-fs-snapshot.conf.5
index 9cf49e1..a4b4f02 100644
--- a/docs/yum-fs-snapshot.conf.5
+++ b/docs/yum-fs-snapshot.conf.5
@@ -1,5 +1,5 @@
 .\" yum-fs-snapshot.conf.5
-.TH YUM-FS-SNAPSHOT.CONF 5 "14 December 2009" "" "File Formats"
+.TH YUM-FS-SNAPSHOT.CONF 5 "3 February 2010" "" "File Formats"
 .SH NAME
 .B yum-fs-snapshot.conf(5)
 
@@ -9,7 +9,7 @@ is the configuration file for
 .B yum-fs-snapshot(1)
 Yum plugin for snapshotting your filesystems before running a yum transaction.
 By default, this plugin will snapshot all filesystems that it is capable of
-snapshotting.
+snapshotting.  This includes block-level snapshots using LVM snapshots.
 .SH FILES
 .I /etc/yum/pluginconf.d/fs-snapshot.conf
 .SH FILE FORMAT
@@ -20,9 +20,20 @@ utilizes configuration options in the form of
 .IP exclude
 This is a space delimited list of the mount points you do not wish to have
 snapshotted by this plugin.
+.SH OPTION - [lvm] section
+.IP enabled
+This is a boolean value used to control whether LVM snapshots will be
+created for filesystems built on LVM logical volumes.
+.SH OPTION - [lvm] section
+.IP lvcreate_size_args
+This is the space delimited lvcreate argument list that is used to
+specify the size of the snapshot LV.  Valid lvcreate size options are -l
+or -L.  If not specified then LVM snapshots will not be created.
 .SH AUTHOR
 .RS
 Josef Bacik <jo...@toxicpanda.com>
+.br
+Mike Snitzer <msnit...@fedoraproject.org>
 .RS
 .SH SEE ALSO
 .BR yum-fs-snapshot(1)
diff --git a/plugins/fs-snapshot/fs-snapshot.conf 
b/plugins/fs-snapshot/fs-snapshot.conf
index e7002aa..3a67c69 100644
--- a/plugins/fs-snapshot/fs-snapshot.conf
+++ b/plugins/fs-snapshot/fs-snapshot.conf
@@ -1,2 +1,7 @@
 [main]
 enabled = 1
+
+[lvm]
+enabled = 0
+# 'lvcreate_size_args' option must specify the snapshot LV size using -L or -l
+#lvcreate_size_args = -l 15%ORIGIN
diff --git a/plugins/fs-snapshot/fs-snapshot.py 
b/plugins/fs-snapshot/fs-snapshot.py
index c9f3586..0a36af4 100644
--- a/plugins/fs-snapshot/fs-snapshot.py
+++ b/plugins/fs-snapshot/fs-snapshot.py
@@ -36,8 +36,98 @@ from subprocess import Popen,PIPE
 requires_api_version = '2.4'
 plugin_type = (TYPE_CORE,)
 
+# Globals
+lvm_key = "create_lvm_snapshot"
+# avoid multiple snapshot-merge checks via inspect_volume_lvm()
+dm_snapshot_merge_checked = 0
+dm_snapshot_merge_support = 0
+
+def kernel_supports_dm_snapshot_merge():
+    # verify the kernel provides the 'snapshot-merge' DM target
+    # - modprobe dm-snapshot; dmsetup targets | grep -q snapshot-merge
+    global dm_snapshot_merge_checked, dm_snapshot_merge_support
+    if dm_snapshot_merge_checked:
+        return dm_snapshot_merge_support
+    os.system("modprobe dm-snapshot")
+    p = Popen(["dmsetup", "targets"], stdout=PIPE, stderr=PIPE)
+    err = p.wait()
+    if not err:
+        output = p.communicate()[0]
+        if not output.find("snapshot-merge") == -1:
+            dm_snapshot_merge_support = 1
+        dm_snapshot_merge_checked = 1
+    return dm_snapshot_merge_support
+
+def inspect_volume_lvm(conduit, volume):
+    """
+    If volume is an LVM logical volume:
+    - translate /dev/mapper name for LVM command use
+    - conditionally establish lvm_key in volume
+    """
+    lvm_support = conduit.confBool('lvm', 'enabled', default=0)
+    if not lvm_support:
+        return 1
+    device = volume["device"]
+    # Inspect DM and LVM devices
+    if device.startswith("/dev/dm-"):
+        conduit.info(2, "fs-snapshot: unable to snapshot DM device: " + device)
+        return 0
+    if device.startswith("/dev/mapper/"):
+        # convert /dev/mapper name to /dev/vg/lv for use with LVM2 tools
+        # - 'dmsetup splitname' will collapse any escaped characters
+        p = Popen(["dmsetup", "splitname", "--separator", "/", "--noheadings",
+                   "-o", "vg_name,lv_name", device], stdout=PIPE, stderr=PIPE)
+        err = p.wait()
+        if err:
+            return 0
+        output = p.communicate()[0]
+        device = output.strip().replace("/dev/mapper/", "/dev/")
+        volume["device"] = device
+
+    # Check if device is managed by lvm
+    # - FIXME filter out snapshot (and other) LVs; for now just rely
+    #   on 'lvcreate' to prevent snapshots of unsupported LV types
+    p = Popen(["lvs", device], stdout=PIPE, stderr=PIPE)
+    err = p.wait()
+    if not err:
+        # FIXME allow creating snapshot LVs even if kernel doesn't
+        # support snapshot-merge based system rollback? make configurable?
+        if not kernel_supports_dm_snapshot_merge():
+            conduit.error(1, "fs-snapshot: skipping volume: %s, "
+                          "kernel doesn't support snapshot-merge" % device)
+            return 0
+        volume[lvm_key] = 1
+    return 1
+
+def inspect_volume(conduit, volume):
+    """
+    Hook to check/filter volume for special characteristics.
+    Returns 0 if volume failed inspection, otherwise 1.
+    All inspect_volume_* methods act as filters; if they
+    return 0 that means this volume failed inspection.
+    """
+    if not inspect_volume_lvm(conduit, volume):
+        return 0
+    # Additional inspect_volume_* methods may prove unnecessary but the
+    # filtering nature of these methods would make them unavoidable; e.g.
+    # just because a volume is LVM doesn't mean other filters should
+    # be short-circuited
+    return 1
+
 def get_volumes(conduit):
-    # return list of volume dictionaries
+    """
+    Return all volumes that may be snapshotted.
+    Each volume is a dictionary that contains descriptive key=value
+    pairs.  All volumes will have 'device', 'mntpnt', and 'fstype'
+    keys.  Extra keys may be established as a side-effect of
+    inspect_volume().
+    """
+    # FIXME may look to return dictionary of volume dictionaries to
+    # allow a volume to be looked up using its path (as the key).
+    # - when a kernel package is being installed: could prove useful to check
+    #   if "/" is an LVM volume and "/boot" is not able to be snapshotted; if
+    #   so warn user that "/boot" changes (e.g. grub's menu.lst) will need to
+    #   be manually rolled back.
     volumes = []
 
     excluded_mntpnts = conduit.confString('main', 'exclude', 
default="").split()
@@ -61,12 +151,17 @@ def get_volumes(conduit):
             if not device.find("/") == 0:
                 continue
 
+            # skip volume if it doesn't pass inspection
+            # - inspect_volume may create additional keys in this volume
+            if not inspect_volume(conduit, volume):
+                continue
+
             volumes.append(volume)
 
         mtabfile.close()
 
-    except Exception as (errno, strerror):
-        msg = "fs-snapshot: error reading /etc/mtab: %s" % (strerror)
+    except Exception, e:
+        msg = "fs-snapshot: error processing mounted volumes: %s" % e
         conduit.error(1, msg)
 
     return volumes
@@ -78,13 +173,10 @@ def _create_snapshot(conduit, snapshot_tag, volume):
     appropriate snapshotting function.  The idea is you could add something for
     lvm snapshots, nilfs2 or whatever else here.
     """
-
-    # at some point it would be nice to add filtering here, so users can 
specify
-    # which filesystems they don't want snapshotted and we'd automatically 
match
-    # them here and just return.
-
     if volume["fstype"] == "btrfs":
         return _create_btrfs_snapshot(conduit, snapshot_tag, volume)
+    elif volume.has_key(lvm_key):
+        return _create_lvm_snapshot(conduit, snapshot_tag, volume)
 
     return 0
 
@@ -116,6 +208,61 @@ def _create_btrfs_snapshot(conduit, snapshot_tag, volume):
         return 1
     return 0
 
+def _create_lvm_snapshot(conduit, snapshot_tag, volume):
+    """
+    Create LVM snapshot LV and tag it with $snapshot_tag.
+    - This assumes that the volume is an origin LV whose VG
+      has enough free space to accommodate a snapshot LV.
+    - Also assumes user has configured 'lvcreate_size_args'.
+    """
+    lvcreate_size_args = conduit.confString('lvm', 'lvcreate_size_args',
+                                            default=None)
+    if not lvcreate_size_args:
+        conduit.error(1, "fs-snapshot: 'lvcreate_size_args' was not provided "
+                      "in the '[lvm]' section of the config file")
+        return 1
+
+    if not lvcreate_size_args.startswith("-L") and not 
lvcreate_size_args.startswith("-l"):
+        conduit.error(1, "fs-snapshot: 'lvcreate_size_args' did not use -L or 
-l")
+        return 1
+
+    device = volume["device"]
+    if device.count('/') != 3:
+        return 1
+
+    mntpnt = volume["mntpnt"]
+    if mntpnt == "/":
+        # FIXME only print a variant of this warning if a kernel
+        # will be installed by the current yum transaction
+        conduit.info(1, "fs-snapshot: WARNING: creating LVM snapshot of root 
LV.  If a kernel is\n"
+                        "                      being installed /boot may need 
to be manually restored\n"
+                        "                      in the event that a system 
rollback proves necessary.")
+
+    snap_device = device + "_" + snapshot_tag
+    snap_lvname = snap_device.split('/')[3]
+    conduit.info(1, "fs-snapshot: snapshotting %s (%s): %s" %
+                 (mntpnt, device, snap_lvname))
+    # Create snapshot LV
+    lvcreate_cmd = ["lvcreate", "-s", "-n", snap_lvname]
+    lvcreate_cmd.extend(lvcreate_size_args.split())
+    lvcreate_cmd.append(device)
+    p = Popen(lvcreate_cmd, stdout=PIPE, stderr=PIPE)
+    err = p.wait()
+    if err:
+        conduit.error(1, "fs-snapshot: failed command: %s\n%s" %
+                      (" ".join(lvcreate_cmd), p.communicate()[1]))
+        return 1
+    # Add tag ($snapshot_tag) to snapshot LV
+    # - should help facilitate merge of all snapshot LVs created
+    #   by a yum transaction, e.g.: lvconvert --merge @snapshot_tag
+    p = Popen(["lvchange", "--addtag", snapshot_tag, snap_device],
+              stdout=PIPE, stderr=PIPE)
+    err = p.wait()
+    if err:
+        conduit.error(1, "fs-snapshot: couldn't add tag to snapshot: %s" %
+                      snap_device)
+    return 0
+
 def init_hook(conduit):
     if hasattr(conduit, 'registerPackageName'):
         conduit.registerPackageName("yum-plugin-fs-snapshot")
-- 
1.6.5.2

_______________________________________________
Yum-devel mailing list
Yum-devel@lists.baseurl.org
http://lists.baseurl.org/mailman/listinfo/yum-devel

Reply via email to