The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/5299
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) ===
From 7f3fac7d14e94a834ce652df514443ceea326dcb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Nov 2018 15:53:56 -0500 Subject: [PATCH 1/4] lxd/snapshots: Always use snap%d as pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- doc/containers.md | 2 +- lxd/container.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/doc/containers.md b/doc/containers.md index a9d0014b32..1ffa09448d 100644 --- a/doc/containers.md +++ b/doc/containers.md @@ -79,7 +79,7 @@ security.syscalls.blacklist\_default | boolean | true | no security.syscalls.whitelist | string | - | no | container\_syscall\_filtering | A '\n' separated list of syscalls to whitelist (mutually exclusive with security.syscalls.blacklist\*) snapshots.schedule | string | - | no | snapshot\_scheduling | Cron expression (`<minute> <hour> <dom> <month> <dow>`) snapshots.schedule.stopped | bool | false | no | snapshot\_scheduling | Controls whether or not stopped containers are to be snapshoted automatically -snapshots.pattern | string | auto-snapshot | no | snapshot\_scheduling | Pongo2 template string which represents the snapshot name +snapshots.pattern | string | snap%d | no | snapshot\_scheduling | Pongo2 template string which represents the snapshot name (used for scheduled snapshots and unnamed snapshots) user.\* | string | - | n/a | - | Free form user key/value storage (can be used in search) The following volatile keys are currently internally used by LXD: diff --git a/lxd/container.go b/lxd/container.go index efab279ddc..5a3a9b48ca 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -1639,7 +1639,7 @@ func autoCreateContainerSnapshots(ctx context.Context, d *Daemon) error { ch := make(chan struct{}) go func() { - snapshotName, err := containerDetermineNextSnapshotName(d, c, "auto-snapshot") + snapshotName, err := containerDetermineNextSnapshotName(d, c, "snap%d") if err != nil { logger.Error("Error retrieving next snapshot name", log.Ctx{"err": err, "container": c}) From 0b47a151216f5b3d9899fbf5149bde8edeaea97c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Nov 2018 15:55:36 -0500 Subject: [PATCH 2/4] lxd/snapshots: Validate pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- shared/container.go | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/shared/container.go b/shared/container.go index 47f2f75a88..cbcd2497cd 100644 --- a/shared/container.go +++ b/shared/container.go @@ -5,6 +5,9 @@ import ( "regexp" "strconv" "strings" + + "github.com/pkg/errors" + "gopkg.in/robfig/cron.v2" ) type ContainerAction string @@ -263,7 +266,22 @@ var KnownContainerConfigKeys = map[string]func(value string) error{ "security.syscalls.blacklist": IsAny, "security.syscalls.whitelist": IsAny, - "snapshots.schedule": IsAny, + "snapshots.schedule": func(value string) error { + if value == "" { + return nil + } + + if len(strings.Split(value, " ")) != 5 { + return fmt.Errorf("Schedule must be of the form: <minute> <hour> <day-of-month> <month> <day-of-week>") + } + + _, err := cron.Parse(fmt.Sprintf("* %s", value)) + if err != nil { + return errors.Wrap(err, "Error parsing schedule") + } + + return nil + }, "snapshots.schedule.stopped": IsBool, "snapshots.pattern": IsAny, From 58ec0b53bb94be59aa72ae2e39416771f2fe199f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Nov 2018 15:56:31 -0500 Subject: [PATCH 3/4] lxd/snapshots: Don't run on startup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/container.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/lxd/container.go b/lxd/container.go index 5a3a9b48ca..9eb91043a4 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -1558,8 +1558,6 @@ func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) { logger.Info("Done creating scheduled container snapshots") } - f(context.Background()) - first := true schedule := func() (time.Duration, error) { interval := time.Minute From d14578dd06122c09b051cbafe8de816a94a1a5f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@ubuntu.com> Date: Wed, 21 Nov 2018 15:56:48 -0500 Subject: [PATCH 4/4] lxd/snapshots: Only spawn operation when needed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Stéphane Graber <stgra...@ubuntu.com> --- lxd/container.go | 127 +++++++++++++++++++++-------------------------- 1 file changed, 56 insertions(+), 71 deletions(-) diff --git a/lxd/container.go b/lxd/container.go index 9eb91043a4..272adf4068 100644 --- a/lxd/container.go +++ b/lxd/container.go @@ -1539,8 +1539,54 @@ func containerCompareSnapshots(source container, target container) ([]container, func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) { f := func(ctx context.Context) { + // Load all local containers + allContainers, err := containerLoadNodeAll(d.State()) + if err != nil { + logger.Error("Failed to load containers for scheduled snapshots", log.Ctx{"err": err}) + } + + // Figure out which need snapshotting (if any) + containers := []container{} + for _, c := range allContainers { + logger.Debugf("considering: %v", c.Name()) + schedule := c.LocalConfig()["snapshots.schedule"] + + if schedule == "" { + logger.Debugf("skipping, empty schedule") + continue + } + + // Extend our schedule to one that is accepted by the used cron parser + sched, err := cron.Parse(fmt.Sprintf("* %s", schedule)) + if err != nil { + logger.Debugf("skipping, parsing error: %v", err) + continue + } + + // Check if it's time to snapshot + now := time.Now() + next := sched.Next(now) + + if now.Add(time.Minute).Before(next) { + logger.Debugf("skipping, %v is after %v", now.Add(time.Minute), next) + continue + } + + // Check if the container is running + if !shared.IsTrue(c.LocalConfig()["snapshots.schedule.stopped"]) && !c.IsRunning() { + logger.Debugf("skipping, container is stopped") + continue + } + + containers = append(containers, c) + } + + if len(containers) == 0 { + return + } + opRun := func(op *operation) error { - return autoCreateContainerSnapshots(ctx, d) + return autoCreateContainerSnapshots(ctx, d, containers) } op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationSnapshotCreate, nil, nil, opRun, nil, nil) @@ -1573,82 +1619,21 @@ func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) { return f, schedule } -func autoCreateContainerSnapshots(ctx context.Context, d *Daemon) error { - containers, err := d.cluster.ContainersNodeList(db.CTypeRegular) - if err != nil { - return errors.Wrap(err, "Unable to retrieve the list of containers") - } - - for _, container := range containers { - cId, err := d.cluster.ContainerID(container) - if err != nil { - return errors.Wrap(err, "Error retrieving container ID") - } - - c, err := containerLoadById(d.State(), cId) - if err != nil { - return errors.Wrap(err, "Error loading container") - } - - schedule := c.LocalConfig()["snapshots.schedule"] - - if schedule == "" || (!shared.IsTrue(c.LocalConfig()["snapshots.schedule.stopped"]) && c.IsRunning()) { - continue - } - - if len(strings.Split(schedule, " ")) != 5 { - logger.Error("Schedule must be of the form: <minute> <hour> <day-of-month> <month> <day-of-week>") - continue - } - - // Extend our schedule to one that is accepted by the used cron parser - sched, err := cron.Parse(fmt.Sprintf("* %s", schedule)) - if err != nil { - logger.Error("Error parsing schedule", log.Ctx{"err": err, - "container": container, "schedule": schedule}) - continue - } - - now := time.Now() - next := sched.Next(now).Format("2006-01-02T15:04") - skip := false - - snapshots, err := c.Snapshots() - if err != nil { - logger.Error("Error retrieving snapshots", log.Ctx{"err": err, - "container": container}) - continue - } - - for _, snap := range snapshots { - // Check whether the container has been snapshotted within (at least) - // the last minute. - if snap.CreationDate().Format("2006-01-02T15:04") == next { - skip = true - break - } - } - - // Skip snapshotting if the container has been snapshotted within (at - // least) the last minute, or it's not time yet. - if skip || now.Format("2006-01-02T15:04") != next { - continue - } - - ch := make(chan struct{}) +func autoCreateContainerSnapshots(ctx context.Context, d *Daemon, containers []container) error { + // Make the snapshots + for _, c := range containers { + ch := make(chan error) go func() { snapshotName, err := containerDetermineNextSnapshotName(d, c, "snap%d") if err != nil { - logger.Error("Error retrieving next snapshot name", log.Ctx{"err": err, - "container": c}) - ch <- struct{}{} + logger.Error("Error retrieving next snapshot name", log.Ctx{"err": err, "container": c}) + ch <- nil return } snapshotName = fmt.Sprintf("%s%s%s", c.Name(), shared.SnapshotDelimiter, snapshotName) args := db.ContainerArgs{ - Project: c.Project(), Architecture: c.Architecture(), Config: c.LocalConfig(), Ctype: db.CTypeSnapshot, @@ -1656,16 +1641,16 @@ func autoCreateContainerSnapshots(ctx context.Context, d *Daemon) error { Ephemeral: c.IsEphemeral(), Name: snapshotName, Profiles: c.Profiles(), + Project: c.Project(), Stateful: false, } _, err = containerCreateAsSnapshot(d.State(), args, c) if err != nil { - logger.Error("Error creating snapshots", log.Ctx{"err": err, - "container": c}) + logger.Error("Error creating snapshots", log.Ctx{"err": err, "container": c}) } - ch <- struct{}{} + ch <- nil }() select { case <-ctx.Done():
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel