Version 0.2

This commit is contained in:
Tim Foster 2008-06-29 18:31:10 +01:00
commit 5646d04b4a
4 changed files with 599 additions and 0 deletions

233
lib/svc/method/zfs-auto-snapshot Executable file
View File

@ -0,0 +1,233 @@
#!/usr/bin/ksh
#
# Copyright 2004 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
. /lib/svc/share/smf_include.sh
result=$SMF_EXIT_OK
# this function validates the properties in the FMRI passed to it, then
# calls a function to create cron job to schedule a snapshot based on them.
# $1 is assumed to be a valid FMRI
function schedule_snapshots {
FMRI=$1
# FIXME need work in here to actually validate the FMRI props
FILESYS=$(svcprop -p zfs/fs-name $FMRI)
INTERVAL=$(svcprop -p zfs/interval $FMRI)
PERIOD=$(svcprop -p zfs/period $FMRI)
OFFSET=$(svcprop -p zfs/offset $FMRI)
# for now, we're forcing the offset to be 0 seconds.
OFFSET=0
echo $(id)
# validate the filesystem
zfs list $FILESYS 2>&1 1> /dev/null
if [ $? -ne 0 ]
then
echo "ERROR: ZFS filesystem in instance $FMRI does not exist"
return 1
fi
# remove anything that's there at the moment
unschedule_snapshots $FMRI
add_cron_job $INTERVAL $PERIOD $OFFSET $FMRI
if [ $? -ne 0 ]
then
echo "Unable to add cron job for $FMRI"
fi
return 0;
}
#
# Adding a cron job that runs exactly every x time-intervals is hard to do
# properly.
#
# For now, what I'm doing, is dividing the interval up into x bite chunks
# and running the cron job that often. The problem comes where the interval
# doesn't evenly divide up into x, leaving us taking to many, or too
# few snapshots at the edge of time intervals.
#
# A new implementation of cron would be nice, but until then, we'll
# just live with this.
#
function add_cron_job { # $INTERVAL $PERIOD $OFFSET $FMRI
case $INTERVAL in
'minutes')
TIMES=$(get_divisor 0 59 $PERIOD)
ENTRY="$TIMES * * * *"
;;
'hours')
TIMES=$(get_divisor 0 23 $PERIOD)
ENTRY="0 $TIMES * * *"
;;
'days')
TIMES=$(get_divisor 1 31 $PERIOD)
ENTRY="0 0 $TIMES * *"
;;
'months')
TIMES=$(get_divisor 1 12 $PERIOD)
ENTRY="0 0 1 $TIMES *"
;;
esac
# adding a cron job is essentially just looking for an existing entry,
# removing it, and appending a new one. Neato.
crontab -l | grep -v "/lib/svc/method/zfs-auto-snapshot $FMRI$" > /tmp/saved-crontab.$$
echo "${ENTRY} /lib/svc/method/zfs-auto-snapshot $FMRI" >> /tmp/saved-crontab.$$
crontab /tmp/saved-crontab.$$
rm /tmp/saved-crontab.$$
return $?
}
# this function removes a cron job was taking snapshots of $FMRI
# $1 is assumed to be a valid FMRI
function unschedule_snapshots {
FMRI=$1
# need work in here to remove the cron job
crontab -l | grep -v "/lib/svc/method/zfs-auto-snapshot $FMRI$" > /tmp/saved-crontab.$$
crontab /tmp/saved-crontab.$$
rm /tmp/saved-crontab.$$
return 0;
}
# this function actually takes the snapshot of the filesystem. This is what
# really does the work. We name snapshots based on a standard time format
# $1 is assumed to be a valid FMRI
function take_snapshot {
FMRI=$1
DATE=$(date +%F-%H:%M:%S)
SNAPNAME="zfs-auto-snap-${DATE}"
FILESYS=$(svcprop -p zfs/fs-name $FMRI)
KEEP=$(svcprop -p zfs/keep $FMRI)
SNAP_CHILDREN=$(svcprop -p zfs/snapshot-children $FMRI)
if [ "${KEEP}" != "all" ]
then
# count snapshots of this FS to see if we need to delete old ones
NUM_SNAPS=$(zfs list -H -t snapshot | grep "$FILESYS@zfs-auto-snap" | wc -l)
if [ "${NUM_SNAPS}" -ge "${KEEP}" ]
then
echo "Deleting snapshots for $FILESYS@zfs-auto-snap is not yet supported"
# FIXME : destroy oldest snapshot
# this is not yet implemented, as I'm waiting for Sarah's
# zfs -s, to allow me to sort snapshots by creation date,
# and then delete the oldest (tail -1)..
fi
fi
# Ok, now say cheese! It'd be nice if the child snapshotting was
# atomic, but we don't yet have that in zfs.
if [ "${SNAP_CHILDREN}" = "true" ]
then
for child in $(zfs list -r -H -o name -t filesystem $FILESYS)
do
zfs snapshot $child@$SNAPNAME
done
else
zfs snapshot $FILESYS@$SNAPNAME
fi
}
# Given a range start, end and width of period, return a comma
# separated string of numbers within that range and conforming to
# that period. This isn't ideal, but it'll do
#
function get_divisor { # start period, end period, width of period
START=$1
END=$2
WIDTH=$3
RANGE=$START
JUMP=$(( $RANGE + $WIDTH ))
while [ $JUMP -lt $END ]
do
RANGE="$RANGE,$JUMP"
JUMP=$(( $JUMP + $WIDTH ))
done
echo $RANGE
}
# Here's the beginning of the main script. As we're a method script for SMF,
# we take start and stop arguments, and assume that the $SMF_FMRI value is being
# set. For start and stop, our task is to create a cron job that will take a
# snapshot of the specified filesystem.
#
# Without start | stop arguments, we assume we're being called from the cron job
# created above, where the argument is the FMRI containing properties we can
# consult to in order to actually take the snapshot.
# $1 start | stop | an FMRI that we want to take snapshots of.
case "$1" in
'start')
schedule_snapshots $SMF_FMRI
if [ $? -eq 0 ]
then
result=$SMF_EXIT_OK
else
echo "Uhho, something went wrong"
result=$SMF_EXIT_ERR_FATAL
fi
;;
'stop')
unschedule_snapshots $SMF_FMRI
if [ $? -eq 0 ]
then
result=$SMF_EXIT_OK
else
echo "Uhho something went wrong"
result=$SMF_EXIT_ERR_FATAL
fi
;;
# the default case, we actually call from the cron job itself that's
# executing this script.
*)
SMF_FMRI=$1
# are we being called with the correct argument (an FMRI) ?
echo $SMF_FMRI | grep "^svc:/"
if [ $? -eq 0 ]
then
take_snapshot $SMF_FMRI
if [ $? -eq 0 ]
then
result=$SMF_EXIT_OK
else
result=$SMF_EXIT_ERR_FATAL
fi
else
echo "Usage from SMF : zfs-auto-snapshot [start | stop]"
echo "Usage from cron: zfs-auto-snapshot svc:/system/filesystem/zfs/auto-snapshot:instance"
fi
;;
esac
exit $result

View File

@ -0,0 +1,47 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type='manifest' name='space-timf'>
<service
name='system/filesystem/zfs/auto-snapshot'
type='service'
version='1'>
<create_default_instance enabled='false' />
<instance name='space-timf' enabled='false' >
<exec_method
type='method'
name='start'
exec='/lib/svc/method/zfs-auto-snapshot start'
timeout_seconds='10' />
<exec_method
type='method'
name='stop'
exec='/lib/svc/method/zfs-auto-snapshot stop'
timeout_seconds='10' />
<property_group name='startd' type='framework'>
<propval name='duration' type='astring' value='transient' />
</property_group>
<property_group name="zfs" type="application">
<propval name="fs-name" type="astring" value="space/timf"
override="true"/>
<propval name="interval" type="astring" value="minutes"
override="true"/>
<propval name="period" type="astring" value="30"
override="true"/>
<propval name="offset" type="astring" value="0"
override="true"/>
<propval name="keep" type="astring" value="all"
override="true"/>
<propval name="snapshot-children" type="boolean" value="false"
override="true"/>
</property_group>
</instance>
<stability value='Unstable' />
</service>
</service_bundle>

195
zfs-auto-snapshot-admin.sh Executable file
View File

@ -0,0 +1,195 @@
#!/bin/ksh
#
# Copyright 2006 Sun Microsystems, Inc. All rights reserved.
# Use is subject to license terms.
#
#
# This script implements a simple wizard to schedule the taking of regular
# snapshots of this file system. Most of the interesting stuff is at the bottom.
#
MAIN_TITLE="Take regular ZFS snapshots"
function get_interval {
# get an interval to take snapshots at
TITLE="${MAIN_TITLE}: Time period"
TEXT="Choose a time period for taking snapshots"
INTERVAL=$(zenity --list --title="${TITLE}" --text="${TEXT}" \
--radiolist --column="select" \
--column="interval" x "minutes" x "hours" x "days" x "months")
if [ $? -eq 1 ]
then
exit 1;
fi
case $INTERVAL in
'minutes')
MAX_VAL=60
;;
'hours')
MAX_VAL=24
;;
'days')
MAX_VAL=31
;;
'months')
MAX_VAL=12
;;
esac
}
function get_period {
# work out the period we want between snapshots
TITLE="${MAIN_TITLE}: Interval"
TEXT="Choose how often you want to take snapshots (eg. every 10 ${INTERVAL})"
PERIOD=$(zenity --scale --title="${TITLE}" --text="${TEXT}" \
--min-value=1 --max-value=${MAX_VAL} --value=10)
if [ $? -eq 1 ]
then
exit 1;
fi
}
function get_maxsnap {
# choose a number of snapshots to save
TITLE="${MAIN_TITLE}: Number to save"
TEXT="Choose a maximum number of snapshots to keep, Cancel disables the limit\n\
\n\
(Note: once you hit this number of snapshots, the oldest will be\n\
automatically deleted to make room)"
KEEP_SNAP=$(zenity --scale --title="${TITLE}" \
--text="${TEXT}" --value=1 --min-value=0 --max-value=100)
if [ $? -eq 1 ]
then
KEEP_SNAP="all"
fi
}
function get_snap_children {
# decide if we want to snapshot children of this filesystem
TITLE="${MAIN_TITLE}: Snapshot recursively"
TEXT="Do you want to automatically snapshot all children of this filesystem ?"
SNAP_CHILDREN=true
$(zenity --question --text="$TEXT")
if [ $? -eq 1 ]
then
SNAP_CHILDREN=false
fi
}
function show_summary {
# let's give the user a summary of what we've done:
echo "SMF instance built to take snapshots using variables:"
echo "interval=$INTERVAL"
echo "period=$PERIOD"
echo "keep_num=$KEEP_SNAP"
echo "recurse=$SNAP_CHILDREN"
TITLE="${MAIN_TITLE}: Summary"
TEXT="The following snapshot schedule will be created :\n\n\
Filesystem = ${FILESYS}\n\
Interval = ${INTERVAL}\n\
Period = ${PERIOD}\n\
Keep snapshots = ${KEEP_SNAP}\n\
Snapshot Children = ${SNAP_CHILDREN}\n\n\
Do you want to write this auto-snapshot manifest now ?"
zenity --question --title="${TITLE}" --text="${TEXT}"
if [ $? -eq 1 ]
then
exit 1;
fi
}
## Functions out of the way, we can start the wizard properly
if [ "$#" != 1 ]
then
echo "Usage: zfs-auto-snapshot-admin.sh [zfs filesystem name]"
exit 1;
fi
FILESYS=$1
#zfs list $FILESYS 2>&1 1> /dev/null
if [ $? -ne 0 ]
then
echo "Unable to see filesystem $1. Exiting now."
exit 1;
fi
get_interval
get_period
get_maxsnap
get_snap_children
show_summary
ESCAPED_NAME=$(echo $1 | sed -e 's#/#-#g')
# Now we can build an SMF manifest to perform these actions...
cat > auto-snapshot-instance.xml <<EOF
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<service_bundle type='manifest' name='$ESCAPED_NAME'>
<service
name='system/filesystem/zfs/auto-snapshot'
type='service'
version='1'>
<create_default_instance enabled='false' />
<instance name='$ESCAPED_NAME' enabled='false' >
<exec_method
type='method'
name='start'
exec='/lib/svc/method/zfs-auto-snapshot start'
timeout_seconds='10' />
<exec_method
type='method'
name='stop'
exec='/lib/svc/method/zfs-auto-snapshot stop'
timeout_seconds='10' />
<property_group name='startd' type='framework'>
<propval name='duration' type='astring' value='transient' />
</property_group>
<property_group name="zfs" type="application">
<propval name="fs-name" type="astring" value="$FILESYS"
override="true"/>
<propval name="interval" type="astring" value="$INTERVAL"
override="true"/>
<propval name="period" type="astring" value="$PERIOD"
override="true"/>
<propval name="offset" type="astring" value="0"
override="true"/>
<propval name="keep" type="astring" value="$KEEP_SNAP"
override="true"/>
<propval name="snapshot-children" type="boolean" value="$SNAP_CHILDREN"
override="true"/>
</property_group>
</instance>
<stability value='Unstable' />
</service>
</service_bundle>
EOF
echo "Thanks, now import the SMF manifest, using the command :"
echo ""
echo " # svccfg import auto-snapshot-instance.xml"
echo ""
echo "then issue the command :"
echo " # svcadm enable svc:/system/filesystem/zfs/auto-snapshot:$ESCAPED_NAME"
echo ""
echo "You can see what work will be done by checking your crontab."

124
zfs-auto-snapshot.xml Executable file
View File

@ -0,0 +1,124 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!--
Copyright 2006 Sun Microsystems, Inc. All rights reserved.
Use is subject to license terms.
-->
<service_bundle type='manifest' name='TIMFzfssnap:filesystem-zfs-auto-snapshot'>
<service
name='system/filesystem/zfs/auto-snapshot'
type='service'
version='1'>
<!-- no point in being able to take snapshots if we don't have a fs -->
<dependency
name='fs-local'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri value='svc:/system/filesystem/local' />
</dependency>
<!-- we also need cron -->
<dependency
name="cron"
grouping="require_all"
restart_on="none"
type="service">
<service_fmri value="svc:/system/cron" />
</dependency>
<property_group name='startd' type='framework'>
<propval name='duration' type='astring' value='transient' />
</property_group>
<!-- the properties we expect that any instance will define
they being :
fs-name : the name of the filesystem we want to snapshot
interval : minutes | hours | days | weeks
period : how many (m,d,h,w) do we wait between snapshots
offset : the offset into the time period we want
keep : how many snapshots we should keep, otherwise, we
delete the oldest when we hit this threshold
snapshot-children : whether we should recursively snapshot
all filesystems contained within.
-->
<property_group name="zfs" type="application">
<propval name="fs-name" type="astring" value="Not set" override="true"/>
<propval name="interval" type="astring" value="Not set" override="true"/>
<propval name="offset" type="astring" value="Not set" override="true"/>
<propval name="snapshot-children" type="boolean" value="false" override="true"/>
<propval name="keep" type="astring" value="all" override="true"/>
</property_group>
<!-- we're *not* defining an instance here : the idea is that instances
of this service will be created, one per set of auto-snapshots we want
to take. For reference purposes, here's what such an instance should
look like :
<instance name='tank-timf' enabled='false' >
<exec_method
type='method'
name='start'
exec='/home/timf/zfs-auto-snapshot %m'
timeout_seconds='10' />
<exec_method
type='method'
name='stop'
exec='/home/timf/zfs-auto-snapshot %m'
timeout_seconds='10' />
<property_group name='startd' type='framework'>
<propval name='duration' type='astring' value='transient' />
</property_group>
<property_group name="zfs" type="application">
<propval name="fs-name" type="astring" value="tank/timf"/>
<propval name="interval" type="astring" value="days"/>
<propval name="period" type="astring" value="7"/>
<propval name="offset" type="astring" value="0"/>
<propval name="snapshot-children" type="boolean" value="false"/>
</property_group>
</instance>
-->
<stability value='Unstable' />
<template>
<common_name>
<loctext xml:lang='C'>
ZFS automatic snapshots
</loctext>
</common_name>
<description>
<loctext xml:lang='C'>
This service provides system support for taking
automatic snapshots of ZFS filesystems.
In order to use this service, you must create
instances per set of automatic snapshots you
want to create.
The on starting a service instance, a cron job
corresponding to the properties set in the
instance is created on the host. This cron job
will regularly take snapshots of the specified
ZFS filesystem.
On stopping the service, that cron job is
removed.
</loctext>
</description>
</template>
</service>
</service_bundle>