[lxc-devel] [lxd/master] Shared API Instances 2

tomponline on Github lxc-bot at linuxcontainers.org
Wed Sep 11 16:34:41 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 487 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190911/9de7dacd/attachment-0001.bin>
-------------- next part --------------
From 62949ba5eaec705943bdeb0adc8183958763f618 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 11 Sep 2019 09:44:29 +0100
Subject: [PATCH 01/18] lxd/instance/instance: Adds functions to convert
 to/from instance.Type and string

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

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

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

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

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

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

- Defines instance type value for containers.

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

- Supports instance.TypeAny if no filter is needed.

Signed-off-by: Thomas Parrott <thomas.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 92ae4f361700b45d696c60b45b35aab628e46f2c 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 13/18] 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 c59207c727a19c3c0ffc8449d8fd5ddb1511055e 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 14/18] 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 e09015ba1146087a8f47d9d0ba6fed5db79b186f 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 15/18] lxd: Updates use of
 ForwardedResponseIfContainerIsRemote to supply instanceType

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

Signed-off-by: Thomas Parrott <thomas.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 b2c6fc6382..b032d60d1d 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -884,7 +884,7 @@ func clusterCopyContainerInternal(d *Daemon, source container, project string, r
 		var err error
 
 		// Load source node.
-		nodeAddress, err = tx.ContainerNodeAddress(project, name)
+		nodeAddress, err = tx.ContainerNodeAddress(project, name, source.Type())
 		if err != nil {
 			return errors.Wrap(err, "Failed to get address of container's node")
 		}
diff --git a/lxd/images.go b/lxd/images.go
index 2ea8330c9f..383ae47643 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -23,12 +23,12 @@ import (
 
 	"github.com/gorilla/mux"
 	"github.com/pkg/errors"
-
 	"gopkg.in/yaml.v2"
 
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/node"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/task"
@@ -36,12 +36,11 @@ import (
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
+	log "github.com/lxc/lxd/shared/log15"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
 	"github.com/lxc/lxd/shared/version"
-
-	log "github.com/lxc/lxd/shared/log15"
 )
 
 var imagesCmd = APIEndpoint{
@@ -637,6 +636,12 @@ func imageCreateInPool(d *Daemon, info *api.Image, storagePool string) error {
 }
 
 func imagesPost(d *Daemon, r *http.Request) Response {
+	// Instance type.
+	instanceType := instance.TypeAny
+	if strings.HasPrefix(mux.CurrentRoute(r).GetName(), "container") {
+		instanceType = instance.TypeContainer
+	}
+
 	project := projectParam(r)
 
 	var err error
@@ -698,7 +703,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		if name != "" {
 			post.Seek(0, 0)
 			r.Body = post
-			response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name)
+			response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 			if err != nil {
 				cleanup(builddir, post)
 				return SmartError(err)

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

These will be moved to new instance.go file.

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

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

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

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

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

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

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


More information about the lxc-devel mailing list