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

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
This PR continues what was started in https://github.com/lxc/lxd/pull/6193 by adding a full set of instance management functions to the `InstanceServer` interface. It also adds the implementations of those functions that use the `/1.0/instances` endpoints.
From d7d30a08104061b1f74a51c92dcfcf184cd0c899 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 11:57:02 +0100
Subject: [PATCH 1/6] client/connection: Adds connect functions that return
 InstanceServer type

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

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

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

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

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

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

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

And aliases existing functions to return old type.

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

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

From 8bf45f7e90846a938f690501afdd2047ab2dbac1 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 15:46:44 +0100
Subject: [PATCH 4/6] client/interfaces: Populates InstanceServer with rest of
 functions

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

diff --git a/client/interfaces.go b/client/interfaces.go
index ae5cf82c9b..7ffce04c88 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -241,6 +241,65 @@ type InstanceServer interface {
 
        UseInstanceTarget(name string) (client InstanceServer)
        UseInstanceProject(name string) (client InstanceServer)
+
+       // Instance functions.
+       GetInstanceNames() (names []string, err error)
+       GetInstances() (instances []api.Instance, err error)
+       GetInstancesFull() (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)
+
+       CopyInstanceStoragePoolVolume(pool string, source InstanceServer, 
sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeCopyArgs) 
(op RemoteOperation, err error)
+       MoveInstanceStoragePoolVolume(pool string, source InstanceServer, 
sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeMoveArgs) 
(op RemoteOperation, err error)
 }
 
 // The ConnectionInfo struct represents general information for a connection

From c4adfff71afccca7c157d55633423130695751d0 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 15:47:41 +0100
Subject: [PATCH 5/6] client/lxd/storage/volumes: Adds storage management shims
 to implement InstanceServer

- CopyInstanceStoragePoolVolume
- MoveInstanceStoragePoolVolume

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

diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index 291ad4f3e2..1f11296558 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -519,6 +519,11 @@ func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, 
source ContainerServer,
        return r.tryCreateStoragePoolVolume(pool, req, info.Addresses)
 }
 
+// CopyInstanceStoragePoolVolume copies an existing instance server storage 
volume.
+func (r *ProtocolLXD) CopyInstanceStoragePoolVolume(pool string, source 
InstanceServer, sourcePool string, volume api.StorageVolume, args 
*StoragePoolVolumeCopyArgs) (RemoteOperation, error) {
+       return r.CopyStoragePoolVolume(pool, source, sourcePool, volume, args)
+}
+
 // MoveStoragePoolVolume renames or moves an existing storage volume
 func (r *ProtocolLXD) MoveStoragePoolVolume(pool string, source 
ContainerServer, sourcePool string, volume api.StorageVolume, args 
*StoragePoolVolumeMoveArgs) (RemoteOperation, error) {
        if !r.HasExtension("storage_api_local_volume_handling") {
@@ -554,6 +559,11 @@ func (r *ProtocolLXD) MoveStoragePoolVolume(pool string, 
source ContainerServer,
        return &rop, nil
 }
 
+// MoveInstanceStoragePoolVolume renames or moves an existing instance server 
storage volume.
+func (r *ProtocolLXD) MoveInstanceStoragePoolVolume(pool string, source 
InstanceServer, sourcePool string, volume api.StorageVolume, args 
*StoragePoolVolumeMoveArgs) (RemoteOperation, error) {
+       return r.MoveStoragePoolVolume(pool, source, sourcePool, volume, args)
+}
+
 // UpdateStoragePoolVolume updates the volume to match the provided 
StoragePoolVolume struct
 func (r *ProtocolLXD) UpdateStoragePoolVolume(pool string, volType string, 
name string, volume api.StorageVolumePut, ETag string) error {
        if !r.HasExtension("storage") {

From 8f2d6659ad7e9cc13f5eb568ff55174b2621e380 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parr...@canonical.com>
Date: Thu, 12 Sep 2019 15:50:34 +0100
Subject: [PATCH 6/6] client/lxd/instances: Adds instance related functions

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

Signed-off-by: Thomas Parrott <thomas.parr...@canonical.com>
---
 client/lxd_instances.go | 1915 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 1915 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..911754542f
--- /dev/null
+++ b/client/lxd_instances.go
@@ -0,0 +1,1915 @@
+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.
+
+// GetInstanceNames returns a list of instance names.
+func (r *ProtocolLXD) GetInstanceNames() ([]string, error) {
+       urls := []string{}
+
+       // Fetch the raw value
+       _, err := r.queryStruct("GET", "/instances", nil, "", &urls)
+       if err != nil {
+               return nil, err
+       }
+
+       // Parse it
+       names := []string{}
+       for _, url := range urls {
+               fields := strings.Split(url, "/instances/")
+               names = append(names, fields[len(fields)-1])
+       }
+
+       return names, nil
+}
+
+// GetInstances returns a list of instances.
+func (r *ProtocolLXD) GetInstances() ([]api.Instance, error) {
+       instances := []api.Instance{}
+
+       // Fetch the raw value
+       _, err := r.queryStruct("GET", "/instances?recursion=1", 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() ([]api.InstanceFull, error) {
+       instances := []api.InstanceFull{}
+
+       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", "/instances?recursion=2", 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{}
+
+       // Fetch the raw value
+       etag, err := r.queryStruct("GET", fmt.Sprintf("/instances/%s", 
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")
+       }
+
+       if args.PoolName == "" {
+               // Send the request
+               op, _, err := r.queryOperation("POST", "/instances", 
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/instances", 
r.httpHost))
+       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) {
+       if 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", "/instances", 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.ContainerOnly {
+                       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.ContainerOnly = args.ContainerOnly
+               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,
+       }
+
+       // 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) {
+       // Send the request
+       op, _, err := r.queryOperation("PUT", fmt.Sprintf("/instances/%s", 
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) {
+       // 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("/instances/%s", 
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) {
+       if 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("/instances/%s", 
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) {
+       // Send the request
+       op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/instances/%s", 
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) {
+       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("/instances/%s/exec", 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, path string) 
(io.ReadCloser, *InstanceFileResponse, error) {
+       // Prepare the HTTP request
+       requestURL, err := shared.URLEncode(
+               fmt.Sprintf("%s/1.0/instances/%s/files", r.httpHost, 
url.PathEscape(instanceName)),
+               map[string]string{"path": path})
+       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, path string, 
args InstanceFileArgs) error {
+       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/instances/%s/files?path=%s", 
r.httpHost, url.PathEscape(instanceName), url.QueryEscape(path))
+
+       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, path string) 
error {
+       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("/instances/%s/files?path=%s", url.PathEscape(instanceName), 
url.QueryEscape(path)), 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) {
+       urls := []string{}
+
+       // Fetch the raw value
+       _, err := r.queryStruct("GET", fmt.Sprintf("/instances/%s/snapshots", 
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("/instances/%s/snapshots/", 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) {
+       snapshots := []api.InstanceSnapshot{}
+
+       // Fetch the raw value
+       _, err := r.queryStruct("GET", 
fmt.Sprintf("/instances/%s/snapshots?recursion=1", 
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) {
+       snapshot := api.InstanceSnapshot{}
+
+       // Fetch the raw value
+       etag, err := r.queryStruct("GET", 
fmt.Sprintf("/instances/%s/snapshots/%s", 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) {
+       // 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("/instances/%s/snapshots", 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) {
+       // 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("/instances/%s/snapshots/%s", 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) {
+       // 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("/instances/%s/snapshots/%s", 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) {
+       // Send the request
+       op, _, err := r.queryOperation("DELETE", 
fmt.Sprintf("/instances/%s/snapshots/%s", 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) {
+       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("/instances/%s/snapshots/%s",
+               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) {
+       state := api.InstanceState{}
+
+       // Fetch the raw value
+       etag, err := r.queryStruct("GET", fmt.Sprintf("/instances/%s/state", 
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) {
+       // Send the request
+       op, _, err := r.queryOperation("PUT", 
fmt.Sprintf("/instances/%s/state", 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) {
+       urls := []string{}
+
+       // Fetch the raw value
+       _, err := r.queryStruct("GET", fmt.Sprintf("/instances/%s/logs", 
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("/instances/%s/logs/", 
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) {
+       // Prepare the HTTP request
+       url := fmt.Sprintf("%s/1.0/instances/%s/logs/%s", r.httpHost, 
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 {
+       // Send the request
+       _, _, err := r.query("DELETE", fmt.Sprintf("/instances/%s/logs/%s", 
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) {
+       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("/instances/%s/metadata", 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 {
+       if !r.HasExtension("container_edit_metadata") {
+               return fmt.Errorf("The server is missing the required 
\"container_edit_metadata\" API extension")
+       }
+
+       url := fmt.Sprintf("/instances/%s/metadata", 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) {
+       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("/instances/%s/metadata/templates", 
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) {
+       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/instances/%s/metadata/templates?path=%s", 
r.httpHost, 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 {
+       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/instances/%s/metadata/templates?path=%s", 
r.httpHost, 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 {
+       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("/instances/%s/metadata/templates?path=%s", 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) {
+       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("/instances/%s/console", 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) {
+       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/instances/%s/console", r.httpHost, 
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 {
+       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("/instances/%s/console", 
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) {
+       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("/instances/%s/backups",
+               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("/instances/%s/backups/",
+                       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) {
+       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("/instances/%s/backups?recursion=1", 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) {
+       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("/instances/%s/backups/%s", 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) {
+       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("/instances/%s/backups",
+               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) {
+       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("/instances/%s/backups/%s",
+               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) {
+       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("/instances/%s/backups/%s",
+               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) {
+       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/instances/%s/backups/%s/export", r.httpHost,
+               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
+}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to