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