Contact
CoCalc Logo Icon
StoreFeaturesDocsShareSupport News AboutSign UpSign In
| Download
Views: 39538
1
###
2
Create rolling snapshots of a given ZFS volume
3
###
4
5
fs = require('fs')
6
async = require('async')
7
winston = require('winston')
8
9
# Set the log level
10
winston.remove(winston.transports.Console)
11
winston.add(winston.transports.Console, {level: 'debug', timestamp:true, colorize:true})
12
13
misc_node = require('smc-util-node/misc_node')
14
misc = require('smc-util/misc')
15
{defaults, required} = misc
16
17
list_snapshots = (filesystem, cb) ->
18
misc_node.execute_code
19
command : 'sudo'
20
args : ['zfs', 'list', '-r', '-H', '-t', 'snapshot', filesystem]
21
cb : (err, output) ->
22
if err
23
cb(err)
24
else
25
v = (misc.split(x)[0].split('@')[1] for x in output.stdout.split('\n') when x.trim())
26
v.sort()
27
cb(undefined, v)
28
29
make_snapshot = (filesystem, snap, cb) ->
30
async.series([
31
(cb) ->
32
misc_node.execute_code
33
command : 'sudo'
34
args : ['zfs', 'snapshot', "#{filesystem}@#{snap}"]
35
cb : cb
36
(cb) ->
37
# read the directory to cause it to be mounted
38
fs.readdir("/#{filesystem}/.zfs/snapshot/#{snap}/", cb)
39
], cb)
40
41
delete_snapshot = (filesystem, snap, cb) ->
42
misc_node.execute_code
43
command : 'sudo'
44
args : ['zfs', 'destroy', "#{filesystem}@#{snap}"]
45
cb : cb
46
47
INTERVALS =
48
five : 5
49
hourly : 60
50
daily : 60*24
51
weekly : 60*24*7
52
monthly : 60*24*7*4
53
54
exports.update_snapshots = (opts) ->
55
opts = defaults opts,
56
filesystem : required
57
five : 12*6 # 6 hours worth of five-minute snapshots to retain
58
hourly : 24*7 # 1 week of hourly snapshots
59
daily : 30 # 1 month of daily snapshots
60
weekly : 8 # 2 months of weekly snapshots
61
monthly : 6 # 6 months of monthly snapshots
62
cb : undefined
63
dbg = (m) -> winston.debug("snapshot('#{opts.filesystem}'): #{m}")
64
dbg()
65
snapshots = undefined
66
to_create = []
67
to_delete = []
68
async.series([
69
(cb) ->
70
dbg("get list of all snapshots")
71
list_snapshots opts.filesystem, (err, x) ->
72
snapshots = x; cb(err)
73
(cb) ->
74
dbg("got #{snapshots.length} snapshots")
75
# determine which snapshots we need to make
76
now = new Date()
77
for name, interval of INTERVALS
78
if opts[name] <= 0 # not making any of these
79
continue
80
# Is there a snapshot with the given name that is within the given
81
# interval of now? If not, make snapshot.
82
v = (s for s in snapshots when misc.endswith(s, '-'+name))
83
if v.length > 0
84
newest = v[v.length-1]
85
t = misc.parse_bup_timestamp(newest)
86
age_m = (now - t)/(60*1000) # age in minutes since snapshot
87
else
88
age_m = 999999999999 # 'infinite'
89
if age_m > interval
90
# will make this snapshot
91
to_create.push("#{misc.to_iso_path(now)}-#{name}")
92
# Are there too many snapshots of the given type? If so, delete them
93
if v.length > opts[name]
94
for s in v.slice(0, v.length - opts[name])
95
to_delete.push(s)
96
cb()
97
(cb) ->
98
dbg("snapshots to make: #{misc.to_json(to_create)}")
99
if to_create.length > 0
100
f = (snap, cb) ->
101
make_snapshot(opts.filesystem, snap, cb)
102
async.map(to_create, f, cb)
103
else
104
cb()
105
(cb) ->
106
dbg("snapshots to delete: #{to_delete}")
107
if to_delete.length > 0
108
f = (snap, cb) ->
109
delete_snapshot(opts.filesystem, snap, cb)
110
async.map(to_delete, f, cb)
111
else
112
cb()
113
], (err) -> opts.cb?(err))
114
115