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

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) ===
Add a `description` field to containers, networks, storage pools and volumes.
It can be changed  through edit operations.

Closes #2022
From c710a38186c5149d05922b9c194ace1154b0daae Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Wed, 26 Apr 2017 11:05:07 +0200
Subject: [PATCH 1/6] Add description field to networks.

Signed-off-by: Alberto Donato <[email protected]>
---
 doc/rest-api.md        |  1 +
 lxc/network.go         |  3 ++-
 lxd/db.go              |  1 +
 lxd/db_networks.go     | 23 ++++++++++++++++++-----
 lxd/db_update.go       |  6 ++++++
 lxd/networks.go        | 43 ++++++++++++++++++++++++-------------------
 shared/api/network.go  |  3 ++-
 test/suites/network.sh |  6 ++++++
 8 files changed, 60 insertions(+), 26 deletions(-)

diff --git a/doc/rest-api.md b/doc/rest-api.md
index 94a883c..8b92db3 100644
--- a/doc/rest-api.md
+++ b/doc/rest-api.md
@@ -1562,6 +1562,7 @@ Input:
 
     {
         "name": "my-network",
+        "description": "My network",
         "config": {
             "ipv4.address": "none",
             "ipv6.address": "2001:470:b368:4242::1/64",
diff --git a/lxc/network.go b/lxc/network.go
index 920a67e..0fdea24 100644
--- a/lxc/network.go
+++ b/lxc/network.go
@@ -447,7 +447,7 @@ func (c *networkCmd) doNetworkList(config *lxd.Config, args 
[]string) error {
                }
 
                strUsedBy := fmt.Sprintf("%d", len(network.UsedBy))
-               data = append(data, []string{network.Name, network.Type, 
strManaged, strUsedBy})
+               data = append(data, []string{network.Name, network.Type, 
strManaged, network.Description, strUsedBy})
        }
 
        table := tablewriter.NewWriter(os.Stdout)
@@ -458,6 +458,7 @@ func (c *networkCmd) doNetworkList(config *lxd.Config, args 
[]string) error {
                i18n.G("NAME"),
                i18n.G("TYPE"),
                i18n.G("MANAGED"),
+               i18n.G("DESCRIPTION"),
                i18n.G("USED BY")})
        sort.Sort(byName(data))
        table.AppendBulk(data)
diff --git a/lxd/db.go b/lxd/db.go
index 73216c6..b2df92b 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -128,6 +128,7 @@ CREATE TABLE IF NOT EXISTS images_source (
 CREATE TABLE IF NOT EXISTS networks (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
+    description TEXT,
     UNIQUE (name)
 );
 CREATE TABLE IF NOT EXISTS networks_config (
diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 3040617..425edf6 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -29,11 +29,12 @@ func dbNetworks(db *sql.DB) ([]string, error) {
 }
 
 func dbNetworkGet(db *sql.DB, name string) (int64, *api.Network, error) {
+       description := sql.NullString{}
        id := int64(-1)
 
-       q := "SELECT id FROM networks WHERE name=?"
+       q := "SELECT id, description FROM networks WHERE name=?"
        arg1 := []interface{}{name}
-       arg2 := []interface{}{&id}
+       arg2 := []interface{}{&id, &description}
        err := dbQueryRowScan(db, q, arg1, arg2)
        if err != nil {
                return -1, nil, err
@@ -49,6 +50,7 @@ func dbNetworkGet(db *sql.DB, name string) (int64, 
*api.Network, error) {
                Managed: true,
                Type:    "bridge",
        }
+       network.Description = description.String
        network.Config = config
 
        return id, &network, nil
@@ -140,13 +142,13 @@ func dbNetworkConfigGet(db *sql.DB, id int64) 
(map[string]string, error) {
        return config, nil
 }
 
-func dbNetworkCreate(db *sql.DB, name string, config map[string]string) 
(int64, error) {
+func dbNetworkCreate(db *sql.DB, name, description string, config 
map[string]string) (int64, error) {
        tx, err := dbBegin(db)
        if err != nil {
                return -1, err
        }
 
-       result, err := tx.Exec("INSERT INTO networks (name) VALUES (?)", name)
+       result, err := tx.Exec("INSERT INTO networks (name, description) VALUES 
(?, ?)", name, description)
        if err != nil {
                tx.Rollback()
                return -1, err
@@ -172,7 +174,7 @@ func dbNetworkCreate(db *sql.DB, name string, config 
map[string]string) (int64,
        return id, nil
 }
 
-func dbNetworkUpdate(db *sql.DB, name string, config map[string]string) error {
+func dbNetworkUpdate(db *sql.DB, name, description string, config 
map[string]string) error {
        id, _, err := dbNetworkGet(db, name)
        if err != nil {
                return err
@@ -183,6 +185,12 @@ func dbNetworkUpdate(db *sql.DB, name string, config 
map[string]string) error {
                return err
        }
 
+       err = dbNetworkUpdateDescription(tx, id, description)
+       if err != nil {
+               tx.Rollback()
+               return err
+       }
+
        err = dbNetworkConfigClear(tx, id)
        if err != nil {
                tx.Rollback()
@@ -198,6 +206,11 @@ func dbNetworkUpdate(db *sql.DB, name string, config 
map[string]string) error {
        return txCommit(tx)
 }
 
+func dbNetworkUpdateDescription(tx *sql.Tx, id int64, description string) 
error {
+       _, err := tx.Exec("UPDATE networks SET description=? WHERE id=?", 
description, id)
+       return err
+}
+
 func dbNetworkConfigAdd(tx *sql.Tx, id int64, config map[string]string) error {
        str := fmt.Sprintf("INSERT INTO networks_config (network_id, key, 
value) VALUES(?, ?, ?)")
        stmt, err := tx.Prepare(str)
diff --git a/lxd/db_update.go b/lxd/db_update.go
index ee1dc71..72c60d5 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -69,6 +69,7 @@ var dbUpdates = []dbUpdate{
        {version: 33, run: dbUpdateFromV32},
        {version: 34, run: dbUpdateFromV33},
        {version: 35, run: dbUpdateFromV34},
+       {version: 36, run: dbUpdateFromV35},
 }
 
 type dbUpdate struct {
@@ -125,6 +126,11 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV35(currentVersion int, version int, d *Daemon) error {
+       _, err := d.db.Exec("ALTER TABLE networks ADD COLUMN description TEXT;")
+       return err
+}
+
 func dbUpdateFromV34(currentVersion int, version int, d *Daemon) error {
        stmt := `
 CREATE TABLE IF NOT EXISTS storage_pools (
diff --git a/lxd/networks.go b/lxd/networks.go
index 1f54977..cefcb38 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -126,7 +126,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
        }
 
        // Create the database entry
-       _, err = dbNetworkCreate(d.db, req.Name, req.Config)
+       _, err = dbNetworkCreate(d.db, req.Name, req.Description, req.Config)
        if err != nil {
                return InternalError(
                        fmt.Errorf("Error inserting %s into database: %s", 
req.Name, err))
@@ -157,7 +157,7 @@ func networkGet(d *Daemon, r *http.Request) Response {
                return SmartError(err)
        }
 
-       etag := []interface{}{n.Name, n.Managed, n.Type, n.Config}
+       etag := []interface{}{n.Name, n.Description, n.Managed, n.Type, 
n.Config}
 
        return SyncResponseETag(true, &n, etag)
 }
@@ -201,6 +201,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, 
error) {
        } else if dbInfo != nil || 
shared.PathExists(fmt.Sprintf("/sys/class/net/%s/bridge", n.Name)) {
                if dbInfo != nil {
                        n.Managed = true
+                       n.Description = dbInfo.Description
                        n.Config = dbInfo.Config
                }
 
@@ -301,7 +302,7 @@ func networkPut(d *Daemon, r *http.Request) Response {
        }
 
        // Validate the ETag
-       etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, 
dbInfo.Config}
+       etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, 
dbInfo.Description, dbInfo.Config}
 
        err = etagCheck(r, etag)
        if err != nil {
@@ -313,7 +314,7 @@ func networkPut(d *Daemon, r *http.Request) Response {
                return BadRequest(err)
        }
 
-       return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
+       return doNetworkUpdate(d, name, dbInfo.Config, req)
 }
 
 func networkPatch(d *Daemon, r *http.Request) Response {
@@ -326,7 +327,7 @@ func networkPatch(d *Daemon, r *http.Request) Response {
        }
 
        // Validate the ETag
-       etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, 
dbInfo.Config}
+       etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, 
dbInfo.Description, dbInfo.Config}
 
        err = etagCheck(r, etag)
        if err != nil {
@@ -350,20 +351,20 @@ func networkPatch(d *Daemon, r *http.Request) Response {
                }
        }
 
-       return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
+       return doNetworkUpdate(d, name, dbInfo.Config, req)
 }
 
-func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, 
newConfig map[string]string) Response {
+func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req 
api.NetworkPut) Response {
        // Validate the configuration
-       err := networkValidateConfig(name, newConfig)
+       err := networkValidateConfig(name, req.Config)
        if err != nil {
                return BadRequest(err)
        }
 
        // When switching to a fan bridge, auto-detect the underlay
-       if newConfig["bridge.mode"] == "fan" {
-               if newConfig["fan.underlay_subnet"] == "" {
-                       newConfig["fan.underlay_subnet"] = "auto"
+       if req.Config["bridge.mode"] == "fan" {
+               if req.Config["fan.underlay_subnet"] == "" {
+                       req.Config["fan.underlay_subnet"] = "auto"
                }
        }
 
@@ -373,7 +374,7 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig 
map[string]string, newCon
                return NotFound
        }
 
-       err = n.Update(api.NetworkPut{Config: newConfig})
+       err = n.Update(req)
        if err != nil {
                return SmartError(err)
        }
@@ -390,7 +391,7 @@ func networkLoadByName(d *Daemon, name string) (*network, 
error) {
                return nil, err
        }
 
-       n := network{daemon: d, id: id, name: name, config: dbInfo.Config}
+       n := network{daemon: d, id: id, name: name, description: 
dbInfo.Description, config: dbInfo.Config}
 
        return &n, nil
 }
@@ -421,9 +422,10 @@ func networkStartup(d *Daemon) error {
 
 type network struct {
        // Properties
-       daemon *Daemon
-       id     int64
-       name   string
+       daemon      *Daemon
+       id          int64
+       name        string
+       description string
 
        // config
        config map[string]string
@@ -1271,6 +1273,7 @@ func (n *network) Update(newNetwork api.NetworkPut) error 
{
 
        // Backup the current state
        oldConfig := map[string]string{}
+       oldDescription := n.description
        err = shared.DeepCopy(&n.config, &oldConfig)
        if err != nil {
                return err
@@ -1284,6 +1287,7 @@ func (n *network) Update(newNetwork api.NetworkPut) error 
{
        defer func() {
                if undoChanges {
                        n.config = oldConfig
+                       n.description = oldDescription
                }
        }()
 
@@ -1315,7 +1319,7 @@ func (n *network) Update(newNetwork api.NetworkPut) error 
{
        }
 
        // Skip on no change
-       if len(changedConfig) == 0 {
+       if len(changedConfig) == 0 && newNetwork.Description == n.description {
                return nil
        }
 
@@ -1351,11 +1355,12 @@ func (n *network) Update(newNetwork api.NetworkPut) 
error {
                }
        }
 
-       // Apply the new configuration
+       // Apply changes
        n.config = newConfig
+       n.description = newNetwork.Description
 
        // Update the database
-       err = dbNetworkUpdate(n.daemon.db, n.name, n.config)
+       err = dbNetworkUpdate(n.daemon.db, n.name, n.description, n.config)
        if err != nil {
                return err
        }
diff --git a/shared/api/network.go b/shared/api/network.go
index 21db460..ac749d8 100644
--- a/shared/api/network.go
+++ b/shared/api/network.go
@@ -22,7 +22,8 @@ type NetworkPost struct {
 //
 // API extension: network
 type NetworkPut struct {
-       Config map[string]string `json:"config" yaml:"config"`
+       Description string            `json:"description" yaml:"description"`
+       Config      map[string]string `json:"config" yaml:"config"`
 }
 
 // Network represents a LXD network
diff --git a/test/suites/network.sh b/test/suites/network.sh
index 9c849f1..69432cf 100644
--- a/test/suites/network.sh
+++ b/test/suites/network.sh
@@ -13,6 +13,12 @@ test_network() {
   lxc network set lxdt$$ ipv6.dhcp.stateful true
   lxc network delete lxdt$$
 
+  # edit network description
+  lxc network create lxdt$$
+  lxc network show lxdt$$ | sed 's/^description:.*/description: foo/' | lxc 
network edit lxdt$$
+  lxc network show lxdt$$ | grep -q 'description: foo'
+  lxc network delete lxdt$$
+
   # Unconfigured bridge
   lxc network create lxdt$$ ipv4.address=none ipv6.address=none
   lxc network delete lxdt$$

From ddb1aba332e27cc6c481884ef1ad5236b8868cc4 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Thu, 27 Apr 2017 18:36:50 +0200
Subject: [PATCH 2/6] Change image description type to text.

Signed-off-by: Alberto Donato <[email protected]>
---
 lxd/db.go        |  2 +-
 lxd/db_update.go | 21 +++++++++++++++++++++
 2 files changed, 22 insertions(+), 1 deletion(-)

diff --git a/lxd/db.go b/lxd/db.go
index b2df92b..6662552 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -104,7 +104,7 @@ CREATE TABLE IF NOT EXISTS images_aliases (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
     image_id INTEGER NOT NULL,
-    description VARCHAR(255),
+    description TEXT,
     FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
     UNIQUE (name)
 );
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 72c60d5..369f4a2 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -70,6 +70,7 @@ var dbUpdates = []dbUpdate{
        {version: 34, run: dbUpdateFromV33},
        {version: 35, run: dbUpdateFromV34},
        {version: 36, run: dbUpdateFromV35},
+       {version: 37, run: dbUpdateFromV36},
 }
 
 type dbUpdate struct {
@@ -126,6 +127,26 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV36(currentVersion int, version int, d *Daemon) error {
+       stmt := `
+CREATE TABLE tmp (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    image_id INTEGER NOT NULL,
+    description TEXT,
+    FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+    UNIQUE (name)
+);
+INSERT INTO tmp (id, name, image_id, description)
+    SELECT id, name, image_id, description
+    FROM images_aliases;
+DROP TABLE images_aliases;
+ALTER TABLE tmp RENAME TO images_aliases;
+`
+       _, err := d.db.Exec(stmt)
+       return err
+}
+
 func dbUpdateFromV35(currentVersion int, version int, d *Daemon) error {
        _, err := d.db.Exec("ALTER TABLE networks ADD COLUMN description TEXT;")
        return err

From 5c05714de95b70f1aadc689c715725d0723fd0bf Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Fri, 28 Apr 2017 15:54:53 +0200
Subject: [PATCH 3/6] Add description field to storage pools.

Signed-off-by: Alberto Donato <[email protected]>
---
 lxc/storage.go             |  3 ++-
 lxd/api_internal.go        |  2 +-
 lxd/db.go                  |  1 +
 lxd/db_storage_pools.go    | 25 ++++++++++++++++-----
 lxd/db_update.go           |  6 ++++++
 lxd/main_test.go           |  4 +++-
 lxd/patches.go             | 22 +++++++++----------
 lxd/storage_pools.go       |  6 +++---
 lxd/storage_pools_utils.go | 54 ++++++++++++++++++++++++----------------------
 shared/api/storage.go      |  3 ++-
 test/suites/storage.sh     | 11 ++++++++++
 11 files changed, 88 insertions(+), 49 deletions(-)

diff --git a/lxc/storage.go b/lxc/storage.go
index 0899c2e..5f71d6e 100644
--- a/lxc/storage.go
+++ b/lxc/storage.go
@@ -596,7 +596,7 @@ func (c *storageCmd) doStoragePoolsList(config *lxd.Config, 
args []string) error
        for _, pool := range pools {
                usedby := strconv.Itoa(len(pool.UsedBy))
 
-               data = append(data, []string{pool.Name, pool.Driver, 
pool.Config["source"], usedby})
+               data = append(data, []string{pool.Name, pool.Description, 
pool.Driver, pool.Config["source"], usedby})
        }
 
        table := tablewriter.NewWriter(os.Stdout)
@@ -605,6 +605,7 @@ func (c *storageCmd) doStoragePoolsList(config *lxd.Config, 
args []string) error
        table.SetRowLine(true)
        table.SetHeader([]string{
                i18n.G("NAME"),
+               i18n.G("DESCRIPTION"),
                i18n.G("DRIVER"),
                i18n.G("SOURCE"),
                i18n.G("USED BY")})
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 2298d84..11d3655 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -204,7 +204,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 
        if poolErr == NoSuchObjectError {
                // Create the storage pool db entry if it doesn't exist.
-               err := storagePoolDBCreate(d, containerPoolName, 
backup.Pool.Driver, backup.Pool.Config)
+               err := storagePoolDBCreate(d, containerPoolName, 
pool.Description, backup.Pool.Driver, backup.Pool.Config)
                if err != nil {
                        return InternalError(err)
                }
diff --git a/lxd/db.go b/lxd/db.go
index 6662552..1e5d0a2 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -184,6 +184,7 @@ CREATE TABLE IF NOT EXISTS schema (
 CREATE TABLE IF NOT EXISTS storage_pools (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
+    description TEXT,
     driver VARCHAR(255) NOT NULL,
     UNIQUE (name)
 );
diff --git a/lxd/db_storage_pools.go b/lxd/db_storage_pools.go
index a98b51c..f155b8e 100644
--- a/lxd/db_storage_pools.go
+++ b/lxd/db_storage_pools.go
@@ -78,9 +78,11 @@ func dbStoragePoolGetID(db *sql.DB, poolName string) (int64, 
error) {
 func dbStoragePoolGet(db *sql.DB, poolName string) (int64, *api.StoragePool, 
error) {
        var poolDriver string
        poolID := int64(-1)
-       query := "SELECT id, driver FROM storage_pools WHERE name=?"
+       description := sql.NullString{}
+
+       query := "SELECT id, driver, description FROM storage_pools WHERE 
name=?"
        inargs := []interface{}{poolName}
-       outargs := []interface{}{&poolID, &poolDriver}
+       outargs := []interface{}{&poolID, &poolDriver, &description}
 
        err := dbQueryRowScan(db, query, inargs, outargs)
        if err != nil {
@@ -96,6 +98,7 @@ func dbStoragePoolGet(db *sql.DB, poolName string) (int64, 
*api.StoragePool, err
                Name:   poolName,
                Driver: poolDriver,
        }
+       storagePool.Description = description.String
        storagePool.Config = config
 
        return poolID, &storagePool, nil
@@ -126,13 +129,13 @@ func dbStoragePoolConfigGet(db *sql.DB, poolID int64) 
(map[string]string, error)
 }
 
 // Create new storage pool.
-func dbStoragePoolCreate(db *sql.DB, poolName string, poolDriver string, 
poolConfig map[string]string) (int64, error) {
+func dbStoragePoolCreate(db *sql.DB, poolName, poolDescription string, 
poolDriver string, poolConfig map[string]string) (int64, error) {
        tx, err := dbBegin(db)
        if err != nil {
                return -1, err
        }
 
-       result, err := tx.Exec("INSERT INTO storage_pools (name, driver) VALUES 
(?, ?)", poolName, poolDriver)
+       result, err := tx.Exec("INSERT INTO storage_pools (name, description, 
driver) VALUES (?, ?, ?)", poolName, poolDescription, poolDriver)
        if err != nil {
                tx.Rollback()
                return -1, err
@@ -188,7 +191,7 @@ func dbStoragePoolConfigAdd(tx *sql.Tx, poolID int64, 
poolConfig map[string]stri
 }
 
 // Update storage pool.
-func dbStoragePoolUpdate(db *sql.DB, poolName string, poolConfig 
map[string]string) error {
+func dbStoragePoolUpdate(db *sql.DB, poolName, description string, poolConfig 
map[string]string) error {
        poolID, _, err := dbStoragePoolGet(db, poolName)
        if err != nil {
                return err
@@ -199,6 +202,12 @@ func dbStoragePoolUpdate(db *sql.DB, poolName string, 
poolConfig map[string]stri
                return err
        }
 
+       err = dbStoragePoolUpdateDescription(tx, poolID, description)
+       if err != nil {
+               tx.Rollback()
+               return err
+       }
+
        err = dbStoragePoolConfigClear(tx, poolID)
        if err != nil {
                tx.Rollback()
@@ -214,6 +223,12 @@ func dbStoragePoolUpdate(db *sql.DB, poolName string, 
poolConfig map[string]stri
        return txCommit(tx)
 }
 
+// Update the storage pool description.
+func dbStoragePoolUpdateDescription(tx *sql.Tx, id int64, description string) 
error {
+       _, err := tx.Exec("UPDATE storage_pools SET description=? WHERE id=?", 
description, id)
+       return err
+}
+
 // Delete storage pool config.
 func dbStoragePoolConfigClear(tx *sql.Tx, poolID int64) error {
        _, err := tx.Exec("DELETE FROM storage_pools_config WHERE 
storage_pool_id=?", poolID)
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 369f4a2..30fc59b 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -71,6 +71,7 @@ var dbUpdates = []dbUpdate{
        {version: 35, run: dbUpdateFromV34},
        {version: 36, run: dbUpdateFromV35},
        {version: 37, run: dbUpdateFromV36},
+       {version: 38, run: dbUpdateFromV37},
 }
 
 type dbUpdate struct {
@@ -127,6 +128,11 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV37(currentVersion int, version int, d *Daemon) error {
+       _, err := d.db.Exec("ALTER TABLE storage_pools ADD COLUMN description 
TEXT;")
+       return err
+}
+
 func dbUpdateFromV36(currentVersion int, version int, d *Daemon) error {
        stmt := `
 CREATE TABLE tmp (
diff --git a/lxd/main_test.go b/lxd/main_test.go
index a6828c8..af0d88e 100644
--- a/lxd/main_test.go
+++ b/lxd/main_test.go
@@ -2,6 +2,7 @@ package main
 
 import (
        "crypto/tls"
+       "fmt"
        "io/ioutil"
        "os"
        "testing"
@@ -61,7 +62,8 @@ func (suite *lxdTestSuite) SetupSuite() {
 
        mockStorage, _ := storageTypeToString(storageTypeMock)
        // Create the database entry for the storage pool.
-       _, err = dbStoragePoolCreate(suite.d.db, 
lxdTestSuiteDefaultStoragePool, mockStorage, poolConfig)
+       poolDescription := fmt.Sprintf("%s storage pool", 
lxdTestSuiteDefaultStoragePool)
+       _, err = dbStoragePoolCreate(suite.d.db, 
lxdTestSuiteDefaultStoragePool, poolDescription, mockStorage, poolConfig)
        if err != nil {
                os.Exit(1)
        }
diff --git a/lxd/patches.go b/lxd/patches.go
index 7bf8abc..837159c 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -310,12 +310,12 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, 
defaultPoolName string,
                if pool.Config == nil {
                        pool.Config = poolConfig
                }
-               err = dbStoragePoolUpdate(d.db, defaultPoolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, defaultPoolName, "", 
pool.Config)
                if err != nil {
                        return err
                }
        } else if err == NoSuchObjectError { // Likely a pristine upgrade.
-               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, 
defaultStorageTypeName, poolConfig)
+               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, "", 
defaultStorageTypeName, poolConfig)
                if err != nil {
                        return err
                }
@@ -607,12 +607,12 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, 
defaultPoolName string, d
                if pool.Config == nil {
                        pool.Config = poolConfig
                }
-               err = dbStoragePoolUpdate(d.db, defaultPoolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, defaultPoolName, 
pool.Description, pool.Config)
                if err != nil {
                        return err
                }
        } else if err == NoSuchObjectError { // Likely a pristine upgrade.
-               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, 
defaultStorageTypeName, poolConfig)
+               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, "", 
defaultStorageTypeName, poolConfig)
                if err != nil {
                        return err
                }
@@ -900,12 +900,12 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, 
defaultPoolName string, d
                if pool.Config == nil {
                        pool.Config = poolConfig
                }
-               err = dbStoragePoolUpdate(d.db, defaultPoolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, defaultPoolName, 
pool.Description, pool.Config)
                if err != nil {
                        return err
                }
        } else if err == NoSuchObjectError { // Likely a pristine upgrade.
-               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, 
defaultStorageTypeName, poolConfig)
+               tmp, err := dbStoragePoolCreate(d.db, defaultPoolName, "", 
defaultStorageTypeName, poolConfig)
                if err != nil {
                        return err
                }
@@ -1400,7 +1400,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, 
defaultPoolName string, d
                if pool.Config == nil {
                        pool.Config = poolConfig
                }
-               err = dbStoragePoolUpdate(d.db, poolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, poolName, pool.Description, 
pool.Config)
                if err != nil {
                        return err
                }
@@ -1432,7 +1432,7 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, 
defaultPoolName string, d
                }
 
                // (Use a tmp variable as Go's scoping is freaking me out.)
-               tmp, err := dbStoragePoolCreate(d.db, poolName, 
defaultStorageTypeName, poolConfig)
+               tmp, err := dbStoragePoolCreate(d.db, poolName, 
defaultStorageTypeName, "", poolConfig)
                if err != nil {
                        logger.Warnf("Storage pool already exists in the 
database. Proceeding...")
                }
@@ -1929,7 +1929,7 @@ func patchStorageApiKeys(name string, d *Daemon) error {
                }
 
                // Update the config in the database.
-               err = dbStoragePoolUpdate(d.db, poolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, poolName, pool.Description, 
pool.Config)
                if err != nil {
                        return err
                }
@@ -2017,7 +2017,7 @@ func patchStorageApiUpdateStorageConfigs(name string, d 
*Daemon) error {
                }
 
                // Update the storage pool config.
-               err = dbStoragePoolUpdate(d.db, poolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, poolName, pool.Description, 
pool.Config)
                if err != nil {
                        return err
                }
@@ -2134,7 +2134,7 @@ func patchStorageApiLxdOnBtrfs(name string, d *Daemon) 
error {
                pool.Config["source"] = getStoragePoolMountPoint(poolName)
 
                // Update the storage pool config.
-               err = dbStoragePoolUpdate(d.db, poolName, pool.Config)
+               err = dbStoragePoolUpdate(d.db, poolName, pool.Description, 
pool.Config)
                if err != nil {
                        return err
                }
diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index bbd23f0..3ee5fe4 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -75,7 +75,7 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
                return BadRequest(fmt.Errorf("No driver provided"))
        }
 
-       err = storagePoolCreateInternal(d, req.Name, req.Driver, req.Config)
+       err = storagePoolCreateInternal(d, req.Name, req.Description, 
req.Driver, req.Config)
        if err != nil {
                return InternalError(err)
        }
@@ -138,7 +138,7 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
                return BadRequest(err)
        }
 
-       err = storagePoolUpdate(d, poolName, req.Config)
+       err = storagePoolUpdate(d, poolName, req.Description, req.Config)
        if err != nil {
                return InternalError(err)
        }
@@ -188,7 +188,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
                return BadRequest(err)
        }
 
-       err = storagePoolUpdate(d, poolName, req.Config)
+       err = storagePoolUpdate(d, poolName, req.Description, req.Config)
        if err != nil {
                return InternalError(fmt.Errorf("failed to update the storage 
pool configuration"))
        }
diff --git a/lxd/storage_pools_utils.go b/lxd/storage_pools_utils.go
index bcac4d1..95761c4 100644
--- a/lxd/storage_pools_utils.go
+++ b/lxd/storage_pools_utils.go
@@ -9,7 +9,7 @@ import (
        "github.com/lxc/lxd/shared/version"
 )
 
-func storagePoolUpdate(d *Daemon, name string, newConfig map[string]string) 
error {
+func storagePoolUpdate(d *Daemon, name, newDescription string, newConfig 
map[string]string) error {
        s, err := storagePoolInit(d, name)
        if err != nil {
                return err
@@ -19,6 +19,7 @@ func storagePoolUpdate(d *Daemon, name string, newConfig 
map[string]string) erro
        newWritable := oldWritable
 
        // Backup the current state
+       oldDescription := oldWritable.Description
        oldConfig := map[string]string{}
        err = shared.DeepCopy(&oldWritable.Config, &oldConfig)
        if err != nil {
@@ -37,34 +38,35 @@ func storagePoolUpdate(d *Daemon, name string, newConfig 
map[string]string) erro
        }()
 
        changedConfig, userOnly := storageConfigDiff(oldConfig, newConfig)
-       // Skip on no change
-       if len(changedConfig) == 0 {
-               return nil
-       }
-
-       newWritable.Config = newConfig
+       // Apply config changes if there are any
+       if len(changedConfig) != 0 {
+               newWritable.Description = newDescription
+               newWritable.Config = newConfig
+
+               // Update the storage pool
+               if !userOnly {
+                       if shared.StringInSlice("driver", changedConfig) {
+                               return fmt.Errorf("the \"driver\" property of a 
storage pool cannot be changed")
+                       }
 
-       // Update the storage pool
-       if !userOnly {
-               if shared.StringInSlice("driver", changedConfig) {
-                       return fmt.Errorf("the \"driver\" property of a storage 
pool cannot be changed")
+                       err = s.StoragePoolUpdate(&newWritable, changedConfig)
+                       if err != nil {
+                               return err
+                       }
                }
 
-               err = s.StoragePoolUpdate(&newWritable, changedConfig)
+               // Apply the new configuration
+               s.SetStoragePoolWritable(&newWritable)
+       }
+
+       // Update the database if something changed
+       if len(changedConfig) != 0 || newDescription != oldDescription {
+               err = dbStoragePoolUpdate(d.db, name, newDescription, newConfig)
                if err != nil {
                        return err
                }
        }
 
-       // Apply the new configuration
-       s.SetStoragePoolWritable(&newWritable)
-
-       // Update the database
-       err = dbStoragePoolUpdate(d.db, name, newConfig)
-       if err != nil {
-               return err
-       }
-
        // Success, update the closure to mark that the changes should be kept.
        undoChanges = false
 
@@ -153,7 +155,7 @@ func profilesUsingPoolGetNames(db *sql.DB, poolName string) 
([]string, error) {
        return usedBy, nil
 }
 
-func storagePoolDBCreate(d *Daemon, poolName string, driver string, config 
map[string]string) error {
+func storagePoolDBCreate(d *Daemon, poolName, poolDescription string, driver 
string, config map[string]string) error {
        // Check if the storage pool name is valid.
        err := storageValidName(poolName)
        if err != nil {
@@ -184,7 +186,7 @@ func storagePoolDBCreate(d *Daemon, poolName string, driver 
string, config map[s
        }
 
        // Create the database entry for the storage pool.
-       _, err = dbStoragePoolCreate(d.db, poolName, driver, config)
+       _, err = dbStoragePoolCreate(d.db, poolName, poolDescription, driver, 
config)
        if err != nil {
                return fmt.Errorf("Error inserting %s into database: %s", 
poolName, err)
        }
@@ -192,8 +194,8 @@ func storagePoolDBCreate(d *Daemon, poolName string, driver 
string, config map[s
        return nil
 }
 
-func storagePoolCreateInternal(d *Daemon, poolName string, driver string, 
config map[string]string) error {
-       err := storagePoolDBCreate(d, poolName, driver, config)
+func storagePoolCreateInternal(d *Daemon, poolName, poolDescription string, 
driver string, config map[string]string) error {
+       err := storagePoolDBCreate(d, poolName, poolDescription, driver, config)
        if err != nil {
                return err
        }
@@ -235,7 +237,7 @@ func storagePoolCreateInternal(d *Daemon, poolName string, 
driver string, config
        configDiff, _ := storageConfigDiff(config, postCreateConfig)
        if len(configDiff) > 0 {
                // Create the database entry for the storage pool.
-               err = dbStoragePoolUpdate(d.db, poolName, postCreateConfig)
+               err = dbStoragePoolUpdate(d.db, poolName, poolDescription, 
postCreateConfig)
                if err != nil {
                        return fmt.Errorf("Error inserting %s into database: 
%s", poolName, err)
                }
diff --git a/shared/api/storage.go b/shared/api/storage.go
index fac6d85..297b28e 100644
--- a/shared/api/storage.go
+++ b/shared/api/storage.go
@@ -25,7 +25,8 @@ type StoragePool struct {
 //
 // API extension: storage
 type StoragePoolPut struct {
-       Config map[string]string `json:"config" yaml:"config"`
+       Description string            `json:"description" yaml:"description"`
+       Config      map[string]string `json:"config" yaml:"config"`
 }
 
 // StorageVolumesPost represents the fields of a new LXD storage pool volume
diff --git a/test/suites/storage.sh b/test/suites/storage.sh
index 2ddbb46..44eb939 100644
--- a/test/suites/storage.sh
+++ b/test/suites/storage.sh
@@ -6,6 +6,17 @@ test_storage() {
   LXD_STORAGE_DIR=$(mktemp -d -p "${TEST_DIR}" XXXXXXXXX)
   chmod +x "${LXD_STORAGE_DIR}"
   spawn_lxd "${LXD_STORAGE_DIR}" false
+
+  # edit storage description
+
+  # shellcheck disable=2039
+  local storage_pool
+  storage_pool="lxdtest-$(basename "${LXD_DIR}")-pool"
+  lxc storage create "$storage_pool" "$lxd_backend"
+  lxc storage show "$storage_pool" | sed 's/^description:.*/description: foo/' 
| lxc storage edit "$storage_pool"
+  lxc storage show "$storage_pool" | grep -q 'description: foo'
+  lxc storage delete "$storage_pool"
+
   (
     set -e
     # shellcheck disable=2030

From b209dc9211c430eb4641d54e302ca0dcf382b0c7 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Tue, 2 May 2017 09:38:03 +0200
Subject: [PATCH 4/6] Add description field to storage volumes.

Signed-off-by: Alberto Donato <[email protected]>
---
 lxc/storage.go               |  3 ++-
 lxd/container_lxc.go         |  2 +-
 lxd/db.go                    |  1 +
 lxd/db_storage_pools.go      | 19 +++++++++++++---
 lxd/db_storage_volumes.go    | 23 ++++++++++++++++++++
 lxd/db_update.go             |  6 +++++
 lxd/patches.go               | 52 ++++++++++++++++++++++----------------------
 lxd/storage_shared.go        |  2 +-
 lxd/storage_volumes.go       |  6 ++---
 lxd/storage_volumes_utils.go | 46 ++++++++++++++++++++-------------------
 shared/api/storage.go        |  4 ++--
 test/suites/storage.sh       | 13 +++++++----
 12 files changed, 114 insertions(+), 63 deletions(-)

diff --git a/lxc/storage.go b/lxc/storage.go
index 5f71d6e..50bcc2a 100644
--- a/lxc/storage.go
+++ b/lxc/storage.go
@@ -680,7 +680,7 @@ func (c *storageCmd) doStoragePoolVolumesList(config 
*lxd.Config, remote string,
        data := [][]string{}
        for _, volume := range volumes {
                usedby := strconv.Itoa(len(volume.UsedBy))
-               data = append(data, []string{volume.Type, volume.Name, usedby})
+               data = append(data, []string{volume.Type, volume.Name, 
volume.Description, usedby})
        }
 
        table := tablewriter.NewWriter(os.Stdout)
@@ -690,6 +690,7 @@ func (c *storageCmd) doStoragePoolVolumesList(config 
*lxd.Config, remote string,
        table.SetHeader([]string{
                i18n.G("TYPE"),
                i18n.G("NAME"),
+               i18n.G("DESCRIPTION"),
                i18n.G("USED BY")})
        sort.Sort(byNameAndType(data))
        table.AppendBulk(data)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 406368c..ac362e3 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -255,7 +255,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) 
(container, error) {
        }
 
        // Create a new database entry for the container's storage volume
-       _, err = dbStoragePoolVolumeCreate(d.db, args.Name, 
storagePoolVolumeTypeContainer, poolID, volumeConfig)
+       _, err = dbStoragePoolVolumeCreate(d.db, args.Name, "", 
storagePoolVolumeTypeContainer, poolID, volumeConfig)
        if err != nil {
                c.Delete()
                return nil, err
diff --git a/lxd/db.go b/lxd/db.go
index 1e5d0a2..b51497a 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -199,6 +199,7 @@ CREATE TABLE IF NOT EXISTS storage_pools_config (
 CREATE TABLE IF NOT EXISTS storage_volumes (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
+    description TEXT,
     storage_pool_id INTEGER NOT NULL,
     type INTEGER NOT NULL,
     UNIQUE (storage_pool_id, name, type),
diff --git a/lxd/db_storage_pools.go b/lxd/db_storage_pools.go
index f155b8e..ad8261f 100644
--- a/lxd/db_storage_pools.go
+++ b/lxd/db_storage_pools.go
@@ -346,6 +346,11 @@ func dbStoragePoolVolumeGetType(db *sql.DB, volumeName 
string, volumeType int, p
                return -1, nil, err
        }
 
+       volumeDescription, err := dbStorageVolumeDescriptionGet(db, volumeID)
+       if err != nil {
+               return -1, nil, err
+       }
+
        volumeTypeName, err := storagePoolVolumeTypeToName(volumeType)
        if err != nil {
                return -1, nil, err
@@ -355,13 +360,14 @@ func dbStoragePoolVolumeGetType(db *sql.DB, volumeName 
string, volumeType int, p
                Type: volumeTypeName,
        }
        storageVolume.Name = volumeName
+       storageVolume.Description = volumeDescription
        storageVolume.Config = volumeConfig
 
        return volumeID, &storageVolume, nil
 }
 
 // Update storage volume attached to a given storage pool.
-func dbStoragePoolVolumeUpdate(db *sql.DB, volumeName string, volumeType int, 
poolID int64, volumeConfig map[string]string) error {
+func dbStoragePoolVolumeUpdate(db *sql.DB, volumeName string, volumeType int, 
poolID int64, volumeDescription string, volumeConfig map[string]string) error {
        volumeID, _, err := dbStoragePoolVolumeGetType(db, volumeName, 
volumeType, poolID)
        if err != nil {
                return err
@@ -384,6 +390,12 @@ func dbStoragePoolVolumeUpdate(db *sql.DB, volumeName 
string, volumeType int, po
                return err
        }
 
+       err = dbStorageVolumeDescriptionUpdate(tx, volumeID, volumeDescription)
+       if err != nil {
+               tx.Rollback()
+               return err
+       }
+
        return txCommit(tx)
 }
 
@@ -424,13 +436,14 @@ func dbStoragePoolVolumeRename(db *sql.DB, oldVolumeName 
string, newVolumeName s
 }
 
 // Create new storage volume attached to a given storage pool.
-func dbStoragePoolVolumeCreate(db *sql.DB, volumeName string, volumeType int, 
poolID int64, volumeConfig map[string]string) (int64, error) {
+func dbStoragePoolVolumeCreate(db *sql.DB, volumeName, volumeDescription 
string, volumeType int, poolID int64, volumeConfig map[string]string) (int64, 
error) {
        tx, err := dbBegin(db)
        if err != nil {
                return -1, err
        }
 
-       result, err := tx.Exec("INSERT INTO storage_volumes (storage_pool_id, 
type, name) VALUES (?, ?, ?)", poolID, volumeType, volumeName)
+       result, err := tx.Exec("INSERT INTO storage_volumes (storage_pool_id, 
type, name, description) VALUES (?, ?, ?, ?)",
+               poolID, volumeType, volumeName, volumeDescription)
        if err != nil {
                tx.Rollback()
                return -1, err
diff --git a/lxd/db_storage_volumes.go b/lxd/db_storage_volumes.go
index 738f387..9cce35f 100644
--- a/lxd/db_storage_volumes.go
+++ b/lxd/db_storage_volumes.go
@@ -30,6 +30,29 @@ func dbStorageVolumeConfigGet(db *sql.DB, volumeID int64) 
(map[string]string, er
        return config, nil
 }
 
+// Get the description of a storage volume.
+func dbStorageVolumeDescriptionGet(db *sql.DB, volumeID int64) (string, error) 
{
+       description := sql.NullString{}
+       query := "SELECT description FROM storage_volumes WHERE id=?"
+       inargs := []interface{}{volumeID}
+       outargs := []interface{}{&description}
+
+       err := dbQueryRowScan(db, query, inargs, outargs)
+       if err != nil {
+               if err == sql.ErrNoRows {
+                       return "", NoSuchObjectError
+               }
+       }
+
+       return description.String, nil
+}
+
+// Update description of a storage volume.
+func dbStorageVolumeDescriptionUpdate(tx *sql.Tx, volumeID int64, description 
string) error {
+       _, err := tx.Exec("UPDATE storage_volumes SET description=? WHERE 
id=?", description, volumeID)
+       return err
+}
+
 // Add new storage volume config into database.
 func dbStorageVolumeConfigAdd(tx *sql.Tx, volumeID int64, volumeConfig 
map[string]string) error {
        str := "INSERT INTO storage_volumes_config (storage_volume_id, key, 
value) VALUES(?, ?, ?)"
diff --git a/lxd/db_update.go b/lxd/db_update.go
index 30fc59b..e68e1ea 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -72,6 +72,7 @@ var dbUpdates = []dbUpdate{
        {version: 36, run: dbUpdateFromV35},
        {version: 37, run: dbUpdateFromV36},
        {version: 38, run: dbUpdateFromV37},
+       {version: 39, run: dbUpdateFromV38},
 }
 
 type dbUpdate struct {
@@ -128,6 +129,11 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV38(currentVersion int, version int, d *Daemon) error {
+       _, err := d.db.Exec("ALTER TABLE storage_volumes ADD COLUMN description 
TEXT;")
+       return err
+}
+
 func dbUpdateFromV37(currentVersion int, version int, d *Daemon) error {
        _, err := d.db.Exec("ALTER TABLE storage_pools ADD COLUMN description 
TEXT;")
        return err
diff --git a/lxd/patches.go b/lxd/patches.go
index 837159c..b80f430 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -364,13 +364,13 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, 
defaultPoolName string,
                _, err = dbStoragePoolVolumeGetTypeID(d.db, ct, 
storagePoolVolumeTypeContainer, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the container.")
-                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, "", containerPoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, ct, "", 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for container \"%s\".", ct)
                                return err
@@ -452,13 +452,13 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, 
defaultPoolName string,
                        _, err = dbStoragePoolVolumeGetTypeID(d.db, cs, 
storagePoolVolumeTypeContainer, poolID)
                        if err == nil {
                                logger.Warnf("Storage volumes database already 
contains an entry for the snapshot.")
-                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, "", snapshotPoolVolumeConfig)
                                if err != nil {
                                        return err
                                }
                        } else if err == NoSuchObjectError {
                                // Insert storage volumes for containers into 
the database.
-                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
"", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
                                if err != nil {
                                        logger.Errorf("Could not insert a 
storage volume for snapshot \"%s\".", cs)
                                        return err
@@ -533,13 +533,13 @@ func upgradeFromStorageTypeBtrfs(name string, d *Daemon, 
defaultPoolName string,
                _, err = dbStoragePoolVolumeGetTypeID(d.db, img, 
storagePoolVolumeTypeImage, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the image.")
-                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, "", imagePoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, img, "", 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for image \"%s\".", img)
                                return err
@@ -651,13 +651,13 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, ct, 
storagePoolVolumeTypeContainer, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the container.")
-                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, "", containerPoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, ct, "", 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for container \"%s\".", ct)
                                return err
@@ -768,13 +768,13 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, cs, 
storagePoolVolumeTypeContainer, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the snapshot.")
-                       err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, "", snapshotPoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, cs, "", 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for snapshot \"%s\".", cs)
                                return err
@@ -798,13 +798,13 @@ func upgradeFromStorageTypeDir(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, img, 
storagePoolVolumeTypeImage, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the image.")
-                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, "", imagePoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, img, "", 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for image \"%s\".", img)
                                return err
@@ -954,13 +954,13 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, ct, 
storagePoolVolumeTypeContainer, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the container.")
-                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, "", containerPoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, ct, "", 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for container \"%s\".", ct)
                                return err
@@ -1109,13 +1109,13 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, 
defaultPoolName string, d
                        _, err = dbStoragePoolVolumeGetTypeID(d.db, cs, 
storagePoolVolumeTypeContainer, poolID)
                        if err == nil {
                                logger.Warnf("Storage volumes database already 
contains an entry for the snapshot.")
-                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, "", snapshotPoolVolumeConfig)
                                if err != nil {
                                        return err
                                }
                        } else if err == NoSuchObjectError {
                                // Insert storage volumes for containers into 
the database.
-                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
"", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
                                if err != nil {
                                        logger.Errorf("Could not insert a 
storage volume for snapshot \"%s\".", cs)
                                        return err
@@ -1280,13 +1280,13 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, img, 
storagePoolVolumeTypeImage, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the image.")
-                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, "", imagePoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, img, "", 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for image \"%s\".", img)
                                return err
@@ -1471,13 +1471,13 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, ct, 
storagePoolVolumeTypeContainer, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the container.")
-                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, "", containerPoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, ct, 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, ct, "", 
storagePoolVolumeTypeContainer, poolID, containerPoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for container \"%s\".", ct)
                                return err
@@ -1557,13 +1557,13 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, 
defaultPoolName string, d
                        _, err = dbStoragePoolVolumeGetTypeID(d.db, cs, 
storagePoolVolumeTypeContainer, poolID)
                        if err == nil {
                                logger.Warnf("Storage volumes database already 
contains an entry for the snapshot.")
-                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               err := dbStoragePoolVolumeUpdate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, "", snapshotPoolVolumeConfig)
                                if err != nil {
                                        return err
                                }
                        } else if err == NoSuchObjectError {
                                // Insert storage volumes for containers into 
the database.
-                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
+                               _, err := dbStoragePoolVolumeCreate(d.db, cs, 
"", storagePoolVolumeTypeContainer, poolID, snapshotPoolVolumeConfig)
                                if err != nil {
                                        logger.Errorf("Could not insert a 
storage volume for snapshot \"%s\".", cs)
                                        return err
@@ -1613,13 +1613,13 @@ func upgradeFromStorageTypeZfs(name string, d *Daemon, 
defaultPoolName string, d
                _, err = dbStoragePoolVolumeGetTypeID(d.db, img, 
storagePoolVolumeTypeImage, poolID)
                if err == nil {
                        logger.Warnf("Storage volumes database already contains 
an entry for the image.")
-                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       err := dbStoragePoolVolumeUpdate(d.db, img, 
storagePoolVolumeTypeImage, poolID, "", imagePoolVolumeConfig)
                        if err != nil {
                                return err
                        }
                } else if err == NoSuchObjectError {
                        // Insert storage volumes for containers into the 
database.
-                       _, err := dbStoragePoolVolumeCreate(d.db, img, 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
+                       _, err := dbStoragePoolVolumeCreate(d.db, img, "", 
storagePoolVolumeTypeImage, poolID, imagePoolVolumeConfig)
                        if err != nil {
                                logger.Errorf("Could not insert a storage 
volume for image \"%s\".", img)
                                return err
@@ -2078,7 +2078,7 @@ func patchStorageApiUpdateStorageConfigs(name string, d 
*Daemon) error {
                        // exist in the db, so it's safe to ignore the error.
                        volumeType, _ := 
storagePoolVolumeTypeNameToType(volume.Type)
                        // Update the volume config.
-                       err = dbStoragePoolVolumeUpdate(d.db, volume.Name, 
volumeType, poolID, volume.Config)
+                       err = dbStoragePoolVolumeUpdate(d.db, volume.Name, 
volumeType, poolID, volume.Description, volume.Config)
                        if err != nil {
                                return err
                        }
@@ -2226,7 +2226,7 @@ func patchStorageApiDetectLVSize(name string, d *Daemon) 
error {
                        // exist in the db, so it's safe to ignore the error.
                        volumeType, _ := 
storagePoolVolumeTypeNameToType(volume.Type)
                        // Update the volume config.
-                       err = dbStoragePoolVolumeUpdate(d.db, volume.Name, 
volumeType, poolID, volume.Config)
+                       err = dbStoragePoolVolumeUpdate(d.db, volume.Name, 
volumeType, poolID, volume.Description, volume.Config)
                        if err != nil {
                                return err
                        }
diff --git a/lxd/storage_shared.go b/lxd/storage_shared.go
index d6378e5..fd5f1c6 100644
--- a/lxd/storage_shared.go
+++ b/lxd/storage_shared.go
@@ -106,7 +106,7 @@ func (s *storageShared) createImageDbPoolVolume(fingerprint 
string) error {
        }
 
        // Create a db entry for the storage volume of the image.
-       _, err = dbStoragePoolVolumeCreate(s.d.db, fingerprint, 
storagePoolVolumeTypeImage, s.poolID, volumeConfig)
+       _, err = dbStoragePoolVolumeCreate(s.d.db, fingerprint, "", 
storagePoolVolumeTypeImage, s.poolID, volumeConfig)
        if err != nil {
                // Try to delete the db entry on error.
                s.deleteImageDbPoolVolume(fingerprint)
diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index a0d09f9..dcb7a64 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -162,7 +162,7 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) 
Response {
        // volume is supposed to be created.
        poolName := mux.Vars(r)["name"]
 
-       err = storagePoolVolumeCreateInternal(d, poolName, req.Name, req.Type, 
req.Config)
+       err = storagePoolVolumeCreateInternal(d, poolName, req.Name, 
req.Description, req.Type, req.Config)
        if err != nil {
                return InternalError(err)
        }
@@ -276,7 +276,7 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request) 
Response {
                return BadRequest(err)
        }
 
-       err = storagePoolVolumeUpdate(d, poolName, req.Name, volumeType, 
req.Config)
+       err = storagePoolVolumeUpdate(d, poolName, req.Name, volumeType, 
req.Description, req.Config)
        if err != nil {
                return InternalError(err)
        }
@@ -349,7 +349,7 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request) 
Response {
                return BadRequest(err)
        }
 
-       err = storagePoolVolumeUpdate(d, poolName, req.Name, volumeType, 
req.Config)
+       err = storagePoolVolumeUpdate(d, poolName, req.Name, volumeType, 
req.Description, req.Config)
        if err != nil {
                return InternalError(err)
        }
diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index a216e36..0f03958 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -88,7 +88,7 @@ func storagePoolVolumeTypeToAPIEndpoint(volumeType int) 
(string, error) {
        return "", fmt.Errorf("invalid storage volume type")
 }
 
-func storagePoolVolumeUpdate(d *Daemon, poolName string, volumeName string, 
volumeType int, newConfig map[string]string) error {
+func storagePoolVolumeUpdate(d *Daemon, poolName string, volumeName string, 
volumeType int, newDescription string, newConfig map[string]string) error {
        s, err := storagePoolVolumeInit(d, poolName, volumeName, volumeType)
        if err != nil {
                return err
@@ -98,6 +98,7 @@ func storagePoolVolumeUpdate(d *Daemon, poolName string, 
volumeName string, volu
        newWritable := oldWritable
 
        // Backup the current state
+       oldDescription := oldWritable.Description
        oldConfig := map[string]string{}
        err = shared.DeepCopy(&oldWritable.Config, &oldConfig)
        if err != nil {
@@ -142,33 +143,34 @@ func storagePoolVolumeUpdate(d *Daemon, poolName string, 
volumeName string, volu
                }
        }
 
-       // Skip on no change
-       if len(changedConfig) == 0 {
-               return nil
-       }
+       // Apply config changes if there are any
+       if len(changedConfig) != 0 {
 
-       // Update the storage pool
-       if !userOnly {
-               err = s.StoragePoolVolumeUpdate(changedConfig)
-               if err != nil {
-                       return err
+               // Update the storage pool
+               if !userOnly {
+                       err = s.StoragePoolVolumeUpdate(changedConfig)
+                       if err != nil {
+                               return err
+                       }
                }
-       }
 
-       newWritable.Config = newConfig
+               newWritable.Config = newConfig
 
-       // Apply the new configuration
-       s.SetStoragePoolVolumeWritable(&newWritable)
+               // Apply the new configuration
+               s.SetStoragePoolVolumeWritable(&newWritable)
+       }
 
        poolID, err := dbStoragePoolGetID(d.db, poolName)
        if err != nil {
                return err
        }
 
-       // Update the database
-       err = dbStoragePoolVolumeUpdate(d.db, volumeName, volumeType, poolID, 
newConfig)
-       if err != nil {
-               return err
+       // Update the database if something changed
+       if len(changedConfig) != 0 || newDescription != oldDescription {
+               err = dbStoragePoolVolumeUpdate(d.db, volumeName, volumeType, 
poolID, newDescription, newConfig)
+               if err != nil {
+                       return err
+               }
        }
 
        // Success, update the closure to mark that the changes should be kept.
@@ -260,7 +262,7 @@ func profilesUsingPoolVolumeGetNames(db *sql.DB, volumeName 
string, volumeType s
        return usedBy, nil
 }
 
-func storagePoolVolumeDBCreate(d *Daemon, poolName string, volumeName string, 
volumeTypeName string, volumeConfig map[string]string) error {
+func storagePoolVolumeDBCreate(d *Daemon, poolName string, volumeName, 
volumeDescription string, volumeTypeName string, volumeConfig 
map[string]string) error {
        // Check that the name of the new storage volume is valid. (For example.
        // zfs pools cannot contain "/" in their names.)
        err := storageValidName(volumeName)
@@ -311,7 +313,7 @@ func storagePoolVolumeDBCreate(d *Daemon, poolName string, 
volumeName string, vo
        }
 
        // Create the database entry for the storage volume.
-       _, err = dbStoragePoolVolumeCreate(d.db, volumeName, volumeType, 
poolID, volumeConfig)
+       _, err = dbStoragePoolVolumeCreate(d.db, volumeName, volumeDescription, 
volumeType, poolID, volumeConfig)
        if err != nil {
                return fmt.Errorf("Error inserting %s of type %s into database: 
%s", poolName, volumeTypeName, err)
        }
@@ -319,8 +321,8 @@ func storagePoolVolumeDBCreate(d *Daemon, poolName string, 
volumeName string, vo
        return nil
 }
 
-func storagePoolVolumeCreateInternal(d *Daemon, poolName string, volumeName 
string, volumeTypeName string, volumeConfig map[string]string) error {
-       err := storagePoolVolumeDBCreate(d, poolName, volumeName, 
volumeTypeName, volumeConfig)
+func storagePoolVolumeCreateInternal(d *Daemon, poolName string, volumeName, 
volumeDescription string, volumeTypeName string, volumeConfig 
map[string]string) error {
+       err := storagePoolVolumeDBCreate(d, poolName, volumeName, 
volumeDescription, volumeTypeName, volumeConfig)
        if err != nil {
                return err
        }
diff --git a/shared/api/storage.go b/shared/api/storage.go
index 297b28e..28b1d32 100644
--- a/shared/api/storage.go
+++ b/shared/api/storage.go
@@ -44,7 +44,6 @@ type StorageVolumesPost struct {
 // API extension: storage
 type StorageVolume struct {
        StorageVolumePut `yaml:",inline"`
-
        Name   string   `json:"name" yaml:"name"`
        Type   string   `json:"type" yaml:"type"`
        UsedBy []string `json:"used_by" yaml:"used_by"`
@@ -54,7 +53,8 @@ type StorageVolume struct {
 //
 // API extension: storage
 type StorageVolumePut struct {
-       Config map[string]string `json:"config" yaml:"config"`
+       Description string            `json:"description" yaml:"description"`
+       Config      map[string]string `json:"config" yaml:"config"`
 }
 
 // Writable converts a full StoragePool struct into a StoragePoolPut struct
diff --git a/test/suites/storage.sh b/test/suites/storage.sh
index 44eb939..3008c5f 100644
--- a/test/suites/storage.sh
+++ b/test/suites/storage.sh
@@ -7,16 +7,21 @@ test_storage() {
   chmod +x "${LXD_STORAGE_DIR}"
   spawn_lxd "${LXD_STORAGE_DIR}" false
 
-  # edit storage description
-
+  # edit storage and pool description
   # shellcheck disable=2039
-  local storage_pool
+  local storage_pool storage_volume
   storage_pool="lxdtest-$(basename "${LXD_DIR}")-pool"
+  storage_volume="${storage_pool}-vol"
   lxc storage create "$storage_pool" "$lxd_backend"
   lxc storage show "$storage_pool" | sed 's/^description:.*/description: foo/' 
| lxc storage edit "$storage_pool"
   lxc storage show "$storage_pool" | grep -q 'description: foo'
-  lxc storage delete "$storage_pool"
 
+  lxc storage volume create "$storage_pool" "$storage_volume"
+  lxc storage volume show "$storage_pool" "$storage_volume" | sed 
's/^description:.*/description: bar/' | lxc storage volume edit "$storage_pool" 
"$storage_volume"
+  lxc storage volume show "$storage_pool" "$storage_volume" | grep -q 
'description: bar'
+  lxc storage volume delete "$storage_pool" "$storage_volume"
+
+  lxc storage delete "$storage_pool"
   (
     set -e
     # shellcheck disable=2030

From 6959aac05dbca3444a16a28b88f763a91150b1d4 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Tue, 2 May 2017 13:43:50 +0200
Subject: [PATCH 5/6] Add description field to containers.

Signed-off-by: Alberto Donato <[email protected]>
---
 lxc/list.go             |  9 ++++++++-
 lxc/list_test.go        |  2 +-
 lxd/container.go        |  2 ++
 lxd/container_lxc.go    | 13 ++++++++++++-
 lxd/container_patch.go  |  1 +
 lxd/container_put.go    |  1 +
 lxd/container_state.go  |  1 +
 lxd/db.go               |  1 +
 lxd/db_containers.go    | 13 ++++++++-----
 lxd/db_update.go        |  6 ++++++
 shared/api/container.go |  1 +
 shared/api/storage.go   |  6 +++---
 test/main.sh            |  2 ++
 test/suites/config.sh   | 31 +++++++++++++++++++++++++++++++
 14 files changed, 78 insertions(+), 11 deletions(-)

diff --git a/lxc/list.go b/lxc/list.go
index 491264a..387e71d 100644
--- a/lxc/list.go
+++ b/lxc/list.go
@@ -92,6 +92,8 @@ Pre-defined column shorthand chars:
 
     c - Creation date
 
+    d - Description
+
     l - Last used date
 
     n - Name
@@ -129,7 +131,7 @@ lxc list -c ns,user.comment:comment
 
 func (c *listCmd) flags() {
        gnuflag.StringVar(&c.columnsRaw, "c", "ns46tS", i18n.G("Columns"))
-       gnuflag.StringVar(&c.columnsRaw, "columns", "ns46tS", i18n.G("Columns"))
+       gnuflag.StringVar(&c.columnsRaw, "columns", "nds46tS", 
i18n.G("Columns"))
        gnuflag.StringVar(&c.format, "format", "table", i18n.G("Format 
(table|json|csv)"))
        gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as 
--columns=nsacPt)"))
 }
@@ -445,6 +447,7 @@ func (c *listCmd) parseColumns() ([]column, error) {
                '6': {i18n.G("IPV6"), c.IP6ColumnData, true, false},
                'a': {i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, 
false},
                'c': {i18n.G("CREATED AT"), c.CreatedColumnData, false, false},
+               'd': {i18n.G("DESCRIPTION"), c.descriptionColumnData, false, 
false},
                'l': {i18n.G("LAST USED AT"), c.LastUsedColumnData, false, 
false},
                'n': {i18n.G("NAME"), c.nameColumnData, false, false},
                'p': {i18n.G("PID"), c.PIDColumnData, true, false},
@@ -535,6 +538,10 @@ func (c *listCmd) nameColumnData(cInfo api.Container, 
cState *api.ContainerState
        return cInfo.Name
 }
 
+func (c *listCmd) descriptionColumnData(cInfo api.Container, cState 
*api.ContainerState, cSnaps []api.ContainerSnapshot) string {
+       return cInfo.Description
+}
+
 func (c *listCmd) statusColumnData(cInfo api.Container, cState 
*api.ContainerState, cSnaps []api.ContainerSnapshot) string {
        return strings.ToUpper(cInfo.Status)
 }
diff --git a/lxc/list_test.go b/lxc/list_test.go
index 8364fa1..6bdf62e 100644
--- a/lxc/list_test.go
+++ b/lxc/list_test.go
@@ -52,7 +52,7 @@ func TestShouldShow(t *testing.T) {
 }
 
 // Used by TestColumns and TestInvalidColumns
-const shorthand = "46abclnpPsSt"
+const shorthand = "46abcdlnpPsSt"
 const alphanum = 
"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 
 func TestColumns(t *testing.T) {
diff --git a/lxd/container.go b/lxd/container.go
index c448762..a5c8624 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -401,6 +401,7 @@ type containerArgs struct {
        // Don't set manually
        Id int
 
+       Description  string
        Architecture int
        BaseImage    string
        Config       map[string]string
@@ -481,6 +482,7 @@ type container interface {
        // Properties
        Id() int
        Name() string
+       Description() string
        Architecture() int
        CreationDate() time.Time
        LastUsedDate() time.Time
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index ac362e3..96ac8eb 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -187,6 +187,7 @@ func containerLXCCreate(d *Daemon, args containerArgs) 
(container, error) {
                daemon:       d,
                id:           args.Id,
                name:         args.Name,
+               description:  args.Description,
                ephemeral:    args.Ephemeral,
                architecture: args.Architecture,
                cType:        args.Ctype,
@@ -351,6 +352,7 @@ func containerLXCLoad(d *Daemon, args containerArgs) 
(container, error) {
                daemon:       d,
                id:           args.Id,
                name:         args.Name,
+               description:  args.Description,
                ephemeral:    args.Ephemeral,
                architecture: args.Architecture,
                cType:        args.Ctype,
@@ -381,6 +383,7 @@ type containerLXC struct {
        ephemeral    bool
        id           int
        name         string
+       description  string
        stateful     bool
 
        // Config
@@ -2512,6 +2515,7 @@ func (c *containerLXC) Render() (interface{}, 
interface{}, error) {
                        StatusCode:      statusCode,
                }
 
+               ct.Description = c.Description()
                ct.Architecture = architectureName
                ct.Config = c.localConfig
                ct.CreatedAt = c.creationDate
@@ -3124,6 +3128,7 @@ func (c *containerLXC) Update(args containerArgs, 
userRequested bool) error {
        }
 
        // Get a copy of the old configuration
+       oldDescription := c.Description()
        oldArchitecture := 0
        err = shared.DeepCopy(&c.architecture, &oldArchitecture)
        if err != nil {
@@ -3173,6 +3178,7 @@ func (c *containerLXC) Update(args containerArgs, 
userRequested bool) error {
        undoChanges := true
        defer func() {
                if undoChanges {
+                       c.description = oldDescription
                        c.architecture = oldArchitecture
                        c.ephemeral = oldEphemeral
                        c.expandedConfig = oldExpandedConfig
@@ -3187,6 +3193,7 @@ func (c *containerLXC) Update(args containerArgs, 
userRequested bool) error {
        }()
 
        // Apply the various changes
+       c.description = args.Description
        c.architecture = args.Architecture
        c.ephemeral = args.Ephemeral
        c.localConfig = args.Config
@@ -3887,7 +3894,7 @@ func (c *containerLXC) Update(args containerArgs, 
userRequested bool) error {
                return err
        }
 
-       err = dbContainerUpdate(tx, c.id, c.architecture, c.ephemeral)
+       err = dbContainerUpdate(tx, c.id, c.description, c.architecture, 
c.ephemeral)
        if err != nil {
                tx.Rollback()
                return err
@@ -6745,6 +6752,10 @@ func (c *containerLXC) Name() string {
        return c.name
 }
 
+func (c *containerLXC) Description() string {
+       return c.description
+}
+
 func (c *containerLXC) Profiles() []string {
        return c.profiles
 }
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index f650c91..0d86bd1 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -101,6 +101,7 @@ func containerPatch(d *Daemon, r *http.Request) Response {
        // Update container configuration
        args := containerArgs{
                Architecture: architecture,
+               Description:  req.Description,
                Config:       req.Config,
                Devices:      req.Devices,
                Ephemeral:    req.Ephemeral,
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 14640c2..29ae21d 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -52,6 +52,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
                do = func(op *operation) error {
                        args := containerArgs{
                                Architecture: architecture,
+                               Description:  configRaw.Description,
                                Config:       configRaw.Config,
                                Devices:      configRaw.Devices,
                                Ephemeral:    configRaw.Ephemeral,
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 3115f58..a766db8 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -100,6 +100,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response 
{
                        if ephemeral {
                                // Unset ephemeral flag
                                args := containerArgs{
+                                       Description:  c.Description(),
                                        Architecture: c.Architecture(),
                                        Config:       c.LocalConfig(),
                                        Devices:      c.LocalDevices(),
diff --git a/lxd/db.go b/lxd/db.go
index b51497a..02cab4c 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -44,6 +44,7 @@ CREATE TABLE IF NOT EXISTS config (
 CREATE TABLE IF NOT EXISTS containers (
     id INTEGER primary key AUTOINCREMENT NOT NULL,
     name VARCHAR(255) NOT NULL,
+    description TEXT,
     architecture INTEGER NOT NULL,
     type INTEGER NOT NULL,
     ephemeral INTEGER NOT NULL DEFAULT 0,
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index 58255da..87cc988 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -53,20 +53,23 @@ func dbContainerId(db *sql.DB, name string) (int, error) {
 
 func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
        var used *time.Time // Hold the db-returned time
+       description := sql.NullString{}
 
        args := containerArgs{}
        args.Name = name
 
        ephemInt := -1
        statefulInt := -1
-       q := "SELECT id, architecture, type, ephemeral, stateful, 
creation_date, last_use_date FROM containers WHERE name=?"
+       q := "SELECT id, description, architecture, type, ephemeral, stateful, 
creation_date, last_use_date FROM containers WHERE name=?"
        arg1 := []interface{}{name}
-       arg2 := []interface{}{&args.Id, &args.Architecture, &args.Ctype, 
&ephemInt, &statefulInt, &args.CreationDate, &used}
+       arg2 := []interface{}{&args.Id, &description, &args.Architecture, 
&args.Ctype, &ephemInt, &statefulInt, &args.CreationDate, &used}
        err := dbQueryRowScan(db, q, arg1, arg2)
        if err != nil {
                return args, err
        }
 
+       args.Description = description.String
+
        if args.Id == -1 {
                return args, fmt.Errorf("Unknown container")
        }
@@ -396,8 +399,8 @@ func dbContainerRename(db *sql.DB, oldName string, newName 
string) error {
        return txCommit(tx)
 }
 
-func dbContainerUpdate(tx *sql.Tx, id int, architecture int, ephemeral bool) 
error {
-       str := fmt.Sprintf("UPDATE containers SET architecture=?, ephemeral=? 
WHERE id=?")
+func dbContainerUpdate(tx *sql.Tx, id int, description string, architecture 
int, ephemeral bool) error {
+       str := fmt.Sprintf("UPDATE containers SET description=?, 
architecture=?, ephemeral=? WHERE id=?")
        stmt, err := tx.Prepare(str)
        if err != nil {
                return err
@@ -409,7 +412,7 @@ func dbContainerUpdate(tx *sql.Tx, id int, architecture 
int, ephemeral bool) err
                ephemeralInt = 1
        }
 
-       if _, err := stmt.Exec(architecture, ephemeralInt, id); err != nil {
+       if _, err := stmt.Exec(description, architecture, ephemeralInt, id); 
err != nil {
                return err
        }
 
diff --git a/lxd/db_update.go b/lxd/db_update.go
index e68e1ea..20e6ce8 100644
--- a/lxd/db_update.go
+++ b/lxd/db_update.go
@@ -73,6 +73,7 @@ var dbUpdates = []dbUpdate{
        {version: 37, run: dbUpdateFromV36},
        {version: 38, run: dbUpdateFromV37},
        {version: 39, run: dbUpdateFromV38},
+       {version: 40, run: dbUpdateFromV39},
 }
 
 type dbUpdate struct {
@@ -129,6 +130,11 @@ func dbUpdatesApplyAll(d *Daemon) error {
 }
 
 // Schema updates begin here
+func dbUpdateFromV39(currentVersion int, version int, d *Daemon) error {
+       _, err := d.db.Exec("ALTER TABLE containers ADD COLUMN description 
TEXT;")
+       return err
+}
+
 func dbUpdateFromV38(currentVersion int, version int, d *Daemon) error {
        _, err := d.db.Exec("ALTER TABLE storage_volumes ADD COLUMN description 
TEXT;")
        return err
diff --git a/shared/api/container.go b/shared/api/container.go
index 849b9bf..ec6ff4d 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -29,6 +29,7 @@ type ContainerPost struct {
 
 // ContainerPut represents the modifiable fields of a LXD container
 type ContainerPut struct {
+       Description  string                       `json:"description" 
yaml:"description"`
        Architecture string                       `json:"architecture" 
yaml:"architecture"`
        Config       map[string]string            `json:"config" yaml:"config"`
        Devices      map[string]map[string]string `json:"devices" 
yaml:"devices"`
diff --git a/shared/api/storage.go b/shared/api/storage.go
index 28b1d32..bfe3d08 100644
--- a/shared/api/storage.go
+++ b/shared/api/storage.go
@@ -44,9 +44,9 @@ type StorageVolumesPost struct {
 // API extension: storage
 type StorageVolume struct {
        StorageVolumePut `yaml:",inline"`
-       Name   string   `json:"name" yaml:"name"`
-       Type   string   `json:"type" yaml:"type"`
-       UsedBy []string `json:"used_by" yaml:"used_by"`
+       Name             string   `json:"name" yaml:"name"`
+       Type             string   `json:"type" yaml:"type"`
+       UsedBy           []string `json:"used_by" yaml:"used_by"`
 }
 
 // StorageVolumePut represents the modifiable fields of a LXD storage volume.
diff --git a/test/main.sh b/test/main.sh
index d470070..5728cb7 100755
--- a/test/main.sh
+++ b/test/main.sh
@@ -611,6 +611,8 @@ run_test test_concurrent "concurrent startup"
 run_test test_snapshots "container snapshots"
 run_test test_snap_restore "snapshot restores"
 run_test test_config_profiles "profiles and configuration"
+run_test test_config_edit "container configuration edit"
+run_test test_config_edit_container_snapshot_pool_config "container and 
snapshot volume configuration edit"
 run_test test_server_config "server configuration"
 run_test test_filemanip "file manipulations"
 run_test test_network "network management"
diff --git a/test/suites/config.sh b/test/suites/config.sh
index d3e9a19..01cb22e 100644
--- a/test/suites/config.sh
+++ b/test/suites/config.sh
@@ -225,3 +225,34 @@ test_config_profiles() {
   lxc stop foo --force
   lxc delete foo
 }
+
+
+test_config_edit() {
+    ensure_import_testimage
+
+    lxc init testimage foo -s "lxdtest-$(basename "${LXD_DIR}")"
+    lxc config show foo | sed 's/^description:.*/description: bar/' | lxc 
config edit foo
+    lxc config show foo | grep -q 'description: bar'
+    lxc delete foo
+}
+
+test_config_edit_container_snapshot_pool_config() {
+    # shellcheck disable=2034,2039,2155
+    local storage_pool="lxdtest-$(basename "${LXD_DIR}")"
+
+    ensure_import_testimage
+
+    lxc init testimage c1 -s "$storage_pool"
+    lxc snapshot c1 s1
+    # edit the container volume name
+    lxc storage volume show "$storage_pool" container/c1 | \
+        sed 's/^description:.*/description: bar/' | \
+        lxc storage volume edit "$storage_pool" container/c1
+    lxc storage volume show "$storage_pool" container/c1 | grep -q 
'description: bar'
+    # edit the container snapshot volume name
+    lxc storage volume show "$storage_pool" container/c1/s1 | \
+        sed 's/^description:.*/description: baz/' | \
+        lxc storage volume edit "$storage_pool" container/c1/s1
+    lxc storage volume show "$storage_pool" container/c1/s1 | grep -q 
'description: baz'
+    lxc delete c1
+}

From 69cb2b619c5177de0f9ed9070adfe058147ce416 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Wed, 3 May 2017 11:58:13 +0200
Subject: [PATCH 6/6] Add entity_description extension.

Signed-off-by: Alberto Donato <[email protected]>
---
 doc/api-extensions.md | 3 +++
 lxd/api_1.0.go        | 1 +
 2 files changed, 4 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index ee37780..4c81df2 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -269,3 +269,6 @@ This key control what host network interface is used for a 
VXLAN tunnel.
 This introduces the btrfs.mount\_options property for btrfs storage pools.
 
 This key controls what mount options will be used for the btrfs storage pool.
+
+## entity\_description
+This adds descriptions to entities like containers, snapshots, networks, 
storage pools and volumes.
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index beb4f79..31ed765 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -105,6 +105,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
                        "storage_rsync_bwlimit",
                        "network_vxlan_interface",
                        "storage_btrfs_mount_options",
+                       "entity_description",
                },
                APIStatus:  "stable",
                APIVersion: version.APIVersion,
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to