[lxc-devel] [lxd/master] Bugfixes and minor features

stgraber on Github lxc-bot at linuxcontainers.org
Sat Feb 27 21:03:45 UTC 2016


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/20160227/3f076825/attachment.bin>
-------------- next part --------------
From 7b184c16f12bec7933109a89f2d97e968da82e23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 27 Feb 2016 15:56:09 -0500
Subject: [PATCH 1/7] tests: Fix failure on networked test
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>
---
 test/suites/remote.sh | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index e5f2107..0c7b37f 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -73,7 +73,7 @@ test_remote_admin() {
 
   # avoid default high port behind some proxies:
   if [ -z "${LXD_OFFLINE:-}" ]; then
-    lxc_remote remote add images images.linuxcontainers.org
+    lxc_remote remote add images1 images.linuxcontainers.org
     lxc_remote remote add images2 images.linuxcontainers.org:443
   fi
 }

From 906e0208bd2d01312482ccd1cb1dccf7dcf940f1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 27 Feb 2016 16:02:08 -0500
Subject: [PATCH 2/7] tests: Fix the number of certs check
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>
---
 test/suites/remote.sh | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/test/suites/remote.sh b/test/suites/remote.sh
index 0c7b37f..ee04ad1 100644
--- a/test/suites/remote.sh
+++ b/test/suites/remote.sh
@@ -65,8 +65,9 @@ test_remote_admin() {
 
   # now re-add under a different alias
   lxc_remote config trust add "${LXD_CONF}/client2.crt"
-  if [ "$(lxc_remote config trust list | wc -l)" -ne 6 ]; then
+  if [ "$(lxc_remote config trust list | wc -l)" -ne 7 ]; then
     echo "wrong number of certs"
+    false
   fi
 
   # Check that we can add domains with valid certs without confirmation:

From cd8d73c9489cb905ecfd3c6c53b1a8e375b6e76e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Feb 2016 23:18:12 -0500
Subject: [PATCH 3/7] Add support for profile descriptions
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>
---
 client.go           |  2 +-
 lxd/db.go           |  3 ++-
 lxd/db_profiles.go  | 61 ++++++++++++++++++++++++++++++++++-------------------
 lxd/db_update.go    | 14 ++++++++++++
 lxd/profiles.go     | 47 +++++++++++++++++++++++------------------
 shared/container.go |  7 +++---
 specs/database.md   |  1 +
 specs/rest-api.md   |  3 +++
 8 files changed, 91 insertions(+), 47 deletions(-)

diff --git a/client.go b/client.go
index c5b1468..4547872 100644
--- a/client.go
+++ b/client.go
@@ -1826,7 +1826,7 @@ func (c *Client) PutProfile(name string, profile shared.ProfileConfig) error {
 	if profile.Name != name {
 		return fmt.Errorf("Cannot change profile name")
 	}
-	body := shared.Jmap{"name": name, "config": profile.Config, "devices": profile.Devices}
+	body := shared.Jmap{"name": name, "description": profile.Description, "config": profile.Config, "devices": profile.Devices}
 	_, err := c.put(fmt.Sprintf("profiles/%s", name), body, Sync)
 	return err
 }
diff --git a/lxd/db.go b/lxd/db.go
index c4f6cf5..a365b0e 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 23
+const DB_CURRENT_VERSION int = 24
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
@@ -127,6 +127,7 @@ CREATE TABLE IF NOT EXISTS images_properties (
 CREATE TABLE IF NOT EXISTS profiles (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
+    description TEXT,
     UNIQUE (name)
 );
 CREATE TABLE IF NOT EXISTS profiles_config (
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 78cfbad..4ab8ef3 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -9,24 +9,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-func dbProfileID(db *sql.DB, profile string) (int64, error) {
-	id := int64(-1)
-
-	rows, err := dbQuery(db, "SELECT id FROM profiles WHERE name=?", profile)
-	if err != nil {
-		return id, err
-	}
-	defer rows.Close()
-
-	for rows.Next() {
-		var xID int64
-		rows.Scan(&xID)
-		id = xID
-	}
-
-	return id, nil
-}
-
 // dbProfiles returns a string list of profiles.
 func dbProfiles(db *sql.DB) ([]string, error) {
 	q := fmt.Sprintf("SELECT name FROM profiles")
@@ -46,14 +28,44 @@ func dbProfiles(db *sql.DB) ([]string, error) {
 	return response, nil
 }
 
-func dbProfileCreate(db *sql.DB, profile string, config map[string]string,
+func dbProfileGet(db *sql.DB, profile string) (int64, *shared.ProfileConfig, error) {
+	id := int64(-1)
+	description := sql.NullString{}
+
+	q := "SELECT id, description FROM profiles WHERE name=?"
+	arg1 := []interface{}{profile}
+	arg2 := []interface{}{&id, &description}
+	err := dbQueryRowScan(db, q, arg1, arg2)
+	if err != nil {
+		return -1, nil, fmt.Errorf("here: %s", err)
+	}
+
+	config, err := dbProfileConfig(db, profile)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	devices, err := dbDevices(db, profile, true)
+	if err != nil {
+		return -1, nil, err
+	}
+
+	return id, &shared.ProfileConfig{
+		Name:        profile,
+		Config:      config,
+		Description: description.String,
+		Devices:     devices,
+	}, nil
+}
+
+func dbProfileCreate(db *sql.DB, profile string, description string, config map[string]string,
 	devices shared.Devices) (int64, error) {
 
 	tx, err := dbBegin(db)
 	if err != nil {
 		return -1, err
 	}
-	result, err := tx.Exec("INSERT INTO profiles (name) VALUES (?)", profile)
+	result, err := tx.Exec("INSERT INTO profiles (name, description) VALUES (?, ?)", profile, description)
 	if err != nil {
 		tx.Rollback()
 		return -1, err
@@ -85,7 +97,7 @@ func dbProfileCreate(db *sql.DB, profile string, config map[string]string,
 }
 
 func dbProfileCreateDefault(db *sql.DB) error {
-	id, err := dbProfileID(db, "default")
+	id, _, err := dbProfileGet(db, "default")
 	if err != nil {
 		return err
 	}
@@ -102,7 +114,7 @@ func dbProfileCreateDefault(db *sql.DB) error {
 			"type":    "nic",
 			"nictype": "bridged",
 			"parent":  "lxcbr0"}}
-	id, err = dbProfileCreate(db, "default", map[string]string{}, devices)
+	id, err = dbProfileCreate(db, "default", "Default LXD profile", map[string]string{}, devices)
 	if err != nil {
 		return err
 	}
@@ -188,6 +200,11 @@ func dbProfileUpdate(db *sql.DB, name string, newName string) error {
 	return err
 }
 
+func dbProfileDescriptionUpdate(tx *sql.Tx, id int64, description string) error {
+	_, err := tx.Exec("UPDATE profiles SET description=? WHERE id=?", description, id)
+	return err
+}
+
 func dbProfileConfigClear(tx *sql.Tx, id int64) error {
 	_, err := tx.Exec("DELETE FROM profiles_config WHERE profile_id=?", id)
 	if err != nil {
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 8f11df8..251e97e 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -15,6 +15,14 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV23(db *sql.DB) error {
+	stmt := `
+ALTER TABLE profiles ADD COLUMN description TEXT;
+INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+	_, err := db.Exec(stmt, 24)
+	return err
+}
+
 func dbUpdateFromV22(db *sql.DB) error {
 	stmt := `
 DELETE FROM containers_devices_config WHERE key='type';
@@ -889,6 +897,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 			return err
 		}
 	}
+	if prevVersion < 24 {
+		err = dbUpdateFromV23(db)
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }
diff --git a/lxd/profiles.go b/lxd/profiles.go
index b37c811..16d563e 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -4,6 +4,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"net/http"
+	"reflect"
 
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
@@ -15,9 +16,10 @@ import (
 
 /* This is used for both profiles post and profile put */
 type profilesPostReq struct {
-	Name    string            `json:"name"`
-	Config  map[string]string `json:"config"`
-	Devices shared.Devices    `json:"devices"`
+	Name        string            `json:"name"`
+	Config      map[string]string `json:"config"`
+	Description string            `json:"description"`
+	Devices     shared.Devices    `json:"devices"`
 }
 
 func profilesGet(d *Daemon, r *http.Request) Response {
@@ -75,7 +77,7 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Update DB entry
-	_, err = dbProfileCreate(d.db, req.Name, req.Config, req.Devices)
+	_, err = dbProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices)
 	if err != nil {
 		return InternalError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
@@ -90,21 +92,8 @@ var profilesCmd = Command{
 	post: profilesPost}
 
 func doProfileGet(d *Daemon, name string) (*shared.ProfileConfig, error) {
-	config, err := dbProfileConfig(d.db, name)
-	if err != nil {
-		return nil, err
-	}
-
-	devices, err := dbDevices(d.db, name, true)
-	if err != nil {
-		return nil, err
-	}
-
-	return &shared.ProfileConfig{
-		Name:    name,
-		Config:  config,
-		Devices: devices,
-	}, nil
+	_, profile, err := dbProfileGet(d.db, name)
+	return profile, err
 }
 
 func profileGet(d *Daemon, r *http.Request) Response {
@@ -168,7 +157,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
 	}
 
 	// Update the database
-	id, err := dbProfileID(d.db, name)
+	id, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
 		return InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
@@ -178,6 +167,24 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
+	if profile.Description != req.Description {
+		err = dbProfileDescriptionUpdate(tx, id, req.Description)
+		if err != nil {
+			tx.Rollback()
+			return InternalError(err)
+		}
+	}
+
+	// Optimize for description-only changes
+	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
+		err = txCommit(tx)
+		if err != nil {
+			return InternalError(err)
+		}
+
+		return EmptySyncResponse
+	}
+
 	err = dbProfileConfigClear(tx, id)
 	if err != nil {
 		tx.Rollback()
diff --git a/shared/container.go b/shared/container.go
index 58bcd8d..0283ba5 100644
--- a/shared/container.go
+++ b/shared/container.go
@@ -115,7 +115,8 @@ const (
 )
 
 type ProfileConfig struct {
-	Name    string            `json:"name"`
-	Config  map[string]string `json:"config"`
-	Devices Devices           `json:"devices"`
+	Name        string            `json:"name"`
+	Config      map[string]string `json:"config"`
+	Description string            `json:"description"`
+	Devices     Devices           `json:"devices"`
 }
diff --git a/specs/database.md b/specs/database.md
index 850d0d9..23bb3fe 100644
--- a/specs/database.md
+++ b/specs/database.md
@@ -225,6 +225,7 @@ Column          | Type          | Default       | Constraint        | Descriptio
 :-----          | :---          | :------       | :---------        | :----------
 id              | INTEGER       | SERIAL        | NOT NULL          | SERIAL
 name            | VARCHAR(255)  | -             | NOT NULL          | Profile name
+description     | TEXT          | -             |                   | Description of the profile
 
 Index: UNIQUE on id AND name
 
diff --git a/specs/rest-api.md b/specs/rest-api.md
index 09fca34..b9ade7d 100644
--- a/specs/rest-api.md
+++ b/specs/rest-api.md
@@ -1402,6 +1402,7 @@ Input:
 
     {
         "name": "my-profilename",
+        "description": "Some description string",
         "config": {
             "limits.memory": "2GB"
         },
@@ -1424,6 +1425,7 @@ Output:
 
     {
         "name": "test",
+        "description": "Some description string",
         "config": {
             "limits.memory": "2GB"
         },
@@ -1447,6 +1449,7 @@ Input:
         "config": {
             "limits.memory": "4GB"
         },
+        "description": "Some description string",
         "devices": {
             "kvm": {
                 "path": "/dev/kvm",

From 72be9ce65a01e5dac7a08718c653ca56c257d0b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 Feb 2016 23:29:32 -0500
Subject: [PATCH 4/7] Add some simplestream aliases
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/simplestreams.go | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/shared/simplestreams.go b/shared/simplestreams.go
index b82a51d..e9843f6 100644
--- a/shared/simplestreams.go
+++ b/shared/simplestreams.go
@@ -150,6 +150,7 @@ func (s *SimpleStreamsManifest) ToLXD() ([]ImageInfo, map[string][][]string) {
 			image.Properties = map[string]string{
 				"os":           product.OperatingSystem,
 				"release":      product.Release,
+				"version":      product.Version,
 				"architecture": product.Architecture,
 				"label":        version.Label,
 				"serial":       name,
@@ -358,6 +359,26 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 			if alias != nil {
 				image.Aliases = append(image.Aliases, *alias)
 			}
+
+			alias = addAlias(fmt.Sprintf("%s/%c", image.Properties["os"], image.Properties["release"][0]), image.Fingerprint)
+			if alias != nil {
+				image.Aliases = append(image.Aliases, *alias)
+			}
+
+			alias = addAlias(fmt.Sprintf("%s/%c/%s", image.Properties["os"], image.Properties["release"][0], image.Properties["serial"]), image.Fingerprint)
+			if alias != nil {
+				image.Aliases = append(image.Aliases, *alias)
+			}
+
+			alias = addAlias(fmt.Sprintf("%s/%s", image.Properties["os"], image.Properties["version"]), image.Fingerprint)
+			if alias != nil {
+				image.Aliases = append(image.Aliases, *alias)
+			}
+
+			alias = addAlias(fmt.Sprintf("%s/%s/%s", image.Properties["os"], image.Properties["version"], image.Properties["serial"]), image.Fingerprint)
+			if alias != nil {
+				image.Aliases = append(image.Aliases, *alias)
+			}
 		}
 
 		// Medium
@@ -366,12 +387,32 @@ func (s *SimpleStreams) applyAliases(images []ImageInfo) ([]ImageInfo, map[strin
 			image.Aliases = append(image.Aliases, *alias)
 		}
 
+		alias = addAlias(fmt.Sprintf("%s/%c/%s", image.Properties["os"], image.Properties["release"][0], image.Properties["architecture"]), image.Fingerprint)
+		if alias != nil {
+			image.Aliases = append(image.Aliases, *alias)
+		}
+
+		alias = addAlias(fmt.Sprintf("%s/%s/%s", image.Properties["os"], image.Properties["version"], image.Properties["architecture"]), image.Fingerprint)
+		if alias != nil {
+			image.Aliases = append(image.Aliases, *alias)
+		}
+
 		// Medium
 		alias = addAlias(fmt.Sprintf("%s/%s/%s/%s", image.Properties["os"], image.Properties["release"], image.Properties["architecture"], image.Properties["serial"]), image.Fingerprint)
 		if alias != nil {
 			image.Aliases = append(image.Aliases, *alias)
 		}
 
+		alias = addAlias(fmt.Sprintf("%s/%c/%s/%s", image.Properties["os"], image.Properties["release"][0], image.Properties["architecture"], image.Properties["serial"]), image.Fingerprint)
+		if alias != nil {
+			image.Aliases = append(image.Aliases, *alias)
+		}
+
+		alias = addAlias(fmt.Sprintf("%s/%s/%s/%s", image.Properties["os"], image.Properties["version"], image.Properties["architecture"], image.Properties["serial"]), image.Fingerprint)
+		if alias != nil {
+			image.Aliases = append(image.Aliases, *alias)
+		}
+
 		newImages = append(newImages, image)
 	}
 

From e31b7c1e8517d4497138adf5501a3fea7ea62746 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 27 Feb 2016 00:54:27 -0500
Subject: [PATCH 5/7] Fix snapshot configuration
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

When snapshotting a container, only its local state should be included.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container_snapshot.go | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 0f333e2..d8cbcd9 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -125,16 +125,15 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		req.Name
 
 	snapshot := func(op *operation) error {
-		config := c.ExpandedConfig()
 		args := containerArgs{
 			Name:         fullName,
 			Ctype:        cTypeSnapshot,
-			Config:       config,
+			Config:       c.LocalConfig(),
 			Profiles:     c.Profiles(),
 			Ephemeral:    c.IsEphemeral(),
-			BaseImage:    config["volatile.base_image"],
+			BaseImage:    c.ExpandedConfig()["volatile.base_image"],
 			Architecture: c.Architecture(),
-			Devices:      c.ExpandedDevices(),
+			Devices:      c.LocalDevices(),
 		}
 
 		_, err := containerCreateAsSnapshot(d, args, c, req.Stateful)

From 4ebe16f929d7c93ffdd0748778d088c63e057d5a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 27 Feb 2016 01:11:45 -0500
Subject: [PATCH 6/7] Don't rely on the filesystem to check if stateful
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Most of our backends don't keep snapshots mounted so we can't look for
state in there, instead lets use the database for that.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/container.go          |  8 +++++---
 lxd/container_lxc.go      | 12 ++++++++++--
 lxd/container_snapshot.go |  5 +++--
 lxd/db.go                 |  3 ++-
 lxd/db_containers.go      | 18 ++++++++++++++----
 lxd/db_update.go          | 14 ++++++++++++++
 specs/database.md         |  1 +
 7 files changed, 49 insertions(+), 12 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index 0abfbd6..70869c3 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -304,6 +304,7 @@ type containerArgs struct {
 	Ephemeral    bool
 	Name         string
 	Profiles     []string
+	Stateful     bool
 }
 
 // The container interface
@@ -345,6 +346,7 @@ type container interface {
 	IsFrozen() bool
 	IsEphemeral() bool
 	IsSnapshot() bool
+	IsStateful() bool
 	IsNesting() bool
 
 	// Hooks
@@ -473,7 +475,7 @@ func containerCreateAsCopy(d *Daemon, args containerArgs, sourceContainer contai
 	return c, nil
 }
 
-func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer container, stateful bool) (container, error) {
+func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer container) (container, error) {
 	// Create the snapshot
 	c, err := containerCreateInternal(d, args)
 	if err != nil {
@@ -481,7 +483,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 	}
 
 	// Deal with state
-	if stateful {
+	if args.Stateful {
 		stateDir := sourceContainer.StatePath()
 		err = os.MkdirAll(stateDir, 0700)
 		if err != nil {
@@ -519,7 +521,7 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 	}
 
 	// Once we're done, remove the state directory
-	if stateful {
+	if args.Stateful {
 		os.RemoveAll(sourceContainer.StatePath())
 	}
 
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b52738c..96c3139 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -97,10 +97,12 @@ func containerLXCCreate(d *Daemon, args containerArgs) (container, error) {
 		ephemeral:    args.Ephemeral,
 		architecture: args.Architecture,
 		cType:        args.Ctype,
+		stateful:     args.Stateful,
 		creationDate: args.CreationDate,
 		profiles:     args.Profiles,
 		localConfig:  args.Config,
-		localDevices: args.Devices}
+		localDevices: args.Devices,
+	}
 
 	// No need to detect storage here, its a new container.
 	c.storage = d.Storage
@@ -196,7 +198,8 @@ func containerLXCLoad(d *Daemon, args containerArgs) (container, error) {
 		creationDate: args.CreationDate,
 		profiles:     args.Profiles,
 		localConfig:  args.Config,
-		localDevices: args.Devices}
+		localDevices: args.Devices,
+		stateful:     args.Stateful}
 
 	// Detect the storage backend
 	s, err := storageForFilename(d, shared.VarPath("containers", strings.Split(c.name, "/")[0]))
@@ -223,6 +226,7 @@ type containerLXC struct {
 	ephemeral    bool
 	id           int
 	name         string
+	stateful     bool
 
 	// Config
 	expandedConfig  map[string]string
@@ -3980,6 +3984,10 @@ func (c *containerLXC) setNetworkLimits(name string, m shared.Device) error {
 }
 
 // Various state query functions
+func (c *containerLXC) IsStateful() bool {
+	return c.stateful
+}
+
 func (c *containerLXC) IsEphemeral() bool {
 	return c.ephemeral
 }
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index d8cbcd9..5d9045b 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -47,7 +47,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 			body := shared.Jmap{
 				"name":       snapName,
 				"created_at": snap.CreationDate(),
-				"stateful":   shared.PathExists(snap.StatePath())}
+				"stateful":   snap.IsStateful()}
 			resultMap = append(resultMap, body)
 		}
 	}
@@ -134,9 +134,10 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 			BaseImage:    c.ExpandedConfig()["volatile.base_image"],
 			Architecture: c.Architecture(),
 			Devices:      c.LocalDevices(),
+			Stateful:     req.Stateful,
 		}
 
-		_, err := containerCreateAsSnapshot(d, args, c, req.Stateful)
+		_, err := containerCreateAsSnapshot(d, args, c)
 		if err != nil {
 			return err
 		}
diff --git a/lxd/db.go b/lxd/db.go
index a365b0e..ce6e8b2 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -34,7 +34,7 @@ type Profile struct {
 // Profiles will contain a list of all Profiles.
 type Profiles []Profile
 
-const DB_CURRENT_VERSION int = 24
+const DB_CURRENT_VERSION int = 25
 
 // CURRENT_SCHEMA contains the current SQLite SQL Schema.
 const CURRENT_SCHEMA string = `
@@ -58,6 +58,7 @@ CREATE TABLE IF NOT EXISTS containers (
     architecture INTEGER NOT NULL,
     type INTEGER NOT NULL,
     ephemeral INTEGER NOT NULL DEFAULT 0,
+    stateful INTEGER NOT NULL DEFAULT 0,
     creation_date DATETIME,
     UNIQUE (name)
 );
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index f7067cd..a6d2e84 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -66,9 +66,10 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 	args.Name = name
 
 	ephemInt := -1
-	q := "SELECT id, architecture, type, ephemeral, creation_date FROM containers WHERE name=?"
+	statefulInt := -1
+	q := "SELECT id, architecture, type, ephemeral, stateful, creation_date FROM containers WHERE name=?"
 	arg1 := []interface{}{name}
-	arg2 := []interface{}{&args.Id, &args.Architecture, &args.Ctype, &ephemInt, &args.CreationDate}
+	arg2 := []interface{}{&args.Id, &args.Architecture, &args.Ctype, &ephemInt, &statefulInt, &args.CreationDate}
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		return args, err
@@ -82,6 +83,10 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 		args.Ephemeral = true
 	}
 
+	if statefulInt == 1 {
+		args.Stateful = true
+	}
+
 	config, err := dbContainerConfig(db, args.Id)
 	if err != nil {
 		return args, err
@@ -124,16 +129,21 @@ func dbContainerCreate(db *sql.DB, args containerArgs) (int, error) {
 		ephemInt = 1
 	}
 
+	statefulInt := 0
+	if args.Stateful == true {
+		statefulInt = 1
+	}
+
 	args.CreationDate = time.Now().UTC()
 
-	str := fmt.Sprintf("INSERT INTO containers (name, architecture, type, ephemeral, creation_date) VALUES (?, ?, ?, ?, ?)")
+	str := fmt.Sprintf("INSERT INTO containers (name, architecture, type, ephemeral, creation_date, stateful) VALUES (?, ?, ?, ?, ?, ?)")
 	stmt, err := tx.Prepare(str)
 	if err != nil {
 		tx.Rollback()
 		return 0, err
 	}
 	defer stmt.Close()
-	result, err := stmt.Exec(args.Name, args.Architecture, args.Ctype, ephemInt, args.CreationDate.Unix())
+	result, err := stmt.Exec(args.Name, args.Architecture, args.Ctype, ephemInt, args.CreationDate.Unix(), statefulInt)
 	if err != nil {
 		tx.Rollback()
 		return 0, err
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 251e97e..a3a1579 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -15,6 +15,14 @@ import (
 	log "gopkg.in/inconshreveable/log15.v2"
 )
 
+func dbUpdateFromV24(db *sql.DB) error {
+	stmt := `
+ALTER TABLE containers ADD COLUMN stateful INTEGER NOT NULL DEFAULT 0;
+INSERT INTO schema (version, updated_at) VALUES (?, strftime("%s"));`
+	_, err := db.Exec(stmt, 25)
+	return err
+}
+
 func dbUpdateFromV23(db *sql.DB) error {
 	stmt := `
 ALTER TABLE profiles ADD COLUMN description TEXT;
@@ -903,6 +911,12 @@ func dbUpdate(d *Daemon, prevVersion int) error {
 			return err
 		}
 	}
+	if prevVersion < 25 {
+		err = dbUpdateFromV24(db)
+		if err != nil {
+			return err
+		}
+	}
 
 	return nil
 }
diff --git a/specs/database.md b/specs/database.md
index 23bb3fe..1c3a703 100644
--- a/specs/database.md
+++ b/specs/database.md
@@ -110,6 +110,7 @@ name            | VARCHAR(255)  | -             | NOT NULL          | Container
 architecture    | INTEGER       | -             | NOT NULL          | Container architecture
 type            | INTEGER       | 0             | NOT NULL          | Container type (0 = container, 1 = container snapshot)
 ephemeral       | INTEGER       | 0             | NOT NULL          | Whether the container is ephemeral (0 = persistent, 1 = ephemeral)
+stateful        | INTEGER       | 0             | NOT NULL          | Whether the snapshot contains state (snapshot only)
 creation\_date  | DATETIME      | -             |                   | Image creation date (user supplied, 0 = unknown)
 
 Index: UNIQUE ON id AND name

From f2a5f999823f0f0746ae8fe46cdded39d8f2542f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Sat, 27 Feb 2016 01:23:44 -0500
Subject: [PATCH 7/7] Catch checkpoint failures
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 | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/lxd/container.go b/lxd/container.go
index 70869c3..34f45b4 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -512,6 +512,10 @@ func containerCreateAsSnapshot(d *Daemon, args containerArgs, sourceContainer co
 		if err2 != nil {
 			shared.Log.Warn("failed to collect criu log file", log.Ctx{"error": err2})
 		}
+
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	// Clone the container


More information about the lxc-devel mailing list