initial rsync-lvm-server script
authorTero Marttila <terom@paivola.fi>
Tue, 14 Feb 2012 14:00:49 +0200
changeset 0 d5fd342015d3
child 1 9211cb493130
initial rsync-lvm-server script
rsync-lvm-server.py
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/rsync-lvm-server.py	Tue Feb 14 14:00:49 2012 +0200
@@ -0,0 +1,156 @@
+#!/usr/bin/python
+
+import subprocess
+import os, os.path
+
+import contextlib
+import logging
+
+logging.basicConfig(
+    format  = '%(processName)s: %(name)s: %(levelname)s %(funcName)s : %(message)s',
+    level   = logging.DEBUG,
+)
+log = logging.getLogger()
+
+LVM                 = '/sbin/lvm'
+LVM_VG              = 'asdf'
+LVM_SNAPSHOT_SIZE   = '5G'
+
+def lvm_name (vg, lv) :
+    """
+        LVM vg/lv name.
+    """
+
+    return '{vg}/{lv}'.format(vg=vg, lv=lv)
+
+def lvm_path (vg, lv) :
+    """
+        Map LVM VG+LV to /dev path.
+    """
+
+    return '/dev/{vg}/{lv}'.format(vg=vg, lv=lv)
+
+def lvm_invoke (cmd, args) :
+    """
+        Invoke LVM command directly.
+
+        Doesn't give any data on stdin, and keeps process stderr.
+        Returns stdout.
+    """
+    
+    log.debug("cmd={cmd}, args={args}".format(cmd=cmd, args=args))
+
+    p = subprocess.Popen([LVM, cmd] + args, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+
+    # get output
+    stdout, stderr = p.communicate(input=None)
+
+    if p.returncode :
+        raise Exception("LVM ({cmd}) failed: {returncode}".format(cmd=cmd, returncode=p.returncode))
+
+    return stdout
+
+def lvm (cmd, *args, **opts) :
+    """
+        Invoke simple LVM command with options/arguments, and no output.
+    """
+
+    log.debug("cmd={cmd}, opts={opts}, args={args}".format(cmd=cmd, args=args, opts=opts))
+
+    # process
+    opts = [('--{opt}'.format(opt=opt), value if value != True else None) for opt, value in opts.iteritems() if value]
+
+    # flatten
+    opts = [str(opt_part) for opt_parts in opts for opt_part in opt_parts if opt_part]
+
+    args = [str(arg) for arg in args if arg]
+
+    # invoke
+    lvm_invoke(cmd, opts + args)
+
+def lvm_snapshot_create (vg, lv, size=LVM_SNAPSHOT_SIZE) :
+    """
+        Create a new LVM snapshot of the given LV.
+        
+        Returns a (snapshot_name, dev_path) tuple.
+    """
+
+    # path to device
+    lv_name = lvm_name(vg, lv)
+    lv_path = lvm_path(vg, lv)
+
+    # snapshot name
+    snap_lv = '{lv}-backup'.format(lv=lv)
+    snap_name = lvm_name(vg, snap_lv)
+    snap_path = lvm_path(vg, snap_lv)
+
+    # verify LV exists
+    lvm('lvs', lv_name)
+    
+    if not os.path.exists(lv_path) :
+        raise Exception("lvm_snapshot: source LV does not exist: {path}".format(path=lv_path))
+
+    if os.path.exists(snap_path) :
+        raise Exception("lvm_snapshot: target LV snapshot already exists: {path}".format(path=snap_path))
+
+    # create
+    lvm('lvcreate', lv_name, snapshot=True, name=snap_lv, size=size)
+
+    # verify
+    if not os.path.exists(snap_path) :
+        raise Exception("lvm_snapshot: target LV snapshot did not appear: {path}".format(path=snap_path))
+
+    # yay
+    return snap_name, snap_path
+
+def lvm_snapshot_remove (name) :
+    """
+        Remove given snapshot volume.
+    """
+
+    # XXX: can't deactivate snapshot volume
+    #lvm('lvchange', name, available='n')
+
+    # XXX: risky!
+    lvm('lvremove', '-f', name)
+
+@contextlib.contextmanager
+def lvm_snapshot (*args, **kwargs) :
+    """
+        A Context Manager for handling an LVM snapshot.
+
+        with lvm_snapshot(vg, lv) as (snapshot_name, snapshot_path) : ...
+    """
+
+    log.debug("creating snapshot: {0}".format(args))
+    name, path = lvm_snapshot_create(*args, **kwargs)
+
+    log.debug("got name={0}, path={1}".format(name, path))
+    yield name, path
+
+    log.debug("cleanup: {0}".format(name))
+    lvm_snapshot_remove(name)
+
+def main (argv) :
+    # XXX: get LV from rsync command
+    lvm_vg='asdf'
+    backup_lv='test'
+
+    # snapshot
+    log.info("Open snapshot...")
+
+    with lvm_snapshot(lvm_vg, backup_lv) as (snapshot_name, snapshot_path):
+        log.info("Snapshot opened: {name}".format(name=snapshot_name))
+
+        # ...
+
+
+        log.info("Done, cleaning up")
+
+    return 1
+
+if __name__ == '__main__' :
+    import sys
+
+    sys.exit(main(sys.argv))
+