[lxc-devel] [lxd/master] Client Instances 2

tomponline on Github lxc-bot at linuxcontainers.org
Thu Sep 12 11:06:20 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 511 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190912/54273cd9/attachment-0001.bin>
-------------- next part --------------
From bdb0a4e19c57f51391609d83df1dd36f19fe155a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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.parrott at 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))
+}


More information about the lxc-devel mailing list