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

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) ===
- Adds InstanceServer interface to `client` package which embeds old ContainerServer interface.
- Adds InstanceServer specific connection and factory functions.

Includes https://github.com/lxc/lxd/pull/6191
From bdb0a4e19c57f51391609d83df1dd36f19fe155a 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 1/9] 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 95c2715955df6f90095304cbb93a176302577eb9 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 2/9] 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 8d898dffcf39d1183cf47da5b7d6772037b41c30 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 3/9] 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 6d22940c5d1c6870ae5a22b98d7477406948f4b2 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 4/9] 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 106c1e3feb..e6858c0077 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -864,7 +864,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 74f72089d45036705b0d996efef7326d8de82b7f 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 5/9] lxd/instance/instance: Uses API instance types for string
 comparison

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

diff --git a/lxd/instance/instance.go b/lxd/instance/instance.go
index 2c3a497e87..2ae566c1b3 100644
--- a/lxd/instance/instance.go
+++ b/lxd/instance/instance.go
@@ -2,6 +2,8 @@ package instance
 
 import (
        "fmt"
+
+       "github.com/lxc/lxd/shared/api"
 )
 
 // Type indicates the type of instance.
@@ -19,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 == "container" || name == "" {
+       if api.InstanceType(name) == api.InstanceTypeContainer || name == "" {
                return TypeContainer, nil
        }
 
@@ -30,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 "container"
+               return string(api.InstanceTypeContainer)
        }
 
        return ""

From 3835468f0edfd6531d346b45ce464b06e71029cc 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 6/9] shared/api: Adds new instance types

- Removes earlier API extension comments.

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..7806b71adf
--- /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         InstanceType   `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"`
+}

From 4fcc56d0bd0008b5c6e9cbe213f34947bcc8bd52 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 11:57:02 +0100
Subject: [PATCH 7/9] client/connection: Adds connect functions that return
 InstanceServer type

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 client/connection.go | 30 +++++++++++++++++++++++++-----
 1 file changed, 25 insertions(+), 5 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index c9a25e1066..6cfa0fb199 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -51,14 +51,14 @@ type ConnectionArgs struct {
        SkipGetServer bool
 }
 
-// ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
+// ConnectLXDInstance lets you connect to a remote LXD daemon over HTTPs.
 //
 // A client certificate (TLSClientCert) and key (TLSClientKey) must be 
provided.
 //
 // If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must 
also be provided.
 //
 // Unless the remote server is trusted by the system CA, the remote 
certificate must be provided (TLSServerCert).
-func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
+func ConnectLXDInstance(url string, args *ConnectionArgs) (InstanceServer, 
error) {
        logger.Debugf("Connecting to a remote LXD over HTTPs")
 
        // Cleanup URL
@@ -67,12 +67,23 @@ func ConnectLXD(url string, args *ConnectionArgs) 
(ContainerServer, error) {
        return httpsLXD(url, args)
 }
 
-// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix 
socket.
+// ConnectLXD lets you connect to a remote LXD daemon over HTTPs.
+//
+// A client certificate (TLSClientCert) and key (TLSClientKey) must be 
provided.
+//
+// If connecting to a LXD daemon running in PKI mode, the PKI CA (TLSCA) must 
also be provided.
+//
+// Unless the remote server is trusted by the system CA, the remote 
certificate must be provided (TLSServerCert).
+func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
+       return ConnectLXDInstance(url, args)
+}
+
+// ConnectLXDInstanceUnix lets you connect to a remote LXD daemon over a local 
unix socket.
 //
 // If the path argument is empty, then $LXD_SOCKET will be used, if
 // unset $LXD_DIR/unix.socket will be used and if that one isn't set
 // either, then the path will default to /var/lib/lxd/unix.socket.
-func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, 
error) {
+func ConnectLXDInstanceUnix(path string, args *ConnectionArgs) 
(InstanceServer, error) {
        logger.Debugf("Connecting to a local LXD over a Unix socket")
 
        // Use empty args if not specified
@@ -122,6 +133,15 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) 
(ContainerServer, error)
        return &server, nil
 }
 
+// ConnectLXDUnix lets you connect to a remote LXD daemon over a local unix 
socket.
+//
+// If the path argument is empty, then $LXD_SOCKET will be used, if
+// unset $LXD_DIR/unix.socket will be used and if that one isn't set
+// either, then the path will default to /var/lib/lxd/unix.socket.
+func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, 
error) {
+       return ConnectLXDInstanceUnix(path, args)
+}
+
 // ConnectPublicLXD lets you connect to a remote public LXD daemon over HTTPs.
 //
 // Unless the remote server is trusted by the system CA, the remote 
certificate must be provided (TLSServerCert).
@@ -170,7 +190,7 @@ func ConnectSimpleStreams(url string, args *ConnectionArgs) 
(ImageServer, error)
 }
 
 // Internal function called by ConnectLXD and ConnectPublicLXD
-func httpsLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
+func httpsLXD(url string, args *ConnectionArgs) (InstanceServer, error) {
        // Use empty args if not specified
        if args == nil {
                args = &ConnectionArgs{}

From 056d14da15c48a1eb82cedf68a6f9375fcd77e6a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 11:57:49 +0100
Subject: [PATCH 8/9] client/interfaces: Creates InstanceServer interface

- Creates new arg types for instances and aliases existing types.

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 client/interfaces.go | 68 +++++++++++++++++++++++++++++++++-----------
 1 file changed, 52 insertions(+), 16 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 6e8cc7df3e..ae5cf82c9b 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -233,6 +233,16 @@ type ContainerServer interface {
        RawOperation(method string, path string, data interface{}, queryETag 
string) (op Operation, ETag string, err error)
 }
 
+// The InstanceServer type represents a full featured LXD server with 
Instances support.
+//
+// API extension: instances
+type InstanceServer interface {
+       ContainerServer
+
+       UseInstanceTarget(name string) (client InstanceServer)
+       UseInstanceProject(name string) (client InstanceServer)
+}
+
 // The ConnectionInfo struct represents general information for a connection
 type ConnectionInfo struct {
        Addresses   []string
@@ -243,8 +253,8 @@ type ConnectionInfo struct {
        Project     string
 }
 
-// The ContainerBackupArgs struct is used when creating a container from a 
backup
-type ContainerBackupArgs struct {
+// The InstanceBackupArgs struct is used when creating a container from a 
backup
+type InstanceBackupArgs struct {
        // The backup file
        BackupFile io.Reader
 
@@ -252,6 +262,9 @@ type ContainerBackupArgs struct {
        PoolName string
 }
 
+// The ContainerBackupArgs struct is used when creating a container from a 
backup
+type ContainerBackupArgs InstanceBackupArgs
+
 // The BackupFileRequest struct is used for a backup download request
 type BackupFileRequest struct {
        // Writer for the backup file
@@ -356,8 +369,8 @@ type StoragePoolVolumeMoveArgs struct {
        StoragePoolVolumeCopyArgs
 }
 
-// The ContainerCopyArgs struct is used to pass additional options during 
container copy
-type ContainerCopyArgs struct {
+// The InstanceCopyArgs struct is used to pass additional options during 
container copy
+type InstanceCopyArgs struct {
        // If set, the container will be renamed on copy
        Name string
 
@@ -375,8 +388,11 @@ type ContainerCopyArgs struct {
        Refresh bool
 }
 
-// The ContainerSnapshotCopyArgs struct is used to pass additional options 
during container copy
-type ContainerSnapshotCopyArgs struct {
+// The ContainerCopyArgs struct is used to pass additional options during 
container copy
+type ContainerCopyArgs InstanceCopyArgs
+
+// The InstanceSnapshotCopyArgs struct is used to pass additional options 
during container copy
+type InstanceSnapshotCopyArgs struct {
        // If set, the container will be renamed on copy
        Name string
 
@@ -388,9 +404,12 @@ type ContainerSnapshotCopyArgs struct {
        Live bool
 }
 
-// The ContainerConsoleArgs struct is used to pass additional options during a
+// The ContainerSnapshotCopyArgs struct is used to pass additional options 
during container copy
+type ContainerSnapshotCopyArgs InstanceSnapshotCopyArgs
+
+// The InstanceConsoleArgs struct is used to pass additional options during a
 // container console session
-type ContainerConsoleArgs struct {
+type InstanceConsoleArgs struct {
        // Bidirectional fd to pass to the container
        Terminal io.ReadWriteCloser
 
@@ -401,13 +420,21 @@ type ContainerConsoleArgs struct {
        ConsoleDisconnect chan bool
 }
 
-// The ContainerConsoleLogArgs struct is used to pass additional options 
during a
+// The ContainerConsoleArgs struct is used to pass additional options during a
+// container console session
+type ContainerConsoleArgs InstanceConsoleArgs
+
+// The InstanceConsoleLogArgs struct is used to pass additional options during 
a
 // container console log request
-type ContainerConsoleLogArgs struct {
+type InstanceConsoleLogArgs struct {
 }
 
-// The ContainerExecArgs struct is used to pass additional options during 
container exec
-type ContainerExecArgs struct {
+// The ContainerConsoleLogArgs struct is used to pass additional options 
during a
+// container console log request
+type ContainerConsoleLogArgs InstanceConsoleLogArgs
+
+// The InstanceExecArgs struct is used to pass additional options during 
container exec
+type InstanceExecArgs struct {
        // Standard input
        Stdin io.ReadCloser
 
@@ -424,8 +451,11 @@ type ContainerExecArgs struct {
        DataDone chan bool
 }
 
-// The ContainerFileArgs struct is used to pass the various options for a 
container file upload
-type ContainerFileArgs struct {
+// The ContainerExecArgs struct is used to pass additional options during 
container exec
+type ContainerExecArgs InstanceExecArgs
+
+// The InstanceFileArgs struct is used to pass the various options for a 
container file upload
+type InstanceFileArgs struct {
        // File content
        Content io.ReadSeeker
 
@@ -445,8 +475,11 @@ type ContainerFileArgs struct {
        WriteMode string
 }
 
-// The ContainerFileResponse struct is used as part of the response for a 
container file download
-type ContainerFileResponse struct {
+// The ContainerFileArgs struct is used to pass the various options for a 
container file upload
+type ContainerFileArgs InstanceFileArgs
+
+// The InstanceFileResponse struct is used as part of the response for a 
container file download
+type InstanceFileResponse struct {
        // User id that owns the file
        UID int64
 
@@ -462,3 +495,6 @@ type ContainerFileResponse struct {
        // If a directory, the list of files inside it
        Entries []string
 }
+
+// The ContainerFileResponse struct is used as part of the response for a 
container file download
+type ContainerFileResponse InstanceFileResponse

From fd53023acfd4a307a6263c79e97f299d75232639 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 11:58:41 +0100
Subject: [PATCH 9/9] client/lxd/server: Adds UseInstanceTarget and
 UseInstanceProject functions

And aliases existing functions to return old type.

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

diff --git a/client/lxd_server.go b/client/lxd_server.go
index 4cc20cc1c6..c1f3ec656c 100644
--- a/client/lxd_server.go
+++ b/client/lxd_server.go
@@ -89,8 +89,8 @@ func (r *ProtocolLXD) GetServerResources() (*api.Resources, 
error) {
        return &resources, nil
 }
 
-// UseProject returns a client that will use a specific project.
-func (r *ProtocolLXD) UseProject(name string) ContainerServer {
+// UseInstanceProject returns a client that will use a specific project.
+func (r *ProtocolLXD) UseInstanceProject(name string) InstanceServer {
        return &ProtocolLXD{
                server:               r.server,
                http:                 r.http,
@@ -106,10 +106,15 @@ func (r *ProtocolLXD) UseProject(name string) 
ContainerServer {
        }
 }
 
-// UseTarget returns a client that will target a specific cluster member.
+// UseProject returns a client that will use a specific project.
+func (r *ProtocolLXD) UseProject(name string) ContainerServer {
+       return ContainerServer(r.UseInstanceProject(name))
+}
+
+// UseInstanceTarget returns a client that will target a specific cluster 
member.
 // Use this member-specific operations such as specific container
 // placement, preparing a new storage pool or network, ...
-func (r *ProtocolLXD) UseTarget(name string) ContainerServer {
+func (r *ProtocolLXD) UseInstanceTarget(name string) InstanceServer {
        return &ProtocolLXD{
                server:               r.server,
                http:                 r.http,
@@ -124,3 +129,10 @@ func (r *ProtocolLXD) UseTarget(name string) 
ContainerServer {
                clusterTarget:        name,
        }
 }
+
+// UseTarget returns a client that will target a specific cluster member.
+// Use this member-specific operations such as specific container
+// placement, preparing a new storage pool or network, ...
+func (r *ProtocolLXD) UseTarget(name string) ContainerServer {
+       return ContainerServer(r.UseInstanceTarget(name))
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to