mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-21 16:03:32 +01:00
snapper: support custom timestamp format
fixes https://github.com/zrepl/zrepl/issues/465 closes https://github.com/zrepl/zrepl/pull/639
This commit is contained in:
parent
6ed4626df9
commit
1da8f848f2
@ -228,10 +228,11 @@ type SnapshottingEnum struct {
|
||||
}
|
||||
|
||||
type SnapshottingPeriodic struct {
|
||||
Type string `yaml:"type"`
|
||||
Prefix string `yaml:"prefix"`
|
||||
Interval time.Duration `yaml:"interval,positive"`
|
||||
Hooks HookList `yaml:"hooks,optional"`
|
||||
Type string `yaml:"type"`
|
||||
Prefix string `yaml:"prefix"`
|
||||
Interval time.Duration `yaml:"interval,positive"`
|
||||
Hooks HookList `yaml:"hooks,optional"`
|
||||
TimestampFormat string `yaml:"timestamp_format,optional,default=dense"`
|
||||
}
|
||||
|
||||
type CronSpec struct {
|
||||
@ -260,10 +261,11 @@ func (s *CronSpec) UnmarshalYAML(unmarshal func(v interface{}, not_strict bool)
|
||||
}
|
||||
|
||||
type SnapshottingCron struct {
|
||||
Type string `yaml:"type"`
|
||||
Prefix string `yaml:"prefix"`
|
||||
Cron CronSpec `yaml:"cron"`
|
||||
Hooks HookList `yaml:"hooks,optional"`
|
||||
Type string `yaml:"type"`
|
||||
Prefix string `yaml:"prefix"`
|
||||
Cron CronSpec `yaml:"cron"`
|
||||
Hooks HookList `yaml:"hooks,optional"`
|
||||
TimestampFormat string `yaml:"timestamp_format,optional,default=dense"`
|
||||
}
|
||||
|
||||
type SnapshottingManual struct {
|
||||
|
@ -35,8 +35,16 @@ jobs:
|
||||
snapshotting:
|
||||
type: periodic
|
||||
prefix: zrepl_
|
||||
timestamp_format: dense
|
||||
interval: 10m
|
||||
`
|
||||
cron := `
|
||||
snapshotting:
|
||||
type: cron
|
||||
prefix: zrepl_
|
||||
timestamp_format: human
|
||||
cron: "10 * * * *"
|
||||
`
|
||||
|
||||
hooks := `
|
||||
snapshotting:
|
||||
@ -76,6 +84,15 @@ jobs:
|
||||
assert.Equal(t, "periodic", snp.Type)
|
||||
assert.Equal(t, 10*time.Minute, snp.Interval)
|
||||
assert.Equal(t, "zrepl_", snp.Prefix)
|
||||
assert.Equal(t, "dense", snp.TimestampFormat)
|
||||
})
|
||||
|
||||
t.Run("cron", func(t *testing.T) {
|
||||
c = testValidConfig(t, fillSnapshotting(cron))
|
||||
snp := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingCron)
|
||||
assert.Equal(t, "cron", snp.Type)
|
||||
assert.Equal(t, "zrepl_", snp.Prefix)
|
||||
assert.Equal(t, "human", snp.TimestampFormat)
|
||||
})
|
||||
|
||||
t.Run("hooks", func(t *testing.T) {
|
||||
@ -88,3 +105,57 @@ jobs:
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestSnapshottingTimestampDefaults(t *testing.T) {
|
||||
tmpl := `
|
||||
jobs:
|
||||
- name: foo
|
||||
type: push
|
||||
connect:
|
||||
type: local
|
||||
listener_name: foo
|
||||
client_identity: bar
|
||||
filesystems: {"<": true}
|
||||
%s
|
||||
pruning:
|
||||
keep_sender:
|
||||
- type: last_n
|
||||
count: 10
|
||||
keep_receiver:
|
||||
- type: last_n
|
||||
count: 10
|
||||
`
|
||||
|
||||
periodic := `
|
||||
snapshotting:
|
||||
type: periodic
|
||||
prefix: zrepl_
|
||||
interval: 10m
|
||||
`
|
||||
cron := `
|
||||
snapshotting:
|
||||
type: cron
|
||||
prefix: zrepl_
|
||||
cron: "10 * * * *"
|
||||
`
|
||||
|
||||
fillSnapshotting := func(s string) string { return fmt.Sprintf(tmpl, s) }
|
||||
var c *Config
|
||||
|
||||
t.Run("periodic", func(t *testing.T) {
|
||||
c = testValidConfig(t, fillSnapshotting(periodic))
|
||||
snp := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingPeriodic)
|
||||
assert.Equal(t, "periodic", snp.Type)
|
||||
assert.Equal(t, 10*time.Minute, snp.Interval)
|
||||
assert.Equal(t, "zrepl_", snp.Prefix)
|
||||
assert.Equal(t, "dense", snp.TimestampFormat) // default was set correctly
|
||||
})
|
||||
|
||||
t.Run("cron", func(t *testing.T) {
|
||||
c = testValidConfig(t, fillSnapshotting(cron))
|
||||
snp := c.Jobs[0].Ret.(*PushJob).Snapshotting.Ret.(*SnapshottingCron)
|
||||
assert.Equal(t, "cron", snp.Type)
|
||||
assert.Equal(t, "zrepl_", snp.Prefix)
|
||||
assert.Equal(t, "dense", snp.TimestampFormat) // default was set correctly
|
||||
})
|
||||
}
|
||||
|
@ -20,8 +20,9 @@ func cronFromConfig(fsf zfs.DatasetFilter, in config.SnapshottingCron) (*Cron, e
|
||||
return nil, errors.Wrap(err, "hook config error")
|
||||
}
|
||||
planArgs := planArgs{
|
||||
prefix: in.Prefix,
|
||||
hooks: hooksList,
|
||||
prefix: in.Prefix,
|
||||
timestampFormat: in.TimestampFormat,
|
||||
hooks: hooksList,
|
||||
}
|
||||
return &Cron{config: in, fsf: fsf, planArgs: planArgs}, nil
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -14,8 +15,9 @@ import (
|
||||
)
|
||||
|
||||
type planArgs struct {
|
||||
prefix string
|
||||
hooks *hooks.List
|
||||
prefix string
|
||||
timestampFormat string
|
||||
hooks *hooks.List
|
||||
}
|
||||
|
||||
type plan struct {
|
||||
@ -58,6 +60,21 @@ type snapProgress struct {
|
||||
runResults hooks.PlanReport
|
||||
}
|
||||
|
||||
func (plan *plan) formatNow(format string) string {
|
||||
now := time.Now().UTC()
|
||||
switch strings.ToLower(format) {
|
||||
case "dense":
|
||||
format = "20060102_150405_000"
|
||||
case "human":
|
||||
format = "2006-01-02_15:04:05"
|
||||
case "iso-8601":
|
||||
format = "2006-01-02T15:04:05.000Z"
|
||||
case "unix-seconds":
|
||||
return strconv.FormatInt(now.Unix(), 10)
|
||||
}
|
||||
return now.Format(format)
|
||||
}
|
||||
|
||||
func (plan *plan) execute(ctx context.Context, dryRun bool) (ok bool) {
|
||||
|
||||
hookMatchCount := make(map[hooks.Hook]int, len(*plan.args.hooks))
|
||||
@ -68,7 +85,7 @@ func (plan *plan) execute(ctx context.Context, dryRun bool) (ok bool) {
|
||||
anyFsHadErr := false
|
||||
// TODO channel programs -> allow a little jitter?
|
||||
for fs, progress := range plan.snaps {
|
||||
suffix := time.Now().In(time.UTC).Format("20060102_150405_000")
|
||||
suffix := plan.formatNow(plan.args.timestampFormat)
|
||||
snapname := fmt.Sprintf("%s%s", plan.args.prefix, suffix)
|
||||
|
||||
ctx := logging.WithInjectedField(ctx, "fs", fs.ToString())
|
||||
|
@ -35,8 +35,9 @@ func periodicFromConfig(g *config.Global, fsf zfs.DatasetFilter, in *config.Snap
|
||||
interval: in.Interval,
|
||||
fsf: fsf,
|
||||
planArgs: planArgs{
|
||||
prefix: in.Prefix,
|
||||
hooks: hookList,
|
||||
prefix: in.Prefix,
|
||||
timestampFormat: in.TimestampFormat,
|
||||
hooks: hookList,
|
||||
},
|
||||
// ctx and log is set in Run()
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ Developers should consult the git commit log or GitHub issue tracker.
|
||||
* `Feature Wishlist on GitHub <https://github.com/zrepl/zrepl/discussions/547>`_
|
||||
|
||||
* |feature| :ref:`Schedule-based snapshotting<job-snapshotting--cron>` using ``cron`` syntax instead of an interval.
|
||||
* |feature| Configurable timestamp format for snapshot names via :ref:`timestamp_format<job-snapshotting-timestamp_format>`.
|
||||
* |feature| Add ``ZREPL_DESTROY_MAX_BATCH_SIZE`` env var (default 0=unlimited).
|
||||
* |bugfix| Fix resuming from interrupted replications that use ``send.raw`` on unencrypted datasets.
|
||||
|
||||
|
@ -62,6 +62,9 @@ The ``periodic`` and ``cron`` snapshotting types share some common options and b
|
||||
type: periodic
|
||||
prefix: zrepl_
|
||||
interval: 10m
|
||||
# Timestamp format that is used as snapshot suffix.
|
||||
# Can be any of "dense" (default), "human", "iso-8601", "unix-seconds" or a custom Go time format (see https://go.dev/src/time/format.go)
|
||||
timestamp_format: dense
|
||||
hooks: ...
|
||||
pruning: ...
|
||||
|
||||
@ -91,6 +94,9 @@ The snapshotter uses the ``prefix`` to identify which snapshots it created.
|
||||
# (second, optional) minute hour day-of-month month day-of-week
|
||||
# This example takes snapshots daily at 3:00.
|
||||
cron: "0 3 * * *"
|
||||
# Timestamp format that is used as snapshot suffix.
|
||||
# Can be any of "dense" (default), "human", "iso-8601", "unix-seconds" or a custom Go time format (see https://go.dev/src/time/format.go)
|
||||
timestamp_format: dense
|
||||
pruning: ...
|
||||
|
||||
In ``cron`` mode, the snapshotter takes snaphots at fixed points in time.
|
||||
@ -98,6 +104,21 @@ See https://en.wikipedia.org/wiki/Cron for details on the syntax.
|
||||
zrepl uses the ``the github.com/robfig/cron/v3`` Go package for parsing.
|
||||
An optional field for "seconds" is supported to take snapshots at sub-minute frequencies.
|
||||
|
||||
.. _job-snapshotting-timestamp_format:
|
||||
|
||||
Timestamp Format
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
The ``cron`` and ``periodic`` snapshotter support configuring a custom timestamp format that is used as suffix for the snapshot name.
|
||||
It can be used by setting ``timestamp_format`` to any of the following values:
|
||||
|
||||
* ``dense`` (default) looks like ``20060102_150405_000``
|
||||
* ``human`` looks like ``2006-01-02_15:04:05``
|
||||
* ``iso-8601`` looks like ``2006-01-02T15:04:05.000Z``
|
||||
* ``unix-seconds`` looks like ``1136214245``
|
||||
* Any custom Go time format accepted by `time.Time#Format <https://go.dev/src/time/format.go>`_.
|
||||
|
||||
|
||||
``manual`` Snapshotting
|
||||
-----------------------
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user