From 4b4f15ddb57505f050ccc7f466bfc21634c45b73 Mon Sep 17 00:00:00 2001
From: David Lehman <dlehman@redhat.com>
Date: Wed, 21 Aug 2013 16:37:38 -0500
Subject: [PATCH 2/2] Add support for btrfs and lvm snapshots.

---
 blivet/devicelibs/btrfs.py |   7 +++
 blivet/devicelibs/lvm.py   |  28 ++++++++++++
 blivet/devices.py          | 109 ++++++++++++++++++++++++++++++++++++++++++++-
 blivet/devicetree.py       |   9 ++--
 4 files changed, 146 insertions(+), 7 deletions(-)

diff --git a/blivet/devicelibs/btrfs.py b/blivet/devicelibs/btrfs.py
index f68fce0..db3ab3b 100644
--- a/blivet/devicelibs/btrfs.py
+++ b/blivet/devicelibs/btrfs.py
@@ -107,6 +107,13 @@ def delete_subvolume(mountpoint, name):
     args = ["subvol", "delete", path]
     return btrfs(args)
 
+def create_snapshot(source, dest):
+    if not os.path.ismount(source):
+        raise ValueError("source is not a mounted subvolume")
+
+    args = ["subvol", "snapshot", source, dest]
+    return btrfs(args)
+
 _SUBVOL_REGEX_STR = r'ID (\d+) gen \d+ parent (\d+) top level \d+ path (.+)\n'
 _SUBVOL_REGEX = re.compile(_SUBVOL_REGEX_STR)
 
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index cf9452d..841aaf4 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -424,6 +424,27 @@ def lvorigin(vg_name, lv_name):
 
     return origin
 
+def lvsnapshotmerge(vg_name, lv_name):
+    args = ["lvconvert", "--merge", "%s/%s" % (vg_name, lv_name)]
+    args += _getConfigArgs() + [lv_name]
+
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("lvsnapshotmerge failed for %s: %s" % (lv_name, msg))
+
+def lvsnapshotcreate(vg_name, lv_name, size, origin):
+    args = ["lvcreate", "-s",
+            "-L", "%dm" % size.convertTo(spec="MiB"),
+            "-n", lv_name]
+    args += _getConfigArgs()
+    args += ["%s/%s" % (vg_name, origin)]
+
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("lvsnapshotcreate failed for %s/%s: %s" % (vg_name, lv_name, msg))
+
 def lvcreate(vg_name, lv_name, size, pvs=[]):
     args = ["lvcreate"] + \
             ["-L", "%dm" % size.convertTo(spec="mib")] + \
@@ -508,6 +529,13 @@ def thinlvcreate(vg_name, pool_name, lv_name, size):
     except LVMError as msg:
         raise LVMError("lvcreate failed for %s/%s: %s" % (vg_name, lv_name, msg))
 
+def thinsnapshotcreate(vg_name, lv_name, origin_name):
+    args = ["lvcreate", "-s", "-n", lv_name, "%s/%s" % (vg_name, origin_name)]
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("lvcreate (snapshot) failed for %s/%s: %s" % (vg_name, lv_name, msg))
+
 def thinlvpoolname(vg_name, lv_name):
     args = ["lvs", "--noheadings", "-o", "pool_lv"] + \
             _getConfigArgs(read_only_locking=True) + \
diff --git a/blivet/devices.py b/blivet/devices.py
index b079a29..2a79714 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -2806,6 +2806,68 @@ class LVMLogicalVolumeDevice(DMDevice):
         elif data.resize:
             data.size = int(self.targetSize)
 
+class LVMSnapShotDevice(LVMLogicalVolumeDevice):
+    """ An LVM snapshot """
+    _type = "lvm snapshot"
+
+    def __init__(self, name, parents=None, size=None, uuid=None,
+                 stripes=1, logSize=0, snapshotSpace=0,
+                 format=None, exists=False, sysfsPath='',
+                 grow=None, maxsize=None, percent=None, origin=None):
+        """ Create a LVMLogicalVolumeDevice instance.
+
+            TODO: sphinxify this
+
+            Arguments:
+
+                name -- the device name (generally a device node's basename)
+                vgdev -- volume group (LVMVolumeGroupDevice instance)
+
+            Keyword Arguments:
+
+                size -- the device's size (in MB)
+                uuid -- the device's UUID
+                sysfsPath -- sysfs device path
+                format -- a DeviceFormat instance
+                exists -- indicates whether this is an existing device
+                origin -- the origin for the snapshot (LVMLogicalVolumeDevice)
+
+                For new (non-existent) LVs only:
+
+                    grow -- whether to grow this LV
+                    maxsize -- maximum size for growable LV (in MB)
+                    percent -- percent of VG space to take
+
+        """
+        self.origin = kwargs.pop("origin", None)
+        if not self.origin:
+            raise DeviceError("lvm snapshot devices require an origin lv")
+
+        # XXX I think snapshotSpace is going to go away
+        # XXX what about stripes, logSize?
+        # XXX percent of what? vg or origin?
+        super(LVMSnapShotDevice, self).__init__(name, parents=parents,
+                                                size=size, uuid=uuid,
+                                                format=format, exists=exists,
+                                                sysfsPath=sysfsPath,
+                                                grow=grow, maxsize=maxsize,
+                                                percent=percent)
+
+    def _create(self):
+        """ Create the device. """
+        log_method_call(self, self.name, status=self.status)
+        lvm.lvsnapshotcreate(self.vg.name, self._name, self.size,
+                             self.origin.lvname)
+
+    def merge(self):
+        log_method_call(self, self.name, status=self.status)
+        self.vg.setup()    # orig=True?
+        self.origin.teardown()
+        self.teardown()
+
+        udev_settle()
+        lvm.lvsnapshotmerge(self.vg.name, self.lvname)
+
 class LVMThinPoolDevice(LVMLogicalVolumeDevice):
     """ An LVM Thin Pool """
     _type = "lvmthinpool"
@@ -2969,6 +3031,23 @@ class LVMThinLogicalVolumeDevice(LVMLogicalVolumeDevice):
         data.thin_volume = True
         data.pool_name = self.pool.lvname
 
+class LVMThinSnapShotDevice(LVMThinLogicalVolumeDevice):
+    """ An LVM Thin Snapshot """
+    _type = "lvmthinsnapshot"
+    _resizable = False
+
+    def __init__(self, *args, **kwargs):
+        self.origin = kwargs.pop("origin", None)
+        if not self.origin:
+            raise DeviceError("lvm snapshot devices require an origin lv")
+
+        super(LVMThinSnapShotDevice, self).__init__(*args, **kwargs)
+
+    def _create(self):
+        """ Create the device. """
+        log_method_call(self, self.name, status=self.status)
+        lvm.thinsnapshotcreate(self.vg.name, self._name, self.origin.lvname)
+
 class MDRaidArrayDevice(StorageDevice):
     """ An mdraid (Linux RAID) device. """
     _type = "mdarray"
@@ -4805,8 +4884,10 @@ class BTRFSSubVolumeDevice(BTRFSDevice):
         if not mountpoint:
             raise RuntimeError("btrfs subvol create requires mounted volume")
 
-        btrfs.create_subvolume(mountpoint, self.name)
-        self.volume._undo_temp_mount()
+        try:
+            btrfs.create_subvolume(mountpoint, self.name)
+        finally:
+            self.volume._undo_temp_mount()
 
     def _postCreate(self):
         super(BTRFSSubVolumeDevice, self)._postCreate()
@@ -4826,3 +4907,27 @@ class BTRFSSubVolumeDevice(BTRFSDevice):
         data.subvol = True
         data.name = self.name
         data.preexist = self.exists
+
+class BTRFSSnapShotDevice(BTRFSSubVolumeDevice):
+    """ A btrfs snapshot pseudo-device. """
+    _type = "btrfs snapshot"
+
+    def __init__(self, *args, **kwargs):
+        self.source = kwargs.pop("source")
+        self.read_only = kwargs.pop("read_only", False)
+        super(BTRFSSnapShotDevice, self).__init__(*args, **kwargs)
+        # TODO: detect existing snapshots?
+
+    def _create(self):
+        log_method_call(self, self.name, status=self.status)
+        self.volume._do_temp_mount()
+        mountpoint = self.volume.format._mountpoint
+        if not mountpoint:
+            raise RuntimeError("btrfs subvol create requires mounted volume")
+
+        source_path = "%s/%s" % (mountpoint, self.source.name)
+        dest_path = "%s/%s" % (mountpoint, self.name)
+        try:
+            btrfs.create_snapshot(source_path, dest_path)
+        finally:
+            self.volume._undo_temp_mount()
diff --git a/blivet/devicetree.py b/blivet/devicetree.py
index 9d79623..c0560bf 100644
--- a/blivet/devicetree.py
+++ b/blivet/devicetree.py
@@ -1313,6 +1313,7 @@ class DeviceTree(object):
         def addLV(lv_name, lv_uuid, lv_attr, lv_size, lv_type):
             lv_class = LVMLogicalVolumeDevice
             lv_parents = [vg_device]
+            lv_kwargs = {}
             name = "%s-%s" % (vg_name, lv_name)
 
             if self.getDeviceByName(name):
@@ -1346,10 +1347,8 @@ class DeviceTree(object):
                                         "found" % (name, origin_device_name))
                             return
 
-                log.debug("adding %s to %s snapshot total"
-                            % (lv_sizes[index], origin.name))
-                origin.snapshotSpace += lv_size
-                return
+                kwargs["origin"] = origin
+                lv_class = LVMSnapShotDevice    # FIXME: thin snapshots?
             elif lv_attr[0] == 'v':
                 # skip vorigins
                 return
@@ -1401,7 +1400,7 @@ class DeviceTree(object):
             if lv_dev is None:
                 lv_device = lv_class(lv_name, parents=lv_parents,
                                      uuid=lv_uuid, size=lv_size,segType=lv_type,
-                                     exists=True)
+                                     exists=True, **lv_kwargs)
                 self._addDevice(lv_device)
                 if flags.installer_mode:
                     lv_device.setup()
-- 
1.8.1.4

