diff --git a/blivet/devicelibs/btrfs.py b/blivet/devicelibs/btrfs.py
index a6b69e3..e179ba4 100644
--- a/blivet/devicelibs/btrfs.py
+++ b/blivet/devicelibs/btrfs.py
@@ -93,7 +93,11 @@ def delete_subvolume(mountpoint, name):
     return btrfs(args)
 
 def create_snapshot(source, dest):
-    pass
+    if not os.path.ismount(source):
+        raise ValueError("source is not a mounted subvolume")
+
+    args = ["subvol", "snapshot", source, dest]
+    return btrfs(args)
 
 def scan_device(path):
     return btrfs(["device", "scan", path])
diff --git a/blivet/devicelibs/lvm.py b/blivet/devicelibs/lvm.py
index cfcc4fb..710a724 100644
--- a/blivet/devicelibs/lvm.py
+++ b/blivet/devicelibs/lvm.py
@@ -340,12 +340,22 @@ def lvorigin(vg_name, lv_name):
 
     return origin
 
-def lvcreate(vg_name, lv_name, size, pvs=[]):
-    args = ["lvcreate"] + \
-            ["-L", "%dm" % size] + \
-            ["-n", lv_name] + \
-            _getConfigArgs() + \
-            [vg_name] + pvs
+def lvmerge(lv_name):
+    args = ["lvconvert", "--merge", lv_name]
+    args += _getConfigArgs() + [lv_name]
+
+    try:
+        lvm(args)
+    except LVMError as msg:
+        raise LVMError("lvmerge failed for %s: %s" % (lv_name, msg))
+
+def lvcreate(vg_name, lv_name, size, pvs=[], snapshot=False):
+    if snapshot:
+    args = ["lvcreate", "-L", "%dm" % size, "-n", lv_name]
+    if snapshot:
+        args.append("-s")
+
+    args += _getConfigArgs() + [vg_name] + pvs
 
     try:
         lvm(args)
diff --git a/blivet/devices.py b/blivet/devices.py
index 28a5a92..e10dc0a 100644
--- a/blivet/devices.py
+++ b/blivet/devices.py
@@ -2621,6 +2621,67 @@ class LVMLogicalVolumeDevice(DMDevice):
             return -1
         return 0
 
+class LVMLSnapShotDevice(LVMLogicalVolumeDevice):
+    """ An LVM snapshot """
+    _type = "lvm snapshot"
+    _resizable = True
+    _packages = ["lvm2"]
+
+    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.
+
+            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
+
+        """
+        # XXX do some checks, mostly that an origin was given
+        # XXX I think snapshotSpace is going to go away
+        # XXX what about stripes, logSize?
+        # XXX percent of what? vg or origin?
+        # XXX TODO: use this class when populating the devicetree
+        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)
+        origin = "%s/%s" % (self.vg.name, self.origin.lvname)
+        lvm.lvcreate(origin, self._name, self.size, snapshot=True)
+
+    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.merge_snapshot("%s/%s" % (self.vg.name, self.lvname))
+
+
 class MDRaidArrayDevice(StorageDevice):
     """ An mdraid (Linux RAID) device. """
     _type = "mdarray"
@@ -4180,8 +4241,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 _destroy(self):
         log_method_call(self, self.name, status=self.status)
@@ -4189,5 +4252,32 @@ class BTRFSSubVolumeDevice(BTRFSDevice):
         mountpoint = self.volume.originalFormat._mountpoint
         if not mountpoint:
             raise RuntimeError("btrfs subvol destroy requires mounted volume")
-        btrfs.delete_subvolume(mountpoint, self.name)
-        self.volume._undo_temp_mount()
+
+        try:
+            btrfs.delete_subvolume(mountpoint, self.name)
+        finally:
+            self.volume._undo_temp_mount()
+
+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()
