The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/1674

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 7b184c16f12bec7933109a89f2d97e968da82e23 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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?= <stgra...@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 <stgra...@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
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to