Russell Coker <[email protected]> writes:
>> If you're interested, let me me know and I'll see if I can publish it
>> (it's not my code so I have to ask first.)
>
> Please do that.
OK, here we go. Hopefully it's intelligible. There's no support or
anything, but feel free to send feedback or patches my way, either on-
or off-list as you deem appropriate.
#!/usr/bin/env python
import datetime
import sys
import os
import getopt
import time
(opts, args) = getopt.getopt(sys.argv[1:], "vd:")
if len(args) < 4:
print "Usage: ", sys.argv[0], " [-v] <path> <d> <w> <m>"
print "\tpath: directory containing backups"
print "\td: number of daily backups to keep"
print "\tw: weekly backups"
print "\tm: monthly backups"
sys.exit(1)
path = args[0]
dailies = int(args[1])
weeklies = int(args[2])
monthlies = int(args[3])
# Filenames are all UTC, so do these calculations in UTC too
today_utc = datetime.datetime.utcnow()
today = today_utc.date()
verbose = False
for (o,v) in opts:
if (o == "-v"):
verbose = True
elif (o == "-d"):
# hidden option for testing.
today = datetime.datetime.strptime(v, "%Y-%m-%d").date()
datelist = []
curdate = today
daydiff = datetime.timedelta(days = -1)
for i in range(0, dailies):
datelist.append(curdate)
curdate += daydiff #daydiff should be -ve
# Weeks: back up to Sunday, remove 7 days at a time
wd = today.weekday()
curweek = today - datetime.timedelta(days = wd)
weekdiff = datetime.timedelta(days = 7)
for i in range(0, weeklies):
if curweek not in datelist:
datelist.append(curweek)
curweek -= weekdiff
# Months: count manually
tm = today.month
ty = today.year
for i in range(0, monthlies):
curmonth = datetime.date(ty, tm, 1)
if curmonth not in datelist:
datelist.append(curmonth)
tm -= 1
if tm == 0:
tm = 12
ty -= 1
# Go through the directory, find one file matching each date, delete the rest
dirs = os.listdir(path)
datelist = set(datelist)
keep = {} # actually not useful for anything
delete = {}
# Sample from the python docs
class UTC(datetime.tzinfo):
def utcoffset(self, dt):
return ZERO
def tzname(self, dt):
return "UTC"
def dst(self, dt):
return ZERO
for dir in dirs:
# {{{ Don't use mtime, there's some weirdness on zhug
#t = os.stat(os.path.join(path, dir)).st_mtime # mtime and ctime should be roughly identical
#d = datetime.date.fromtimestamp(t)
# }}}
ts = time.strptime(dir, "%Y-%m-%dT%H:%M:%SZ")
dt = datetime.datetime(ts[0], ts[1], ts[2], ts[3], ts[4], ts[5], ts[6], UTC())
d = dt.date()
if d in datelist:
keep[d] = dir
datelist.remove(d)
else:
# Keep the delete list searchable in case there are missed dates
# This list will be used during deletion, so make sure multiple
# backups on the same date won't mess it up.
# This will break if there are enough backups on the same date
# that they catch up with the real backups
if d in delete:
d = datetime.date.min
while d in delete:
d = d + datetime.timedelta(days = 1)
delete[d] = dir
# If there's anything left in datelist it represents a gap in the backups.
# Search forwards to find a replacement. It's more likely a backup will run
# early than late.
# Helper function: find the lowest element in the list above elem
# list must be sorted
# This could also be implemented as a binary search
def nexthighest(list, elem):
for e in list:
if e > elem:
return e
return None
for remain in datelist:
keys = delete.keys()
keys.sort() # in-place sort, can't inline it
replacement = nexthighest(keys, remain)
if (replacement == None):
# print >> sys.stderr, "No valid backup for ", remain
pass # This is going to be fairly common, keep stdout clean
else:
# If the replacement backup is newer than another backup to be
# kept, forget about it.
# i.e. 2010-05-01 was missing
# 2010-05-30 is the next newest backup to be deleted
# 2010-05-15 is being kept as well
kkeys = keep.keys()
kkeys.sort()
next = nexthighest(kkeys, remain)
if (replacement > next):
# print >> sys.stderr, "No valid backup for ", remain
continue
keep[replacement] = delete[replacement]
del delete[replacement]
if verbose:
keepvs = keep.values()
keepvs.sort()
deletevs = delete.values()
deletevs.sort()
print "Keeping:\n", "\n".join(keepvs)
print "Deleting:\n", "\n".join(deletevs)
else:
print "\n".join(delete.values())
#!/bin/bash
if [ $# -lt 2 ]
then
cat <<EOF
usage: $0 <path> <snapshot>
path The root of the share
snapshot The specific snapshot to remove
'path' will usually end at hostname:path. Snapshot will only be a single
timestamp. Do not include the '.zfs/snapshot/' in between.
EOF
exit 1
fi
. /export/home/cyber/src/zbm/sql/zbm_cfg.sh
psql=/usr/postgres/8.2/bin/psql
logfile=/var/log/zbm_expire.log
path=$1
snapshot=$2
# $1 is base path (up to :share), $2 is the snapshot itself
# Base path for snapshots is $hosted_backup_backups_fs/$client/$source_host:$source_path_colon
# hosted_backup_backups_fs is /tank/hosted-backup/backups/
# so zfs destroy /tank/hosted-backup/backups/$client/$host_blah@timestamp
full_path="${path}/.zfs/snapshot/${snapshot}"
# db path is /tank/hosted-backup/backups/insightsrc/mail.insightsrc.com.au:srv:share/.zfs/snapshot/2010-11-18T14:05:08Z
# Let the FK ON DELETE CASCADE do the hard work
echo "DELETE FROM filesystem_info WHERE path = '${full_path}';" | $psql -A --username "$DBUSER" --host "$DBHOST" --port "$DBPORT" --dbname "$DBNAME" > $logfile
# zfs doesn't want the leading /
zfs_path=`echo $path | sed 's|^/||'`
zfs destroy "${zfs_path}@${snapshot}"
#!/bin/bash
#set -x
expire="/tank/hosted-backup/bin/expire.py"
remove="/tank/hosted-backup/bin/remove.sh"
if [ -n "$1" ]
then
dry_run="true"
fi
for backup in /tank/hosted-backup/config-aging/*
do
#share=`basename "$backup" .pub`
share="$backup"
company=`basename $share | cut -d: -f1`
path=`echo $share | cut -d@ -f2`
dailies=-1
weeklies=-1
monthlies=-1
. $backup
if [ "$dailies" -eq -1 -o "$weeklies" -eq -1 -o "$monthlies" -eq -1 ]
then
true # Error message?
else
share_root=/tank/hosted-backup/backups/$company/$path
full_path="$share_root/.zfs/snapshot"
if [ -n "$dry_run" ]
then
echo "full_path: $full_path company $company path $path"
$expire -v $full_path $dailies $weeklies $monthlies
else
$expire $full_path $dailies $weeklies $monthlies | xargs -L 1 $remove $share_root
fi
fi
done
#!/bin/bash
# Generate a directory with a year of 'snapshots' for testing
if [ ! -d "test" ]
then
mkdir test
fi
cd test
for (( i = 0 ; i < 365 ; i++ ))
do
dn=`date -d "today - $i days" +%Y-%m-%d`
mkdir $dn
touch -d "today - $i days" $dn
done
dailies=14
weeklies=8
monthlies=6
_______________________________________________
luv-main mailing list
[email protected]
http://lists.luv.asn.au/listinfo/luv-main