forked from extern/zfs-auto-snapshot
Version 0.2
This commit is contained in:
commit
5646d04b4a
233
lib/svc/method/zfs-auto-snapshot
Executable file
233
lib/svc/method/zfs-auto-snapshot
Executable 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
|
47
sample-auto-snapshot-instance.xml
Normal file
47
sample-auto-snapshot-instance.xml
Normal 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
195
zfs-auto-snapshot-admin.sh
Executable 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
124
zfs-auto-snapshot.xml
Executable 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>
|
Loading…
Reference in New Issue
Block a user