import getpass, os, stat, sys, time
from optparse import OptionParser
-import lbs
-# We support up to "LBS Snapshot v0.6" formats, but are also limited by the lbs
+# Automatically set Python path, based on script directory. This should be
+# removed if the tools are properly installed somewhere.
+script_directory = os.path.dirname(sys.argv[0])
+sys.path.append(os.path.join(script_directory, 'python'))
+
+import cumulus
+
+# Compatibility
+lbs = cumulus
+
+# We support up to "LBS Snapshot v0.8" formats, but are also limited by the lbs
# module.
-FORMAT_VERSION = min(lbs.FORMAT_VERSION, (0, 6))
+FORMAT_VERSION = min(lbs.FORMAT_VERSION, (0, 8))
def check_version(format):
ver = lbs.parse_metadata_version(format)
parser = OptionParser(usage="%prog [option]... command [arg]...")
parser.add_option("-v", action="store_true", dest="verbose", default=False,
help="increase verbosity")
+parser.add_option("-n", action="store_true", dest="dry_run", default=False,
+ help="dry run")
parser.add_option("--store", dest="store",
help="specify path to backup data store")
parser.add_option("--localdb", dest="localdb",
# Syntax: $0 --data=DATADIR list-snapshot-sizes
def cmd_list_snapshot_sizes():
lowlevel = lbs.LowlevelDataStore(options.store)
+ lowlevel.scan()
store = lbs.ObjectStore(lowlevel)
previous = set()
for s in sorted(lowlevel.list_snapshots()):
d = lbs.parse_full(store.load_snapshot(s))
check_version(d['Format'])
+
+ try:
+ intent = float(d['Backup-Intent'])
+ except:
+ intent = 1.0
+
segments = d['Segments'].split()
- (size, added, removed) = (0, 0, 0)
+ (size, added, removed, addcount, remcount) = (0, 0, 0, 0, 0)
for seg in segments:
segsize = lowlevel.lowlevel_stat(seg + ".tar.gpg")['size']
size += segsize
- if seg not in previous: added += segsize
+ if seg not in previous:
+ added += segsize
+ addcount += 1
for seg in previous:
if seg not in segments:
removed += lowlevel.lowlevel_stat(seg + ".tar.gpg")['size']
+ remcount += 1
previous = set(segments)
- print "%s: %.3f +%.3f -%.3f" % (s, size / 1024.0**2, added / 1024.0**2, removed / 1024.0**2)
+ print "%s [%s]: %.3f +%.3f -%.3f (+%d/-%d segments)" % (s, intent, size / 1024.0**2, added / 1024.0**2, removed / 1024.0**2, addcount, remcount)
+
+# Search for any files which are not needed by any current snapshots and offer
+# to delete them.
+# Syntax: $0 --store=DATADIR gc
+def cmd_garbage_collect():
+ lowlevel = lbs.LowlevelDataStore(options.store)
+ lowlevel.scan()
+ store = lbs.ObjectStore(lowlevel)
+ snapshots = set(lowlevel.list_snapshots())
+ segments = set()
+ for s in snapshots:
+ d = lbs.parse_full(store.load_snapshot(s))
+ check_version(d['Format'])
+ segments.update(d['Segments'].split())
+
+ referenced = snapshots.union(segments)
+ reclaimed = 0
+ for (t, r) in cumulus.store.type_patterns.items():
+ for f in lowlevel.store.list(t):
+ m = r.match(f)
+ if m is None or m.group(1) not in referenced:
+ print "Garbage:", (t, f)
+ reclaimed += lowlevel.store.stat(t, f)['size']
+ if not options.dry_run:
+ lowlevel.store.delete(t, f)
+ print "Reclaimed space:", reclaimed
# Build checksum list for objects in the given segments, or all segments if
# none are specified.
cmd_read_metadata(args[0])
elif cmd == 'list-snapshot-sizes':
cmd_list_snapshot_sizes()
+elif cmd == 'gc':
+ cmd_garbage_collect()
elif cmd == 'verify-snapshots':
cmd_verify_snapshots(args)
elif cmd == 'restore-snapshot':