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