[lxc-devel] [lxd/master] client: Make Operation and RemoteOperation interfaces

stgraber on Github lxc-bot at linuxcontainers.org
Wed Mar 21 18:09:55 UTC 2018


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 354 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20180321/377e3489/attachment.bin>
-------------- next part --------------
From ccf7bee7f3eb0300667e2216a7fb434d01c2bf47 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 21 Mar 2018 13:48:53 -0400
Subject: [PATCH] client: Make Operation and RemoteOperation interfaces
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go          |  69 ++++++++++++++++++----------
 client/lxd.go                 |   6 +--
 client/lxd_cluster.go         |   2 +-
 client/lxd_containers.go      | 104 +++++++++++++++++++++++-------------------
 client/lxd_images.go          |  19 ++++----
 client/lxd_storage_volumes.go |   8 ++--
 client/operations.go          |  40 +++++++++-------
 lxc/config.go                 |   2 +-
 lxc/copy.go                   |   2 +-
 lxc/delete.go                 |   2 +-
 lxc/exec.go                   |   3 +-
 lxc/image.go                  |   6 ++-
 lxc/publish.go                |   3 +-
 lxc/storage.go                |   2 +-
 lxc/utils/cancel.go           |   2 +-
 lxd-p2c/utils.go              |   8 ++--
 lxd/container_console.go      |   5 +-
 lxd/container_exec.go         |   5 +-
 lxd/containers_post.go        |   5 +-
 lxd/main_init.go              |   2 +-
 20 files changed, 172 insertions(+), 123 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index d92096af3..051ef104f 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -11,6 +11,25 @@ import (
 	"github.com/lxc/lxd/shared/ioprogress"
 )
 
+// The Operation type represents a currently running operation.
+type Operation interface {
+	AddHandler(function func(api.Operation)) (target *EventTarget, err error)
+	Cancel() (err error)
+	Get() (op api.Operation)
+	GetWebsocket(secret string) (conn *websocket.Conn, err error)
+	RemoveHandler(target *EventTarget) (err error)
+	Refresh() (err error)
+	Wait() (err error)
+}
+
+// The RemoteOperation type represents an Operation that may be using multiple servers.
+type RemoteOperation interface {
+	AddHandler(function func(api.Operation)) (target *EventTarget, err error)
+	CancelTarget() (err error)
+	GetTarget() (op *api.Operation, err error)
+	Wait() (err error)
+}
+
 // The Server type represents a generic read-only server.
 type Server interface {
 	GetConnectionInfo() (info *ConnectionInfo, err error)
@@ -63,16 +82,16 @@ type ContainerServer interface {
 	GetContainerNames() (names []string, err error)
 	GetContainers() (containers []api.Container, err error)
 	GetContainer(name string) (container *api.Container, ETag string, err error)
-	CreateContainer(container api.ContainersPost) (op *Operation, err error)
-	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
-	CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op *RemoteOperation, err error)
-	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
-	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
-	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
-	DeleteContainer(name string) (op *Operation, err error)
-
-	ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (op *Operation, err error)
-	ConsoleContainer(containerName string, console api.ContainerConsolePost, args *ContainerConsoleArgs) (op *Operation, err error)
+	CreateContainer(container api.ContainersPost) (op Operation, err error)
+	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op RemoteOperation, err error)
+	CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (op RemoteOperation, err error)
+	UpdateContainer(name string, container api.ContainerPut, ETag string) (op Operation, err error)
+	RenameContainer(name string, container api.ContainerPost) (op Operation, err error)
+	MigrateContainer(name string, container api.ContainerPost) (op Operation, err error)
+	DeleteContainer(name string) (op Operation, err error)
+
+	ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (op Operation, err error)
+	ConsoleContainer(containerName string, console api.ContainerConsolePost, args *ContainerConsoleArgs) (op Operation, err error)
 	GetContainerConsoleLog(containerName string, args *ContainerConsoleLogArgs) (content io.ReadCloser, err error)
 	DeleteContainerConsoleLog(containerName string, args *ContainerConsoleLogArgs) (err error)
 
@@ -83,14 +102,14 @@ type ContainerServer interface {
 	GetContainerSnapshotNames(containerName string) (names []string, err error)
 	GetContainerSnapshots(containerName string) (snapshots []api.ContainerSnapshot, err error)
 	GetContainerSnapshot(containerName string, name string) (snapshot *api.ContainerSnapshot, ETag string, err error)
-	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op *Operation, err error)
-	CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op *RemoteOperation, err error)
-	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
-	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op *Operation, err error)
-	DeleteContainerSnapshot(containerName string, name string) (op *Operation, err error)
+	CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (op Operation, err error)
+	CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (op RemoteOperation, err error)
+	RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op Operation, err error)
+	MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (op Operation, err error)
+	DeleteContainerSnapshot(containerName string, name string) (op Operation, err error)
 
 	GetContainerState(name string) (state *api.ContainerState, ETag string, err error)
-	UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (op *Operation, err error)
+	UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (op Operation, err error)
 
 	GetContainerLogfiles(name string) (logfiles []string, err error)
 	GetContainerLogfile(name string, filename string) (content io.ReadCloser, err error)
@@ -109,12 +128,12 @@ type ContainerServer interface {
 	GetEvents() (listener *EventListener, err error)
 
 	// Image functions
-	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
-	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
+	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op Operation, err error)
+	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op RemoteOperation, err error)
 	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
-	DeleteImage(fingerprint string) (op *Operation, err error)
-	RefreshImage(fingerprint string) (op *Operation, err error)
-	CreateImageSecret(fingerprint string) (op *Operation, err error)
+	DeleteImage(fingerprint string) (op Operation, err error)
+	RefreshImage(fingerprint string) (op Operation, err error)
+	CreateImageSecret(fingerprint string) (op Operation, err error)
 	CreateImageAlias(alias api.ImageAliasesPost) (err error)
 	UpdateImageAlias(name string, alias api.ImageAliasesEntryPut, ETag string) (err error)
 	RenameImageAlias(name string, alias api.ImageAliasesEntryPost) (err error)
@@ -163,12 +182,12 @@ type ContainerServer interface {
 	UpdateStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePut, ETag string) (err error)
 	DeleteStoragePoolVolume(pool string, volType string, name string) (err error)
 	RenameStoragePoolVolume(pool string, volType string, name string, volume api.StorageVolumePost) (err error)
-	CopyStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeCopyArgs) (op *RemoteOperation, err error)
-	MoveStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeMoveArgs) (op *RemoteOperation, err error)
+	CopyStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeCopyArgs) (op RemoteOperation, err error)
+	MoveStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeMoveArgs) (op RemoteOperation, err error)
 
 	// Cluster functions ("cluster" API extensions)
 	GetCluster() (cluster *api.Cluster, ETag string, err error)
-	UpdateCluster(cluster api.ClusterPut, ETag string) (op *Operation, err error)
+	UpdateCluster(cluster api.ClusterPut, ETag string) (op Operation, err error)
 	DeleteClusterMember(name string, force bool) (err error)
 	GetClusterMemberNames() (names []string, err error)
 	GetClusterMembers() (members []api.ClusterMember, err error)
@@ -178,7 +197,7 @@ type ContainerServer interface {
 	// Internal functions (for internal use)
 	RawQuery(method string, path string, data interface{}, queryETag string) (resp *api.Response, ETag string, err error)
 	RawWebsocket(path string) (conn *websocket.Conn, err error)
-	RawOperation(method string, path string, data interface{}, queryETag string) (op *Operation, ETag string, err error)
+	RawOperation(method string, path string, data interface{}, queryETag string) (op Operation, ETag string, err error)
 }
 
 // The ConnectionInfo struct represents general information for a connection
diff --git a/client/lxd.go b/client/lxd.go
index 4218015d1..96458efed 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -114,7 +114,7 @@ func (r *ProtocolLXD) RawWebsocket(path string) (*websocket.Conn, error) {
 
 // RawOperation allows direct querying of a LXD API endpoint returning
 // background operations.
-func (r *ProtocolLXD) RawOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
+func (r *ProtocolLXD) RawOperation(method string, path string, data interface{}, ETag string) (Operation, string, error) {
 	return r.queryOperation(method, path, data, ETag)
 }
 
@@ -235,7 +235,7 @@ func (r *ProtocolLXD) queryStruct(method string, path string, data interface{},
 	return etag, nil
 }
 
-func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (*Operation, string, error) {
+func (r *ProtocolLXD) queryOperation(method string, path string, data interface{}, ETag string) (Operation, string, error) {
 	// Attempt to setup an early event listener
 	listener, err := r.GetEvents()
 	if err != nil {
@@ -263,7 +263,7 @@ func (r *ProtocolLXD) queryOperation(method string, path string, data interface{
 	}
 
 	// Setup an Operation wrapper
-	op := Operation{
+	op := operation{
 		Operation: *respOperation,
 		r:         r,
 		listener:  listener,
diff --git a/client/lxd_cluster.go b/client/lxd_cluster.go
index 6a807af01..f21a5bb4b 100644
--- a/client/lxd_cluster.go
+++ b/client/lxd_cluster.go
@@ -24,7 +24,7 @@ func (r *ProtocolLXD) GetCluster() (*api.Cluster, string, error) {
 }
 
 // UpdateCluster requests to bootstrap a new cluster
-func (r *ProtocolLXD) UpdateCluster(cluster api.ClusterPut, ETag string) (*Operation, error) {
+func (r *ProtocolLXD) UpdateCluster(cluster api.ClusterPut, ETag string) (Operation, error) {
 	if !r.HasExtension("clustering") {
 		return nil, fmt.Errorf("The server is missing the required \"clustering\" API extension")
 	}
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 233f6c807..fddbf0ac0 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -63,7 +63,7 @@ func (r *ProtocolLXD) GetContainer(name string) (*api.Container, string, error)
 }
 
 // CreateContainer requests that LXD creates a new container
-func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation, error) {
+func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (Operation, error) {
 	if container.Source.ContainerOnly {
 		if !r.HasExtension("container_only_migration") {
 			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
@@ -83,12 +83,12 @@ func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation,
 	return op, nil
 }
 
-func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string) (RemoteOperation, error) {
 	if len(urls) == 0 {
 		return nil, fmt.Errorf("The source server isn't listening on the network")
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		chDone: make(chan bool),
 	}
 
@@ -138,7 +138,7 @@ func (r *ProtocolLXD) tryCreateContainer(req api.ContainersPost, urls []string)
 }
 
 // CreateContainerFromImage is a convenience function to make it easier to create a container from an existing image
-func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Image, req api.ContainersPost) (*RemoteOperation, error) {
+func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Image, req api.ContainersPost) (RemoteOperation, error) {
 	// Set the minimal source fields
 	req.Source.Type = "image"
 
@@ -153,7 +153,7 @@ func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Ima
 			return nil, err
 		}
 
-		rop := RemoteOperation{
+		rop := remoteOperation{
 			targetOp: op,
 			chDone:   make(chan bool),
 		}
@@ -201,7 +201,7 @@ func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Ima
 }
 
 // CopyContainer copies a container from a remote server. Additional options can be passed using ContainerCopyArgs
-func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (*RemoteOperation, error) {
+func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Container, args *ContainerCopyArgs) (RemoteOperation, error) {
 	// Base request
 	req := api.ContainersPost{
 		Name:         container.Name,
@@ -261,7 +261,7 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 			return nil, err
 		}
 
-		rop := RemoteOperation{
+		rop := remoteOperation{
 			targetOp: op,
 			chDone:   make(chan bool),
 		}
@@ -298,15 +298,16 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		if err != nil {
 			return nil, err
 		}
+		opAPI := op.Get()
 
 		targetSecrets := map[string]string{}
-		for k, v := range op.Metadata {
+		for k, v := range opAPI.Metadata {
 			targetSecrets[k] = v.(string)
 		}
 
 		// Prepare the source request
 		target := api.ContainerPostTarget{}
-		target.Operation = op.ID
+		target.Operation = opAPI.ID
 		target.Websockets = targetSecrets
 		target.Certificate = info.Certificate
 		sourceReq.Target = &target
@@ -324,9 +325,10 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 	if err != nil {
 		return nil, err
 	}
+	opAPI := op.Get()
 
 	sourceSecrets := map[string]string{}
-	for k, v := range op.Metadata {
+	for k, v := range opAPI.Metadata {
 		sourceSecrets[k] = v.(string)
 	}
 
@@ -341,21 +343,22 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 		if err != nil {
 			return nil, err
 		}
+		targetOpAPI := targetOp.Get()
 
 		// Extract the websockets
 		targetSecrets := map[string]string{}
-		for k, v := range targetOp.Metadata {
+		for k, v := range targetOpAPI.Metadata {
 			targetSecrets[k] = v.(string)
 		}
 
 		// Launch the relay
-		err = r.proxyMigration(targetOp, targetSecrets, source, op, sourceSecrets)
+		err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
 		if err != nil {
 			return nil, err
 		}
 
 		// Prepare a tracking operation
-		rop := RemoteOperation{
+		rop := remoteOperation{
 			targetOp: targetOp,
 			chDone:   make(chan bool),
 		}
@@ -372,14 +375,14 @@ func (r *ProtocolLXD) CopyContainer(source ContainerServer, container api.Contai
 	// Pull mode migration
 	req.Source.Type = "migration"
 	req.Source.Mode = "pull"
-	req.Source.Operation = op.ID
+	req.Source.Operation = opAPI.ID
 	req.Source.Websockets = sourceSecrets
 	req.Source.Certificate = info.Certificate
 
 	return r.tryCreateContainer(req, info.Addresses)
 }
 
-func (r *ProtocolLXD) proxyMigration(targetOp *Operation, targetSecrets map[string]string, source ContainerServer, sourceOp *Operation, sourceSecrets map[string]string) error {
+func (r *ProtocolLXD) proxyMigration(targetOp *operation, targetSecrets map[string]string, source ContainerServer, sourceOp *operation, sourceSecrets map[string]string) error {
 	// Sanity checks
 	for n := range targetSecrets {
 		_, ok := sourceSecrets[n]
@@ -465,7 +468,7 @@ func (r *ProtocolLXD) proxyMigration(targetOp *Operation, targetSecrets map[stri
 }
 
 // UpdateContainer updates the container definition
-func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (*Operation, error) {
+func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("PUT", fmt.Sprintf("/containers/%s", url.QueryEscape(name)), container, ETag)
 	if err != nil {
@@ -476,7 +479,7 @@ func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, E
 }
 
 // RenameContainer requests that LXD renames the container
-func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost) (*Operation, error) {
+func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost) (Operation, error) {
 	// Sanity check
 	if container.Migration {
 		return nil, fmt.Errorf("Can't ask for a migration through RenameContainer")
@@ -491,12 +494,12 @@ func (r *ProtocolLXD) RenameContainer(name string, container api.ContainerPost)
 	return op, nil
 }
 
-func (r *ProtocolLXD) tryMigrateContainer(source ContainerServer, name string, req api.ContainerPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryMigrateContainer(source ContainerServer, name string, req api.ContainerPost, urls []string) (RemoteOperation, error) {
 	if len(urls) == 0 {
 		return nil, fmt.Errorf("The target server isn't listening on the network")
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		chDone: make(chan bool),
 	}
 
@@ -542,7 +545,7 @@ func (r *ProtocolLXD) tryMigrateContainer(source ContainerServer, name string, r
 }
 
 // MigrateContainer requests that LXD prepares for a container migration
-func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost) (*Operation, error) {
+func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost) (Operation, error) {
 	if container.ContainerOnly {
 		if !r.HasExtension("container_only_migration") {
 			return nil, fmt.Errorf("The server is missing the required \"container_only_migration\" API extension")
@@ -569,7 +572,7 @@ func (r *ProtocolLXD) MigrateContainer(name string, container api.ContainerPost)
 }
 
 // DeleteContainer requests that LXD deletes the container
-func (r *ProtocolLXD) DeleteContainer(name string) (*Operation, error) {
+func (r *ProtocolLXD) DeleteContainer(name string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/containers/%s", url.QueryEscape(name)), nil, "")
 	if err != nil {
@@ -580,7 +583,7 @@ func (r *ProtocolLXD) DeleteContainer(name string) (*Operation, error) {
 }
 
 // ExecContainer requests that LXD spawns a command inside the container
-func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (*Operation, error) {
+func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExecPost, args *ContainerExecArgs) (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")
@@ -592,13 +595,14 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 	if err != nil {
 		return nil, err
 	}
+	opAPI := op.Get()
 
 	// Process additional arguments
 	if args != nil {
 		// Parse the fds
 		fds := map[string]string{}
 
-		value, ok := op.Metadata["fds"]
+		value, ok := opAPI.Metadata["fds"]
 		if ok {
 			values := value.(map[string]interface{})
 			for k, v := range values {
@@ -608,7 +612,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 
 		// Call the control handler with a connection to the control socket
 		if args.Control != nil && fds["control"] != "" {
-			conn, err := r.GetOperationWebsocket(op.ID, fds["control"])
+			conn, err := r.GetOperationWebsocket(opAPI.ID, fds["control"])
 			if err != nil {
 				return nil, err
 			}
@@ -620,7 +624,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 			// Handle interactive sections
 			if args.Stdin != nil && args.Stdout != nil {
 				// Connect to the websocket
-				conn, err := r.GetOperationWebsocket(op.ID, fds["0"])
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
 				if err != nil {
 					return nil, err
 				}
@@ -647,7 +651,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 
 			// Handle stdin
 			if fds["0"] != "" {
-				conn, err := r.GetOperationWebsocket(op.ID, fds["0"])
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
 				if err != nil {
 					return nil, err
 				}
@@ -658,7 +662,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 
 			// Handle stdout
 			if fds["1"] != "" {
-				conn, err := r.GetOperationWebsocket(op.ID, fds["1"])
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["1"])
 				if err != nil {
 					return nil, err
 				}
@@ -669,7 +673,7 @@ func (r *ProtocolLXD) ExecContainer(containerName string, exec api.ContainerExec
 
 			// Handle stderr
 			if fds["2"] != "" {
-				conn, err := r.GetOperationWebsocket(op.ID, fds["2"])
+				conn, err := r.GetOperationWebsocket(opAPI.ID, fds["2"])
 				if err != nil {
 					return nil, err
 				}
@@ -904,7 +908,7 @@ func (r *ProtocolLXD) GetContainerSnapshot(containerName string, name string) (*
 }
 
 // CreateContainerSnapshot requests that LXD creates a new snapshot for the container
-func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (*Operation, error) {
+func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api.ContainerSnapshotsPost) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("POST", fmt.Sprintf("/containers/%s/snapshots", url.QueryEscape(containerName)), snapshot, "")
 	if err != nil {
@@ -915,7 +919,7 @@ func (r *ProtocolLXD) CreateContainerSnapshot(containerName string, snapshot api
 }
 
 // CopyContainerSnapshot copies a snapshot from a remote server into a new container. Additional options can be passed using ContainerCopyArgs
-func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (*RemoteOperation, error) {
+func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api.ContainerSnapshot, args *ContainerSnapshotCopyArgs) (RemoteOperation, error) {
 	// Base request
 	fields := strings.SplitN(snapshot.Name, shared.SnapshotDelimiter, 2)
 	cName := fields[0]
@@ -976,7 +980,7 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 			return nil, err
 		}
 
-		rop := RemoteOperation{
+		rop := remoteOperation{
 			targetOp: op,
 			chDone:   make(chan bool),
 		}
@@ -1015,15 +1019,16 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 		if err != nil {
 			return nil, err
 		}
+		opAPI := op.Get()
 
 		targetSecrets := map[string]string{}
-		for k, v := range op.Metadata {
+		for k, v := range opAPI.Metadata {
 			targetSecrets[k] = v.(string)
 		}
 
 		// Prepare the source request
 		target := api.ContainerPostTarget{}
-		target.Operation = op.ID
+		target.Operation = opAPI.ID
 		target.Websockets = targetSecrets
 		target.Certificate = info.Certificate
 		sourceReq.Target = &target
@@ -1041,9 +1046,10 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 	if err != nil {
 		return nil, err
 	}
+	opAPI := op.Get()
 
 	sourceSecrets := map[string]string{}
-	for k, v := range op.Metadata {
+	for k, v := range opAPI.Metadata {
 		sourceSecrets[k] = v.(string)
 	}
 
@@ -1058,21 +1064,22 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 		if err != nil {
 			return nil, err
 		}
+		targetOpAPI := targetOp.Get()
 
 		// Extract the websockets
 		targetSecrets := map[string]string{}
-		for k, v := range targetOp.Metadata {
+		for k, v := range targetOpAPI.Metadata {
 			targetSecrets[k] = v.(string)
 		}
 
 		// Launch the relay
-		err = r.proxyMigration(targetOp, targetSecrets, source, op, sourceSecrets)
+		err = r.proxyMigration(targetOp.(*operation), targetSecrets, source, op.(*operation), sourceSecrets)
 		if err != nil {
 			return nil, err
 		}
 
 		// Prepare a tracking operation
-		rop := RemoteOperation{
+		rop := remoteOperation{
 			targetOp: targetOp,
 			chDone:   make(chan bool),
 		}
@@ -1089,7 +1096,7 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 	// Pull mode migration
 	req.Source.Type = "migration"
 	req.Source.Mode = "pull"
-	req.Source.Operation = op.ID
+	req.Source.Operation = opAPI.ID
 	req.Source.Websockets = sourceSecrets
 	req.Source.Certificate = info.Certificate
 
@@ -1097,7 +1104,7 @@ func (r *ProtocolLXD) CopyContainerSnapshot(source ContainerServer, snapshot api
 }
 
 // RenameContainerSnapshot requests that LXD renames the snapshot
-func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
+func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (Operation, error) {
 	// Sanity check
 	if container.Migration {
 		return nil, fmt.Errorf("Can't ask for a migration through RenameContainerSnapshot")
@@ -1112,12 +1119,12 @@ func (r *ProtocolLXD) RenameContainerSnapshot(containerName string, name string,
 	return op, nil
 }
 
-func (r *ProtocolLXD) tryMigrateContainerSnapshot(source ContainerServer, containerName string, name string, req api.ContainerSnapshotPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryMigrateContainerSnapshot(source ContainerServer, containerName string, name string, req api.ContainerSnapshotPost, urls []string) (RemoteOperation, error) {
 	if len(urls) == 0 {
 		return nil, fmt.Errorf("The target server isn't listening on the network")
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		chDone: make(chan bool),
 	}
 
@@ -1163,7 +1170,7 @@ func (r *ProtocolLXD) tryMigrateContainerSnapshot(source ContainerServer, contai
 }
 
 // MigrateContainerSnapshot requests that LXD prepares for a snapshot migration
-func (r *ProtocolLXD) MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (*Operation, error) {
+func (r *ProtocolLXD) MigrateContainerSnapshot(containerName string, name string, container api.ContainerSnapshotPost) (Operation, error) {
 	// Sanity check
 	if !container.Migration {
 		return nil, fmt.Errorf("Can't ask for a rename through MigrateContainerSnapshot")
@@ -1179,7 +1186,7 @@ func (r *ProtocolLXD) MigrateContainerSnapshot(containerName string, name string
 }
 
 // DeleteContainerSnapshot requests that LXD deletes the container snapshot
-func (r *ProtocolLXD) DeleteContainerSnapshot(containerName string, name string) (*Operation, error) {
+func (r *ProtocolLXD) DeleteContainerSnapshot(containerName string, name string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/containers/%s/snapshots/%s", url.QueryEscape(containerName), url.QueryEscape(name)), nil, "")
 	if err != nil {
@@ -1203,7 +1210,7 @@ func (r *ProtocolLXD) GetContainerState(name string) (*api.ContainerState, strin
 }
 
 // UpdateContainerState updates the container to match the requested state
-func (r *ProtocolLXD) UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (*Operation, error) {
+func (r *ProtocolLXD) UpdateContainerState(name string, state api.ContainerStatePut, ETag string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("PUT", fmt.Sprintf("/containers/%s/state", url.QueryEscape(name)), state, ETag)
 	if err != nil {
@@ -1409,7 +1416,7 @@ func (r *ProtocolLXD) DeleteContainerTemplateFile(name string, templateName stri
 }
 
 // ConsoleContainer requests that LXD attaches to the console device of a container.
-func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.ContainerConsolePost, args *ContainerConsoleArgs) (*Operation, error) {
+func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.ContainerConsolePost, args *ContainerConsoleArgs) (Operation, error) {
 	if !r.HasExtension("console") {
 		return nil, fmt.Errorf("The server is missing the required \"console\" API extension")
 	}
@@ -1419,6 +1426,7 @@ func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.Contain
 	if err != nil {
 		return nil, err
 	}
+	opAPI := op.Get()
 
 	if args == nil || args.Terminal == nil {
 		return nil, fmt.Errorf("A terminal must be set")
@@ -1431,7 +1439,7 @@ func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.Contain
 	// Parse the fds
 	fds := map[string]string{}
 
-	value, ok := op.Metadata["fds"]
+	value, ok := opAPI.Metadata["fds"]
 	if ok {
 		values := value.(map[string]interface{})
 		for k, v := range values {
@@ -1445,7 +1453,7 @@ func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.Contain
 		return nil, fmt.Errorf("Did not receive a file descriptor for the control channel")
 	}
 
-	controlConn, err = r.GetOperationWebsocket(op.ID, fds["control"])
+	controlConn, err = r.GetOperationWebsocket(opAPI.ID, fds["control"])
 	if err != nil {
 		return nil, err
 	}
@@ -1453,7 +1461,7 @@ func (r *ProtocolLXD) ConsoleContainer(containerName string, console api.Contain
 	go args.Control(controlConn)
 
 	// Connect to the websocket
-	conn, err := r.GetOperationWebsocket(op.ID, fds["0"])
+	conn, err := r.GetOperationWebsocket(opAPI.ID, fds["0"])
 	if err != nil {
 		return nil, err
 	}
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 51058b1d0..2f6f2e677 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -68,8 +68,9 @@ func (r *ProtocolLXD) GetImageSecret(fingerprint string) (string, error) {
 	if err != nil {
 		return "", err
 	}
+	opAPI := op.Get()
 
-	return op.Metadata["secret"].(string), nil
+	return opAPI.Metadata["secret"].(string), nil
 }
 
 // GetPrivateImage is similar to GetImage but allows passing a secret download token
@@ -280,7 +281,7 @@ func (r *ProtocolLXD) GetImageAlias(name string) (*api.ImageAliasesEntry, string
 }
 
 // CreateImage requests that LXD creates, copies or import a new image
-func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (*Operation, error) {
+func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (Operation, error) {
 	if image.CompressionAlgorithm != "" {
 		if !r.HasExtension("image_compression_algorithm") {
 			return nil, fmt.Errorf("The server is missing the required \"image_compression_algorithm\" API extension")
@@ -423,7 +424,7 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 	}
 
 	// Setup an Operation wrapper
-	op := Operation{
+	op := operation{
 		Operation: *respOperation,
 		r:         r,
 		chActive:  make(chan bool),
@@ -433,12 +434,12 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 }
 
 // tryCopyImage iterates through the source server URLs until one lets it download the image
-func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (RemoteOperation, error) {
 	if len(urls) == 0 {
 		return nil, fmt.Errorf("The source server isn't listening on the network")
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		chDone: make(chan bool),
 	}
 
@@ -515,7 +516,7 @@ func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOp
 }
 
 // CopyImage copies an image from a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (*RemoteOperation, error) {
+func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (RemoteOperation, error) {
 	// Sanity checks
 	if r == source {
 		return nil, fmt.Errorf("The source and target servers must be different")
@@ -579,7 +580,7 @@ func (r *ProtocolLXD) UpdateImage(fingerprint string, image api.ImagePut, ETag s
 }
 
 // DeleteImage requests that LXD removes an image from the store
-func (r *ProtocolLXD) DeleteImage(fingerprint string) (*Operation, error) {
+func (r *ProtocolLXD) DeleteImage(fingerprint string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("DELETE", fmt.Sprintf("/images/%s", url.QueryEscape(fingerprint)), nil, "")
 	if err != nil {
@@ -590,7 +591,7 @@ func (r *ProtocolLXD) DeleteImage(fingerprint string) (*Operation, error) {
 }
 
 // RefreshImage requests that LXD issues an image refresh
-func (r *ProtocolLXD) RefreshImage(fingerprint string) (*Operation, error) {
+func (r *ProtocolLXD) RefreshImage(fingerprint string) (Operation, error) {
 	if !r.HasExtension("image_force_refresh") {
 		return nil, fmt.Errorf("The server is missing the required \"image_force_refresh\" API extension")
 	}
@@ -605,7 +606,7 @@ func (r *ProtocolLXD) RefreshImage(fingerprint string) (*Operation, error) {
 }
 
 // CreateImageSecret requests that LXD issues a temporary image secret
-func (r *ProtocolLXD) CreateImageSecret(fingerprint string) (*Operation, error) {
+func (r *ProtocolLXD) CreateImageSecret(fingerprint string) (Operation, error) {
 	// Send the request
 	op, _, err := r.queryOperation("POST", fmt.Sprintf("/images/%s/secret", url.QueryEscape(fingerprint)), nil, "")
 	if err != nil {
diff --git a/client/lxd_storage_volumes.go b/client/lxd_storage_volumes.go
index eabeea5a1..c20c5e3f2 100644
--- a/client/lxd_storage_volumes.go
+++ b/client/lxd_storage_volumes.go
@@ -95,7 +95,7 @@ func (r *ProtocolLXD) CreateStoragePoolVolume(pool string, volume api.StorageVol
 }
 
 // CopyStoragePoolVolume copies an existing storage volume
-func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeCopyArgs) (*RemoteOperation, error) {
+func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, source ContainerServer, sourcePool string, volume api.StorageVolume, args *StoragePoolVolumeCopyArgs) (RemoteOperation, error) {
 	if !r.HasExtension("storage_api_local_volume_handling") {
 		return nil, fmt.Errorf("The server is missing the required \"storage_api_local_volume_handling\" API extension")
 	}
@@ -120,7 +120,7 @@ func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, source ContainerServer,
 		return nil, err
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		targetOp: op,
 		chDone:   make(chan bool),
 	}
@@ -135,7 +135,7 @@ func (r *ProtocolLXD) CopyStoragePoolVolume(pool string, source ContainerServer,
 }
 
 // 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) {
+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") {
 		return nil, fmt.Errorf("The server is missing the required \"storage_api_local_volume_handling\" API extension")
 	}
@@ -155,7 +155,7 @@ func (r *ProtocolLXD) MoveStoragePoolVolume(pool string, source ContainerServer,
 		return nil, err
 	}
 
-	rop := RemoteOperation{
+	rop := remoteOperation{
 		targetOp: op,
 		chDone:   make(chan bool),
 	}
diff --git a/client/operations.go b/client/operations.go
index 90544ff96..fc015c220 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -11,7 +11,7 @@ import (
 )
 
 // The Operation type represents an ongoing LXD operation (asynchronous processing)
-type Operation struct {
+type operation struct {
 	api.Operation
 
 	r            *ProtocolLXD
@@ -23,7 +23,7 @@ type Operation struct {
 }
 
 // AddHandler adds a function to be called whenever an event is received
-func (op *Operation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
+func (op *operation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
 	// Make sure we have a listener setup
 	err := op.setupListener()
 	if err != nil {
@@ -53,17 +53,22 @@ func (op *Operation) AddHandler(function func(api.Operation)) (*EventTarget, err
 }
 
 // Cancel will request that LXD cancels the operation (if supported)
-func (op *Operation) Cancel() error {
+func (op *operation) Cancel() error {
 	return op.r.DeleteOperation(op.ID)
 }
 
+// Get returns the API operation struct
+func (op *operation) Get() api.Operation {
+	return op.Operation
+}
+
 // GetWebsocket returns a raw websocket connection from the operation
-func (op *Operation) GetWebsocket(secret string) (*websocket.Conn, error) {
+func (op *operation) GetWebsocket(secret string) (*websocket.Conn, error) {
 	return op.r.GetOperationWebsocket(op.ID, secret)
 }
 
 // RemoveHandler removes a function to be called whenever an event is received
-func (op *Operation) RemoveHandler(target *EventTarget) error {
+func (op *operation) RemoveHandler(target *EventTarget) error {
 	// Make sure we're not racing with ourselves
 	op.handlerLock.Lock()
 	defer op.handlerLock.Unlock()
@@ -77,7 +82,7 @@ func (op *Operation) RemoveHandler(target *EventTarget) error {
 }
 
 // Refresh pulls the current version of the operation and updates the struct
-func (op *Operation) Refresh() error {
+func (op *operation) Refresh() error {
 	// Don't bother with a manual update if we are listening for events
 	if op.handlerReady {
 		return nil
@@ -96,7 +101,7 @@ func (op *Operation) Refresh() error {
 }
 
 // Wait lets you wait until the operation reaches a final state
-func (op *Operation) Wait() error {
+func (op *operation) Wait() error {
 	// Check if not done already
 	if op.StatusCode.IsFinal() {
 		if op.Err != "" {
@@ -122,7 +127,7 @@ func (op *Operation) Wait() error {
 	return nil
 }
 
-func (op *Operation) setupListener() error {
+func (op *operation) setupListener() error {
 	// Make sure we're not racing with ourselves
 	op.handlerLock.Lock()
 	defer op.handlerLock.Unlock()
@@ -245,7 +250,7 @@ func (op *Operation) setupListener() error {
 	return nil
 }
 
-func (op *Operation) extractOperation(data interface{}) *api.Operation {
+func (op *operation) extractOperation(data interface{}) *api.Operation {
 	// Extract the metadata
 	meta, ok := data.(map[string]interface{})["metadata"]
 	if !ok {
@@ -272,9 +277,9 @@ func (op *Operation) extractOperation(data interface{}) *api.Operation {
 	return &newOp
 }
 
-// The RemoteOperation type represents an ongoing LXD operation between two servers
-type RemoteOperation struct {
-	targetOp *Operation
+// The remoteOperation type represents an ongoing LXD operation between two servers
+type remoteOperation struct {
+	targetOp Operation
 
 	handlers []func(api.Operation)
 
@@ -284,7 +289,7 @@ type RemoteOperation struct {
 }
 
 // AddHandler adds a function to be called whenever an event is received
-func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
+func (op *remoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
 	var err error
 	var target *EventTarget
 
@@ -309,7 +314,7 @@ func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarge
 }
 
 // CancelTarget attempts to cancel the target operation
-func (op *RemoteOperation) CancelTarget() error {
+func (op *remoteOperation) CancelTarget() error {
 	if op.targetOp == nil {
 		return fmt.Errorf("No associated target operation")
 	}
@@ -318,16 +323,17 @@ func (op *RemoteOperation) CancelTarget() error {
 }
 
 // GetTarget returns the target operation
-func (op *RemoteOperation) GetTarget() (*api.Operation, error) {
+func (op *remoteOperation) GetTarget() (*api.Operation, error) {
 	if op.targetOp == nil {
 		return nil, fmt.Errorf("No associated target operation")
 	}
 
-	return &op.targetOp.Operation, nil
+	opAPI := op.targetOp.Get()
+	return &opAPI, nil
 }
 
 // Wait lets you wait until the operation reaches a final state
-func (op *RemoteOperation) Wait() error {
+func (op *remoteOperation) Wait() error {
 	<-op.chDone
 
 	if op.chPost != nil {
diff --git a/lxc/config.go b/lxc/config.go
index 68b7b604c..8a50fb921 100644
--- a/lxc/config.go
+++ b/lxc/config.go
@@ -809,7 +809,7 @@ func (c *configCmd) doContainerConfigEdit(client lxd.ContainerServer, cont strin
 		newdata := api.ContainerPut{}
 		err = yaml.Unmarshal(content, &newdata)
 		if err == nil {
-			var op *lxd.Operation
+			var op lxd.Operation
 			op, err = client.UpdateContainer(cont, newdata, etag)
 			if err == nil {
 				err = op.Wait()
diff --git a/lxc/copy.go b/lxc/copy.go
index 3f9290506..89c0e932f 100644
--- a/lxc/copy.go
+++ b/lxc/copy.go
@@ -101,7 +101,7 @@ func (c *copyCmd) copyContainer(conf *config.Config, sourceResource string,
 		}
 	}
 
-	var op *lxd.RemoteOperation
+	var op lxd.RemoteOperation
 	if shared.IsSnapshot(sourceName) {
 		// Prepare the container creation request
 		args := lxd.ContainerSnapshotCopyArgs{
diff --git a/lxc/delete.go b/lxc/delete.go
index 0863cf38f..12fed0d64 100644
--- a/lxc/delete.go
+++ b/lxc/delete.go
@@ -50,7 +50,7 @@ func (c *deleteCmd) promptDelete(name string) error {
 }
 
 func (c *deleteCmd) doDelete(d lxd.ContainerServer, name string) error {
-	var op *lxd.Operation
+	var op lxd.Operation
 	var err error
 
 	if shared.IsSnapshot(name) {
diff --git a/lxc/exec.go b/lxc/exec.go
index 42b7a32fc..89f90654f 100644
--- a/lxc/exec.go
+++ b/lxc/exec.go
@@ -234,6 +234,7 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 	if err != nil {
 		return err
 	}
+	opAPI := op.Get()
 
 	// Wait for any remaining I/O to be flushed
 	<-execArgs.DataDone
@@ -249,6 +250,6 @@ func (c *execCmd) run(conf *config.Config, args []string) error {
 		termios.Restore(cfd, oldttystate)
 	}
 
-	os.Exit(int(op.Metadata["return"].(float64)))
+	os.Exit(int(opAPI.Metadata["return"].(float64)))
 	return nil
 }
diff --git a/lxc/image.go b/lxc/image.go
index eb104ec02..595ee72dc 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -543,10 +543,11 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			if err != nil {
 				return err
 			}
+			opAPI := op.Get()
 
 			// Check if refreshed
 			refreshed := false
-			flag, ok := op.Metadata["refreshed"]
+			flag, ok := opAPI.Metadata["refreshed"]
 			if ok {
 				refreshed = flag.(bool)
 			}
@@ -772,9 +773,10 @@ func (c *imageCmd) run(conf *config.Config, args []string) error {
 			progress.Done("")
 			return err
 		}
+		opAPI := op.Get()
 
 		// Get the fingerprint
-		fingerprint := op.Metadata["fingerprint"].(string)
+		fingerprint := opAPI.Metadata["fingerprint"].(string)
 		progress.Done(fmt.Sprintf(i18n.G("Image imported with fingerprint: %s"), fingerprint))
 
 		// Add the aliases
diff --git a/lxc/publish.go b/lxc/publish.go
index 5d2275e2e..cea9a50f4 100644
--- a/lxc/publish.go
+++ b/lxc/publish.go
@@ -212,9 +212,10 @@ func (c *publishCmd) run(conf *config.Config, args []string) error {
 	if err != nil {
 		return err
 	}
+	opAPI := op.Get()
 
 	// Grab the fingerprint
-	fingerprint := op.Metadata["fingerprint"].(string)
+	fingerprint := opAPI.Metadata["fingerprint"].(string)
 
 	// For remote publish, copy to target now
 	if cRemote != iRemote {
diff --git a/lxc/storage.go b/lxc/storage.go
index b2497c509..1fe41eb79 100644
--- a/lxc/storage.go
+++ b/lxc/storage.go
@@ -1118,7 +1118,7 @@ func (c *storageCmd) doStoragePoolVolumeCopy(client lxd.ContainerServer, src str
 		return err
 	}
 
-	op := &lxd.RemoteOperation{}
+	var op lxd.RemoteOperation
 	opMsg := ""
 	finalMsg := ""
 	if move {
diff --git a/lxc/utils/cancel.go b/lxc/utils/cancel.go
index f1b065cbd..4f936a044 100644
--- a/lxc/utils/cancel.go
+++ b/lxc/utils/cancel.go
@@ -11,7 +11,7 @@ import (
 )
 
 // CancelableWait waits for an operation and cancel it on SIGINT/SIGTERM
-func CancelableWait(op *lxd.RemoteOperation, progress *ProgressRenderer) error {
+func CancelableWait(op lxd.RemoteOperation, progress *ProgressRenderer) error {
 	// Signal handling
 	chSignal := make(chan os.Signal)
 	signal.Notify(chSignal, os.Interrupt)
diff --git a/lxd-p2c/utils.go b/lxd-p2c/utils.go
index dda9f2954..6a30bf36e 100644
--- a/lxd-p2c/utils.go
+++ b/lxd-p2c/utils.go
@@ -15,14 +15,16 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
-func transferRootfs(dst lxd.ContainerServer, op *lxd.Operation, rootfs string) error {
+func transferRootfs(dst lxd.ContainerServer, op lxd.Operation, rootfs string) error {
+	opAPI := op.Get()
+
 	// Connect to the websockets
-	wsControl, err := dst.GetOperationWebsocket(op.ID, op.Metadata["control"].(string))
+	wsControl, err := op.GetWebsocket(opAPI.Metadata["control"].(string))
 	if err != nil {
 		return err
 	}
 
-	wsFs, err := dst.GetOperationWebsocket(op.ID, op.Metadata["fs"].(string))
+	wsFs, err := op.GetWebsocket(opAPI.Metadata["fs"].(string))
 	if err != nil {
 		return err
 	}
diff --git a/lxd/container_console.go b/lxd/container_console.go
index 300bbbf14..85af58799 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -271,13 +271,16 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		return SmartError(err)
 	}
+
 	if client != nil {
 		url := fmt.Sprintf("/containers/%s/console", name)
 		op, _, err := client.RawOperation("POST", url, post, "")
 		if err != nil {
 			return SmartError(err)
 		}
-		return ForwardedOperationResponse(&op.Operation)
+
+		opAPI := op.Get()
+		return ForwardedOperationResponse(&opAPI)
 	}
 
 	c, err := containerLoadByName(d.State(), name)
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 3edcfc44f..0807c3cb7 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -349,13 +349,16 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		return SmartError(err)
 	}
+
 	if client != nil {
 		url := fmt.Sprintf("/containers/%s/exec", name)
 		op, _, err := client.RawOperation("POST", url, post, "")
 		if err != nil {
 			return SmartError(err)
 		}
-		return ForwardedOperationResponse(&op.Operation)
+
+		opAPI := op.Get()
+		return ForwardedOperationResponse(&opAPI)
 	}
 
 	c, err := containerLoadByName(d.State(), name)
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index ec5e664a4..9d2cb3f59 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -551,12 +551,15 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			if err != nil {
 				return SmartError(err)
 			}
+
 			logger.Debugf("Forward container post request to %s", address)
 			op, err := client.UseTarget(targetNode).CreateContainer(req)
 			if err != nil {
 				return SmartError(err)
 			}
-			return ForwardedOperationResponse(&op.Operation)
+
+			opAPI := op.Get()
+			return ForwardedOperationResponse(&opAPI)
 		}
 	}
 
diff --git a/lxd/main_init.go b/lxd/main_init.go
index 3c043e77a..28f63bbd4 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -572,7 +572,7 @@ func (cmd *CmdInit) initConfig(client lxd.ContainerServer, config map[string]int
 // Turn on clustering.
 func (cmd *CmdInit) initCluster(client lxd.ContainerServer, put api.ClusterPut, password string) (reverter, error) {
 	var reverter func() error
-	var op *lxd.Operation
+	var op lxd.Operation
 	var err error
 	if put.ClusterAddress == "" {
 		op, err = client.UpdateCluster(put, "")


More information about the lxc-devel mailing list