[lxc-devel] [lxd/master] Tweak to automated snapshots

stgraber on Github lxc-bot at linuxcontainers.org
Wed Nov 21 21:46:37 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20181121/59c28089/attachment.bin>
-------------- next part --------------
From 7f3fac7d14e94a834ce652df514443ceea326dcb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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?= <stgraber at 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 <stgraber at 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():


More information about the lxc-devel mailing list