mirror of
https://github.com/zrepl/zrepl.git
synced 2024-11-22 00:13:52 +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
@ -232,6 +232,7 @@ type SnapshottingPeriodic struct {
|
|||||||
Prefix string `yaml:"prefix"`
|
Prefix string `yaml:"prefix"`
|
||||||
Interval time.Duration `yaml:"interval,positive"`
|
Interval time.Duration `yaml:"interval,positive"`
|
||||||
Hooks HookList `yaml:"hooks,optional"`
|
Hooks HookList `yaml:"hooks,optional"`
|
||||||
|
TimestampFormat string `yaml:"timestamp_format,optional,default=dense"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type CronSpec struct {
|
type CronSpec struct {
|
||||||
@ -264,6 +265,7 @@ type SnapshottingCron struct {
|
|||||||
Prefix string `yaml:"prefix"`
|
Prefix string `yaml:"prefix"`
|
||||||
Cron CronSpec `yaml:"cron"`
|
Cron CronSpec `yaml:"cron"`
|
||||||
Hooks HookList `yaml:"hooks,optional"`
|
Hooks HookList `yaml:"hooks,optional"`
|
||||||
|
TimestampFormat string `yaml:"timestamp_format,optional,default=dense"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type SnapshottingManual struct {
|
type SnapshottingManual struct {
|
||||||
|
@ -35,8 +35,16 @@ jobs:
|
|||||||
snapshotting:
|
snapshotting:
|
||||||
type: periodic
|
type: periodic
|
||||||
prefix: zrepl_
|
prefix: zrepl_
|
||||||
|
timestamp_format: dense
|
||||||
interval: 10m
|
interval: 10m
|
||||||
`
|
`
|
||||||
|
cron := `
|
||||||
|
snapshotting:
|
||||||
|
type: cron
|
||||||
|
prefix: zrepl_
|
||||||
|
timestamp_format: human
|
||||||
|
cron: "10 * * * *"
|
||||||
|
`
|
||||||
|
|
||||||
hooks := `
|
hooks := `
|
||||||
snapshotting:
|
snapshotting:
|
||||||
@ -76,6 +84,15 @@ jobs:
|
|||||||
assert.Equal(t, "periodic", snp.Type)
|
assert.Equal(t, "periodic", snp.Type)
|
||||||
assert.Equal(t, 10*time.Minute, snp.Interval)
|
assert.Equal(t, 10*time.Minute, snp.Interval)
|
||||||
assert.Equal(t, "zrepl_", snp.Prefix)
|
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) {
|
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
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -21,6 +21,7 @@ func cronFromConfig(fsf zfs.DatasetFilter, in config.SnapshottingCron) (*Cron, e
|
|||||||
}
|
}
|
||||||
planArgs := planArgs{
|
planArgs := planArgs{
|
||||||
prefix: in.Prefix,
|
prefix: in.Prefix,
|
||||||
|
timestampFormat: in.TimestampFormat,
|
||||||
hooks: hooksList,
|
hooks: hooksList,
|
||||||
}
|
}
|
||||||
return &Cron{config: in, fsf: fsf, planArgs: planArgs}, nil
|
return &Cron{config: in, fsf: fsf, planArgs: planArgs}, nil
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@ -15,6 +16,7 @@ import (
|
|||||||
|
|
||||||
type planArgs struct {
|
type planArgs struct {
|
||||||
prefix string
|
prefix string
|
||||||
|
timestampFormat string
|
||||||
hooks *hooks.List
|
hooks *hooks.List
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,6 +60,21 @@ type snapProgress struct {
|
|||||||
runResults hooks.PlanReport
|
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) {
|
func (plan *plan) execute(ctx context.Context, dryRun bool) (ok bool) {
|
||||||
|
|
||||||
hookMatchCount := make(map[hooks.Hook]int, len(*plan.args.hooks))
|
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
|
anyFsHadErr := false
|
||||||
// TODO channel programs -> allow a little jitter?
|
// TODO channel programs -> allow a little jitter?
|
||||||
for fs, progress := range plan.snaps {
|
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)
|
snapname := fmt.Sprintf("%s%s", plan.args.prefix, suffix)
|
||||||
|
|
||||||
ctx := logging.WithInjectedField(ctx, "fs", fs.ToString())
|
ctx := logging.WithInjectedField(ctx, "fs", fs.ToString())
|
||||||
|
@ -36,6 +36,7 @@ func periodicFromConfig(g *config.Global, fsf zfs.DatasetFilter, in *config.Snap
|
|||||||
fsf: fsf,
|
fsf: fsf,
|
||||||
planArgs: planArgs{
|
planArgs: planArgs{
|
||||||
prefix: in.Prefix,
|
prefix: in.Prefix,
|
||||||
|
timestampFormat: in.TimestampFormat,
|
||||||
hooks: hookList,
|
hooks: hookList,
|
||||||
},
|
},
|
||||||
// ctx and log is set in Run()
|
// 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 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| :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).
|
* |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.
|
* |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
|
type: periodic
|
||||||
prefix: zrepl_
|
prefix: zrepl_
|
||||||
interval: 10m
|
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: ...
|
hooks: ...
|
||||||
pruning: ...
|
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
|
# (second, optional) minute hour day-of-month month day-of-week
|
||||||
# This example takes snapshots daily at 3:00.
|
# This example takes snapshots daily at 3:00.
|
||||||
cron: "0 3 * * *"
|
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: ...
|
pruning: ...
|
||||||
|
|
||||||
In ``cron`` mode, the snapshotter takes snaphots at fixed points in time.
|
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.
|
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.
|
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
|
``manual`` Snapshotting
|
||||||
-----------------------
|
-----------------------
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user