[lxc-devel] [lxd/master] lxd: Switch over to instance types

tomponline on Github lxc-bot at linuxcontainers.org
Fri Sep 13 13:22:37 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190913/6fe70672/attachment-0001.bin>
-------------- next part --------------
From 78a8703a0829f917d2a5b84f5e167459e3489cb5 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 13 Sep 2019 10:44:07 +0100
Subject: [PATCH 1/5] shared/api/instance: Adds InstanceOnly field to
 InstancePost and InstanceSource

- Deprecates ContainerOnly field.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/api/instance.go        | 6 ++++--
 shared/api/instance_backup.go | 6 ++++--
 2 files changed, 8 insertions(+), 4 deletions(-)

diff --git a/shared/api/instance.go b/shared/api/instance.go
index 7806b71adf..f94fbc7501 100644
--- a/shared/api/instance.go
+++ b/shared/api/instance.go
@@ -32,7 +32,8 @@ 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"`
+	InstanceOnly  bool                `json:"instance_only" yaml:"instance_only"`
+	ContainerOnly bool                `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
 	Target        *InstancePostTarget `json:"target" yaml:"target"`
 }
 
@@ -126,7 +127,8 @@ type InstanceSource struct {
 	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"`
+	InstanceOnly  bool              `json:"instance_only,omitempty" yaml:"instance_only,omitempty"`
+	ContainerOnly bool              `json:"container_only,omitempty" yaml:"container_only,omitempty"` // Deprecated, use InstanceOnly.
 	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
index 64b282337b..afd143c982 100644
--- a/shared/api/instance_backup.go
+++ b/shared/api/instance_backup.go
@@ -8,7 +8,8 @@ import "time"
 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"`
+	InstanceOnly     bool      `json:"instance_only" yaml:"instance_only"`
+	ContainerOnly    bool      `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
 	OptimizedStorage bool      `json:"optimized_storage" yaml:"optimized_storage"`
 }
 
@@ -19,7 +20,8 @@ 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"`
+	InstanceOnly     bool      `json:"instance_only" yaml:"instance_only"`
+	ContainerOnly    bool      `json:"container_only" yaml:"container_only"` // Deprecated, use InstanceOnly.
 	OptimizedStorage bool      `json:"optimized_storage" yaml:"optimized_storage"`
 }
 

From 5c8d3898510ba2cc982f821f453c312dfaf560dd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 15:46:44 +0100
Subject: [PATCH 2/5] client/interfaces: Populates InstanceServer with rest of
 functions

- Also adds structs used for arguments to the Instance functions.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 client/interfaces.go | 191 +++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 182 insertions(+), 9 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 2fe2e1aadb..a80428f7de 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -136,6 +136,63 @@ type InstanceServer interface {
 	UpdateContainerTemplateFile(containerName string, templateName string, content io.ReadSeeker) (err error)
 	DeleteContainerTemplateFile(name string, templateName string) (err error)
 
+	// Instance functions.
+	GetInstanceNames(instanceType api.InstanceType) (names []string, err error)
+	GetInstances(instanceType api.InstanceType) (instances []api.Instance, err error)
+	GetInstancesFull(instanceType api.InstanceType) (instances []api.InstanceFull, err error)
+	GetInstance(name string) (instance *api.Instance, ETag string, err error)
+	CreateInstance(instance api.InstancesPost) (op Operation, err error)
+	CreateInstanceFromImage(source ImageServer, image api.Image, imgcontainer api.InstancesPost) (op RemoteOperation, err error)
+	CopyInstance(source InstanceServer, instance api.Instance, args *InstanceCopyArgs) (op RemoteOperation, err error)
+	UpdateInstance(name string, instance api.InstancePut, ETag string) (op Operation, err error)
+	RenameInstance(name string, instance api.InstancePost) (op Operation, err error)
+	MigrateInstance(name string, instance api.InstancePost) (op Operation, err error)
+	DeleteInstance(name string) (op Operation, err error)
+
+	ExecInstance(instanceName string, exec api.InstanceExecPost, args *InstanceExecArgs) (op Operation, err error)
+	ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (op Operation, err error)
+	GetInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (content io.ReadCloser, err error)
+	DeleteInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (err error)
+
+	GetInstanceFile(instanceName string, path string) (content io.ReadCloser, resp *InstanceFileResponse, err error)
+	CreateInstanceFile(instanceName string, path string, args InstanceFileArgs) (err error)
+	DeleteInstanceFile(instanceName string, path string) (err error)
+
+	GetInstanceSnapshotNames(instanceName string) (names []string, err error)
+	GetInstanceSnapshots(instanceName string) (snapshots []api.InstanceSnapshot, err error)
+	GetInstanceSnapshot(instanceName string, name string) (snapshot *api.InstanceSnapshot, ETag string, err error)
+	CreateInstanceSnapshot(instanceName string, snapshot api.InstanceSnapshotsPost) (op Operation, err error)
+	CopyInstanceSnapshot(source InstanceServer, instanceName string, snapshot api.InstanceSnapshot, args *InstanceSnapshotCopyArgs) (op RemoteOperation, err error)
+	RenameInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (op Operation, err error)
+	MigrateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (op Operation, err error)
+	DeleteInstanceSnapshot(instanceName string, name string) (op Operation, err error)
+	UpdateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPut, ETag string) (op Operation, err error)
+
+	GetInstanceBackupNames(instanceName string) (names []string, err error)
+	GetInstanceBackups(instanceName string) (backups []api.InstanceBackup, err error)
+	GetInstanceBackup(instanceName string, name string) (backup *api.InstanceBackup, ETag string, err error)
+	CreateInstanceBackup(instanceName string, backup api.InstanceBackupsPost) (op Operation, err error)
+	RenameInstanceBackup(instanceName string, name string, backup api.InstanceBackupPost) (op Operation, err error)
+	DeleteInstanceBackup(instanceName string, name string) (op Operation, err error)
+	GetInstanceBackupFile(instanceName string, name string, req *BackupFileRequest) (resp *BackupFileResponse, err error)
+	CreateInstanceFromBackup(args InstanceBackupArgs) (op Operation, err error)
+
+	GetInstanceState(name string) (state *api.InstanceState, ETag string, err error)
+	UpdateInstanceState(name string, state api.InstanceStatePut, ETag string) (op Operation, err error)
+
+	GetInstanceLogfiles(name string) (logfiles []string, err error)
+	GetInstanceLogfile(name string, filename string) (content io.ReadCloser, err error)
+	DeleteInstanceLogfile(name string, filename string) (err error)
+
+	GetInstanceMetadata(name string) (metadata *api.ImageMetadata, ETag string, err error)
+	SetInstanceMetadata(name string, metadata api.ImageMetadata, ETag string) (err error)
+
+	GetInstanceTemplateFiles(instanceName string) (templates []string, err error)
+	GetInstanceTemplateFile(instanceName string, templateName string) (content io.ReadCloser, err error)
+	CreateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) (err error)
+	UpdateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) (err error)
+	DeleteInstanceTemplateFile(name string, templateName string) (err error)
+
 	// Event handling functions
 	GetEvents() (listener *EventListener, err error)
 
@@ -233,7 +290,7 @@ type InstanceServer interface {
 	RawOperation(method string, path string, data interface{}, queryETag string) (op Operation, ETag string, err error)
 }
 
-// The ConnectionInfo struct represents general information for a connection
+// The ConnectionInfo struct represents general information for a connection.
 type ConnectionInfo struct {
 	Addresses   []string
 	Certificate string
@@ -243,7 +300,7 @@ type ConnectionInfo struct {
 	Project     string
 }
 
-// The BackupFileRequest struct is used for a backup download request
+// The BackupFileRequest struct is used for a backup download request.
 type BackupFileRequest struct {
 	// Writer for the backup file
 	BackupFile io.WriteSeeker
@@ -255,13 +312,13 @@ type BackupFileRequest struct {
 	Canceler *cancel.Canceler
 }
 
-// The BackupFileResponse struct is used as the response for backup downloads
+// The BackupFileResponse struct is used as the response for backup downloads.
 type BackupFileResponse struct {
 	// Size of backup file
 	Size int64
 }
 
-// The ImageCreateArgs struct is used for direct image upload
+// The ImageCreateArgs struct is used for direct image upload.
 type ImageCreateArgs struct {
 	// Reader for the meta file
 	MetaFile io.Reader
@@ -279,7 +336,7 @@ type ImageCreateArgs struct {
 	ProgressHandler func(progress ioprogress.ProgressData)
 }
 
-// The ImageFileRequest struct is used for an image download request
+// The ImageFileRequest struct is used for an image download request.
 type ImageFileRequest struct {
 	// Writer for the metadata file
 	MetaFile io.WriteSeeker
@@ -298,7 +355,7 @@ type ImageFileRequest struct {
 	DeltaSourceRetriever func(fingerprint string, file string) string
 }
 
-// The ImageFileResponse struct is used as the response for image downloads
+// The ImageFileResponse struct is used as the response for image downloads.
 type ImageFileResponse struct {
 	// Filename for the metadata file
 	MetaName string
@@ -313,7 +370,7 @@ type ImageFileResponse struct {
 	RootfsSize int64
 }
 
-// The ImageCopyArgs struct is used to pass additional options during image copy
+// The ImageCopyArgs struct is used to pass additional options during image copy.
 type ImageCopyArgs struct {
 	// Aliases to add to the copied image.
 	Aliases []api.ImageAlias
@@ -329,7 +386,7 @@ type ImageCopyArgs struct {
 }
 
 // The StoragePoolVolumeCopyArgs struct is used to pass additional options
-// during storage volume copy
+// during storage volume copy.
 type StoragePoolVolumeCopyArgs struct {
 	// New name for the target
 	Name string
@@ -342,7 +399,123 @@ type StoragePoolVolumeCopyArgs struct {
 }
 
 // The StoragePoolVolumeMoveArgs struct is used to pass additional options
-// during storage volume move
+// during storage volume move.
 type StoragePoolVolumeMoveArgs struct {
 	StoragePoolVolumeCopyArgs
 }
+
+// The InstanceBackupArgs struct is used when creating a instance from a backup.
+type InstanceBackupArgs struct {
+	// The backup file
+	BackupFile io.Reader
+
+	// Storage pool to use
+	PoolName string
+}
+
+// The InstanceCopyArgs struct is used to pass additional options during instance copy.
+type InstanceCopyArgs struct {
+	// If set, the instance will be renamed on copy
+	Name string
+
+	// If set, the instance running state will be transferred (live migration)
+	Live bool
+
+	// If set, only the instance will copied, its snapshots won't
+	InstanceOnly bool
+
+	// The transfer mode, can be "pull" (default), "push" or "relay"
+	Mode string
+
+	// API extension: container_incremental_copy
+	// Perform an incremental copy
+	Refresh bool
+}
+
+// The InstanceSnapshotCopyArgs struct is used to pass additional options during instance copy.
+type InstanceSnapshotCopyArgs struct {
+	// If set, the instance will be renamed on copy
+	Name string
+
+	// The transfer mode, can be "pull" (default), "push" or "relay"
+	Mode string
+
+	// API extension: container_snapshot_stateful_migration
+	// If set, the instance running state will be transferred (live migration)
+	Live bool
+}
+
+// The InstanceConsoleArgs struct is used to pass additional options during a
+// instance console session.
+type InstanceConsoleArgs struct {
+	// Bidirectional fd to pass to the instance
+	Terminal io.ReadWriteCloser
+
+	// Control message handler (window resize)
+	Control func(conn *websocket.Conn)
+
+	// Closing this Channel causes a disconnect from the instance's console
+	ConsoleDisconnect chan bool
+}
+
+// The InstanceConsoleLogArgs struct is used to pass additional options during a
+// instance console log request.
+type InstanceConsoleLogArgs struct {
+}
+
+// The InstanceExecArgs struct is used to pass additional options during instance exec.
+type InstanceExecArgs struct {
+	// Standard input
+	Stdin io.ReadCloser
+
+	// Standard output
+	Stdout io.WriteCloser
+
+	// Standard error
+	Stderr io.WriteCloser
+
+	// Control message handler (window resize, signals, ...)
+	Control func(conn *websocket.Conn)
+
+	// Channel that will be closed when all data operations are done
+	DataDone chan bool
+}
+
+// The InstanceFileArgs struct is used to pass the various options for a instance file upload.
+type InstanceFileArgs struct {
+	// File content
+	Content io.ReadSeeker
+
+	// User id that owns the file
+	UID int64
+
+	// Group id that owns the file
+	GID int64
+
+	// File permissions
+	Mode int
+
+	// File type (file or directory)
+	Type string
+
+	// File write mode (overwrite or append)
+	WriteMode string
+}
+
+// The InstanceFileResponse struct is used as part of the response for a instance file download.
+type InstanceFileResponse struct {
+	// User id that owns the file
+	UID int64
+
+	// Group id that owns the file
+	GID int64
+
+	// File permissions
+	Mode int
+
+	// File type (file or directory)
+	Type string
+
+	// If a directory, the list of files inside it
+	Entries []string
+}

From 2f56959aadff63472b949130106b44582de39b2a Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 15:50:34 +0100
Subject: [PATCH 3/5] client/lxd/instances: Adds instance related functions

These functions use the /1.0/instances endpoints on the server.

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 client/lxd_instances.go | 2156 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 2156 insertions(+)
 create mode 100644 client/lxd_instances.go

diff --git a/client/lxd_instances.go b/client/lxd_instances.go
new file mode 100644
index 0000000000..07228f54bb
--- /dev/null
+++ b/client/lxd_instances.go
@@ -0,0 +1,2156 @@
+package lxd
+
+import (
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+	"net/url"
+	"strings"
+
+	"github.com/gorilla/websocket"
+
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/cancel"
+	"github.com/lxc/lxd/shared/ioprogress"
+	"github.com/lxc/lxd/shared/units"
+)
+
+// Instance handling functions.
+
+// instanceTypeToPath converts the instance type to a URL path prefix and query string values.
+// If the remote server doesn't have the instances extension then the /containers endpoint is used
+// as long as the requested instanceType is any or container.
+func (r *ProtocolLXD) instanceTypeToPath(instanceType api.InstanceType) (string, url.Values, error) {
+	v := url.Values{}
+
+	// If the remote server doesn't support instances extension, check that only containers
+	// or any type has been requested and then fallback to using the old /containers endpoint.
+	if !r.HasExtension("instances") {
+		if instanceType == api.InstanceTypeContainer || instanceType == api.InstanceTypeAny {
+			return "/containers", v, nil
+		}
+
+		return "", v, fmt.Errorf("Requested instance type not supported by server")
+	}
+
+	// If a specific instance type has been requested, add the instance-type filter parameter
+	// to the returned URL values so that it can be used in the final URL if needed to filter
+	// the result set being returned.
+	if instanceType != api.InstanceTypeAny {
+		v.Set("instance-type", string(instanceType))
+	}
+
+	return "/instances", v, nil
+}
+
+// GetInstanceNames returns a list of instance names.
+func (r *ProtocolLXD) GetInstanceNames(instanceType api.InstanceType) ([]string, error) {
+	urls := []string{}
+
+	path, v, err := r.instanceTypeToPath(instanceType)
+	if err != nil {
+		return nil, err
+	}
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	prefix := path + "/"
+	for _, url := range urls {
+		fields := strings.Split(url, prefix)
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetInstances returns a list of instances.
+func (r *ProtocolLXD) GetInstances(instanceType api.InstanceType) ([]api.Instance, error) {
+	instances := []api.Instance{}
+
+	path, v, err := r.instanceTypeToPath(instanceType)
+	if err != nil {
+		return nil, err
+	}
+
+	v.Set("recursion", "1")
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &instances)
+	if err != nil {
+		return nil, err
+	}
+
+	return instances, nil
+}
+
+// GetInstancesFull returns a list of instances including snapshots, backups and state.
+func (r *ProtocolLXD) GetInstancesFull(instanceType api.InstanceType) ([]api.InstanceFull, error) {
+	instances := []api.InstanceFull{}
+
+	path, v, err := r.instanceTypeToPath(instanceType)
+	if err != nil {
+		return nil, err
+	}
+
+	v.Set("recursion", "2")
+
+	if !r.HasExtension("container_full") {
+		return nil, fmt.Errorf("The server is missing the required \"container_full\" API extension")
+	}
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s?%s", path, v.Encode()), nil, "", &instances)
+	if err != nil {
+		return nil, err
+	}
+
+	return instances, nil
+}
+
+// GetInstance returns the instance entry for the provided name.
+func (r *ProtocolLXD) GetInstance(name string) (*api.Instance, string, error) {
+	instance := api.Instance{}
+
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, "", err
+	}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "", &instance)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &instance, etag, nil
+}
+
+// CreateInstanceFromBackup is a convenience function to make it easier to
+// create a instance from a backup
+func (r *ProtocolLXD) CreateInstanceFromBackup(args InstanceBackupArgs) (Operation, error) {
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if args.PoolName == "" {
+		// Send the request
+		op, _, err := r.queryOperation("POST", path, args.BackupFile, "")
+		if err != nil {
+			return nil, err
+		}
+
+		return op, nil
+	}
+
+	if !r.HasExtension("container_backup_override_pool") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup_override_pool\" API extension")
+	}
+
+	// Prepare the HTTP request
+	reqURL, err := r.setQueryAttributes(fmt.Sprintf("%s/1.0%s", r.httpHost, path))
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("POST", reqURL, args.BackupFile)
+	if err != nil {
+		return nil, err
+	}
+
+	req.Header.Set("Content-Type", "application/octet-stream")
+	req.Header.Set("X-LXD-pool", args.PoolName)
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return nil, err
+	}
+	defer resp.Body.Close()
+
+	// Handle errors
+	response, _, err := lxdParseResponse(resp)
+	if err != nil {
+		return nil, err
+	}
+
+	// Get to the operation
+	respOperation, err := response.MetadataAsOperation()
+	if err != nil {
+		return nil, err
+	}
+
+	// Setup an Operation wrapper
+	op := operation{
+		Operation: *respOperation,
+		r:         r,
+		chActive:  make(chan bool),
+	}
+
+	return &op, nil
+}
+
+// CreateInstance requests that LXD creates a new instance.
+func (r *ProtocolLXD) CreateInstance(instance api.InstancesPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(instance.Type)
+	if err != nil {
+		return nil, err
+	}
+
+	if instance.Source.InstanceOnly || instance.Source.ContainerOnly {
+		if !r.HasExtension("container_only_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		}
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", path, instance, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+func (r *ProtocolLXD) tryCreateInstance(req api.InstancesPost, urls []string) (RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The source server isn't listening on the network")
+	}
+
+	rop := remoteOperation{
+		chDone: make(chan bool),
+	}
+
+	operation := req.Source.Operation
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := map[string]error{}
+		for _, serverURL := range urls {
+			if operation == "" {
+				req.Source.Server = serverURL
+			} else {
+				req.Source.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+			}
+
+			op, err := r.CreateInstance(req)
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = remoteOperationError("Failed instance creation", errors)
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
+// CreateInstanceFromImage is a convenience function to make it easier to create a instance from an existing image.
+func (r *ProtocolLXD) CreateInstanceFromImage(source ImageServer, image api.Image, req api.InstancesPost) (RemoteOperation, error) {
+	// Set the minimal source fields
+	req.Source.Type = "image"
+
+	// Optimization for the local image case
+	if r == source {
+		// Always use fingerprints for local case
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+
+		op, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := remoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Minimal source fields for remote image
+	req.Source.Mode = "pull"
+
+	// If we have an alias and the image is public, use that
+	if req.Source.Alias != "" && image.Public {
+		req.Source.Fingerprint = ""
+	} else {
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	req.Source.Protocol = info.Protocol
+	req.Source.Certificate = info.Certificate
+
+	// Generate secret token if needed
+	if !image.Public {
+		secret, err := source.GetImageSecret(image.Fingerprint)
+		if err != nil {
+			return nil, err
+		}
+
+		req.Source.Secret = secret
+	}
+
+	return r.tryCreateInstance(req, info.Addresses)
+}
+
+// CopyInstance copies a instance from a remote server. Additional options can be passed using InstanceCopyArgs.
+func (r *ProtocolLXD) CopyInstance(source InstanceServer, instance api.Instance, args *InstanceCopyArgs) (RemoteOperation, error) {
+	// Base request
+	req := api.InstancesPost{
+		Name:        instance.Name,
+		InstancePut: instance.Writable(),
+	}
+	req.Source.BaseImage = instance.Config["volatile.base_image"]
+
+	// Process the copy arguments
+	if args != nil {
+		// Sanity checks
+		if args.InstanceOnly {
+			if !r.HasExtension("container_only_migration") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_only_migration\" API extension")
+			}
+
+			if !source.HasExtension("container_only_migration") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_only_migration\" API extension")
+			}
+		}
+
+		if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+			if !r.HasExtension("container_push") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+			}
+
+			if !source.HasExtension("container_push") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+			}
+		}
+
+		if args.Mode == "push" && !source.HasExtension("container_push_target") {
+			return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
+		}
+
+		if args.Refresh {
+			if !r.HasExtension("container_incremental_copy") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_incremental_copy\" API extension")
+			}
+
+			if !source.HasExtension("container_incremental_copy") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_incremental_copy\" API extension")
+			}
+		}
+
+		// Allow overriding the target name
+		if args.Name != "" {
+			req.Name = args.Name
+		}
+
+		req.Source.Live = args.Live
+		req.Source.InstanceOnly = args.InstanceOnly
+		req.Source.ContainerOnly = args.InstanceOnly // For legacy servers.
+		req.Source.Refresh = args.Refresh
+	}
+
+	if req.Source.Live {
+		req.Source.Live = instance.StatusCode == api.Running
+	}
+
+	sourceInfo, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get source connection info: %v", err)
+	}
+
+	destInfo, err := r.GetConnectionInfo()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get destination connection info: %v", err)
+	}
+
+	// Optimization for the local copy case
+	if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || instance.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
+		// Project handling
+		if destInfo.Project != sourceInfo.Project {
+			if !r.HasExtension("container_copy_project") {
+				return nil, fmt.Errorf("The server is missing the required \"container_copy_project\" API extension")
+			}
+
+			req.Source.Project = sourceInfo.Project
+		}
+
+		// Local copy source fields
+		req.Source.Type = "copy"
+		req.Source.Source = instance.Name
+
+		// Copy the instance
+		op, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := remoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Source request
+	sourceReq := api.InstancePost{
+		Migration:     true,
+		Live:          req.Source.Live,
+		ContainerOnly: req.Source.ContainerOnly, // Deprecated, use InstanceOnly.
+		InstanceOnly:  req.Source.InstanceOnly,
+	}
+
+	// Push mode migration
+	if args != nil && args.Mode == "push" {
+		// Get target server connection information
+		info, err := r.GetConnectionInfo()
+		if err != nil {
+			return nil, err
+		}
+
+		// Create the instance
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+		req.Source.Refresh = args.Refresh
+
+		op, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+
+		opAPI := op.Get()
+
+		targetSecrets := map[string]string{}
+		for k, v := range opAPI.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Prepare the source request
+		target := api.InstancePostTarget{}
+		target.Operation = opAPI.ID
+		target.Websockets = targetSecrets
+		target.Certificate = info.Certificate
+		sourceReq.Target = &target
+
+		return r.tryMigrateInstance(source, instance.Name, sourceReq, info.Addresses)
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	op, err := source.MigrateInstance(instance.Name, sourceReq)
+	if err != nil {
+		return nil, err
+	}
+	opAPI := op.Get()
+
+	sourceSecrets := map[string]string{}
+	for k, v := range opAPI.Metadata {
+		sourceSecrets[k] = v.(string)
+	}
+
+	// Relay mode migration
+	if args != nil && args.Mode == "relay" {
+		// Push copy source fields
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		// Start the process
+		targetOp, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+		targetOpAPI := targetOp.Get()
+
+		// Extract the websockets
+		targetSecrets := map[string]string{}
+		for k, v := range targetOpAPI.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Launch the relay
+		err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
+		if err != nil {
+			return nil, err
+		}
+
+		// Prepare a tracking operation
+		rop := remoteOperation{
+			targetOp: targetOp,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Pull mode migration
+	req.Source.Type = "migration"
+	req.Source.Mode = "pull"
+	req.Source.Operation = opAPI.ID
+	req.Source.Websockets = sourceSecrets
+	req.Source.Certificate = info.Certificate
+
+	return r.tryCreateInstance(req, info.Addresses)
+}
+
+func (r *ProtocolLXD) proxyInstanceMigration(targetOp *operation, targetSecrets map[string]string, source InstanceServer, sourceOp *operation, sourceSecrets map[string]string) error {
+	// Sanity checks
+	for n := range targetSecrets {
+		_, ok := sourceSecrets[n]
+		if !ok {
+			return fmt.Errorf("Migration target expects the \"%s\" socket but source isn't providing it", n)
+		}
+	}
+
+	if targetSecrets["control"] == "" {
+		return fmt.Errorf("Migration target didn't setup the required \"control\" socket")
+	}
+
+	// Struct used to hold everything together
+	type proxy struct {
+		done       chan bool
+		sourceConn *websocket.Conn
+		targetConn *websocket.Conn
+	}
+
+	proxies := map[string]*proxy{}
+
+	// Connect the control socket
+	sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets["control"])
+	if err != nil {
+		return err
+	}
+
+	targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets["control"])
+	if err != nil {
+		return err
+	}
+
+	proxies["control"] = &proxy{
+		done:       shared.WebsocketProxy(sourceConn, targetConn),
+		sourceConn: sourceConn,
+		targetConn: targetConn,
+	}
+
+	// Connect the data sockets
+	for name := range sourceSecrets {
+		if name == "control" {
+			continue
+		}
+
+		// Handle resets (used for multiple objects)
+		sourceConn, err := source.GetOperationWebsocket(sourceOp.ID, sourceSecrets[name])
+		if err != nil {
+			break
+		}
+
+		targetConn, err := r.GetOperationWebsocket(targetOp.ID, targetSecrets[name])
+		if err != nil {
+			break
+		}
+
+		proxies[name] = &proxy{
+			sourceConn: sourceConn,
+			targetConn: targetConn,
+			done:       shared.WebsocketProxy(sourceConn, targetConn),
+		}
+	}
+
+	// Cleanup once everything is done
+	go func() {
+		// Wait for control socket
+		<-proxies["control"].done
+		proxies["control"].sourceConn.Close()
+		proxies["control"].targetConn.Close()
+
+		// Then deal with the others
+		for name, proxy := range proxies {
+			if name == "control" {
+				continue
+			}
+
+			<-proxy.done
+			proxy.sourceConn.Close()
+			proxy.targetConn.Close()
+		}
+	}()
+
+	return nil
+}
+
+// UpdateInstance updates the instance definition.
+func (r *ProtocolLXD) UpdateInstance(name string, instance api.InstancePut, ETag string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, ETag)
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// RenameInstance requests that LXD renames the instance.
+func (r *ProtocolLXD) RenameInstance(name string, instance api.InstancePost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Sanity check
+	if instance.Migration {
+		return nil, fmt.Errorf("Can't ask for a migration through RenameInstance")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+func (r *ProtocolLXD) tryMigrateInstance(source InstanceServer, name string, req api.InstancePost, urls []string) (RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The target server isn't listening on the network")
+	}
+
+	rop := remoteOperation{
+		chDone: make(chan bool),
+	}
+
+	operation := req.Target.Operation
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := map[string]error{}
+		for _, serverURL := range urls {
+			req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+
+			op, err := source.MigrateInstance(name, req)
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = remoteOperationError("Failed instance migration", errors)
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
+// MigrateInstance requests that LXD prepares for a instance migration.
+func (r *ProtocolLXD) MigrateInstance(name string, instance api.InstancePost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if instance.InstanceOnly || instance.ContainerOnly {
+		if !r.HasExtension("container_only_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
+		}
+	}
+
+	// Sanity check
+	if !instance.Migration {
+		return nil, fmt.Errorf("Can't ask for a rename through MigrateInstance")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), instance, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// DeleteInstance requests that LXD deletes the instance.
+func (r *ProtocolLXD) DeleteInstance(name string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s", path, url.PathEscape(name)), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// ExecInstance requests that LXD spawns a command inside the instance.
+func (r *ProtocolLXD) ExecInstance(instanceName string, exec api.InstanceExecPost, args *InstanceExecArgs) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if exec.RecordOutput {
+		if !r.HasExtension("container_exec_recording") {
+			return nil, fmt.Errorf("The server is missing the required \"container_exec_recording\" API extension")
+		}
+	}
+
+	if exec.User > 0 || exec.Group > 0 || exec.Cwd != "" {
+		if !r.HasExtension("container_exec_user_group_cwd") {
+			return nil, fmt.Errorf("The server is missing the required \"container_exec_user_group_cwd\" API extension")
+		}
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/exec", path, url.PathEscape(instanceName)), exec, "")
+	if err != nil {
+		return nil, err
+	}
+	opAPI := op.Get()
+
+	// Process additional arguments
+	if args != nil {
+		// Parse the fds
+		fds := map[string]string{}
+
+		value, ok := opAPI.Metadata["fds"]
+		if ok {
+			values := value.(map[string]interface{})
+			for k, v := range values {
+				fds[k] = v.(string)
+			}
+		}
+
+		// Call the control handler with a connection to the control socket
+		if args.Control != nil && fds["control"] != "" {
+			conn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
+			if err != nil {
+				return nil, err
+			}
+
+			go args.Control(conn)
+		}
+
+		if exec.Interactive {
+			// Handle interactive sections
+			if args.Stdin != nil && args.Stdout != nil {
+				// Connect to the websocket
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+				if err != nil {
+					return nil, err
+				}
+
+				// And attach stdin and stdout to it
+				go func() {
+					shared.WebsocketSendStream(conn, args.Stdin, -1)
+					<-shared.WebsocketRecvStream(args.Stdout, conn)
+					conn.Close()
+
+					if args.DataDone != nil {
+						close(args.DataDone)
+					}
+				}()
+			} else {
+				if args.DataDone != nil {
+					close(args.DataDone)
+				}
+			}
+		} else {
+			// Handle non-interactive sessions
+			dones := map[int]chan bool{}
+			conns := []*websocket.Conn{}
+
+			// Handle stdin
+			if fds["0"] != "" {
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones[0] = shared.WebsocketSendStream(conn, args.Stdin, -1)
+			}
+
+			// Handle stdout
+			if fds["1"] != "" {
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["1"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones[1] = shared.WebsocketRecvStream(args.Stdout, conn)
+			}
+
+			// Handle stderr
+			if fds["2"] != "" {
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["2"])
+				if err != nil {
+					return nil, err
+				}
+
+				conns = append(conns, conn)
+				dones[2] = shared.WebsocketRecvStream(args.Stderr, conn)
+			}
+
+			// Wait for everything to be done
+			go func() {
+				for i, chDone := range dones {
+					// Skip stdin, dealing with it separately below
+					if i == 0 {
+						continue
+					}
+
+					<-chDone
+				}
+
+				if fds["0"] != "" {
+					if args.Stdin != nil {
+						args.Stdin.Close()
+					}
+
+					// Empty the stdin channel but don't block on it as
+					// stdin may be stuck in Read()
+					go func() {
+						<-dones[0]
+					}()
+				}
+
+				for _, conn := range conns {
+					conn.Close()
+				}
+
+				if args.DataDone != nil {
+					close(args.DataDone)
+				}
+			}()
+		}
+	}
+
+	return op, nil
+}
+
+// GetInstanceFile retrieves the provided path from the instance.
+func (r *ProtocolLXD) GetInstanceFile(instanceName string, filePath string) (io.ReadCloser, *InstanceFileResponse, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Prepare the HTTP request
+	requestURL, err := shared.URLEncode(
+		fmt.Sprintf("%s/1.0%s/%s/files", r.httpHost, path, url.PathEscape(instanceName)),
+		map[string]string{"path": filePath})
+	if err != nil {
+		return nil, nil, err
+	}
+
+	requestURL, err = r.setQueryAttributes(requestURL)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	req, err := http.NewRequest("GET", requestURL, nil)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return nil, nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(resp)
+		if err != nil {
+			return nil, nil, err
+		}
+	}
+
+	// Parse the headers
+	uid, gid, mode, fileType, _ := shared.ParseLXDFileHeaders(resp.Header)
+	fileResp := InstanceFileResponse{
+		UID:  uid,
+		GID:  gid,
+		Mode: mode,
+		Type: fileType,
+	}
+
+	if fileResp.Type == "directory" {
+		// Decode the response
+		response := api.Response{}
+		decoder := json.NewDecoder(resp.Body)
+
+		err = decoder.Decode(&response)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		// Get the file list
+		entries := []string{}
+		err = response.MetadataAsStruct(&entries)
+		if err != nil {
+			return nil, nil, err
+		}
+
+		fileResp.Entries = entries
+
+		return nil, &fileResp, err
+	}
+
+	return resp.Body, &fileResp, err
+}
+
+// CreateInstanceFile tells LXD to create a file in the instance.
+func (r *ProtocolLXD) CreateInstanceFile(instanceName string, filePath string, args InstanceFileArgs) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if args.Type == "directory" {
+		if !r.HasExtension("directory_manipulation") {
+			return fmt.Errorf("The server is missing the required \"directory_manipulation\" API extension")
+		}
+	}
+
+	if args.Type == "symlink" {
+		if !r.HasExtension("file_symlinks") {
+			return fmt.Errorf("The server is missing the required \"file_symlinks\" API extension")
+		}
+	}
+
+	if args.WriteMode == "append" {
+		if !r.HasExtension("file_append") {
+			return fmt.Errorf("The server is missing the required \"file_append\" API extension")
+		}
+	}
+
+	// Prepare the HTTP request
+	requestURL := fmt.Sprintf("%s/1.0%s/%s/files?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(filePath))
+
+	requestURL, err = r.setQueryAttributes(requestURL)
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest("POST", requestURL, args.Content)
+	if err != nil {
+		return err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Set the various headers
+	if args.UID > -1 {
+		req.Header.Set("X-LXD-uid", fmt.Sprintf("%d", args.UID))
+	}
+
+	if args.GID > -1 {
+		req.Header.Set("X-LXD-gid", fmt.Sprintf("%d", args.GID))
+	}
+
+	if args.Mode > -1 {
+		req.Header.Set("X-LXD-mode", fmt.Sprintf("%04o", args.Mode))
+	}
+
+	if args.Type != "" {
+		req.Header.Set("X-LXD-type", args.Type)
+	}
+
+	if args.WriteMode != "" {
+		req.Header.Set("X-LXD-write", args.WriteMode)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return err
+	}
+
+	// Check the return value for a cleaner error
+	_, _, err = lxdParseResponse(resp)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// DeleteInstanceFile deletes a file in the instance.
+func (r *ProtocolLXD) DeleteInstanceFile(instanceName string, filePath string) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if !r.HasExtension("file_delete") {
+		return fmt.Errorf("The server is missing the required \"file_delete\" API extension")
+	}
+
+	// Send the request
+	_, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/files?path=%s", path, url.PathEscape(instanceName), url.QueryEscape(filePath)), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetInstanceSnapshotNames returns a list of snapshot names for the instance.
+func (r *ProtocolLXD) GetInstanceSnapshotNames(instanceName string) ([]string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, uri := range urls {
+		fields := strings.Split(uri, fmt.Sprintf("%s/%s/snapshots/", path, url.PathEscape(instanceName)))
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetInstanceSnapshots returns a list of snapshots for the instance.
+func (r *ProtocolLXD) GetInstanceSnapshots(instanceName string) ([]api.InstanceSnapshot, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	snapshots := []api.InstanceSnapshot{}
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots?recursion=1", path, url.PathEscape(instanceName)), nil, "", &snapshots)
+	if err != nil {
+		return nil, err
+	}
+
+	return snapshots, nil
+}
+
+// GetInstanceSnapshot returns a Snapshot struct for the provided instance and snapshot names
+func (r *ProtocolLXD) GetInstanceSnapshot(instanceName string, name string) (*api.InstanceSnapshot, string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, "", err
+	}
+
+	snapshot := api.InstanceSnapshot{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", &snapshot)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &snapshot, etag, nil
+}
+
+// CreateInstanceSnapshot requests that LXD creates a new snapshot for the instance.
+func (r *ProtocolLXD) CreateInstanceSnapshot(instanceName string, snapshot api.InstanceSnapshotsPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Validate the request
+	if snapshot.ExpiresAt != nil && !r.HasExtension("snapshot_expiry_creation") {
+		return nil, fmt.Errorf("The server is missing the required \"snapshot_expiry_creation\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots", path, url.PathEscape(instanceName)), snapshot, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// CopyInstanceSnapshot copies a snapshot from a remote server into a new instance. Additional options can be passed using InstanceCopyArgs.
+func (r *ProtocolLXD) CopyInstanceSnapshot(source InstanceServer, instanceName string, snapshot api.InstanceSnapshot, args *InstanceSnapshotCopyArgs) (RemoteOperation, error) {
+	// Backward compatibility (with broken Name field)
+	fields := strings.Split(snapshot.Name, shared.SnapshotDelimiter)
+	cName := instanceName
+	sName := fields[len(fields)-1]
+
+	// Base request
+	req := api.InstancesPost{
+		Name: cName,
+		InstancePut: api.InstancePut{
+			Architecture: snapshot.Architecture,
+			Config:       snapshot.Config,
+			Devices:      snapshot.Devices,
+			Ephemeral:    snapshot.Ephemeral,
+			Profiles:     snapshot.Profiles,
+		},
+	}
+
+	if snapshot.Stateful && args.Live {
+		if !r.HasExtension("container_snapshot_stateful_migration") {
+			return nil, fmt.Errorf("The server is missing the required \"container_snapshot_stateful_migration\" API extension")
+		}
+		req.InstancePut.Stateful = snapshot.Stateful
+		req.Source.Live = args.Live
+	}
+	req.Source.BaseImage = snapshot.Config["volatile.base_image"]
+
+	// Process the copy arguments
+	if args != nil {
+		// Sanity checks
+		if shared.StringInSlice(args.Mode, []string{"push", "relay"}) {
+			if !r.HasExtension("container_push") {
+				return nil, fmt.Errorf("The target server is missing the required \"container_push\" API extension")
+			}
+
+			if !source.HasExtension("container_push") {
+				return nil, fmt.Errorf("The source server is missing the required \"container_push\" API extension")
+			}
+		}
+
+		if args.Mode == "push" && !source.HasExtension("container_push_target") {
+			return nil, fmt.Errorf("The source server is missing the required \"container_push_target\" API extension")
+		}
+
+		// Allow overriding the target name
+		if args.Name != "" {
+			req.Name = args.Name
+		}
+	}
+
+	sourceInfo, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get source connection info: %v", err)
+	}
+
+	destInfo, err := r.GetConnectionInfo()
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get destination connection info: %v", err)
+	}
+
+	instance, _, err := source.GetInstance(cName)
+	if err != nil {
+		return nil, fmt.Errorf("Failed to get instance info: %v", err)
+	}
+
+	// Optimization for the local copy case
+	if destInfo.URL == sourceInfo.URL && destInfo.SocketPath == sourceInfo.SocketPath && (!r.IsClustered() || instance.Location == r.clusterTarget || r.HasExtension("cluster_internal_copy")) {
+		// Project handling
+		if destInfo.Project != sourceInfo.Project {
+			if !r.HasExtension("container_copy_project") {
+				return nil, fmt.Errorf("The server is missing the required \"container_copy_project\" API extension")
+			}
+
+			req.Source.Project = sourceInfo.Project
+		}
+
+		// Local copy source fields
+		req.Source.Type = "copy"
+		req.Source.Source = fmt.Sprintf("%s/%s", cName, sName)
+
+		// Copy the instance
+		op, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+
+		rop := remoteOperation{
+			targetOp: op,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Source request
+	sourceReq := api.InstanceSnapshotPost{
+		Migration: true,
+		Name:      args.Name,
+	}
+	if snapshot.Stateful && args.Live {
+		sourceReq.Live = args.Live
+	}
+
+	// Push mode migration
+	if args != nil && args.Mode == "push" {
+		// Get target server connection information
+		info, err := r.GetConnectionInfo()
+		if err != nil {
+			return nil, err
+		}
+
+		// Create the instance
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		op, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+		opAPI := op.Get()
+
+		targetSecrets := map[string]string{}
+		for k, v := range opAPI.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Prepare the source request
+		target := api.InstancePostTarget{}
+		target.Operation = opAPI.ID
+		target.Websockets = targetSecrets
+		target.Certificate = info.Certificate
+		sourceReq.Target = &target
+
+		return r.tryMigrateInstanceSnapshot(source, cName, sName, sourceReq, info.Addresses)
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	op, err := source.MigrateInstanceSnapshot(cName, sName, sourceReq)
+	if err != nil {
+		return nil, err
+	}
+	opAPI := op.Get()
+
+	sourceSecrets := map[string]string{}
+	for k, v := range opAPI.Metadata {
+		sourceSecrets[k] = v.(string)
+	}
+
+	// Relay mode migration
+	if args != nil && args.Mode == "relay" {
+		// Push copy source fields
+		req.Source.Type = "migration"
+		req.Source.Mode = "push"
+
+		// Start the process
+		targetOp, err := r.CreateInstance(req)
+		if err != nil {
+			return nil, err
+		}
+		targetOpAPI := targetOp.Get()
+
+		// Extract the websockets
+		targetSecrets := map[string]string{}
+		for k, v := range targetOpAPI.Metadata {
+			targetSecrets[k] = v.(string)
+		}
+
+		// Launch the relay
+		err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
+		if err != nil {
+			return nil, err
+		}
+
+		// Prepare a tracking operation
+		rop := remoteOperation{
+			targetOp: targetOp,
+			chDone:   make(chan bool),
+		}
+
+		// Forward targetOp to remote op
+		go func() {
+			rop.err = rop.targetOp.Wait()
+			close(rop.chDone)
+		}()
+
+		return &rop, nil
+	}
+
+	// Pull mode migration
+	req.Source.Type = "migration"
+	req.Source.Mode = "pull"
+	req.Source.Operation = opAPI.ID
+	req.Source.Websockets = sourceSecrets
+	req.Source.Certificate = info.Certificate
+
+	return r.tryCreateInstance(req, info.Addresses)
+}
+
+// RenameInstanceSnapshot requests that LXD renames the snapshot.
+func (r *ProtocolLXD) RenameInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Sanity check
+	if instance.Migration {
+		return nil, fmt.Errorf("Can't ask for a migration through RenameInstanceSnapshot")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+func (r *ProtocolLXD) tryMigrateInstanceSnapshot(source InstanceServer, instanceName string, name string, req api.InstanceSnapshotPost, urls []string) (RemoteOperation, error) {
+	if len(urls) == 0 {
+		return nil, fmt.Errorf("The target server isn't listening on the network")
+	}
+
+	rop := remoteOperation{
+		chDone: make(chan bool),
+	}
+
+	operation := req.Target.Operation
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := map[string]error{}
+		for _, serverURL := range urls {
+			req.Target.Operation = fmt.Sprintf("%s/1.0/operations/%s", serverURL, url.PathEscape(operation))
+
+			op, err := source.MigrateInstanceSnapshot(instanceName, name, req)
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors[serverURL] = err
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = remoteOperationError("Failed instance migration", errors)
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
+// MigrateInstanceSnapshot requests that LXD prepares for a snapshot migration.
+func (r *ProtocolLXD) MigrateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Sanity check
+	if !instance.Migration {
+		return nil, fmt.Errorf("Can't ask for a rename through MigrateInstanceSnapshot")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// DeleteInstanceSnapshot requests that LXD deletes the instance snapshot.
+func (r *ProtocolLXD) DeleteInstanceSnapshot(instanceName string, name string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// UpdateInstanceSnapshot requests that LXD updates the instance snapshot.
+func (r *ProtocolLXD) UpdateInstanceSnapshot(instanceName string, name string, instance api.InstanceSnapshotPut, ETag string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("snapshot_expiry") {
+		return nil, fmt.Errorf("The server is missing the required \"snapshot_expiry\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/snapshots/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), instance, ETag)
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// GetInstanceState returns a InstanceState entry for the provided instance name.
+func (r *ProtocolLXD) GetInstanceState(name string) (*api.InstanceState, string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, "", err
+	}
+
+	state := api.InstanceState{}
+
+	// Fetch the raw value
+	etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), nil, "", &state)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &state, etag, nil
+}
+
+// UpdateInstanceState updates the instance to match the requested state.
+func (r *ProtocolLXD) UpdateInstanceState(name string, state api.InstanceStatePut, ETag string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("PUT", fmt.Sprintf("%s/%s/state", path, url.PathEscape(name)), state, ETag)
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// GetInstanceLogfiles returns a list of logfiles for the instance.
+func (r *ProtocolLXD) GetInstanceLogfiles(name string) ([]string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	urls := []string{}
+
+	// Fetch the raw value
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/logs", path, url.PathEscape(name)), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	logfiles := []string{}
+	for _, uri := range logfiles {
+		fields := strings.Split(uri, fmt.Sprintf("%s/%s/logs/", path, url.PathEscape(name)))
+		logfiles = append(logfiles, fields[len(fields)-1])
+	}
+
+	return logfiles, nil
+}
+
+// GetInstanceLogfile returns the content of the requested logfile.
+//
+// Note that it's the caller's responsibility to close the returned ReadCloser
+func (r *ProtocolLXD) GetInstanceLogfile(name string, filename string) (io.ReadCloser, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	// Prepare the HTTP request
+	url := fmt.Sprintf("%s/1.0%s/%s/logs/%s", r.httpHost, path, url.PathEscape(name), url.PathEscape(filename))
+
+	url, err = r.setQueryAttributes(url)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return resp.Body, err
+}
+
+// DeleteInstanceLogfile deletes the requested logfile.
+func (r *ProtocolLXD) DeleteInstanceLogfile(name string, filename string) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	// Send the request
+	_, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/logs/%s", path, url.PathEscape(name), url.PathEscape(filename)), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetInstanceMetadata returns instance metadata.
+func (r *ProtocolLXD) GetInstanceMetadata(name string) (*api.ImageMetadata, string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, "", err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, "", fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	metadata := api.ImageMetadata{}
+
+	url := fmt.Sprintf("%s/%s/metadata", path, url.PathEscape(name))
+	etag, err := r.queryStruct("GET", url, nil, "", &metadata)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &metadata, etag, err
+}
+
+// SetInstanceMetadata sets the content of the instance metadata file.
+func (r *ProtocolLXD) SetInstanceMetadata(name string, metadata api.ImageMetadata, ETag string) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/%s/metadata", path, url.PathEscape(name))
+	_, _, err = r.query("PUT", url, metadata, ETag)
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetInstanceTemplateFiles returns the list of names of template files for a instance.
+func (r *ProtocolLXD) GetInstanceTemplateFiles(instanceName string) ([]string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	templates := []string{}
+
+	url := fmt.Sprintf("%s/%s/metadata/templates", path, url.PathEscape(instanceName))
+	_, err = r.queryStruct("GET", url, nil, "", &templates)
+	if err != nil {
+		return nil, err
+	}
+
+	return templates, nil
+}
+
+// GetInstanceTemplateFile returns the content of a template file for a instance.
+func (r *ProtocolLXD) GetInstanceTemplateFile(instanceName string, templateName string) (io.ReadCloser, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return nil, fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0%s/%s/metadata/templates?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(templateName))
+
+	url, err = r.setQueryAttributes(url)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return resp.Body, err
+}
+
+// CreateInstanceTemplateFile creates an a template for a instance.
+func (r *ProtocolLXD) CreateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) error {
+	return r.setInstanceTemplateFile(instanceName, templateName, content, "POST")
+}
+
+// UpdateInstanceTemplateFile updates the content for a instance template file.
+func (r *ProtocolLXD) UpdateInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker) error {
+	return r.setInstanceTemplateFile(instanceName, templateName, content, "PUT")
+}
+
+func (r *ProtocolLXD) setInstanceTemplateFile(instanceName string, templateName string, content io.ReadSeeker, httpMethod string) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+
+	url := fmt.Sprintf("%s/1.0%s/%s/metadata/templates?path=%s", r.httpHost, path, url.PathEscape(instanceName), url.QueryEscape(templateName))
+
+	url, err = r.setQueryAttributes(url)
+	if err != nil {
+		return err
+	}
+
+	req, err := http.NewRequest(httpMethod, url, content)
+	if err != nil {
+		return err
+	}
+	req.Header.Set("Content-Type", "application/octet-stream")
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(resp)
+		if err != nil {
+			return err
+		}
+	}
+	return err
+}
+
+// DeleteInstanceTemplateFile deletes a template file for a instance.
+func (r *ProtocolLXD) DeleteInstanceTemplateFile(name string, templateName string) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if !r.HasExtension("container_edit_metadata") {
+		return fmt.Errorf("The server is missing the required \"container_edit_metadata\" API extension")
+	}
+	_, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/metadata/templates?path=%s", path, url.PathEscape(name), url.QueryEscape(templateName)), nil, "")
+	return err
+}
+
+// ConsoleInstance requests that LXD attaches to the console device of a instance.
+func (r *ProtocolLXD) ConsoleInstance(instanceName string, console api.InstanceConsolePost, args *InstanceConsoleArgs) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("console") {
+		return nil, fmt.Errorf("The server is missing the required \"console\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), console, "")
+	if err != nil {
+		return nil, err
+	}
+	opAPI := op.Get()
+
+	if args == nil || args.Terminal == nil {
+		return nil, fmt.Errorf("A terminal must be set")
+	}
+
+	if args.Control == nil {
+		return nil, fmt.Errorf("A control channel must be set")
+	}
+
+	// Parse the fds
+	fds := map[string]string{}
+
+	value, ok := opAPI.Metadata["fds"]
+	if ok {
+		values := value.(map[string]interface{})
+		for k, v := range values {
+			fds[k] = v.(string)
+		}
+	}
+
+	var controlConn *websocket.Conn
+	// Call the control handler with a connection to the control socket
+	if fds["control"] == "" {
+		return nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
+	}
+
+	controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds["control"])
+	if err != nil {
+		return nil, err
+	}
+
+	go args.Control(controlConn)
+
+	// Connect to the websocket
+	conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
+	if err != nil {
+		return nil, err
+	}
+
+	// Detach from console.
+	go func(consoleDisconnect <-chan bool) {
+		<-consoleDisconnect
+		msg := websocket.FormatCloseMessage(websocket.CloseNormalClosure, "Detaching from console")
+		// We don't care if this fails. This is just for convenience.
+		controlConn.WriteMessage(websocket.CloseMessage, msg)
+		controlConn.Close()
+	}(args.ConsoleDisconnect)
+
+	// And attach stdin and stdout to it
+	go func() {
+		shared.WebsocketSendStream(conn, args.Terminal, -1)
+		<-shared.WebsocketRecvStream(args.Terminal, conn)
+		conn.Close()
+	}()
+
+	return op, nil
+}
+
+// GetInstanceConsoleLog requests that LXD attaches to the console device of a instance.
+//
+// Note that it's the caller's responsibility to close the returned ReadCloser
+func (r *ProtocolLXD) GetInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) (io.ReadCloser, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("console") {
+		return nil, fmt.Errorf("The server is missing the required \"console\" API extension")
+	}
+
+	// Prepare the HTTP request
+	url := fmt.Sprintf("%s/1.0%s/%s/console", r.httpHost, path, url.PathEscape(instanceName))
+
+	url, err = r.setQueryAttributes(url)
+	if err != nil {
+		return nil, err
+	}
+
+	req, err := http.NewRequest("GET", url, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	// Set the user agent
+	if r.httpUserAgent != "" {
+		req.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Send the request
+	resp, err := r.do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	// Check the return value for a cleaner error
+	if resp.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(resp)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return resp.Body, err
+}
+
+// DeleteInstanceConsoleLog deletes the requested instance's console log.
+func (r *ProtocolLXD) DeleteInstanceConsoleLog(instanceName string, args *InstanceConsoleLogArgs) error {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return err
+	}
+
+	if !r.HasExtension("console") {
+		return fmt.Errorf("The server is missing the required \"console\" API extension")
+	}
+
+	// Send the request
+	_, _, err = r.query("DELETE", fmt.Sprintf("%s/%s/console", path, url.PathEscape(instanceName)), nil, "")
+	if err != nil {
+		return err
+	}
+
+	return nil
+}
+
+// GetInstanceBackupNames returns a list of backup names for the instance.
+func (r *ProtocolLXD) GetInstanceBackupNames(instanceName string) ([]string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Fetch the raw value
+	urls := []string{}
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), nil, "", &urls)
+	if err != nil {
+		return nil, err
+	}
+
+	// Parse it
+	names := []string{}
+	for _, uri := range urls {
+		fields := strings.Split(uri, fmt.Sprintf("%s/%s/backups/", path, url.PathEscape(instanceName)))
+		names = append(names, fields[len(fields)-1])
+	}
+
+	return names, nil
+}
+
+// GetInstanceBackups returns a list of backups for the instance.
+func (r *ProtocolLXD) GetInstanceBackups(instanceName string) ([]api.InstanceBackup, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Fetch the raw value
+	backups := []api.InstanceBackup{}
+
+	_, err = r.queryStruct("GET", fmt.Sprintf("%s/%s/backups?recursion=1", path, url.PathEscape(instanceName)), nil, "", &backups)
+	if err != nil {
+		return nil, err
+	}
+
+	return backups, nil
+}
+
+// GetInstanceBackup returns a Backup struct for the provided instance and backup names.
+func (r *ProtocolLXD) GetInstanceBackup(instanceName string, name string) (*api.InstanceBackup, string, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, "", err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, "", fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Fetch the raw value
+	backup := api.InstanceBackup{}
+	etag, err := r.queryStruct("GET", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "", &backup)
+	if err != nil {
+		return nil, "", err
+	}
+
+	return &backup, etag, nil
+}
+
+// CreateInstanceBackup requests that LXD creates a new backup for the instance.
+func (r *ProtocolLXD) CreateInstanceBackup(instanceName string, backup api.InstanceBackupsPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups", path, url.PathEscape(instanceName)), backup, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// RenameInstanceBackup requests that LXD renames the backup.
+func (r *ProtocolLXD) RenameInstanceBackup(instanceName string, name string, backup api.InstanceBackupPost) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("POST", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), backup, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// DeleteInstanceBackup requests that LXD deletes the instance backup.
+func (r *ProtocolLXD) DeleteInstanceBackup(instanceName string, name string) (Operation, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Send the request
+	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("%s/%s/backups/%s", path, url.PathEscape(instanceName), url.PathEscape(name)), nil, "")
+	if err != nil {
+		return nil, err
+	}
+
+	return op, nil
+}
+
+// GetInstanceBackupFile requests the instance backup content.
+func (r *ProtocolLXD) GetInstanceBackupFile(instanceName string, name string, req *BackupFileRequest) (*BackupFileResponse, error) {
+	path, _, err := r.instanceTypeToPath(api.InstanceTypeAny)
+	if err != nil {
+		return nil, err
+	}
+
+	if !r.HasExtension("container_backup") {
+		return nil, fmt.Errorf("The server is missing the required \"container_backup\" API extension")
+	}
+
+	// Build the URL
+	uri := fmt.Sprintf("%s/1.0%s/%s/backups/%s/export", r.httpHost, path, url.PathEscape(instanceName), url.PathEscape(name))
+	if r.project != "" {
+		uri += fmt.Sprintf("?project=%s", url.QueryEscape(r.project))
+	}
+
+	// Prepare the download request
+	request, err := http.NewRequest("GET", uri, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	if r.httpUserAgent != "" {
+		request.Header.Set("User-Agent", r.httpUserAgent)
+	}
+
+	// Start the request
+	response, doneCh, err := cancel.CancelableDownload(req.Canceler, r.http, request)
+	if err != nil {
+		return nil, err
+	}
+	defer response.Body.Close()
+	defer close(doneCh)
+
+	if response.StatusCode != http.StatusOK {
+		_, _, err := lxdParseResponse(response)
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	// Handle the data
+	body := response.Body
+	if req.ProgressHandler != nil {
+		body = &ioprogress.ProgressReader{
+			ReadCloser: response.Body,
+			Tracker: &ioprogress.ProgressTracker{
+				Length: response.ContentLength,
+				Handler: func(percent int64, speed int64) {
+					req.ProgressHandler(ioprogress.ProgressData{Text: fmt.Sprintf("%d%% (%s/s)", percent, units.GetByteSizeString(speed, 2))})
+				},
+			},
+		}
+	}
+
+	size, err := io.Copy(req.BackupFile, body)
+	if err != nil {
+		return nil, err
+	}
+
+	resp := BackupFileResponse{}
+	resp.Size = size
+
+	return &resp, nil
+}

From c7e6044d542aa31a4f1423c94e29c00f71de090b Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 17:27:00 +0100
Subject: [PATCH 4/5] shared/netutils/network/linux: Updates NetnsGetifaddrs to
 use Instance types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 shared/netutils/network_linux.go | 14 +++++++-------
 1 file changed, 7 insertions(+), 7 deletions(-)

diff --git a/shared/netutils/network_linux.go b/shared/netutils/network_linux.go
index 9d1216122f..e593dc87cc 100644
--- a/shared/netutils/network_linux.go
+++ b/shared/netutils/network_linux.go
@@ -25,7 +25,7 @@ import (
 // #cgo CFLAGS: -std=gnu11 -Wvla
 import "C"
 
-func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error) {
+func NetnsGetifaddrs(initPID int32) (map[string]api.InstanceStateNetwork, error) {
 	var netnsid_aware C.bool
 	var ifaddrs *C.struct_netns_ifaddrs
 	var netnsID C.__s32
@@ -57,15 +57,15 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
 
 	// We're using the interface name as key here but we should really
 	// switch to the ifindex at some point to handle ip aliasing correctly.
-	networks := map[string]api.ContainerStateNetwork{}
+	networks := map[string]api.InstanceStateNetwork{}
 
 	for addr := ifaddrs; addr != nil; addr = addr.ifa_next {
 		var address [C.INET6_ADDRSTRLEN]C.char
 		addNetwork, networkExists := networks[C.GoString(addr.ifa_name)]
 		if !networkExists {
-			addNetwork = api.ContainerStateNetwork{
-				Addresses: []api.ContainerStateNetworkAddress{},
-				Counters:  api.ContainerStateNetworkCounters{},
+			addNetwork = api.InstanceStateNetwork{
+				Addresses: []api.InstanceStateNetworkAddress{},
+				Counters:  api.InstanceStateNetworkCounters{},
 			}
 		}
 
@@ -117,7 +117,7 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
 			}
 
 			if addNetwork.Addresses == nil {
-				addNetwork.Addresses = []api.ContainerStateNetworkAddress{}
+				addNetwork.Addresses = []api.InstanceStateNetworkAddress{}
 			}
 
 			goAddrString := C.GoString(address_str)
@@ -138,7 +138,7 @@ func NetnsGetifaddrs(initPID int32) (map[string]api.ContainerStateNetwork, error
 				scope = "link"
 			}
 
-			address := api.ContainerStateNetworkAddress{}
+			address := api.InstanceStateNetworkAddress{}
 			address.Family = family
 			address.Address = goAddrString
 			address.Netmask = fmt.Sprintf("%d", int(addr.ifa_prefixlen))

From 6026e038cf228a90a3b5188a4f1910a10aa727bd Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Thu, 12 Sep 2019 18:02:46 +0100
Subject: [PATCH 5/5] lxd: Switches over to Instance types

Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
 lxd/api_1.0.go               |  2 +-
 lxd/api_cluster.go           |  6 ++--
 lxd/api_cluster_test.go      |  6 ++--
 lxd/api_internal.go          |  2 +-
 lxd/backup.go                |  4 +--
 lxd/certificates.go          |  2 +-
 lxd/cluster/connect.go       |  8 +++---
 lxd/cluster/notify.go        |  6 ++--
 lxd/cluster/notify_test.go   |  4 +--
 lxd/cluster/upgrade.go       |  2 +-
 lxd/container.go             |  4 +--
 lxd/container_backup.go      |  6 ++--
 lxd/container_console.go     |  4 +--
 lxd/container_exec.go        |  4 +--
 lxd/container_lxc.go         | 52 ++++++++++++++++-----------------
 lxd/container_patch.go       |  2 +-
 lxd/container_post.go        |  6 ++--
 lxd/container_put.go         |  2 +-
 lxd/container_snapshot.go    | 14 ++++-----
 lxd/container_state.go       |  2 +-
 lxd/container_test.go        |  2 +-
 lxd/containers_get.go        | 56 ++++++++++++++++++------------------
 lxd/containers_post.go       | 26 ++++++++---------
 lxd/images.go                |  4 +--
 lxd/init.go                  |  4 +--
 lxd/main_init_auto.go        |  2 +-
 lxd/main_init_dump.go        |  2 +-
 lxd/main_init_interactive.go | 14 ++++-----
 lxd/main_init_preseed.go     |  2 +-
 lxd/migrate_container.go     |  2 +-
 lxd/networks.go              |  6 ++--
 lxd/profiles.go              |  2 +-
 lxd/response.go              |  4 +--
 lxd/storage_pools.go         |  8 +++---
 34 files changed, 136 insertions(+), 136 deletions(-)

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 9bc2db9ea0..cf0b1aea2e 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -432,7 +432,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 	if err != nil {
 		return SmartError(err)
 	}
-	err = notifier(func(client lxd.ContainerServer) error {
+	err = notifier(func(client lxd.InstanceServer) error {
 		server, etag, err := client.GetServer()
 		if err != nil {
 			return err
diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go
index 544d417894..6980defc7b 100644
--- a/lxd/api_cluster.go
+++ b/lxd/api_cluster.go
@@ -573,7 +573,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 				return
 			}
 
-			imageImport := func(client lxd.ContainerServer, fingerprint string, projects []string) error {
+			imageImport := func(client lxd.InstanceServer, fingerprint string, projects []string) error {
 				err := imageImportFromNode(filepath.Join(d.os.VarDir, "images"), client, fingerprint)
 				if err != nil {
 					return err
@@ -677,7 +677,7 @@ func clusterPutDisable(d *Daemon) Response {
 //
 // We pass to LXD client instances, one connected to ourselves (the joining
 // node) and one connected to the target cluster node to join.
-func clusterInitMember(d, client lxd.ContainerServer, memberConfig []api.ClusterMemberConfigKey) error {
+func clusterInitMember(d, client lxd.InstanceServer, memberConfig []api.ClusterMemberConfigKey) error {
 	data := initDataNode{}
 
 	// Fetch all pools currently defined in the cluster.
@@ -788,7 +788,7 @@ func clusterInitMember(d, client lxd.ContainerServer, memberConfig []api.Cluster
 // mode can be accepted into the cluster and obtain joining information such as
 // the cluster private certificate.
 func clusterAcceptMember(
-	client lxd.ContainerServer,
+	client lxd.InstanceServer,
 	name, address string, schema, apiExt int,
 	pools []api.StoragePool, networks []api.Network) (*internalClusterPostAcceptResponse, error) {
 
diff --git a/lxd/api_cluster_test.go b/lxd/api_cluster_test.go
index d45d53e7a1..edaa436ce9 100644
--- a/lxd/api_cluster_test.go
+++ b/lxd/api_cluster_test.go
@@ -596,7 +596,7 @@ func TestCluster_NodeRename(t *testing.T) {
 // Test helper for cluster-related APIs.
 type clusterFixture struct {
 	t       *testing.T
-	clients map[*Daemon]lxd.ContainerServer
+	clients map[*Daemon]lxd.InstanceServer
 	daemons []*Daemon
 }
 
@@ -716,9 +716,9 @@ func (f *clusterFixture) RegisterCertificate(daemon1, daemon2 *Daemon, name, pas
 
 // Get a client for the given daemon connected via UNIX socket, creating one if
 // needed.
-func (f *clusterFixture) ClientUnix(daemon *Daemon) lxd.ContainerServer {
+func (f *clusterFixture) ClientUnix(daemon *Daemon) lxd.InstanceServer {
 	if f.clients == nil {
-		f.clients = make(map[*Daemon]lxd.ContainerServer)
+		f.clients = make(map[*Daemon]lxd.InstanceServer)
 	}
 	client, ok := f.clients[daemon]
 	if !ok {
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 588f75e05a..06b05f3f35 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -531,7 +531,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		defer initPool.StoragePoolUmount()
 	}
 
-	existingSnapshots := []*api.ContainerSnapshot{}
+	existingSnapshots := []*api.InstanceSnapshot{}
 	needForce := fmt.Errorf(`The snapshot does not exist on disk. Pass ` +
 		`"force" to discard non-existing snapshots`)
 
diff --git a/lxd/backup.go b/lxd/backup.go
index fd58faf2d0..b3b252d17d 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -146,8 +146,8 @@ func (b *backup) Delete() error {
 	return doBackupDelete(b.state, b.name, b.container.Name())
 }
 
-func (b *backup) Render() *api.ContainerBackup {
-	return &api.ContainerBackup{
+func (b *backup) Render() *api.InstanceBackup {
+	return &api.InstanceBackup{
 		Name:             strings.SplitN(b.name, "/", 2)[1],
 		CreatedAt:        b.creationDate,
 		ExpiresAt:        b.expiryDate,
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 3630ec59ec..463c04a9d2 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -204,7 +204,7 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 		req.Name = name
 		req.Type = "client"
 
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			return client.CreateCertificate(req)
 		})
 		if err != nil {
diff --git a/lxd/cluster/connect.go b/lxd/cluster/connect.go
index b97625ffd1..fb8634f0ad 100644
--- a/lxd/cluster/connect.go
+++ b/lxd/cluster/connect.go
@@ -19,7 +19,7 @@ import (
 // If 'notify' switch is true, then the user agent will be set to the special
 // value 'lxd-cluster-notifier', which can be used in some cases to distinguish
 // between a regular client request and an internal cluster request.
-func Connect(address string, cert *shared.CertInfo, notify bool) (lxd.ContainerServer, error) {
+func Connect(address string, cert *shared.CertInfo, notify bool) (lxd.InstanceServer, error) {
 	args := &lxd.ConnectionArgs{
 		TLSServerCert: string(cert.PublicKey()),
 		TLSClientCert: string(cert.PublicKey()),
@@ -38,7 +38,7 @@ 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, instanceType instance.Type) (lxd.ContainerServer, error) {
+func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert *shared.CertInfo, instanceType instance.Type) (lxd.InstanceServer, error) {
 	var address string // Node address
 	err := cluster.Transaction(func(tx *db.ClusterTx) error {
 		var err error
@@ -62,7 +62,7 @@ func ConnectIfContainerIsRemote(cluster *db.Cluster, project, name string, cert
 //
 // If there is more than one node with a matching volume name, an error is
 // returned.
-func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName string, volumeType int, cert *shared.CertInfo) (lxd.ContainerServer, error) {
+func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName string, volumeType int, cert *shared.CertInfo) (lxd.InstanceServer, error) {
 	var addresses []string // Node addresses
 	err := cluster.Transaction(func(tx *db.ClusterTx) error {
 		var err error
@@ -99,7 +99,7 @@ func ConnectIfVolumeIsRemote(cluster *db.Cluster, poolID int64, volumeName strin
 	return Connect(address, cert, false)
 }
 
-// SetupTrust is a convenience around ContainerServer.CreateCertificate that
+// SetupTrust is a convenience around InstanceServer.CreateCertificate that
 // adds the given client certificate to the trusted pool of the cluster at the
 // given address, using the given password.
 func SetupTrust(cert, targetAddress, targetCert, targetPassword string) error {
diff --git a/lxd/cluster/notify.go b/lxd/cluster/notify.go
index 1b6b5dce59..f360719402 100644
--- a/lxd/cluster/notify.go
+++ b/lxd/cluster/notify.go
@@ -16,7 +16,7 @@ import (
 
 // Notifier is a function that invokes the given function against each node in
 // the cluster excluding the invoking one.
-type Notifier func(hook func(lxd.ContainerServer) error) error
+type Notifier func(hook func(lxd.InstanceServer) error) error
 
 // NotifierPolicy can be used to tweak the behavior of NewNotifier in case of
 // some nodes are down.
@@ -38,7 +38,7 @@ func NewNotifier(state *state.State, cert *shared.CertInfo, policy NotifierPolic
 
 	// Fast-track the case where we're not clustered at all.
 	if address == "" {
-		nullNotifier := func(func(lxd.ContainerServer) error) error { return nil }
+		nullNotifier := func(func(lxd.InstanceServer) error) error { return nil }
 		return nullNotifier, nil
 	}
 
@@ -73,7 +73,7 @@ func NewNotifier(state *state.State, cert *shared.CertInfo, policy NotifierPolic
 		return nil, err
 	}
 
-	notifier := func(hook func(lxd.ContainerServer) error) error {
+	notifier := func(hook func(lxd.InstanceServer) error) error {
 		errs := make([]error, len(peers))
 		wg := sync.WaitGroup{}
 		wg.Add(len(peers))
diff --git a/lxd/cluster/notify_test.go b/lxd/cluster/notify_test.go
index 848820619e..0d00379e96 100644
--- a/lxd/cluster/notify_test.go
+++ b/lxd/cluster/notify_test.go
@@ -33,7 +33,7 @@ func TestNewNotifier(t *testing.T) {
 	require.NoError(t, err)
 
 	peers := make(chan string, 2)
-	hook := func(client lxd.ContainerServer) error {
+	hook := func(client lxd.InstanceServer) error {
 		server, _, err := client.GetServer()
 		require.NoError(t, err)
 		peers <- server.Config["cluster.https_address"].(string)
@@ -88,7 +88,7 @@ func TestNewNotify_NotifyAlive(t *testing.T) {
 	assert.NoError(t, err)
 
 	i := 0
-	hook := func(client lxd.ContainerServer) error {
+	hook := func(client lxd.InstanceServer) error {
 		i++
 		return nil
 	}
diff --git a/lxd/cluster/upgrade.go b/lxd/cluster/upgrade.go
index a3ddd4d75a..b7eabc2424 100644
--- a/lxd/cluster/upgrade.go
+++ b/lxd/cluster/upgrade.go
@@ -22,7 +22,7 @@ func NotifyUpgradeCompleted(state *state.State, cert *shared.CertInfo) error {
 	if err != nil {
 		return err
 	}
-	return notifier(func(client lxd.ContainerServer) error {
+	return notifier(func(client lxd.InstanceServer) error {
 		info, err := client.GetConnectionInfo()
 		if err != nil {
 			return errors.Wrap(err, "failed to get connection info")
diff --git a/lxd/container.go b/lxd/container.go
index cd0edf6a2e..7b9e63c0a5 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -272,8 +272,8 @@ type container interface {
 
 	// Status
 	Render() (interface{}, interface{}, error)
-	RenderFull() (*api.ContainerFull, interface{}, error)
-	RenderState() (*api.ContainerState, error)
+	RenderFull() (*api.InstanceFull, interface{}, error)
+	RenderState() (*api.InstanceState, error)
 	IsPrivileged() bool
 	IsRunning() bool
 	IsFrozen() bool
diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index 7f950a222c..242ec3ad3a 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -50,7 +50,7 @@ func containerBackupsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	resultString := []string{}
-	resultMap := []*api.ContainerBackup{}
+	resultMap := []*api.InstanceBackup{}
 
 	for _, backup := range backups {
 		if !recursion {
@@ -112,7 +112,7 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
 		return InternalError(err)
 	}
 
-	req := api.ContainerBackupsPost{}
+	req := api.InstanceBackupsPost{}
 
 	err = json.Unmarshal(body, &req)
 	if err != nil {
@@ -237,7 +237,7 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
 		return response
 	}
 
-	req := api.ContainerBackupPost{}
+	req := api.InstanceBackupPost{}
 	err = json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
 		return BadRequest(err)
diff --git a/lxd/container_console.go b/lxd/container_console.go
index 2ccf9094f4..e590dc6dc4 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -166,7 +166,7 @@ func (s *consoleWs) Do(op *operation) error {
 				break
 			}
 
-			command := api.ContainerConsoleControl{}
+			command := api.InstanceConsoleControl{}
 
 			err = json.Unmarshal(buf, &command)
 			if err != nil {
@@ -265,7 +265,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
-	post := api.ContainerConsolePost{}
+	post := api.InstanceConsolePost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return BadRequest(err)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 358b88f555..7e507d879f 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -202,7 +202,7 @@ func (s *execWs) Do(op *operation) error {
 					break
 				}
 
-				command := api.ContainerExecControl{}
+				command := api.InstanceExecControl{}
 
 				if err := json.Unmarshal(buf, &command); err != nil {
 					logger.Debugf("Failed to unmarshal control socket command: %s", err)
@@ -351,7 +351,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
-	post := api.ContainerExecPost{}
+	post := api.InstanceExecPost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
 		return BadRequest(err)
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index d9835a142e..f9df25a005 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3236,7 +3236,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
 		// Prepare the ETag
 		etag := []interface{}{c.expiryDate}
 
-		ct := api.ContainerSnapshot{
+		ct := api.InstanceSnapshot{
 			CreatedAt:       c.creationDate,
 			ExpandedConfig:  c.expandedConfig,
 			ExpandedDevices: c.expandedDevices.CloneNative(),
@@ -3264,7 +3264,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
 	}
 	statusCode := lxcStatusCode(cState)
 
-	ct := api.Container{
+	ct := api.Instance{
 		ExpandedConfig:  c.expandedConfig,
 		ExpandedDevices: c.expandedDevices.CloneNative(),
 		Name:            c.name,
@@ -3286,7 +3286,7 @@ func (c *containerLXC) Render() (interface{}, interface{}, error) {
 	return &ct, etag, nil
 }
 
-func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
+func (c *containerLXC) RenderFull() (*api.InstanceFull, interface{}, error) {
 	if c.IsSnapshot() {
 		return nil, nil, fmt.Errorf("RenderFull only works with containers")
 	}
@@ -3298,7 +3298,7 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
 	}
 
 	// Convert to ContainerFull
-	ct := api.ContainerFull{Container: *base.(*api.Container)}
+	ct := api.InstanceFull{Instance: *base.(*api.Instance)}
 
 	// Add the ContainerState
 	ct.State, err = c.RenderState()
@@ -3319,10 +3319,10 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
 		}
 
 		if ct.Snapshots == nil {
-			ct.Snapshots = []api.ContainerSnapshot{}
+			ct.Snapshots = []api.InstanceSnapshot{}
 		}
 
-		ct.Snapshots = append(ct.Snapshots, *render.(*api.ContainerSnapshot))
+		ct.Snapshots = append(ct.Snapshots, *render.(*api.InstanceSnapshot))
 	}
 
 	// Add the ContainerBackups
@@ -3335,7 +3335,7 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
 		render := backup.Render()
 
 		if ct.Backups == nil {
-			ct.Backups = []api.ContainerBackup{}
+			ct.Backups = []api.InstanceBackup{}
 		}
 
 		ct.Backups = append(ct.Backups, *render)
@@ -3344,13 +3344,13 @@ func (c *containerLXC) RenderFull() (*api.ContainerFull, interface{}, error) {
 	return &ct, etag, nil
 }
 
-func (c *containerLXC) RenderState() (*api.ContainerState, error) {
+func (c *containerLXC) RenderState() (*api.InstanceState, error) {
 	cState, err := c.getLxcState()
 	if err != nil {
 		return nil, err
 	}
 	statusCode := lxcStatusCode(cState)
-	status := api.ContainerState{
+	status := api.InstanceState{
 		Status:     statusCode.String(),
 		StatusCode: statusCode,
 	}
@@ -3994,10 +3994,10 @@ func (c *containerLXC) VolatileSet(changes map[string]string) error {
 }
 
 type backupFile struct {
-	Container *api.Container           `yaml:"container"`
-	Snapshots []*api.ContainerSnapshot `yaml:"snapshots"`
-	Pool      *api.StoragePool         `yaml:"pool"`
-	Volume    *api.StorageVolume       `yaml:"volume"`
+	Container *api.Instance           `yaml:"container"`
+	Snapshots []*api.InstanceSnapshot `yaml:"snapshots"`
+	Pool      *api.StoragePool        `yaml:"pool"`
+	Volume    *api.StorageVolume      `yaml:"volume"`
 }
 
 func writeBackupFile(c container) error {
@@ -4022,7 +4022,7 @@ func writeBackupFile(c container) error {
 		return errors.Wrap(err, "Failed to get snapshots")
 	}
 
-	var sis []*api.ContainerSnapshot
+	var sis []*api.InstanceSnapshot
 
 	for _, s := range snapshots {
 		si, _, err := s.Render()
@@ -4030,7 +4030,7 @@ func writeBackupFile(c container) error {
 			return err
 		}
 
-		sis = append(sis, si.(*api.ContainerSnapshot))
+		sis = append(sis, si.(*api.InstanceSnapshot))
 	}
 
 	poolName, err := c.StoragePool()
@@ -4050,7 +4050,7 @@ func writeBackupFile(c container) error {
 	}
 
 	data, err := yaml.Marshal(&backupFile{
-		Container: ci.(*api.Container),
+		Container: ci.(*api.Instance),
 		Snapshots: sis,
 		Pool:      pool,
 		Volume:    volume,
@@ -5969,8 +5969,8 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	return nil, 0, attachedPid, nil
 }
 
-func (c *containerLXC) cpuState() api.ContainerStateCPU {
-	cpu := api.ContainerStateCPU{}
+func (c *containerLXC) cpuState() api.InstanceStateCPU {
+	cpu := api.InstanceStateCPU{}
 
 	if !c.state.OS.CGroupCPUacctController {
 		return cpu
@@ -5994,8 +5994,8 @@ func (c *containerLXC) cpuState() api.ContainerStateCPU {
 	return cpu
 }
 
-func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
-	disk := map[string]api.ContainerStateDisk{}
+func (c *containerLXC) diskState() map[string]api.InstanceStateDisk {
+	disk := map[string]api.InstanceStateDisk{}
 
 	// Initialize storage interface for the container.
 	err := c.initStorage()
@@ -6017,14 +6017,14 @@ func (c *containerLXC) diskState() map[string]api.ContainerStateDisk {
 			continue
 		}
 
-		disk[dev.Name] = api.ContainerStateDisk{Usage: usage}
+		disk[dev.Name] = api.InstanceStateDisk{Usage: usage}
 	}
 
 	return disk
 }
 
-func (c *containerLXC) memoryState() api.ContainerStateMemory {
-	memory := api.ContainerStateMemory{}
+func (c *containerLXC) memoryState() api.InstanceStateMemory {
+	memory := api.InstanceStateMemory{}
 
 	if !c.state.OS.CGroupMemoryController {
 		return memory
@@ -6067,8 +6067,8 @@ func (c *containerLXC) memoryState() api.ContainerStateMemory {
 	return memory
 }
 
-func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
-	result := map[string]api.ContainerStateNetwork{}
+func (c *containerLXC) networkState() map[string]api.InstanceStateNetwork {
+	result := map[string]api.InstanceStateNetwork{}
 
 	pid := c.InitPID()
 	if pid < 1 {
@@ -6105,7 +6105,7 @@ func (c *containerLXC) networkState() map[string]api.ContainerStateNetwork {
 		// setns() + netns_getifaddrs() style retrieval.
 		c.state.OS.NetnsGetifaddrs = false
 
-		nw := map[string]api.ContainerStateNetwork{}
+		nw := map[string]api.InstanceStateNetwork{}
 		err = json.Unmarshal([]byte(out), &nw)
 		if err != nil {
 			logger.Error("Failure to read forkgetnet json", log.Ctx{"container": c.name, "err": err})
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index c7be6f8f64..829a16a423 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -65,7 +65,7 @@ func containerPatch(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	req := api.ContainerPut{}
+	req := api.InstancePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_post.go b/lxd/container_post.go
index e030133472..10729cc3e8 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -157,7 +157,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		return BadRequest(err)
 	}
 
-	req := api.ContainerPost{}
+	req := api.InstancePost{}
 	err = json.NewDecoder(rdr2).Decode(&req)
 	if err != nil {
 		return BadRequest(err)
@@ -348,11 +348,11 @@ func containerPostClusteringMigrate(d *Daemon, c container, oldName, newName, ne
 		// If the destination name is not set, we have generated a random name for
 		// the new container, so we need to rename it.
 		if isSameName {
-			containerPost := api.ContainerPost{
+			instancePost := api.InstancePost{
 				Name: oldName,
 			}
 
-			op, err := dest.RenameContainer(destName, containerPost)
+			op, err := dest.RenameInstance(destName, instancePost)
 			if err != nil {
 				return errors.Wrap(err, "Failed to issue rename container API request")
 			}
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 6ac1426f96..ae29ec4a3e 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -55,7 +55,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		return PreconditionFailed(err)
 	}
 
-	configRaw := api.ContainerPut{}
+	configRaw := api.InstancePut{}
 	if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
 		return BadRequest(err)
 	}
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index d800cb0590..585a22b02b 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -41,7 +41,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 
 	recursion := util.IsRecursionRequest(r)
 	resultString := []string{}
-	resultMap := []*api.ContainerSnapshot{}
+	resultMap := []*api.InstanceSnapshot{}
 
 	if !recursion {
 		snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
@@ -76,7 +76,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 				continue
 			}
 
-			resultMap = append(resultMap, render.(*api.ContainerSnapshot))
+			resultMap = append(resultMap, render.(*api.InstanceSnapshot))
 		}
 	}
 
@@ -117,7 +117,7 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		return SmartError(err)
 	}
 
-	req := api.ContainerSnapshotsPost{}
+	req := api.InstanceSnapshotsPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -257,7 +257,7 @@ func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response
 			return InternalError(err)
 		}
 
-		configRaw := api.ContainerSnapshotPut{}
+		configRaw := api.InstanceSnapshotPut{}
 
 		err = json.Unmarshal(body, &configRaw)
 		if err != nil {
@@ -308,7 +308,7 @@ func snapshotGet(sc container, name string) Response {
 		return SmartError(err)
 	}
 
-	return SyncResponse(true, render.(*api.ContainerSnapshot))
+	return SyncResponse(true, render.(*api.InstanceSnapshot))
 }
 
 func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
@@ -329,13 +329,13 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 		rdr2 := ioutil.NopCloser(bytes.NewBuffer(body))
 		rdr3 := ioutil.NopCloser(bytes.NewBuffer(body))
 
-		req := api.ContainerPost{}
+		req := api.InstancePost{}
 		err = json.NewDecoder(rdr2).Decode(&req)
 		if err != nil {
 			return BadRequest(err)
 		}
 
-		reqNew := api.ContainerSnapshotPost{}
+		reqNew := api.InstanceSnapshotPost{}
 		err = json.NewDecoder(rdr3).Decode(&reqNew)
 		if err != nil {
 			return BadRequest(err)
diff --git a/lxd/container_state.go b/lxd/container_state.go
index c47309546a..96497daaec 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -65,7 +65,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 		return response
 	}
 
-	raw := api.ContainerStatePut{}
+	raw := api.InstanceStatePut{}
 
 	// We default to -1 (i.e. no timeout) here instead of 0 (instant
 	// timeout).
diff --git a/lxd/container_test.go b/lxd/container_test.go
index 55f9fe3443..878b88b24c 100644
--- a/lxd/container_test.go
+++ b/lxd/container_test.go
@@ -105,7 +105,7 @@ func (suite *containerTestSuite) TestContainer_ProfilesOverwriteDefaultNic() {
 	out, _, err := c.Render()
 	suite.Req.Nil(err)
 
-	state := out.(*api.Container)
+	state := out.(*api.Instance)
 	defer c.Delete()
 
 	suite.Equal(
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 60193da982..2dbb3a2713 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -44,8 +44,8 @@ func containersGet(d *Daemon, r *http.Request) Response {
 
 func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	resultString := []string{}
-	resultList := []*api.Container{}
-	resultFullList := []*api.ContainerFull{}
+	resultList := []*api.Instance{}
+	resultFullList := []*api.InstanceFull{}
 	resultMu := sync.Mutex{}
 
 	// Instance type.
@@ -101,9 +101,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 	}
 
 	// Append containers to list and handle errors
-	resultListAppend := func(name string, c api.Container, err error) {
+	resultListAppend := func(name string, c api.Instance, err error) {
 		if err != nil {
-			c = api.Container{
+			c = api.Instance{
 				Name:       name,
 				Status:     api.Error.String(),
 				StatusCode: api.Error,
@@ -115,9 +115,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		resultMu.Unlock()
 	}
 
-	resultFullListAppend := func(name string, c api.ContainerFull, err error) {
+	resultFullListAppend := func(name string, c api.InstanceFull, err error) {
 		if err != nil {
-			c = api.ContainerFull{Container: api.Container{
+			c = api.InstanceFull{Instance: api.Instance{
 				Name:       name,
 				Status:     api.Error.String(),
 				StatusCode: api.Error,
@@ -143,9 +143,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 		if recursion > 0 && address == "0.0.0.0" {
 			for _, container := range containers {
 				if recursion == 1 {
-					resultListAppend(container, api.Container{}, fmt.Errorf("unavailable"))
+					resultListAppend(container, api.Instance{}, fmt.Errorf("unavailable"))
 				} else {
-					resultFullListAppend(container, api.ContainerFull{}, fmt.Errorf("unavailable"))
+					resultFullListAppend(container, api.InstanceFull{}, fmt.Errorf("unavailable"))
 				}
 			}
 
@@ -164,7 +164,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 					cs, err := doContainersGetFromNode(project, address, cert, instanceType)
 					if err != nil {
 						for _, name := range containers {
-							resultListAppend(name, api.Container{}, err)
+							resultListAppend(name, api.Instance{}, err)
 						}
 
 						return
@@ -180,7 +180,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 				cs, err := doContainersFullGetFromNode(project, address, cert, instanceType)
 				if err != nil {
 					for _, name := range containers {
-						resultFullListAppend(name, api.ContainerFull{}, err)
+						resultFullListAppend(name, api.InstanceFull{}, err)
 					}
 
 					return
@@ -224,9 +224,9 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 						if recursion == 1 {
 							c, _, err := nodeCts[container].Render()
 							if err != nil {
-								resultListAppend(container, api.Container{}, err)
+								resultListAppend(container, api.Instance{}, err)
 							} else {
-								resultListAppend(container, *c.(*api.Container), err)
+								resultListAppend(container, *c.(*api.Instance), err)
 							}
 
 							continue
@@ -234,7 +234,7 @@ func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
 
 						c, _, err := nodeCts[container].RenderFull()
 						if err != nil {
-							resultFullListAppend(container, api.ContainerFull{}, err)
+							resultFullListAppend(container, api.InstanceFull{}, err)
 						} else {
 							resultFullListAppend(container, *c, err)
 						}
@@ -276,8 +276,8 @@ 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, instanceType instance.Type) ([]api.Container, error) {
-	f := func() ([]api.Container, error) {
+func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.Instance, error) {
+	f := func() ([]api.Instance, error) {
 		client, err := cluster.Connect(node, cert, true)
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
@@ -285,9 +285,9 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
 
 		client = client.UseProject(project)
 
-		containers, err := client.GetContainers()
+		containers, err := client.GetInstances(api.InstanceType(instanceType.String()))
 		if err != nil {
-			return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
+			return nil, errors.Wrapf(err, "Failed to get instances from node %s", node)
 		}
 
 		return containers, nil
@@ -296,7 +296,7 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
 	timeout := time.After(30 * time.Second)
 	done := make(chan struct{})
 
-	var containers []api.Container
+	var containers []api.Instance
 	var err error
 
 	go func() {
@@ -306,15 +306,15 @@ func doContainersGetFromNode(project, node string, cert *shared.CertInfo, instan
 
 	select {
 	case <-timeout:
-		err = fmt.Errorf("Timeout getting containers from node %s", node)
+		err = fmt.Errorf("Timeout getting instances from node %s", node)
 	case <-done:
 	}
 
 	return containers, err
 }
 
-func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.ContainerFull, error) {
-	f := func() ([]api.ContainerFull, error) {
+func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, instanceType instance.Type) ([]api.InstanceFull, error) {
+	f := func() ([]api.InstanceFull, error) {
 		client, err := cluster.Connect(node, cert, true)
 		if err != nil {
 			return nil, errors.Wrapf(err, "Failed to connect to node %s", node)
@@ -322,30 +322,30 @@ func doContainersFullGetFromNode(project, node string, cert *shared.CertInfo, in
 
 		client = client.UseProject(project)
 
-		containers, err := client.GetContainersFull()
+		instances, err := client.GetInstancesFull(api.InstanceType(instanceType.String()))
 		if err != nil {
-			return nil, errors.Wrapf(err, "Failed to get containers from node %s", node)
+			return nil, errors.Wrapf(err, "Failed to get instances from node %s", node)
 		}
 
-		return containers, nil
+		return instances, nil
 	}
 
 	timeout := time.After(30 * time.Second)
 	done := make(chan struct{})
 
-	var containers []api.ContainerFull
+	var instances []api.InstanceFull
 	var err error
 
 	go func() {
-		containers, err = f()
+		instances, err = f()
 		done <- struct{}{}
 	}()
 
 	select {
 	case <-timeout:
-		err = fmt.Errorf("Timeout getting containers from node %s", node)
+		err = fmt.Errorf("Timeout getting instances from node %s", node)
 	case <-done:
 	}
 
-	return containers, err
+	return instances, err
 }
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index e6858c0077..b087a6b762 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -30,7 +30,7 @@ import (
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-func createFromImage(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response {
 	var hash string
 	var err error
 
@@ -149,7 +149,7 @@ func createFromImage(d *Daemon, project string, req *api.ContainersPost) Respons
 	return OperationResponse(op)
 }
 
-func createFromNone(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response {
 	args := db.ContainerArgs{
 		Project:     project,
 		Config:      req.Config,
@@ -185,7 +185,7 @@ func createFromNone(d *Daemon, project string, req *api.ContainersPost) Response
 	return OperationResponse(op)
 }
 
-func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Response {
 	// Validate migration mode
 	if req.Source.Mode != "pull" && req.Source.Mode != "push" {
 		return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
@@ -427,7 +427,7 @@ func createFromMigration(d *Daemon, project string, req *api.ContainersPost) Res
 	return OperationResponse(op)
 }
 
-func createFromCopy(d *Daemon, project string, req *api.ContainersPost) Response {
+func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response {
 	if req.Source.Source == "" {
 		return BadRequest(fmt.Errorf("must specify a source container"))
 	}
@@ -679,7 +679,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	// Parse the request
-	req := api.ContainersPost{}
+	req := api.InstancesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 		return BadRequest(err)
 	}
@@ -716,8 +716,8 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			client = client.UseProject(project)
 			client = client.UseTarget(targetNode)
 
-			logger.Debugf("Forward container post request to %s", address)
-			op, err := client.CreateContainer(req)
+			logger.Debugf("Forward instance post request to %s", address)
+			op, err := client.CreateInstance(req)
 			if err != nil {
 				return SmartError(err)
 			}
@@ -798,7 +798,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 }
 
-func containerFindStoragePool(d *Daemon, project string, req *api.ContainersPost) (string, string, string, map[string]string, Response) {
+func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, Response) {
 	// Grab the container's root device if one is specified
 	storagePool := ""
 	storagePoolProfile := ""
@@ -855,7 +855,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.ContainersPost
 	return storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, nil
 }
 
-func clusterCopyContainerInternal(d *Daemon, source container, project string, req *api.ContainersPost) Response {
+func clusterCopyContainerInternal(d *Daemon, source container, project string, req *api.InstancesPost) Response {
 	name := req.Source.Source
 
 	// Locate the source of the container
@@ -892,27 +892,27 @@ func clusterCopyContainerInternal(d *Daemon, source container, project string, r
 	if shared.IsSnapshot(req.Source.Source) {
 		cName, sName, _ := shared.ContainerGetParentAndSnapshotName(req.Source.Source)
 
-		pullReq := api.ContainerSnapshotPost{
+		pullReq := api.InstanceSnapshotPost{
 			Migration: true,
 			Live:      req.Source.Live,
 			Name:      req.Name,
 		}
 
-		op, err := client.MigrateContainerSnapshot(cName, sName, pullReq)
+		op, err := client.MigrateInstanceSnapshot(cName, sName, pullReq)
 		if err != nil {
 			return SmartError(err)
 		}
 
 		opAPI = op.Get()
 	} else {
-		pullReq := api.ContainerPost{
+		pullReq := api.InstancePost{
 			Migration:     true,
 			Live:          req.Source.Live,
 			ContainerOnly: req.Source.ContainerOnly,
 			Name:          req.Name,
 		}
 
-		op, err := client.MigrateContainer(req.Source.Source, pullReq)
+		op, err := client.MigrateInstance(req.Source.Source, pullReq)
 		if err != nil {
 			return SmartError(err)
 		}
diff --git a/lxd/images.go b/lxd/images.go
index 383ae47643..2e3dfe594c 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -1406,7 +1406,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 			return nil
 		}
 
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			op, err := client.DeleteImage(imgInfo.Fingerprint)
 			if err != nil {
 				return errors.Wrap(err, "Failed to request to delete image from peer node")
@@ -1951,7 +1951,7 @@ func imageSecret(d *Daemon, r *http.Request) Response {
 	return OperationResponse(op)
 }
 
-func imageImportFromNode(imagesDir string, client lxd.ContainerServer, fingerprint string) error {
+func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprint string) error {
 	// Prepare the temp files
 	buildDir, err := ioutil.TempDir(imagesDir, "lxd_build_")
 	if err != nil {
diff --git a/lxd/init.go b/lxd/init.go
index 74c16ef7b4..ba961a5b85 100644
--- a/lxd/init.go
+++ b/lxd/init.go
@@ -26,7 +26,7 @@ type initDataCluster struct {
 // It's used both by the 'lxd init' command and by the PUT /1.0/cluster API.
 //
 // In case of error, the returned function can be used to revert the changes.
-func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), error) {
+func initDataNodeApply(d lxd.InstanceServer, config initDataNode) (func(), error) {
 	// Handle reverts
 	reverts := []func(){}
 	revert := func() {
@@ -339,7 +339,7 @@ func initDataNodeApply(d lxd.ContainerServer, config initDataNode) (func(), erro
 // Helper to initialize LXD clustering.
 //
 // Used by the 'lxd init' command.
-func initDataClusterApply(d lxd.ContainerServer, config *initDataCluster) error {
+func initDataClusterApply(d lxd.InstanceServer, config *initDataCluster) error {
 	if config == nil || !config.Enabled {
 		return nil
 	}
diff --git a/lxd/main_init_auto.go b/lxd/main_init_auto.go
index a819bca3e2..4022c91f30 100644
--- a/lxd/main_init_auto.go
+++ b/lxd/main_init_auto.go
@@ -11,7 +11,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunAuto(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
 	// Sanity checks
 	if c.flagStorageBackend != "" && !shared.StringInSlice(c.flagStorageBackend, supportedStoragePoolDrivers) {
 		return nil, fmt.Errorf("The requested backend '%s' isn't supported by lxd init", c.flagStorageBackend)
diff --git a/lxd/main_init_dump.go b/lxd/main_init_dump.go
index 4ce72e3f90..087d123fa8 100644
--- a/lxd/main_init_dump.go
+++ b/lxd/main_init_dump.go
@@ -10,7 +10,7 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-func (c *cmdInit) RunDump(d lxd.ContainerServer) error {
+func (c *cmdInit) RunDump(d lxd.InstanceServer) error {
 	currentServer, _, err := d.GetServer()
 	if err != nil {
 		return errors.Wrap(err, "Failed to retrieve current server configuration")
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index af4e14ee6b..3abb254241 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -24,7 +24,7 @@ import (
 	"github.com/lxc/lxd/shared/idmap"
 )
 
-func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
 	// Initialize config
 	config := cmdInitData{}
 	config.Node.Config = map[string]interface{}{}
@@ -98,7 +98,7 @@ func (c *cmdInit) RunInteractive(cmd *cobra.Command, args []string, d lxd.Contai
 	return &config, nil
 }
 
-func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askClustering(config *cmdInitData, d lxd.InstanceServer) error {
 	if cli.AskBool("Would you like to use LXD clustering? (yes/no) [default=no]: ", "no") {
 		config.Cluster = &initDataCluster{}
 		config.Cluster.Enabled = true
@@ -209,7 +209,7 @@ func (c *cmdInit) askClustering(config *cmdInitData, d lxd.ContainerServer) erro
 	return nil
 }
 
-func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.InstanceServer) error {
 	if !cli.AskBool("Would you like to connect to a MAAS server? (yes/no) [default=no]: ", "no") {
 		return nil
 	}
@@ -230,7 +230,7 @@ func (c *cmdInit) askMAAS(config *cmdInitData, d lxd.ContainerServer) error {
 	return nil
 }
 
-func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.InstanceServer) error {
 	if config.Cluster != nil || !cli.AskBool("Would you like to create a new local network bridge? (yes/no) [default=yes]: ", "yes") {
 		// At this time, only the Ubuntu kernel supports the Fan, detect it
 		fanKernel := false
@@ -386,7 +386,7 @@ func (c *cmdInit) askNetworking(config *cmdInitData, d lxd.ContainerServer) erro
 	return nil
 }
 
-func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askStorage(config *cmdInitData, d lxd.InstanceServer) error {
 	if config.Cluster != nil {
 		if cli.AskBool("Do you want to configure a new local storage pool? (yes/no) [default=yes]: ", "yes") {
 			err := c.askStoragePool(config, d, "local")
@@ -412,7 +412,7 @@ func (c *cmdInit) askStorage(config *cmdInitData, d lxd.ContainerServer) error {
 	return c.askStoragePool(config, d, "all")
 }
 
-func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.ContainerServer, poolType string) error {
+func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.InstanceServer, poolType string) error {
 	// Figure out the preferred storage driver
 	availableBackends := c.availableStorageDrivers(poolType)
 
@@ -599,7 +599,7 @@ your Linux distribution and run "lxd init" again afterwards.
 	return nil
 }
 
-func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.ContainerServer) error {
+func (c *cmdInit) askDaemon(config *cmdInitData, d lxd.InstanceServer) error {
 	// Detect lack of uid/gid
 	idmapset, err := idmap.DefaultIdmapSet("", "")
 	if (err != nil || len(idmapset.Idmap) == 0 || idmapset.Usable() != nil) && shared.RunningInUserNS() {
diff --git a/lxd/main_init_preseed.go b/lxd/main_init_preseed.go
index 0f75481d6c..c6b5349494 100644
--- a/lxd/main_init_preseed.go
+++ b/lxd/main_init_preseed.go
@@ -11,7 +11,7 @@ import (
 	"github.com/lxc/lxd/client"
 )
 
-func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.ContainerServer) (*cmdInitData, error) {
+func (c *cmdInit) RunPreseed(cmd *cobra.Command, args []string, d lxd.InstanceServer) (*cmdInitData, error) {
 	// Read the YAML
 	bytes, err := ioutil.ReadAll(os.Stdin)
 	if err != nil {
diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 47e3e2d209..d33a9d6d6a 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -1133,7 +1133,7 @@ func (c *migrationSink) Do(migrateOp *operation) error {
 	}
 }
 
-func (s *migrationSourceWs) ConnectContainerTarget(target api.ContainerPostTarget) error {
+func (s *migrationSourceWs) ConnectContainerTarget(target api.InstancePostTarget) error {
 	return s.ConnectTarget(target.Certificate, target.Operation, target.Websockets)
 }
 
diff --git a/lxd/networks.go b/lxd/networks.go
index 1b15144929..cad3d9d1c5 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -266,7 +266,7 @@ func networksPostCluster(d *Daemon, req api.NetworksPost) error {
 	if err != nil {
 		return err
 	}
-	notifyErr := notifier(func(client lxd.ContainerServer) error {
+	notifyErr := notifier(func(client lxd.InstanceServer) error {
 		server, _, err := client.GetServer()
 		if err != nil {
 			return err
@@ -491,7 +491,7 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 		if err != nil {
 			return SmartError(err)
 		}
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			return client.DeleteNetwork(name)
 		})
 		if err != nil {
@@ -824,7 +824,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 			return SmartError(err)
 		}
 
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			memberLeases, err := client.GetNetworkLeases(name)
 			if err != nil {
 				return err
diff --git a/lxd/profiles.go b/lxd/profiles.go
index cc2354320d..6c6e7c6c17 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -257,7 +257,7 @@ func profilePut(d *Daemon, r *http.Request) Response {
 			return SmartError(err)
 		}
 
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UseProject(project).UpdateProfile(name, profile.ProfilePut, "")
 		})
 		if err != nil {
diff --git a/lxd/response.go b/lxd/response.go
index bd0980fb6b..7bca1b4e17 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -111,7 +111,7 @@ func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]
 var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
 
 type forwardedResponse struct {
-	client  lxd.ContainerServer
+	client  lxd.InstanceServer
 	request *http.Request
 }
 
@@ -154,7 +154,7 @@ func (r *forwardedResponse) String() string {
 
 // ForwardedResponse takes a request directed to a node and forwards it to
 // another node, writing back the response it gegs.
-func ForwardedResponse(client lxd.ContainerServer, request *http.Request) Response {
+func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
 	return &forwardedResponse{
 		client:  client,
 		request: request,
diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index 443323eee8..a9c5e60b10 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -237,7 +237,7 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
 	if err != nil {
 		return err
 	}
-	notifyErr := notifier(func(client lxd.ContainerServer) error {
+	notifyErr := notifier(func(client lxd.InstanceServer) error {
 		server, _, err := client.GetServer()
 		if err != nil {
 			return err
@@ -369,7 +369,7 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
 		if err != nil {
 			return SmartError(err)
 		}
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
 		})
 		if err != nil {
@@ -456,7 +456,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
 		if err != nil {
 			return SmartError(err)
 		}
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
 		})
 		if err != nil {
@@ -599,7 +599,7 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
 		if err != nil {
 			return SmartError(err)
 		}
-		err = notifier(func(client lxd.ContainerServer) error {
+		err = notifier(func(client lxd.InstanceServer) error {
 			_, _, err := client.GetServer()
 			if err != nil {
 				return err


More information about the lxc-devel mailing list