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

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) ===
This PR duplicates all existing container types and renames them with an instance prefix.

This is instead of the original approach of type aliases https://github.com/lxc/lxd/pull/6187
From 62949ba5eaec705943bdeb0adc8183958763f618 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 09:44:29 +0100
Subject: [PATCH 01/18] lxd/instance/instance: Adds functions to convert
 to/from instance.Type and string

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/instance/instance.go | 28 ++++++++++++++++++++++++++++
 1 file changed, 28 insertions(+)

diff --git a/lxd/instance/instance.go b/lxd/instance/instance.go
index 10952e4821..bf15662dfc 100644
--- a/lxd/instance/instance.go
+++ b/lxd/instance/instance.go
@@ -1,5 +1,11 @@
 package instance
 
+import (
+       "fmt"
+
+       "github.com/lxc/lxd/shared/api"
+)
+
 // Type indicates the type of instance.
 type Type int
 
@@ -9,3 +15,25 @@ const (
        // TypeContainer represents a container instance type.
        TypeContainer = Type(0)
 )
+
+// New validates the supplied string against the allowed types of instance and 
returns the internal
+// representation of that type. If empty string is supplied then the type 
returned is TypeContainer.
+// If an invalid name is supplied an error will be returned.
+func New(name string) (Type, error) {
+       // If "container" or "" is supplied, return type as TypeContainer.
+       if name == api.InstanceTypeContainer || name == "" {
+               return TypeContainer, nil
+       }
+
+       return -1, fmt.Errorf("Invalid instance type")
+}
+
+// String converts the internal representation of instance type to a string 
used in API requests.
+// Returns empty string if value is not a valid instance type.
+func (instanceType Type) String() string {
+       if instanceType == TypeContainer {
+               return api.InstanceTypeContainer
+       }
+
+       return ""
+}

From e8a8d793f1480b63165c99f53096c96a2f4f2840 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 13:39:19 +0100
Subject: [PATCH 02/18] api: Adds instances extension

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 doc/api-extensions.md | 3 +++
 shared/version/api.go | 1 +
 2 files changed, 4 insertions(+)

diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index dc4ef31970..e562f4a32e 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -823,3 +823,6 @@ Export infiniband character device information (issm, umad, 
uverb) as part of th
 This introduces two new configuration keys `storage.images\_volume` and
 `storage.backups\_volume` to allow for a storage volume on an existing
 pool be used for storing the daemon-wide images and backups artifacts.
+
+## instances
+This introduces the concept of instances, of which currently the only type is 
"container".
diff --git a/shared/version/api.go b/shared/version/api.go
index 201e834828..dd9979868f 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -164,6 +164,7 @@ var APIExtensions = []string{
        "storage_shifted",
        "resources_infiniband",
        "daemon_storage",
+       "instances",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 98965fcecc04b76a67c673bc9be50e1b4d78831d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 14:23:16 +0100
Subject: [PATCH 03/18] shared/api/container: Adds Type to Container and
 ContainersPost

- Defines instance type value for containers.

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 shared/api/container.go | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/shared/api/container.go b/shared/api/container.go
index ed41a6e61e..88ce8341ab 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -4,6 +4,9 @@ import (
        "time"
 )
 
+// InstanceTypeContainer defines the instance type value for a container.
+const InstanceTypeContainer = "container"
+
 // ContainersPost represents the fields available for a new LXD container
 type ContainersPost struct {
        ContainerPut `yaml:",inline"`
@@ -12,6 +15,9 @@ type ContainersPost struct {
        Source ContainerSource `json:"source" yaml:"source"`
 
        InstanceType string `json:"instance_type" yaml:"instance_type"`
+
+       // API extension: instances
+       Type string `json:"type" yaml:"type"`
 }
 
 // ContainerPost represents the fields required to rename/move a LXD container
@@ -73,6 +79,9 @@ type Container struct {
 
        // API extension: clustering
        Location string `json:"location" yaml:"location"`
+
+       // API extension: instances
+       Type string `json:"type" yaml:"type"`
 }
 
 // ContainerFull is a combination of Container, ContainerState and 
CotnainerSnapshot

From 15a3c55747ae24de3eec16dfcf8514fb8f1928f5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 14:24:01 +0100
Subject: [PATCH 04/18] lxd/containers/post: Converts from string instance type
 and instance.Type

- Defaults to container instance type if not supplied during POST.

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/containers_post.go | 28 ++++++++++++++++++++++++----
 1 file changed, 24 insertions(+), 4 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 106c1e3feb..b2c6fc6382 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -93,11 +93,16 @@ func createFromImage(d *Daemon, project string, req 
*api.ContainersPost) Respons
                return BadRequest(fmt.Errorf("Must specify one of alias, 
fingerprint or properties for init from image"))
        }
 
+       dbType, err := instance.New(req.Type)
+       if err != nil {
+               return BadRequest(fmt.Errorf("Invalid instance type"))
+       }
+
        run := func(op *operation) error {
                args := db.ContainerArgs{
                        Project:     project,
                        Config:      req.Config,
-                       Type:        instance.TypeContainer,
+                       Type:        dbType,
                        Description: req.Description,
                        Devices:     config.NewDevices(req.Devices),
                        Ephemeral:   req.Ephemeral,
@@ -150,10 +155,15 @@ func createFromImage(d *Daemon, project string, req 
*api.ContainersPost) Respons
 }
 
 func createFromNone(d *Daemon, project string, req *api.ContainersPost) 
Response {
+       dbType, err := instance.New(req.Type)
+       if err != nil {
+               return BadRequest(fmt.Errorf("Invalid instance type"))
+       }
+
        args := db.ContainerArgs{
                Project:     project,
                Config:      req.Config,
-               Type:        instance.TypeContainer,
+               Type:        dbType,
                Description: req.Description,
                Devices:     config.NewDevices(req.Devices),
                Ephemeral:   req.Ephemeral,
@@ -204,13 +214,18 @@ func createFromMigration(d *Daemon, project string, req 
*api.ContainersPost) Res
                req.Profiles = []string{"default"}
        }
 
+       dbType, err := instance.New(req.Type)
+       if err != nil {
+               return BadRequest(fmt.Errorf("Invalid instance type"))
+       }
+
        // Prepare the container creation request
        args := db.ContainerArgs{
                Project:      project,
                Architecture: architecture,
                BaseImage:    req.Source.BaseImage,
                Config:       req.Config,
-               Type:         instance.TypeContainer,
+               Type:         dbType,
                Devices:      config.NewDevices(req.Devices),
                Description:  req.Description,
                Ephemeral:    req.Ephemeral,
@@ -551,12 +566,17 @@ func createFromCopy(d *Daemon, project string, req 
*api.ContainersPost) Response
                }
        }
 
+       dbType, err := instance.New(req.Type)
+       if err != nil {
+               return BadRequest(fmt.Errorf("Invalid instance type"))
+       }
+
        args := db.ContainerArgs{
                Project:      targetProject,
                Architecture: source.Architecture(),
                BaseImage:    req.Source.BaseImage,
                Config:       req.Config,
-               Type:         instance.TypeContainer,
+               Type:         dbType,
                Description:  req.Description,
                Devices:      config.NewDevices(req.Devices),
                Ephemeral:    req.Ephemeral,

From ac2acfa788a7d5385045b53e01d2c63235d2585d Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 15:31:42 +0100
Subject: [PATCH 05/18] lxd/api/internal: Set instance type from request data

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/api_internal.go | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 588f75e05a..29d18c662b 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -900,13 +900,19 @@ func internalImport(d *Daemon, r *http.Request) Response {
        if err != nil {
                return SmartError(err)
        }
+
+       dbType, err := instance.New(backup.Container.Type)
+       if err != nil {
+               return SmartError(fmt.Errorf("Invalid instance type"))
+       }
+
        _, err = containerCreateInternal(d.State(), db.ContainerArgs{
                Project:      projectName,
                Architecture: arch,
                BaseImage:    baseImage,
                Config:       backup.Container.Config,
                CreationDate: backup.Container.CreatedAt,
-               Type:         instance.TypeContainer,
+               Type:         dbType,
                Description:  backup.Container.Description,
                Devices:      deviceConfig.NewDevices(backup.Container.Devices),
                Ephemeral:    backup.Container.Ephemeral,
@@ -1012,7 +1018,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
                        BaseImage:    baseImage,
                        Config:       snap.Config,
                        CreationDate: snap.CreatedAt,
-                       Type:         instance.TypeContainer,
+                       Type:         dbType,
                        Snapshot:     true,
                        Devices:      deviceConfig.NewDevices(snap.Devices),
                        Ephemeral:    snap.Ephemeral,

From 28a32d75b3bc3a0e20ae2d8c964e44cced07d6ad Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:15:54 +0100
Subject: [PATCH 06/18] lxd/db/instances/mapper: Updates InstanceList to use
 instance.TypeAny

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/db/instances.mapper.go | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/lxd/db/instances.mapper.go b/lxd/db/instances.mapper.go
index f5b9e5c9cf..74389ef9e7 100644
--- a/lxd/db/instances.mapper.go
+++ b/lxd/db/instances.mapper.go
@@ -5,10 +5,13 @@ package db
 import (
        "database/sql"
        "fmt"
+
+       "github.com/pkg/errors"
+
        "github.com/lxc/lxd/lxd/db/cluster"
        "github.com/lxc/lxd/lxd/db/query"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared/api"
-       "github.com/pkg/errors"
 )
 
 var _ = api.ServerEnvironment{}
@@ -163,7 +166,7 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) 
([]Instance, error) {
        if filter.Node != "" {
                criteria["Node"] = filter.Node
        }
-       if filter.Type != -1 {
+       if filter.Type != instance.TypeAny {
                criteria["Type"] = filter.Type
        }
 

From 43c03601dca2f100782ddeab9b037a09db1c3c50 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:16:51 +0100
Subject: [PATCH 07/18] lxd/db/containers: Updates container filtering
 functions to support instance.Type

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/db/containers.go | 54 ++++++++++++++++++++++++++++++++------------
 1 file changed, 40 insertions(+), 14 deletions(-)

diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index 78822a0f7c..068da2532a 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -241,22 +241,35 @@ SELECT nodes.id, nodes.address
 // string, to distinguish it from remote nodes.
 //
 // Containers whose node is down are addeded to the special address "0.0.0.0".
-func (c *ClusterTx) ContainersListByNodeAddress(project string) 
(map[string][]string, error) {
+func (c *ClusterTx) ContainersListByNodeAddress(project string, instanceType 
instance.Type) (map[string][]string, error) {
        offlineThreshold, err := c.NodeOfflineThreshold()
        if err != nil {
                return nil, err
        }
 
-       stmt := `
+       args := make([]interface{}, 0, 2) // Expect up to 2 filters.
+       var filters strings.Builder
+
+       // Project filter.
+       filters.WriteString("projects.name = ?")
+       args = append(args, project)
+
+       // Instance type filter.
+       if instanceType != instance.TypeAny {
+               filters.WriteString(" AND instances.type = ?")
+               args = append(args, instanceType)
+       }
+
+       stmt := fmt.Sprintf(`
 SELECT instances.name, nodes.id, nodes.address, nodes.heartbeat
   FROM instances
   JOIN nodes ON nodes.id = instances.node_id
   JOIN projects ON projects.id = instances.project_id
-  WHERE instances.type=?
-    AND projects.name = ?
+  WHERE %s
   ORDER BY instances.id
-`
-       rows, err := c.tx.Query(stmt, instance.TypeContainer, project)
+`, filters.String())
+
+       rows, err := c.tx.Query(stmt, args...)
        if err != nil {
                return nil, err
        }
@@ -328,16 +341,29 @@ func (c *ClusterTx) ContainerListExpanded() ([]Instance, 
error) {
 
 // ContainersByNodeName returns a map associating each container to the name of
 // its node.
-func (c *ClusterTx) ContainersByNodeName(project string) (map[string]string, 
error) {
-       stmt := `
+func (c *ClusterTx) ContainersByNodeName(project string, instanceType 
instance.Type) (map[string]string, error) {
+       args := make([]interface{}, 0, 2) // Expect up to 2 filters.
+       var filters strings.Builder
+
+       // Project filter.
+       filters.WriteString("projects.name = ?")
+       args = append(args, project)
+
+       // Instance type filter.
+       if instanceType != instance.TypeAny {
+               filters.WriteString(" AND instances.type = ?")
+               args = append(args, instanceType)
+       }
+
+       stmt := fmt.Sprintf(`
 SELECT instances.name, nodes.name
   FROM instances
   JOIN nodes ON nodes.id = instances.node_id
   JOIN projects ON projects.id = instances.project_id
-  WHERE instances.type=?
-    AND projects.name = ?
-`
-       rows, err := c.tx.Query(stmt, instance.TypeContainer, project)
+  WHERE %s
+`, filters.String())
+
+       rows, err := c.tx.Query(stmt, args...)
        if err != nil {
                return nil, err
        }
@@ -490,7 +516,7 @@ func (c *ClusterTx) ContainerNodeList() ([]Instance, error) 
{
 }
 
 // ContainerNodeProjectList returns all container objects on the local node 
within the given project.
-func (c *ClusterTx) ContainerNodeProjectList(project string) ([]Instance, 
error) {
+func (c *ClusterTx) ContainerNodeProjectList(project string, instanceType 
instance.Type) ([]Instance, error) {
        node, err := c.NodeName()
        if err != nil {
                return nil, errors.Wrap(err, "Local node name")
@@ -498,7 +524,7 @@ func (c *ClusterTx) ContainerNodeProjectList(project 
string) ([]Instance, error)
        filter := InstanceFilter{
                Project: project,
                Node:    node,
-               Type:    instance.TypeContainer,
+               Type:    instanceType,
        }
 
        return c.InstanceList(filter)

From cdce67eae625b3b6a3c5dff193a6a6f6ac4f37af Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:17:22 +0100
Subject: [PATCH 08/18] lxd/container: Updates containerLoadNodeProjectAll to
 support instance.Type filtering

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/container.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/container.go b/lxd/container.go
index a6a04c3837..cd0edf6a2e 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -1152,12 +1152,12 @@ func containerLoadNodeAll(s *state.State) ([]container, 
error) {
 }
 
 // Load all containers of this nodes under the given project.
-func containerLoadNodeProjectAll(s *state.State, project string) ([]container, 
error) {
+func containerLoadNodeProjectAll(s *state.State, project string, instanceType 
instance.Type) ([]container, error) {
        // Get all the container arguments
        var cts []db.Instance
        err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
                var err error
-               cts, err = tx.ContainerNodeProjectList(project)
+               cts, err = tx.ContainerNodeProjectList(project, instanceType)
                if err != nil {
                        return err
                }

From 09d2a2dc84d89141724de1aa5401d4f0f81936ea Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:17:52 +0100
Subject: [PATCH 09/18] lxd/container/lxc: Adds instance Type string field
 population in Render()

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/container_lxc.go | 1 +
 1 file changed, 1 insertion(+)

diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index f0fdd9c791..b1e67d781a 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3246,6 +3246,7 @@ func (c *containerLXC) Render() (interface{}, 
interface{}, error) {
                Status:          statusCode.String(),
                StatusCode:      statusCode,
                Location:        c.node,
+               Type:            c.Type().String(),
        }
 
        ct.Description = c.description

From fb28d9f01c4ef2d815a08bbc6d442e8ee72610f6 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:18:47 +0100
Subject: [PATCH 10/18] lxd/containers/get: Makes /1.0/containers filter by
 instance type container

- However there is still work to do in doContainersGetFromNode and 
doContainersFullGetFromNode to actually filter, however this requires changing 
the client package.

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/containers_get.go | 32 +++++++++++++++++++++++---------
 1 file changed, 23 insertions(+), 9 deletions(-)

diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index e45c458aa5..60193da982 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -5,17 +5,21 @@ import (
        "net/http"
        "sort"
        "strconv"
+       "strings"
        "sync"
        "time"
 
+       "github.com/gorilla/mux"
+       "github.com/pkg/errors"
+
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
        "github.com/lxc/lxd/lxd/db/query"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/version"
-       "github.com/pkg/errors"
 )
 
 func containersGet(d *Daemon, r *http.Request) Response {
@@ -44,6 +48,12 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
        resultFullList := []*api.ContainerFull{}
        resultMu := sync.Mutex{}
 
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        // Parse the recursion field
        recursionStr := r.FormValue("recursion")
 
@@ -61,12 +71,12 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
        err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
                var err error
 
-               result, err = tx.ContainersListByNodeAddress(project)
+               result, err = tx.ContainersListByNodeAddress(project, 
instanceType)
                if err != nil {
                        return err
                }
 
-               nodes, err = tx.ContainersByNodeName(project)
+               nodes, err = tx.ContainersByNodeName(project, instanceType)
                if err != nil {
                        return err
                }
@@ -80,7 +90,7 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
        // Get the local containers
        nodeCts := map[string]container{}
        if recursion > 0 {
-               cts, err := containerLoadNodeProjectAll(d.State(), project)
+               cts, err := containerLoadNodeProjectAll(d.State(), project, 
instanceType)
                if err != nil {
                        return nil, err
                }
@@ -151,7 +161,7 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
                                cert := d.endpoints.NetworkCert()
 
                                if recursion == 1 {
-                                       cs, err := 
doContainersGetFromNode(project, address, cert)
+                                       cs, err := 
doContainersGetFromNode(project, address, cert, instanceType)
                                        if err != nil {
                                                for _, name := range containers 
{
                                                        resultListAppend(name, 
api.Container{}, err)
@@ -167,7 +177,7 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
                                        return
                                }
 
-                               cs, err := doContainersFullGetFromNode(project, 
address, cert)
+                               cs, err := doContainersFullGetFromNode(project, 
address, cert, instanceType)
                                if err != nil {
                                        for _, name := range containers {
                                                resultFullListAppend(name, 
api.ContainerFull{}, err)
@@ -186,7 +196,11 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
 
                if recursion == 0 {
                        for _, container := range containers {
-                               url := fmt.Sprintf("/%s/containers/%s", 
version.APIVersion, container)
+                               instancePath := "instances"
+                               if instanceType == instance.TypeContainer {
+                                       instancePath = "containers"
+                               }
+                               url := fmt.Sprintf("/%s/%s/%s", 
version.APIVersion, instancePath, container)
                                resultString = append(resultString, url)
                        }
                } else {
@@ -262,7 +276,7 @@ func doContainersGet(d *Daemon, r *http.Request) 
(interface{}, error) {
 
 // Fetch information about the containers on the given remote node, using the
 // rest API and with a timeout of 30 seconds.
-func doContainersGetFromNode(project, node string, cert *shared.CertInfo) 
([]api.Container, error) {
+func doContainersGetFromNode(project, node string, cert *shared.CertInfo, 
instanceType instance.Type) ([]api.Container, error) {
        f := func() ([]api.Container, error) {
                client, err := cluster.Connect(node, cert, true)
                if err != nil {
@@ -299,7 +313,7 @@ func doContainersGetFromNode(project, node string, cert 
*shared.CertInfo) ([]api
        return containers, err
 }
 
-func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo) 
([]api.ContainerFull, error) {
+func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, 
instanceType instance.Type) ([]api.ContainerFull, error) {
        f := func() ([]api.ContainerFull, error) {
                client, err := cluster.Connect(node, cert, true)
                if err != nil {

From 3d773c4da37c325297ec509279dfc8e23045d99f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <t...@tomp.uk>
Date: Tue, 10 Sep 2019 17:40:52 +0100
Subject: [PATCH 11/18] lxd/db/containers/test: Fixes tests

Signed-off-by: Thomas Parrott <t...@tomp.uk>
---
 lxd/db/containers_test.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/db/containers_test.go b/lxd/db/containers_test.go
index bc2adf4fa8..ef829b9e37 100644
--- a/lxd/db/containers_test.go
+++ b/lxd/db/containers_test.go
@@ -313,7 +313,7 @@ func TestContainersListByNodeAddress(t *testing.T) {
        addContainer(t, tx, nodeID3, "c3")
        addContainer(t, tx, nodeID2, "c4")
 
-       result, err := tx.ContainersListByNodeAddress("default")
+       result, err := tx.ContainersListByNodeAddress("default", 
instance.TypeContainer)
        require.NoError(t, err)
        assert.Equal(
                t,
@@ -337,7 +337,7 @@ func TestContainersByNodeName(t *testing.T) {
        addContainer(t, tx, nodeID2, "c1")
        addContainer(t, tx, nodeID1, "c2")
 
-       result, err := tx.ContainersByNodeName("default")
+       result, err := tx.ContainersByNodeName("default", 
instance.TypeContainer)
        require.NoError(t, err)
        assert.Equal(
                t,

From 3ea17480785f7e324840d75f0d315ccd6abbf028 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 11:11:22 +0100
Subject: [PATCH 12/18] lxd/db/containers: Adds instanceType filter to
 ContainerNodeAddress

- Supports instance.TypeAny if no filter is needed.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/db/containers.go | 43 +++++++++++++++++++++++++++++++++----------
 1 file changed, 33 insertions(+), 10 deletions(-)

diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index 068da2532a..0334215e06 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -175,30 +175,53 @@ SELECT instances.name FROM instances
 // with the given name in the given project.
 //
 // It returns the empty string if the container is hosted on this node.
-func (c *ClusterTx) ContainerNodeAddress(project string, name string) (string, 
error) {
+func (c *ClusterTx) ContainerNodeAddress(project string, name string, 
instanceType instance.Type) (string, error) {
        var stmt string
-       args := []interface{}{project}
+
+       args := make([]interface{}, 0, 4) // Expect up to 4 filters.
+       var filters strings.Builder
+
+       // Project filter.
+       filters.WriteString("projects.name = ?")
+       args = append(args, project)
+
+       // Instance type filter.
+       if instanceType != instance.TypeAny {
+               filters.WriteString(" AND instances.type = ?")
+               args = append(args, instanceType)
+       }
 
        if strings.Contains(name, shared.SnapshotDelimiter) {
                parts := strings.SplitN(name, shared.SnapshotDelimiter, 2)
-               stmt = `
+
+               // Instance name filter.
+               filters.WriteString(" AND instances.name = ?")
+               args = append(args, parts[0])
+
+               // Snapshot name filter.
+               filters.WriteString(" AND instances_snapshots.name = ?")
+               args = append(args, parts[1])
+
+               stmt = fmt.Sprintf(`
 SELECT nodes.id, nodes.address
   FROM nodes
   JOIN instances ON instances.node_id = nodes.id
   JOIN projects ON projects.id = instances.project_id
   JOIN instances_snapshots ON instances_snapshots.instance_id = instances.id
- WHERE projects.name = ? AND instances.name = ? AND instances_snapshots.name = 
?
-`
-               args = append(args, parts[0], parts[1])
+ WHERE %s
+`, filters.String())
        } else {
-               stmt = `
+               // Instance name filter.
+               filters.WriteString(" AND instances.name = ?")
+               args = append(args, name)
+
+               stmt = fmt.Sprintf(`
 SELECT nodes.id, nodes.address
   FROM nodes
   JOIN instances ON instances.node_id = nodes.id
   JOIN projects ON projects.id = instances.project_id
- WHERE projects.name = ? AND instances.name = ?
-`
-               args = append(args, name)
+ WHERE %s
+`, filters.String())
        }
 
        var address string

From 92ae4f361700b45d696c60b45b35aab628e46f2c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 11:12:49 +0100
Subject: [PATCH 13/18] lxd/cluster/connect: Adds instanceType filter to
 ConnectIfContainerIsRemote

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/cluster/connect.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/cluster/connect.go b/lxd/cluster/connect.go
index 710d164fe6..b97625ffd1 100644
--- a/lxd/cluster/connect.go
+++ b/lxd/cluster/connect.go
@@ -7,6 +7,7 @@ import (
 
        lxd "github.com/lxc/lxd/client"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/pkg/errors"
@@ -37,11 +38,11 @@ func Connect(address string, cert *shared.CertInfo, notify 
bool) (lxd.ContainerS
 // running the container with the given name. If it's not the local node will
 // connect to it and return the connected client, otherwise it will just return
 // nil.
-func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, 
cert *shared.CertInfo) (lxd.ContainerServer, error) {
+func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, 
cert *shared.CertInfo, instanceType instance.Type) (lxd.ContainerServer, error) 
{
        var address string // Node address
        err := cluster.Transaction(func(tx *db.ClusterTx) error {
                var err error
-               address, err = tx.ContainerNodeAddress(project, name)
+               address, err = tx.ContainerNodeAddress(project, name, 
instanceType)
                return err
        })
        if err != nil {

From c59207c727a19c3c0ffc8449d8fd5ddb1511055e Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 11:13:47 +0100
Subject: [PATCH 14/18] lxd/response: Adds instanceType filter to
 ForwardedResponseIfContainerIsRemote

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/response.go | 5 +++--
 1 file changed, 3 insertions(+), 2 deletions(-)

diff --git a/lxd/response.go b/lxd/response.go
index 76b58b3b3c..bd0980fb6b 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -18,6 +18,7 @@ import (
        lxd "github.com/lxc/lxd/client"
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -191,9 +192,9 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request 
*http.Request) Respons
 // ForwardedResponseIfContainerIsRemote redirects a request to the node running
 // the container with the given name. If the container is local, nothing gets
 // done and nil is returned.
-func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, 
name string) (Response, error) {
+func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, 
name string, instanceType instance.Type) (Response, error) {
        cert := d.endpoints.NetworkCert()
-       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert)
+       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert, instanceType)
        if err != nil {
                return nil, err
        }

From e09015ba1146087a8f47d9d0ba6fed5db79b186f Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 11:14:10 +0100
Subject: [PATCH 15/18] lxd: Updates use of
 ForwardedResponseIfContainerIsRemote to supply instanceType

- This has the effect of validating whether the supplied instance name exists 
as the same type as the context of the endpoint.
- Instance type is derived from the context of the endpoint.
- If context is /1.0/containers then instance.TypeContainer is used.
- If context is /1.0/instances then instance.TypeAny is used.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/container_backup.go   | 49 ++++++++++++++++++++++++++++++++++-----
 lxd/container_console.go  | 18 ++++++++++++--
 lxd/container_delete.go   | 10 +++++++-
 lxd/container_exec.go     | 12 +++++++---
 lxd/container_file.go     | 10 +++++++-
 lxd/container_get.go      | 11 ++++++++-
 lxd/container_logs.go     | 26 ++++++++++++++++++---
 lxd/container_metadata.go | 44 +++++++++++++++++++++++++++++------
 lxd/container_patch.go    | 10 +++++++-
 lxd/container_post.go     | 18 ++++++++++----
 lxd/container_put.go      | 10 +++++++-
 lxd/container_snapshot.go | 25 +++++++++++++++++---
 lxd/container_state.go    | 18 ++++++++++++--
 lxd/containers_post.go    |  2 +-
 lxd/images.go             | 13 +++++++----
 15 files changed, 235 insertions(+), 41 deletions(-)

diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index bbf436e126..7f950a222c 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -11,6 +11,7 @@ import (
        "github.com/pkg/errors"
 
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -18,11 +19,17 @@ import (
 )
 
 func containerBackupsGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        cname := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
cname)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
cname, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -64,11 +71,17 @@ func containerBackupsGet(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerBackupsPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -176,12 +189,18 @@ func containerBackupsPost(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerBackupGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -199,12 +218,18 @@ func containerBackupGet(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerBackupPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -253,12 +278,18 @@ func containerBackupPost(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerBackupDelete(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -294,12 +325,18 @@ func containerBackupDelete(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerBackupExportGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
        backupName := mux.Vars(r)["backupName"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_console.go b/lxd/container_console.go
index c498f6cad7..2ccf9094f4 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -8,6 +8,7 @@ import (
        "os"
        "os/exec"
        "strconv"
+       "strings"
        "sync"
        "syscall"
 
@@ -18,6 +19,7 @@ import (
 
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -254,6 +256,12 @@ func (s *consoleWs) Do(op *operation) error {
 }
 
 func containerConsolePost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
@@ -270,7 +278,7 @@ func containerConsolePost(d *Daemon, r *http.Request) 
Response {
 
        // Forward the request if the container is remote.
        cert := d.endpoints.NetworkCert()
-       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert)
+       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -343,11 +351,17 @@ func containerConsolePost(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerConsoleLogGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Forward the request if the container is remote.
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_delete.go b/lxd/container_delete.go
index 6e6d653928..319f7cd853 100644
--- a/lxd/container_delete.go
+++ b/lxd/container_delete.go
@@ -3,17 +3,25 @@ package main
 import (
        "fmt"
        "net/http"
+       "strings"
 
        "github.com/gorilla/mux"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
 )
 
 func containerDelete(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index e53fad8727..358b88f555 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -19,13 +19,13 @@ import (
 
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
+       log "github.com/lxc/lxd/shared/log15"
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/netutils"
        "github.com/lxc/lxd/shared/version"
-
-       log "github.com/lxc/lxd/shared/log15"
 )
 
 type execWs struct {
@@ -342,6 +342,12 @@ func (s *execWs) Do(op *operation) error {
 }
 
 func containerExecPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
@@ -357,7 +363,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response 
{
 
        // Forward the request if the container is remote.
        cert := d.endpoints.NetworkCert()
-       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert)
+       client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, 
name, cert, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_file.go b/lxd/container_file.go
index e9358c091a..21f8b33693 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -7,17 +7,25 @@ import (
        "net/http"
        "os"
        "path/filepath"
+       "strings"
 
        "github.com/gorilla/mux"
 
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
 )
 
 func containerFileHandler(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_get.go b/lxd/container_get.go
index 565699364d..7dacde2f60 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -2,16 +2,25 @@ package main
 
 import (
        "net/http"
+       "strings"
 
        "github.com/gorilla/mux"
+
+       "github.com/lxc/lxd/lxd/instance"
 )
 
 func containerGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index 92e026d3e2..72b3dfa80e 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -9,6 +9,7 @@ import (
 
        "github.com/gorilla/mux"
 
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/version"
 )
@@ -38,11 +39,18 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
         * However, we should check this name and ensure it's a valid container
         * name just so that people can't list arbitrary directories.
         */
+
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -84,11 +92,17 @@ func validLogFileName(fname string) bool {
 }
 
 func containerLogGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -115,11 +129,17 @@ func containerLogGet(d *Daemon, r *http.Request) Response 
{
 }
 
 func containerLogDelete(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_metadata.go b/lxd/container_metadata.go
index adfce53c57..78b7f182c9 100644
--- a/lxd/container_metadata.go
+++ b/lxd/container_metadata.go
@@ -10,20 +10,26 @@ import (
        "path/filepath"
        "strings"
 
-       "gopkg.in/yaml.v2"
-
        "github.com/gorilla/mux"
+       "gopkg.in/yaml.v2"
 
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
 )
 
 func containerMetadataGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -75,11 +81,17 @@ func containerMetadataGet(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerMetadataPut(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -124,11 +136,17 @@ func containerMetadataPut(d *Daemon, r *http.Request) 
Response {
 
 // Return a list of templates used in a container or the content of a template
 func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -213,11 +231,17 @@ func containerMetadataTemplatesGet(d *Daemon, r 
*http.Request) Response {
 
 // Add a container template file
 func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -280,12 +304,18 @@ func containerMetadataTemplatesPostPut(d *Daemon, r 
*http.Request) Response {
 
 // Delete a container template
 func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
 
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index 18f454dcf0..c7be6f8f64 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -6,11 +6,13 @@ import (
        "fmt"
        "io/ioutil"
        "net/http"
+       "strings"
 
        "github.com/gorilla/mux"
 
        "github.com/lxc/lxd/lxd/db"
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -18,13 +20,19 @@ import (
 )
 
 func containerPatch(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
 
        // Get the container
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_post.go b/lxd/container_post.go
index 3865b646c1..e030133472 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -6,6 +6,7 @@ import (
        "fmt"
        "io/ioutil"
        "net/http"
+       "strings"
 
        "github.com/gorilla/mux"
        "github.com/pborman/uuid"
@@ -14,6 +15,7 @@ import (
        lxd "github.com/lxc/lxd/client"
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        driver "github.com/lxc/lxd/lxd/storage"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -27,6 +29,12 @@ var internalClusterContainerMovedCmd = APIEndpoint{
 }
 
 func containerPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
 
        name := mux.Vars(r)["name"]
@@ -70,7 +78,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
                        targetNodeOffline = 
node.IsOffline(config.OfflineThreshold())
 
                        // Load source node.
-                       address, err := tx.ContainerNodeAddress(project, name)
+                       address, err := tx.ContainerNodeAddress(project, name, 
instanceType)
                        if err != nil {
                                return errors.Wrap(err, "Failed to get address 
of container's node")
                        }
@@ -121,7 +129,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
        // and we'll either forward the request or load the container.
        if targetNode == "" || !sourceNodeOffline {
                // Handle requests targeted to a container on a different node
-               response, err := ForwardedResponseIfContainerIsRemote(d, r, 
project, name)
+               response, err := ForwardedResponseIfContainerIsRemote(d, r, 
project, name, instanceType)
                if err != nil {
                        return SmartError(err)
                }
@@ -181,7 +189,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
                                return SmartError(err)
                        }
                        if pool.Driver == "ceph" {
-                               return 
containerPostClusteringMigrateWithCeph(d, c, project, name, req.Name, 
targetNode)
+                               return 
containerPostClusteringMigrateWithCeph(d, c, project, name, req.Name, 
targetNode, instanceType)
                        }
 
                        // If this is not a ceph-based container, make sure
@@ -393,7 +401,7 @@ func containerPostClusteringMigrate(d *Daemon, c container, 
oldName, newName, ne
 }
 
 // Special case migrating a container backed by ceph across two cluster nodes.
-func containerPostClusteringMigrateWithCeph(d *Daemon, c container, project, 
oldName, newName, newNode string) Response {
+func containerPostClusteringMigrateWithCeph(d *Daemon, c container, project, 
oldName, newName, newNode string, instanceType instance.Type) Response {
        run := func(*operation) error {
                // If source node is online (i.e. we're serving the request on
                // it, and c != nil), let's unmap the RBD volume locally
@@ -467,7 +475,7 @@ func containerPostClusteringMigrateWithCeph(d *Daemon, c 
container, project, old
 
                // Create the container mount point on the target node
                cert := d.endpoints.NetworkCert()
-               client, err := cluster.ConnectIfContainerIsRemote(d.cluster, 
project, newName, cert)
+               client, err := cluster.ConnectIfContainerIsRemote(d.cluster, 
project, newName, cert, instanceType)
                if err != nil {
                        return errors.Wrap(err, "Failed to connect to target 
node")
                }
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 27b5be9f43..6ac1426f96 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -4,11 +4,13 @@ import (
        "encoding/json"
        "fmt"
        "net/http"
+       "strings"
 
        "github.com/gorilla/mux"
 
        "github.com/lxc/lxd/lxd/db"
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
@@ -21,13 +23,19 @@ import (
  * the named snapshot
  */
 func containerPut(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
 
        // Get the container
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 67fe2e71d4..d800cb0590 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -13,6 +13,7 @@ import (
        "github.com/gorilla/mux"
 
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/util"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
@@ -20,11 +21,17 @@ import (
 )
 
 func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        cname := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
cname)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
cname, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -81,11 +88,17 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -170,11 +183,17 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) 
Response {
 }
 
 func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        containerName := mux.Vars(r)["name"]
        snapshotName := mux.Vars(r)["snapshotName"]
 
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
containerName)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
containerName, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 8b86a0a362..c47309546a 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -4,21 +4,29 @@ import (
        "encoding/json"
        "fmt"
        "net/http"
+       "strings"
        "time"
 
        "github.com/gorilla/mux"
 
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
 )
 
 func containerState(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
@@ -39,11 +47,17 @@ func containerState(d *Daemon, r *http.Request) Response {
 }
 
 func containerStatePut(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
        name := mux.Vars(r)["name"]
 
        // Handle requests targeted to a container on a different node
-       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name)
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, project, 
name, instanceType)
        if err != nil {
                return SmartError(err)
        }
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index b2c6fc6382..b032d60d1d 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -884,7 +884,7 @@ func clusterCopyContainerInternal(d *Daemon, source 
container, project string, r
                var err error
 
                // Load source node.
-               nodeAddress, err = tx.ContainerNodeAddress(project, name)
+               nodeAddress, err = tx.ContainerNodeAddress(project, name, 
source.Type())
                if err != nil {
                        return errors.Wrap(err, "Failed to get address of 
container's node")
                }
diff --git a/lxd/images.go b/lxd/images.go
index 2ea8330c9f..383ae47643 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -23,12 +23,12 @@ import (
 
        "github.com/gorilla/mux"
        "github.com/pkg/errors"
-
        "gopkg.in/yaml.v2"
 
        lxd "github.com/lxc/lxd/client"
        "github.com/lxc/lxd/lxd/cluster"
        "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/node"
        "github.com/lxc/lxd/lxd/state"
        "github.com/lxc/lxd/lxd/task"
@@ -36,12 +36,11 @@ import (
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/ioprogress"
+       log "github.com/lxc/lxd/shared/log15"
        "github.com/lxc/lxd/shared/logger"
        "github.com/lxc/lxd/shared/logging"
        "github.com/lxc/lxd/shared/osarch"
        "github.com/lxc/lxd/shared/version"
-
-       log "github.com/lxc/lxd/shared/log15"
 )
 
 var imagesCmd = APIEndpoint{
@@ -637,6 +636,12 @@ func imageCreateInPool(d *Daemon, info *api.Image, 
storagePool string) error {
 }
 
 func imagesPost(d *Daemon, r *http.Request) Response {
+       // Instance type.
+       instanceType := instance.TypeAny
+       if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+               instanceType = instance.TypeContainer
+       }
+
        project := projectParam(r)
 
        var err error
@@ -698,7 +703,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
                if name != "" {
                        post.Seek(0, 0)
                        r.Body = post
-                       response, err := 
ForwardedResponseIfContainerIsRemote(d, r, project, name)
+                       response, err := 
ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
                        if err != nil {
                                cleanup(builddir, post)
                                return SmartError(err)

From 1f8873c21d8df743aba31e2f0772839efd085a2c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 17:30:33 +0100
Subject: [PATCH 16/18] shared/api/container: Removes instance type definitions

These will be moved to new instance.go file.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 shared/api/container.go | 3 ---
 1 file changed, 3 deletions(-)

diff --git a/shared/api/container.go b/shared/api/container.go
index 88ce8341ab..c99eb0533b 100644
--- a/shared/api/container.go
+++ b/shared/api/container.go
@@ -4,9 +4,6 @@ import (
        "time"
 )
 
-// InstanceTypeContainer defines the instance type value for a container.
-const InstanceTypeContainer = "container"
-
 // ContainersPost represents the fields available for a new LXD container
 type ContainersPost struct {
        ContainerPut `yaml:",inline"`

From 5d1134db107443bba98973f521b02d619902d216 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 17:30:59 +0100
Subject: [PATCH 17/18] lxd/instance/instance: Uses API instance types for
 string comparison

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 lxd/instance/instance.go | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/lxd/instance/instance.go b/lxd/instance/instance.go
index bf15662dfc..2ae566c1b3 100644
--- a/lxd/instance/instance.go
+++ b/lxd/instance/instance.go
@@ -21,7 +21,7 @@ const (
 // If an invalid name is supplied an error will be returned.
 func New(name string) (Type, error) {
        // If "container" or "" is supplied, return type as TypeContainer.
-       if name == api.InstanceTypeContainer || name == "" {
+       if api.InstanceType(name) == api.InstanceTypeContainer || name == "" {
                return TypeContainer, nil
        }
 
@@ -32,7 +32,7 @@ func New(name string) (Type, error) {
 // Returns empty string if value is not a valid instance type.
 func (instanceType Type) String() string {
        if instanceType == TypeContainer {
-               return api.InstanceTypeContainer
+               return string(api.InstanceTypeContainer)
        }
 
        return ""

From d489938cb1584bf3c392a023cf11062d14ef4e88 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Wed, 11 Sep 2019 17:31:26 +0100
Subject: [PATCH 18/18] shared/api: Adds new instance types

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 shared/api/instance.go          | 132 ++++++++++++++++++++++++++++++++
 shared/api/instance_backup.go   |  31 ++++++++
 shared/api/instance_console.go  |  17 ++++
 shared/api/instance_exec.go     |  26 +++++++
 shared/api/instance_snapshot.go |  60 +++++++++++++++
 shared/api/instance_state.go    |  84 ++++++++++++++++++++
 6 files changed, 350 insertions(+)
 create mode 100644 shared/api/instance.go
 create mode 100644 shared/api/instance_backup.go
 create mode 100644 shared/api/instance_console.go
 create mode 100644 shared/api/instance_exec.go
 create mode 100644 shared/api/instance_snapshot.go
 create mode 100644 shared/api/instance_state.go

diff --git a/shared/api/instance.go b/shared/api/instance.go
new file mode 100644
index 0000000000..fa376a4a0e
--- /dev/null
+++ b/shared/api/instance.go
@@ -0,0 +1,132 @@
+package api
+
+import (
+       "time"
+)
+
+// InstanceType represents the type if instance being returned or requested 
via the API.
+type InstanceType string
+
+// InstanceTypeAny defines the instance type value for requesting any instance 
type.
+const InstanceTypeAny = InstanceType("")
+
+// InstanceTypeContainer defines the instance type value for a container.
+const InstanceTypeContainer = InstanceType("container")
+
+// InstancesPost represents the fields available for a new LXD instance.
+//
+// API extension: instances
+type InstancesPost struct {
+       InstancePut `yaml:",inline"`
+
+       Name         string         `json:"name" yaml:"name"`
+       Source       InstanceSource `json:"source" yaml:"source"`
+       InstanceType string         `json:"instance_type" yaml:"instance_type"`
+       Type         string         `json:"type" yaml:"type"`
+}
+
+// InstancePost represents the fields required to rename/move a LXD instance.
+//
+// API extension: instances
+type InstancePost struct {
+       Name          string              `json:"name" yaml:"name"`
+       Migration     bool                `json:"migration" yaml:"migration"`
+       Live          bool                `json:"live" yaml:"live"`
+       ContainerOnly bool                `json:"container_only" 
yaml:"container_only"`
+       Target        *InstancePostTarget `json:"target" yaml:"target"`
+}
+
+// InstancePostTarget represents the migration target host and operation.
+//
+// API extension: instances
+type InstancePostTarget struct {
+       Certificate string            `json:"certificate" yaml:"certificate"`
+       Operation   string            `json:"operation,omitempty" 
yaml:"operation,omitempty"`
+       Websockets  map[string]string `json:"secrets,omitempty" 
yaml:"secrets,omitempty"`
+}
+
+// InstancePut represents the modifiable fields of a LXD instance.
+//
+// API extension: instances
+type InstancePut struct {
+       Architecture string                       `json:"architecture" 
yaml:"architecture"`
+       Config       map[string]string            `json:"config" yaml:"config"`
+       Devices      map[string]map[string]string `json:"devices" 
yaml:"devices"`
+       Ephemeral    bool                         `json:"ephemeral" 
yaml:"ephemeral"`
+       Profiles     []string                     `json:"profiles" 
yaml:"profiles"`
+       Restore      string                       `json:"restore,omitempty" 
yaml:"restore,omitempty"`
+       Stateful     bool                         `json:"stateful" 
yaml:"stateful"`
+       Description  string                       `json:"description" 
yaml:"description"`
+}
+
+// Instance represents a LXD instance.
+//
+// API extension: instances
+type Instance struct {
+       InstancePut `yaml:",inline"`
+
+       CreatedAt       time.Time                    `json:"created_at" 
yaml:"created_at"`
+       ExpandedConfig  map[string]string            `json:"expanded_config" 
yaml:"expanded_config"`
+       ExpandedDevices map[string]map[string]string `json:"expanded_devices" 
yaml:"expanded_devices"`
+       Name            string                       `json:"name" yaml:"name"`
+       Status          string                       `json:"status" 
yaml:"status"`
+       StatusCode      StatusCode                   `json:"status_code" 
yaml:"status_code"`
+       LastUsedAt      time.Time                    `json:"last_used_at" 
yaml:"last_used_at"`
+       Location        string                       `json:"location" 
yaml:"location"`
+       Type            string                       `json:"type" yaml:"type"`
+}
+
+// InstanceFull is a combination of Instance, InstanceBackup, InstanceState 
and InstanceSnapshot.
+//
+// API extension: instances
+type InstanceFull struct {
+       Instance `yaml:",inline"`
+
+       Backups   []InstanceBackup   `json:"backups" yaml:"backups"`
+       State     *InstanceState     `json:"state" yaml:"state"`
+       Snapshots []InstanceSnapshot `json:"snapshots" yaml:"snapshots"`
+}
+
+// Writable converts a full Instance struct into a InstancePut struct (filters 
read-only fields).
+//
+// API extension: instances
+func (c *Instance) Writable() InstancePut {
+       return c.InstancePut
+}
+
+// IsActive checks whether the instance state indicates the instance is active.
+//
+// API extension: instances
+func (c Instance) IsActive() bool {
+       switch c.StatusCode {
+       case Stopped:
+               return false
+       case Error:
+               return false
+       default:
+               return true
+       }
+}
+
+// InstanceSource represents the creation source for a new instance.
+//
+// API extension: instances
+type InstanceSource struct {
+       Type          string            `json:"type" yaml:"type"`
+       Certificate   string            `json:"certificate" yaml:"certificate"`
+       Alias         string            `json:"alias,omitempty" 
yaml:"alias,omitempty"`
+       Fingerprint   string            `json:"fingerprint,omitempty" 
yaml:"fingerprint,omitempty"`
+       Properties    map[string]string `json:"properties,omitempty" 
yaml:"properties,omitempty"`
+       Server        string            `json:"server,omitempty" 
yaml:"server,omitempty"`
+       Secret        string            `json:"secret,omitempty" 
yaml:"secret,omitempty"`
+       Protocol      string            `json:"protocol,omitempty" 
yaml:"protocol,omitempty"`
+       BaseImage     string            `json:"base-image,omitempty" 
yaml:"base-image,omitempty"`
+       Mode          string            `json:"mode,omitempty" 
yaml:"mode,omitempty"`
+       Operation     string            `json:"operation,omitempty" 
yaml:"operation,omitempty"`
+       Websockets    map[string]string `json:"secrets,omitempty" 
yaml:"secrets,omitempty"`
+       Source        string            `json:"source,omitempty" 
yaml:"source,omitempty"`
+       Live          bool              `json:"live,omitempty" 
yaml:"live,omitempty"`
+       ContainerOnly bool              `json:"container_only,omitempty" 
yaml:"container_only,omitempty"`
+       Refresh       bool              `json:"refresh,omitempty" 
yaml:"refresh,omitempty"`
+       Project       string            `json:"project,omitempty" 
yaml:"project,omitempty"`
+}
diff --git a/shared/api/instance_backup.go b/shared/api/instance_backup.go
new file mode 100644
index 0000000000..64b282337b
--- /dev/null
+++ b/shared/api/instance_backup.go
@@ -0,0 +1,31 @@
+package api
+
+import "time"
+
+// InstanceBackupsPost represents the fields available for a new LXD instance 
backup.
+//
+// API extension: instances
+type InstanceBackupsPost struct {
+       Name             string    `json:"name" yaml:"name"`
+       ExpiresAt        time.Time `json:"expires_at" yaml:"expires_at"`
+       ContainerOnly    bool      `json:"container_only" yaml:"container_only"`
+       OptimizedStorage bool      `json:"optimized_storage" 
yaml:"optimized_storage"`
+}
+
+// InstanceBackup represents a LXD instance backup.
+//
+// API extension: instances
+type InstanceBackup struct {
+       Name             string    `json:"name" yaml:"name"`
+       CreatedAt        time.Time `json:"created_at" yaml:"created_at"`
+       ExpiresAt        time.Time `json:"expires_at" yaml:"expires_at"`
+       ContainerOnly    bool      `json:"container_only" yaml:"container_only"`
+       OptimizedStorage bool      `json:"optimized_storage" 
yaml:"optimized_storage"`
+}
+
+// InstanceBackupPost represents the fields available for the renaming of a 
instance backup.
+//
+// API extension: instances
+type InstanceBackupPost struct {
+       Name string `json:"name" yaml:"name"`
+}
diff --git a/shared/api/instance_console.go b/shared/api/instance_console.go
new file mode 100644
index 0000000000..614beb1245
--- /dev/null
+++ b/shared/api/instance_console.go
@@ -0,0 +1,17 @@
+package api
+
+// InstanceConsoleControl represents a message on the instance console 
"control" socket.
+//
+// API extension: instances
+type InstanceConsoleControl struct {
+       Command string            `json:"command" yaml:"command"`
+       Args    map[string]string `json:"args" yaml:"args"`
+}
+
+// InstanceConsolePost represents a LXD instance console request.
+//
+// API extension: instances
+type InstanceConsolePost struct {
+       Width  int `json:"width" yaml:"width"`
+       Height int `json:"height" yaml:"height"`
+}
diff --git a/shared/api/instance_exec.go b/shared/api/instance_exec.go
new file mode 100644
index 0000000000..4579b2c89d
--- /dev/null
+++ b/shared/api/instance_exec.go
@@ -0,0 +1,26 @@
+package api
+
+// InstanceExecControl represents a message on the instance exec "control" 
socket.
+//
+// API extension: instances
+type InstanceExecControl struct {
+       Command string            `json:"command" yaml:"command"`
+       Args    map[string]string `json:"args" yaml:"args"`
+       Signal  int               `json:"signal" yaml:"signal"`
+}
+
+// InstanceExecPost represents a LXD instance exec request.
+//
+// API extension: instances
+type InstanceExecPost struct {
+       Command      []string          `json:"command" yaml:"command"`
+       WaitForWS    bool              `json:"wait-for-websocket" 
yaml:"wait-for-websocket"`
+       Interactive  bool              `json:"interactive" yaml:"interactive"`
+       Environment  map[string]string `json:"environment" yaml:"environment"`
+       Width        int               `json:"width" yaml:"width"`
+       Height       int               `json:"height" yaml:"height"`
+       RecordOutput bool              `json:"record-output" 
yaml:"record-output"`
+       User         uint32            `json:"user" yaml:"user"`
+       Group        uint32            `json:"group" yaml:"group"`
+       Cwd          string            `json:"cwd" yaml:"cwd"`
+}
diff --git a/shared/api/instance_snapshot.go b/shared/api/instance_snapshot.go
new file mode 100644
index 0000000000..bdd93544b2
--- /dev/null
+++ b/shared/api/instance_snapshot.go
@@ -0,0 +1,60 @@
+package api
+
+import (
+       "time"
+)
+
+// InstanceSnapshotsPost represents the fields available for a new LXD 
instance snapshot.
+//
+// API extension: instances
+type InstanceSnapshotsPost struct {
+       Name     string `json:"name" yaml:"name"`
+       Stateful bool   `json:"stateful" yaml:"stateful"`
+
+       // API extension: snapshot_expiry_creation
+       ExpiresAt *time.Time `json:"expires_at" yaml:"expires_at"`
+}
+
+// InstanceSnapshotPost represents the fields required to rename/move a LXD 
instance snapshot.
+//
+// API extension: instances
+type InstanceSnapshotPost struct {
+       Name      string              `json:"name" yaml:"name"`
+       Migration bool                `json:"migration" yaml:"migration"`
+       Target    *InstancePostTarget `json:"target" yaml:"target"`
+       Live      bool                `json:"live,omitempty" 
yaml:"live,omitempty"`
+}
+
+// InstanceSnapshotPut represents the modifiable fields of a LXD instance 
snapshot.
+//
+// API extension: instances
+type InstanceSnapshotPut struct {
+       Architecture string                       `json:"architecture" 
yaml:"architecture"`
+       Config       map[string]string            `json:"config" yaml:"config"`
+       Devices      map[string]map[string]string `json:"devices" 
yaml:"devices"`
+       Ephemeral    bool                         `json:"ephemeral" 
yaml:"ephemeral"`
+       Profiles     []string                     `json:"profiles" 
yaml:"profiles"`
+       ExpiresAt    time.Time                    `json:"expires_at" 
yaml:"expires_at"`
+}
+
+// InstanceSnapshot represents a LXD instance snapshot.
+//
+// API extension: instances
+type InstanceSnapshot struct {
+       InstanceSnapshotPut `yaml:",inline"`
+
+       CreatedAt       time.Time                    `json:"created_at" 
yaml:"created_at"`
+       ExpandedConfig  map[string]string            `json:"expanded_config" 
yaml:"expanded_config"`
+       ExpandedDevices map[string]map[string]string `json:"expanded_devices" 
yaml:"expanded_devices"`
+       LastUsedAt      time.Time                    `json:"last_used_at" 
yaml:"last_used_at"`
+       Name            string                       `json:"name" yaml:"name"`
+       Stateful        bool                         `json:"stateful" 
yaml:"stateful"`
+}
+
+// Writable converts a full InstanceSnapshot struct into a InstanceSnapshotPut 
struct
+// (filters read-only fields).
+//
+// API extension: instances
+func (c *InstanceSnapshot) Writable() InstanceSnapshotPut {
+       return c.InstanceSnapshotPut
+}
diff --git a/shared/api/instance_state.go b/shared/api/instance_state.go
new file mode 100644
index 0000000000..cd7823cbac
--- /dev/null
+++ b/shared/api/instance_state.go
@@ -0,0 +1,84 @@
+package api
+
+// InstanceStatePut represents the modifiable fields of a LXD instance's state.
+//
+// API extension: instances
+type InstanceStatePut struct {
+       Action   string `json:"action" yaml:"action"`
+       Timeout  int    `json:"timeout" yaml:"timeout"`
+       Force    bool   `json:"force" yaml:"force"`
+       Stateful bool   `json:"stateful" yaml:"stateful"`
+}
+
+// InstanceState represents a LXD instance's state.
+//
+// API extension: instances
+type InstanceState struct {
+       Status     string                          `json:"status" yaml:"status"`
+       StatusCode StatusCode                      `json:"status_code" 
yaml:"status_code"`
+       Disk       map[string]InstanceStateDisk    `json:"disk" yaml:"disk"`
+       Memory     InstanceStateMemory             `json:"memory" yaml:"memory"`
+       Network    map[string]InstanceStateNetwork `json:"network" 
yaml:"network"`
+       Pid        int64                           `json:"pid" yaml:"pid"`
+       Processes  int64                           `json:"processes" 
yaml:"processes"`
+       CPU        InstanceStateCPU                `json:"cpu" yaml:"cpu"`
+}
+
+// InstanceStateDisk represents the disk information section of a LXD 
instance's state.
+//
+// API extension: instances
+type InstanceStateDisk struct {
+       Usage int64 `json:"usage" yaml:"usage"`
+}
+
+// InstanceStateCPU represents the cpu information section of a LXD instance's 
state.
+//
+// API extension: instances
+type InstanceStateCPU struct {
+       Usage int64 `json:"usage" yaml:"usage"`
+}
+
+// InstanceStateMemory represents the memory information section of a LXD 
instance's state.
+//
+// API extension: instances
+type InstanceStateMemory struct {
+       Usage         int64 `json:"usage" yaml:"usage"`
+       UsagePeak     int64 `json:"usage_peak" yaml:"usage_peak"`
+       SwapUsage     int64 `json:"swap_usage" yaml:"swap_usage"`
+       SwapUsagePeak int64 `json:"swap_usage_peak" yaml:"swap_usage_peak"`
+}
+
+// InstanceStateNetwork represents the network information section of a LXD 
instance's state.
+//
+// API extension: instances
+type InstanceStateNetwork struct {
+       Addresses []InstanceStateNetworkAddress `json:"addresses" 
yaml:"addresses"`
+       Counters  InstanceStateNetworkCounters  `json:"counters" 
yaml:"counters"`
+       Hwaddr    string                        `json:"hwaddr" yaml:"hwaddr"`
+       HostName  string                        `json:"host_name" 
yaml:"host_name"`
+       Mtu       int                           `json:"mtu" yaml:"mtu"`
+       State     string                        `json:"state" yaml:"state"`
+       Type      string                        `json:"type" yaml:"type"`
+}
+
+// InstanceStateNetworkAddress represents a network address as part of the 
network section of a LXD
+// instance's state.
+//
+// API extension: instances
+type InstanceStateNetworkAddress struct {
+       Family  string `json:"family" yaml:"family"`
+       Address string `json:"address" yaml:"address"`
+       Netmask string `json:"netmask" yaml:"netmask"`
+       Scope   string `json:"scope" yaml:"scope"`
+}
+
+// InstanceStateNetworkCounters represents packet counters as part of the 
network section of a LXD
+// instance's state.
+//
+// API extension: instances
+type InstanceStateNetworkCounters struct {
+       BytesReceived   int64 `json:"bytes_received" yaml:"bytes_received"`
+       BytesSent       int64 `json:"bytes_sent" yaml:"bytes_sent"`
+       PacketsReceived int64 `json:"packets_received" yaml:"packets_received"`
+       PacketsSent     int64 `json:"packets_sent" yaml:"packets_sent"`
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to