[lxc-devel] [lxd/master] Move response and operations to their own packages

monstermunchkin on Github lxc-bot at linuxcontainers.org
Thu Sep 26 12:40:19 UTC 2019


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190926/0d5662c0/attachment-0001.bin>
-------------- next part --------------
From 3de200988e557e333ae01f0a57fa24cdc9fe9d30 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 25 Sep 2019 10:02:36 +0200
Subject: [PATCH 1/2] lxd: Move operations and response to separate packages

This moves both operations and response to their own packages.

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 lxd/api.go                      |   9 +-
 lxd/api_1.0.go                  |  91 ++---
 lxd/api_cluster.go              | 170 ++++-----
 lxd/api_internal.go             | 197 +++++-----
 lxd/api_project.go              |  96 ++---
 lxd/backup.go                   |   6 +-
 lxd/certificates.go             |  81 ++---
 lxd/container.go                |   9 +-
 lxd/container_backup.go         | 138 +++----
 lxd/container_console.go        |  90 ++---
 lxd/container_delete.go         |  26 +-
 lxd/container_exec.go           |  48 +--
 lxd/container_file.go           |  69 ++--
 lxd/container_get.go            |  19 +-
 lxd/container_instance_types.go |   5 +-
 lxd/container_logs.go           |  63 ++--
 lxd/container_lxc.go            |   7 +-
 lxd/container_metadata.go       | 149 ++++----
 lxd/container_patch.go          |  29 +-
 lxd/container_post.go           |  90 ++---
 lxd/container_put.go            |  32 +-
 lxd/container_snapshot.go       | 156 ++++----
 lxd/container_state.go          |  64 ++--
 lxd/containers_get.go           |   9 +-
 lxd/containers_post.go          | 182 +++++-----
 lxd/daemon.go                   |  47 +--
 lxd/daemon_images.go            |   9 +-
 lxd/events.go                   |   3 +-
 lxd/images.go                   | 284 +++++++--------
 lxd/instance_interface.go       |   3 +-
 lxd/logging.go                  |   5 +-
 lxd/main.go                     |   8 +
 lxd/migrate.go                  |   5 +-
 lxd/migrate_container.go        |  15 +-
 lxd/migrate_storage_volumes.go  |   5 +-
 lxd/networks.go                 | 171 ++++-----
 lxd/operations.go               | 620 ++++----------------------------
 lxd/operations/operations.go    | 543 ++++++++++++++++++++++++++++
 lxd/operations/response.go      |  96 +++++
 lxd/operations/websocket.go     |  63 ++++
 lxd/profiles.go                 |  87 ++---
 lxd/resources.go                |  29 +-
 lxd/response.go                 | 494 +------------------------
 lxd/response/response.go        | 425 ++++++++++++++++++++++
 lxd/storage.go                  |  13 +-
 lxd/storage_btrfs.go            |   5 +-
 lxd/storage_ceph.go             |   5 +-
 lxd/storage_cephfs.go           |   5 +-
 lxd/storage_dir.go              |   5 +-
 lxd/storage_lvm.go              |   5 +-
 lxd/storage_migration.go        |  13 +-
 lxd/storage_migration_btrfs.go  |   5 +-
 lxd/storage_migration_ceph.go   |   5 +-
 lxd/storage_migration_zfs.go    |   5 +-
 lxd/storage_mock.go             |   5 +-
 lxd/storage_pools.go            | 153 ++++----
 lxd/storage_volumes.go          | 356 +++++++++---------
 lxd/storage_volumes_snapshot.go | 190 +++++-----
 lxd/storage_zfs.go              |   5 +-
 59 files changed, 2881 insertions(+), 2641 deletions(-)
 create mode 100644 lxd/operations/operations.go
 create mode 100644 lxd/operations/response.go
 create mode 100644 lxd/operations/websocket.go
 create mode 100644 lxd/response/response.go

diff --git a/lxd/api.go b/lxd/api.go
index a22297b741..cdca5ae0c7 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -10,6 +10,7 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared/logger"
 )
 
@@ -22,7 +23,7 @@ func RestServer(d *Daemon) *http.Server {
 
 	mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json")
-		SyncResponse(true, []string{"/1.0"}).Render(w)
+		response.SyncResponse(true, []string{"/1.0"}).Render(w)
 	})
 
 	for endpoint, f := range d.gateway.HandlerFuncs(d.NodeRefreshTask) {
@@ -50,7 +51,7 @@ func RestServer(d *Daemon) *http.Server {
 	mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		logger.Info("Sending top level 404", log.Ctx{"url": r.URL})
 		w.Header().Set("Content-Type", "application/json")
-		NotFound(nil).Render(w)
+		response.NotFound(nil).Render(w)
 	})
 
 	return &http.Server{Handler: &lxdHttpServer{r: mux, d: d}}
@@ -74,8 +75,8 @@ func (s *lxdHttpServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
 			return nil
 		})
 		if err != nil {
-			response := SmartError(err)
-			response.Render(rw)
+			resp := response.SmartError(err)
+			resp.Render(rw)
 			return
 		}
 	}
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 7339ca26b7..cd7182ba9c 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -13,6 +13,7 @@ import (
 	"github.com/lxc/lxd/lxd/config"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -83,7 +84,7 @@ var api10 = []APIEndpoint{
 	storagePoolVolumeTypeImageCmd,
 }
 
-func api10Get(d *Daemon, r *http.Request) Response {
+func api10Get(d *Daemon, r *http.Request) response.Response {
 	authMethods := []string{"tls"}
 	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		config, err := cluster.ConfigLoad(tx)
@@ -100,7 +101,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	srv := api.ServerUntrusted{
 		APIExtensions: version.APIExtensions,
@@ -113,34 +114,34 @@ func api10Get(d *Daemon, r *http.Request) Response {
 
 	// If untrusted, return now
 	if d.checkTrustedClient(r) != nil {
-		return SyncResponseETag(true, srv, nil)
+		return response.SyncResponseETag(true, srv, nil)
 	}
 
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	srv.Auth = "trusted"
 
 	uname, err := shared.Uname()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	address, err := node.HTTPSAddress(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	addresses, err := util.ListenAddresses(address)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// When clustered, use the node name, otherwise use the hostname.
@@ -151,12 +152,12 @@ func api10Get(d *Daemon, r *http.Request) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	} else {
 		hostname, err := os.Hostname()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		serverName = hostname
 	}
@@ -166,7 +167,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	if certificate != "" {
 		certificateFingerprint, err = shared.CertFingerprintStr(certificate)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
@@ -175,7 +176,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	for _, architecture := range d.os.Architectures {
 		architectureName, err := osarch.ArchitectureName(architecture)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 		architectures = append(architectures, architectureName)
 	}
@@ -238,22 +239,22 @@ func api10Get(d *Daemon, r *http.Request) Response {
 	fullSrv.Environment = env
 	fullSrv.Config, err = daemonConfigRender(d.State())
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponseETag(true, fullSrv, fullSrv.Config)
+	return response.SyncResponseETag(true, fullSrv, fullSrv.Config)
 }
 
-func api10Put(d *Daemon, r *http.Request) Response {
+func api10Put(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	req := api.ServerPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// If this is a notification from a cluster node, just run the triggers
@@ -271,56 +272,56 @@ func api10Put(d *Daemon, r *http.Request) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		err = doApi10UpdateTriggers(d, nil, changed, nil, config)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	render, err := daemonConfigRender(d.State())
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	err = util.EtagCheck(r, render)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	return doApi10Update(d, req, false)
 }
 
-func api10Patch(d *Daemon, r *http.Request) Response {
+func api10Patch(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	render, err := daemonConfigRender(d.State())
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	err = util.EtagCheck(r, render)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.ServerPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Config == nil {
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	return doApi10Update(d, req, true)
 }
 
-func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
+func doApi10Update(d *Daemon, req api.ServerPut, patch bool) response.Response {
 	s := d.State()
 
 	// First deal with config specific to the local daemon
@@ -336,7 +337,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return InternalError(errors.Wrap(err, "Failed to check for cluster state"))
+		return response.InternalError(errors.Wrap(err, "Failed to check for cluster state"))
 	}
 
 	nodeChanged := map[string]string{}
@@ -382,9 +383,9 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 	if err != nil {
 		switch err.(type) {
 		case config.ErrorList:
-			return BadRequest(err)
+			return response.BadRequest(err)
 		default:
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
@@ -403,7 +404,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 		}
 
 		if hasCandid && hasRBAC {
-			return BadRequest(fmt.Errorf("RBAC and Candid are mutually exclusive"))
+			return response.BadRequest(fmt.Errorf("RBAC and Candid are mutually exclusive"))
 		}
 	}
 
@@ -426,16 +427,16 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 	if err != nil {
 		switch err.(type) {
 		case config.ErrorList:
-			return BadRequest(err)
+			return response.BadRequest(err)
 		default:
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	// Notify the other nodes about changes
 	notifier, err := cluster.NewNotifier(s, d.endpoints.NetworkCert(), cluster.NotifyAlive)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	err = notifier(func(client lxd.InstanceServer) error {
 		server, etag, err := client.GetServer()
@@ -452,15 +453,15 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
 	})
 	if err != nil {
 		logger.Debugf("Failed to notify other nodes about config change: %v", err)
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = doApi10UpdateTriggers(d, nodeChanged, clusterChanged, newNodeConfig, newClusterConfig)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]string, nodeConfig *node.Config, clusterConfig *cluster.Config) error {
diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go
index 6980defc7b..fef01aa0c5 100644
--- a/lxd/api_cluster.go
+++ b/lxd/api_cluster.go
@@ -20,6 +20,8 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	storagedriver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -68,7 +70,7 @@ var internalClusterPromoteCmd = APIEndpoint{
 }
 
 // Return information about the cluster.
-func clusterGet(d *Daemon, r *http.Request) Response {
+func clusterGet(d *Daemon, r *http.Request) response.Response {
 	name := ""
 	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		var err error
@@ -76,7 +78,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// If the name is set to the hard-coded default node name, then
@@ -87,7 +89,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
 
 	memberConfig, err := clusterGetMemberConfig(d.cluster)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	cluster := api.Cluster{
@@ -96,7 +98,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
 		MemberConfig: memberConfig,
 	}
 
-	return SyncResponseETag(true, cluster, cluster)
+	return response.SyncResponseETag(true, cluster, cluster)
 }
 
 // Fetch information about all node-specific configuration keys set on the
@@ -169,21 +171,21 @@ func clusterGetMemberConfig(cluster *db.Cluster) ([]api.ClusterMemberConfigKey,
 // - disable clustering on a node
 //
 // The client is required to be trusted.
-func clusterPut(d *Daemon, r *http.Request) Response {
+func clusterPut(d *Daemon, r *http.Request) response.Response {
 	req := api.ClusterPut{}
 
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if req.ServerName == "" && req.Enabled {
-		return BadRequest(fmt.Errorf("ServerName is required when enabling clustering"))
+		return response.BadRequest(fmt.Errorf("ServerName is required when enabling clustering"))
 	}
 	if req.ServerName != "" && !req.Enabled {
-		return BadRequest(fmt.Errorf("ServerName must be empty when disabling clustering"))
+		return response.BadRequest(fmt.Errorf("ServerName must be empty when disabling clustering"))
 	}
 
 	// Disable clustering.
@@ -201,8 +203,8 @@ func clusterPut(d *Daemon, r *http.Request) Response {
 	return clusterPutJoin(d, req)
 }
 
-func clusterPutBootstrap(d *Daemon, req api.ClusterPut) Response {
-	run := func(op *operation) error {
+func clusterPutBootstrap(d *Daemon, req api.ClusterPut) response.Response {
+	run := func(op *operations.Operation) error {
 		// The default timeout when non-clustered is one minute, let's
 		// lower it down now that we'll likely have to make requests
 		// over the network.
@@ -250,39 +252,39 @@ func clusterPutBootstrap(d *Daemon, req api.ClusterPut) Response {
 		return nil
 	})
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationClusterBootstrap, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationClusterBootstrap, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Add the cluster flag from the agent
 	version.UserAgentFeatures([]string{"cluster"})
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
+func clusterPutJoin(d *Daemon, req api.ClusterPut) response.Response {
 	// Make sure basic pre-conditions are met.
 	if len(req.ClusterCertificate) == 0 {
-		return BadRequest(fmt.Errorf("No target cluster node certificate provided"))
+		return response.BadRequest(fmt.Errorf("No target cluster node certificate provided"))
 	}
 
 	clusterAddress, err := node.ClusterAddress(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if clusterAddress != "" {
-		return BadRequest(fmt.Errorf("This server is already clustered"))
+		return response.BadRequest(fmt.Errorf("This server is already clustered"))
 	}
 
 	address, err := node.HTTPSAddress(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if address == "" {
 		if req.ServerAddress == "" {
-			return BadRequest(fmt.Errorf("No core.https_address config key is set on this node"))
+			return response.BadRequest(fmt.Errorf("No core.https_address config key is set on this node"))
 		}
 
 		// The user has provided a server address, and no networking
@@ -294,7 +296,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 		// won't actually update the database config.
 		err = d.endpoints.NetworkUpdateAddress(req.ServerAddress)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		err := d.db.Transaction(func(tx *db.NodeTx) error {
@@ -310,7 +312,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		address = req.ServerAddress
@@ -322,7 +324,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 			if !util.IsAddressCovered(req.ServerAddress, address) {
 				err := d.endpoints.ClusterUpdateAddress(req.ServerAddress)
 				if err != nil {
-					return SmartError(err)
+					return response.SmartError(err)
 				}
 				address = req.ServerAddress
 			}
@@ -340,7 +342,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
@@ -354,7 +356,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 	fingerprint := cert.Fingerprint()
 
 	// Asynchronously join the cluster.
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		logger.Debug("Running cluster join operation")
 
 		// If the user has provided a cluster password, setup the trust
@@ -608,20 +610,20 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
 	resources := map[string][]string{}
 	resources["cluster"] = []string{}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationClusterJoin, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationClusterJoin, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // Disable clustering on a node.
-func clusterPutDisable(d *Daemon) Response {
+func clusterPutDisable(d *Daemon) response.Response {
 	// Close the cluster database
 	err := d.cluster.Close()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Update our TLS configuration using our original certificate.
@@ -632,25 +634,25 @@ func clusterPutDisable(d *Daemon) Response {
 		}
 		err := os.Remove(path)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 	cert, err := util.LoadCert(d.os.VarDir)
 	if err != nil {
-		return InternalError(errors.Wrap(err, "failed to parse node certificate"))
+		return response.InternalError(errors.Wrap(err, "failed to parse node certificate"))
 	}
 
 	// Reset the cluster database and make it local to this node.
 	d.endpoints.NetworkUpdateCert(cert)
 	err = d.gateway.Reset(cert)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Re-open the cluster database
 	address, err := node.HTTPSAddress(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	store := d.gateway.NodeStore()
 	d.cluster, err = db.OpenCluster(
@@ -661,7 +663,7 @@ func clusterPutDisable(d *Daemon) Response {
 		dqlitedriver.WithContext(d.gateway.Context()),
 	)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Stop the clustering tasks
@@ -670,7 +672,7 @@ func clusterPutDisable(d *Daemon) Response {
 	// Remove the cluster flag from the agent
 	version.UserAgentFeatures(nil)
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Initialize storage pools and networks on this node.
@@ -814,12 +816,12 @@ func clusterAcceptMember(
 	return info, nil
 }
 
-func clusterNodesGet(d *Daemon, r *http.Request) Response {
+func clusterNodesGet(d *Daemon, r *http.Request) response.Response {
 	recursion := util.IsRecursionRequest(r)
 
 	nodes, err := cluster.List(d.State())
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	var result interface{}
@@ -834,27 +836,27 @@ func clusterNodesGet(d *Daemon, r *http.Request) Response {
 		result = urls
 	}
 
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
-func clusterNodeGet(d *Daemon, r *http.Request) Response {
+func clusterNodeGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	nodes, err := cluster.List(d.State())
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	for _, node := range nodes {
 		if node.ServerName == name {
-			return SyncResponseETag(true, node, node)
+			return response.SyncResponseETag(true, node, node)
 		}
 	}
 
-	return NotFound(fmt.Errorf("Node '%s' not found", name))
+	return response.NotFound(fmt.Errorf("Node '%s' not found", name))
 }
 
-func clusterNodePost(d *Daemon, r *http.Request) Response {
+func clusterNodePost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	req := api.ClusterMemberPost{}
@@ -862,20 +864,20 @@ func clusterNodePost(d *Daemon, r *http.Request) Response {
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
 		return tx.NodeRename(name, req.ServerName)
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func clusterNodeDelete(d *Daemon, r *http.Request) Response {
+func clusterNodeDelete(d *Daemon, r *http.Request) response.Response {
 	force, err := strconv.Atoi(r.FormValue("force"))
 	if err != nil {
 		force = 0
@@ -888,7 +890,7 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
 	// make it leave the database cluster, if it's part of it.
 	address, err := cluster.Leave(d.State(), d.gateway, name, force == 1)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if force != 1 {
@@ -897,28 +899,28 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
 		cert := d.endpoints.NetworkCert()
 		client, err := cluster.Connect(address, cert, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		networks, err := d.cluster.Networks()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		for _, name := range networks {
 			err := client.DeleteNetwork(name)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
 
 		// Delete all the pools on this node
 		pools, err := d.cluster.StoragePools()
 		if err != nil && err != db.ErrNoSuchObject {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		for _, name := range pools {
 			err := client.DeleteStoragePool(name)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
 	}
@@ -926,7 +928,7 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
 	// Remove node from the database
 	err = cluster.Purge(d.cluster, name)
 	if err != nil {
-		return SmartError(errors.Wrap(err, "failed to remove node from database"))
+		return response.SmartError(errors.Wrap(err, "failed to remove node from database"))
 	}
 	// Try to notify the leader.
 	err = tryClusterRebalance(d)
@@ -940,17 +942,17 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
 		cert := d.endpoints.NetworkCert()
 		client, err := cluster.Connect(address, cert, false)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		put := api.ClusterPut{}
 		put.Enabled = false
 		_, err = client.UpdateCluster(put, "")
 		if err != nil {
-			return SmartError(errors.Wrap(err, "failed to cleanup the node"))
+			return response.SmartError(errors.Wrap(err, "failed to cleanup the node"))
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // This function is used to notify the leader that a node was removed, it will
@@ -973,29 +975,29 @@ func tryClusterRebalance(d *Daemon) error {
 	return nil
 }
 
-func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
+func internalClusterPostAccept(d *Daemon, r *http.Request) response.Response {
 	req := internalClusterPostAcceptRequest{}
 
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	// Redirect all requests to the leader, which is the one with
 	// knowning what nodes are part of the raft cluster.
 	address, err := node.ClusterAddress(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	leader, err := d.gateway.LeaderAddress()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	if address != leader {
 		logger.Debugf("Redirect node accept request to %s", leader)
@@ -1004,23 +1006,23 @@ func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
 			Path:   "/internal/cluster/accept",
 			Host:   leader,
 		}
-		return SyncResponseRedirect(url.String())
+		return response.SyncResponseRedirect(url.String())
 	}
 
 	// Check that the pools and networks provided by the joining node have
 	// configs that match the cluster ones.
 	err = clusterCheckStoragePoolsMatch(d.cluster, req.StoragePools)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	err = clusterCheckNetworksMatch(d.cluster, req.Networks)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	nodes, err := cluster.Accept(d.State(), d.gateway, req.Name, req.Address, req.Schema, req.API)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	accepted := internalClusterPostAcceptResponse{
 		RaftNodes:  make([]internalRaftNode, len(nodes)),
@@ -1030,7 +1032,7 @@ func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
 		accepted.RaftNodes[i].ID = node.ID
 		accepted.RaftNodes[i].Address = node.Address
 	}
-	return SyncResponse(true, accepted)
+	return response.SyncResponse(true, accepted)
 }
 
 // A request for the /internal/cluster/accept endpoint.
@@ -1043,7 +1045,7 @@ type internalClusterPostAcceptRequest struct {
 	Networks     []api.Network     `json:"networks" yaml:"networks"`
 }
 
-// A Response for the /internal/cluster/accept endpoint.
+// A response.Response for the /internal/cluster/accept endpoint.
 type internalClusterPostAcceptResponse struct {
 	RaftNodes  []internalRaftNode `json:"raft_nodes" yaml:"raft_nodes"`
 	PrivateKey []byte             `json:"private_key" yaml:"private_key"`
@@ -1057,16 +1059,16 @@ type internalRaftNode struct {
 
 // Used to update the cluster after a database node has been removed, and
 // possibly promote another one as database node.
-func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
+func internalClusterPostRebalance(d *Daemon, r *http.Request) response.Response {
 	// Redirect all requests to the leader, which is the one with with
 	// up-to-date knowledge of what nodes are part of the raft cluster.
 	localAddress, err := node.ClusterAddress(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	leader, err := d.gateway.LeaderAddress()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	if localAddress != leader {
 		logger.Debugf("Redirect cluster rebalance request to %s", leader)
@@ -1075,7 +1077,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 			Path:   "/internal/cluster/rebalance",
 			Host:   leader,
 		}
-		return SyncResponseRedirect(url.String())
+		return response.SyncResponseRedirect(url.String())
 	}
 
 	logger.Debugf("Rebalance cluster")
@@ -1083,7 +1085,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 	// Check if we have a spare node to promote.
 	address, nodes, err := cluster.Rebalance(d.State(), d.gateway)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if address == "" {
@@ -1100,7 +1102,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 			return nil
 		})
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		hbState := &cluster.APIHeartbeat{}
@@ -1108,7 +1110,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 
 		cert, err := util.LoadCert(d.os.VarDir)
 		if err != nil {
-			return InternalError(errors.Wrap(err, "failed to parse cluster certificate"))
+			return response.InternalError(errors.Wrap(err, "failed to parse cluster certificate"))
 		}
 
 		for _, raftNode := range nodes {
@@ -1119,7 +1121,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 			go cluster.HeartbeatNode(context.Background(), raftNode.Address, cert, hbState)
 		}
 
-		return SyncResponse(true, nil)
+		return response.SyncResponse(true, nil)
 	}
 
 	// Tell the node to promote itself.
@@ -1134,29 +1136,29 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.Connect(address, cert, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	_, _, err = client.RawQuery("POST", "/internal/cluster/promote", post, "")
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, nil)
+	return response.SyncResponse(true, nil)
 }
 
 // Used to promote the local non-database node to be a database one.
-func internalClusterPostPromote(d *Daemon, r *http.Request) Response {
+func internalClusterPostPromote(d *Daemon, r *http.Request) response.Response {
 	req := internalClusterPostPromoteRequest{}
 
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if len(req.RaftNodes) == 0 {
-		return BadRequest(fmt.Errorf("No raft nodes provided"))
+		return response.BadRequest(fmt.Errorf("No raft nodes provided"))
 	}
 
 	nodes := make([]db.RaftNode, len(req.RaftNodes))
@@ -1166,10 +1168,10 @@ func internalClusterPostPromote(d *Daemon, r *http.Request) Response {
 	}
 	err = cluster.Promote(d.State(), d.gateway, nodes)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, nil)
+	return response.SyncResponse(true, nil)
 }
 
 // A request for the /internal/cluster/promote endpoint.
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index e6534ed8ef..fd10613dcb 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -24,6 +24,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/project"
+	"github.com/lxc/lxd/lxd/response"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -103,51 +104,51 @@ var internalRAFTSnapshotCmd = APIEndpoint{
 	Get: APIEndpointAction{Handler: internalRAFTSnapshot},
 }
 
-func internalWaitReady(d *Daemon, r *http.Request) Response {
+func internalWaitReady(d *Daemon, r *http.Request) response.Response {
 	select {
 	case <-d.readyChan:
 	default:
-		return Unavailable(fmt.Errorf("LXD daemon not ready yet"))
+		return response.Unavailable(fmt.Errorf("LXD daemon not ready yet"))
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalShutdown(d *Daemon, r *http.Request) Response {
+func internalShutdown(d *Daemon, r *http.Request) response.Response {
 	d.shutdownChan <- struct{}{}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalContainerOnStart(d *Daemon, r *http.Request) Response {
+func internalContainerOnStart(d *Daemon, r *http.Request) response.Response {
 	id, err := strconv.Atoi(mux.Vars(r)["id"])
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	inst, err := instanceLoadById(d.State(), id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance is not container type"))
+		return response.SmartError(fmt.Errorf("Instance is not container type"))
 	}
 
 	c := inst.(container)
 	err = c.OnStart()
 	if err != nil {
 		logger.Error("The start hook failed", log.Ctx{"container": c.Name(), "err": err})
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalContainerOnStopNS(d *Daemon, r *http.Request) Response {
+func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
 	id, err := strconv.Atoi(mux.Vars(r)["id"])
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	target := queryParam(r, "target")
@@ -158,27 +159,27 @@ func internalContainerOnStopNS(d *Daemon, r *http.Request) Response {
 
 	inst, err := instanceLoadById(d.State(), id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance is not container type"))
+		return response.SmartError(fmt.Errorf("Instance is not container type"))
 	}
 
 	c := inst.(container)
 	err = c.OnStopNS(target, netns)
 	if err != nil {
 		logger.Error("The stopns hook failed", log.Ctx{"container": c.Name(), "err": err})
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalContainerOnStop(d *Daemon, r *http.Request) Response {
+func internalContainerOnStop(d *Daemon, r *http.Request) response.Response {
 	id, err := strconv.Atoi(mux.Vars(r)["id"])
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	target := queryParam(r, "target")
@@ -188,21 +189,21 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
 
 	inst, err := instanceLoadById(d.State(), id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance is not container type"))
+		return response.SmartError(fmt.Errorf("Instance is not container type"))
 	}
 
 	c := inst.(container)
 	err = c.OnStop(target)
 	if err != nil {
 		logger.Error("The stop hook failed", log.Ctx{"container": c.Name(), "err": err})
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 type internalSQLDump struct {
@@ -226,11 +227,11 @@ type internalSQLResult struct {
 }
 
 // Perform a database dump.
-func internalSQLGet(d *Daemon, r *http.Request) Response {
+func internalSQLGet(d *Daemon, r *http.Request) response.Response {
 	database := r.FormValue("database")
 
 	if !shared.StringInSlice(database, []string{"local", "global"}) {
-		return BadRequest(fmt.Errorf("Invalid database"))
+		return response.BadRequest(fmt.Errorf("Invalid database"))
 	}
 
 	schemaFormValue := r.FormValue("schema")
@@ -251,31 +252,31 @@ func internalSQLGet(d *Daemon, r *http.Request) Response {
 
 	tx, err := db.Begin()
 	if err != nil {
-		return SmartError(errors.Wrap(err, "failed to start transaction"))
+		return response.SmartError(errors.Wrap(err, "failed to start transaction"))
 	}
 	defer tx.Rollback()
 	dump, err := query.Dump(tx, schema, schemaOnly == 1)
 	if err != nil {
-		return SmartError(errors.Wrapf(err, "failed dump database %s", database))
+		return response.SmartError(errors.Wrapf(err, "failed dump database %s", database))
 	}
-	return SyncResponse(true, internalSQLDump{Text: dump})
+	return response.SyncResponse(true, internalSQLDump{Text: dump})
 }
 
 // Execute queries.
-func internalSQLPost(d *Daemon, r *http.Request) Response {
+func internalSQLPost(d *Daemon, r *http.Request) response.Response {
 	req := &internalSQLQuery{}
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if !shared.StringInSlice(req.Database, []string{"local", "global"}) {
-		return BadRequest(fmt.Errorf("Invalid database"))
+		return response.BadRequest(fmt.Errorf("Invalid database"))
 	}
 
 	if req.Query == "" {
-		return BadRequest(fmt.Errorf("No query provided"))
+		return response.BadRequest(fmt.Errorf("No query provided"))
 	}
 
 	var db *sql.DB
@@ -289,7 +290,7 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
 
 	if req.Query == ".sync" {
 		d.gateway.Sync()
-		return SyncResponse(true, batch)
+		return response.SyncResponse(true, batch)
 	}
 
 	for _, query := range strings.Split(req.Query, ";") {
@@ -303,7 +304,7 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
 
 		tx, err := db.Begin()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if strings.HasPrefix(strings.ToUpper(query), "SELECT") {
@@ -318,13 +319,13 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
 			}
 		}
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		batch.Results = append(batch.Results, result)
 	}
 
-	return SyncResponse(true, batch)
+	return response.SyncResponse(true, batch)
 }
 
 func internalSQLSelect(tx *sql.Tx, query string, result *internalSQLResult) error {
@@ -409,32 +410,32 @@ type internalImportPost struct {
 	Force bool   `json:"force" yaml:"force"`
 }
 
-func internalImport(d *Daemon, r *http.Request) Response {
+func internalImport(d *Daemon, r *http.Request) response.Response {
 	projectName := projectParam(r)
 
 	req := &internalImportPost{}
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf(`The name of the container ` +
+		return response.BadRequest(fmt.Errorf(`The name of the container ` +
 			`is required`))
 	}
 
 	storagePoolsPath := shared.VarPath("storage-pools")
 	storagePoolsDir, err := os.Open(storagePoolsPath)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Get a list of all storage pools.
 	storagePoolNames, err := storagePoolsDir.Readdirnames(-1)
 	if err != nil {
 		storagePoolsDir.Close()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	storagePoolsDir.Close()
 
@@ -451,10 +452,10 @@ func internalImport(d *Daemon, r *http.Request) Response {
 
 	// Sanity checks.
 	if len(containerMntPoints) > 1 {
-		return BadRequest(fmt.Errorf(`The container "%s" seems to `+
+		return response.BadRequest(fmt.Errorf(`The container "%s" seems to `+
 			`exist on multiple storage pools`, req.Name))
 	} else if len(containerMntPoints) != 1 {
-		return BadRequest(fmt.Errorf(`The container "%s" does not `+
+		return response.BadRequest(fmt.Errorf(`The container "%s" does not `+
 			`seem to exist on any storage pool`, req.Name))
 	}
 
@@ -463,11 +464,11 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	containerMntPoint := containerMntPoints[0]
 	isEmpty, err := shared.PathIsEmpty(containerMntPoint)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if isEmpty {
-		return BadRequest(fmt.Errorf(`The container's directory "%s" `+
+		return response.BadRequest(fmt.Errorf(`The container's directory "%s" `+
 			`appears to be empty. Please ensure that the `+
 			`container's storage volume is mounted`,
 			containerMntPoint))
@@ -477,7 +478,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	backupYamlPath := filepath.Join(containerMntPoint, "backup.yaml")
 	backup, err := slurpBackupFile(backupYamlPath)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Update snapshot names to include container name (if needed)
@@ -492,13 +493,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	poolID, pool, poolErr := d.cluster.StoragePoolGet(containerPoolName)
 	if poolErr != nil {
 		if poolErr != db.ErrNoSuchObject {
-			return SmartError(poolErr)
+			return response.SmartError(poolErr)
 		}
 	}
 
 	if backup.Pool == nil {
 		// We don't know what kind of storage type the pool is.
-		return BadRequest(fmt.Errorf(`No storage pool struct in the ` +
+		return response.BadRequest(fmt.Errorf(`No storage pool struct in the ` +
 			`backup file found. The storage pool needs to be ` +
 			`recovered manually`))
 	}
@@ -509,23 +510,23 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			backup.Pool.Driver, backup.Pool.Config)
 		if err != nil {
 			err = errors.Wrap(err, "Create storage pool database entry")
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		poolID, err = d.cluster.StoragePoolGetID(containerPoolName)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	} else {
 		if backup.Pool.Name != containerPoolName {
-			return BadRequest(fmt.Errorf(`The storage pool "%s" `+
+			return response.BadRequest(fmt.Errorf(`The storage pool "%s" `+
 				`the container was detected on does not match `+
 				`the storage pool "%s" specified in the `+
 				`backup file`, containerPoolName, backup.Pool.Name))
 		}
 
 		if backup.Pool.Driver != pool.Driver {
-			return BadRequest(fmt.Errorf(`The storage pool's `+
+			return response.BadRequest(fmt.Errorf(`The storage pool's `+
 				`"%s" driver "%s" conflicts with the driver `+
 				`"%s" recorded in the container's backup file`,
 				containerPoolName, pool.Driver, backup.Pool.Driver))
@@ -535,12 +536,12 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	initPool, err := storagePoolInit(d.State(), backup.Pool.Name)
 	if err != nil {
 		err = errors.Wrap(err, "Initialize storage")
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	ourMount, err := initPool.StoragePoolMount()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	if ourMount {
 		defer initPool.StoragePoolUmount()
@@ -553,7 +554,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	// retrieve on-disk pool name
 	_, _, poolName := initPool.GetContainerPoolInfo()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Retrieve all snapshots that exist on disk.
@@ -564,24 +565,24 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			snapshotsDirPath := driver.GetSnapshotMountPoint(projectName, poolName, req.Name)
 			snapshotsDir, err := os.Open(snapshotsDirPath)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
 			if err != nil {
 				snapshotsDir.Close()
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			snapshotsDir.Close()
 		case "dir":
 			snapshotsDirPath := driver.GetSnapshotMountPoint(projectName, poolName, req.Name)
 			snapshotsDir, err := os.Open(snapshotsDirPath)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
 			if err != nil {
 				snapshotsDir.Close()
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			snapshotsDir.Close()
 		case "lvm":
@@ -589,7 +590,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			msg, err := shared.RunCommand("lvs", "-o", "lv_name",
 				onDiskPoolName, "--noheadings")
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			snaps := strings.Fields(msg)
@@ -618,7 +619,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				storagePoolVolumeTypeNameContainer, userName)
 			if err != nil {
 				if err != db.ErrNoSuchObject {
-					return InternalError(err)
+					return response.InternalError(err)
 				}
 			}
 
@@ -634,7 +635,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			snaps, err := zfsPoolListSnapshots(onDiskPoolName,
 				fmt.Sprintf("containers/%s", req.Name))
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			for _, v := range snaps {
@@ -655,7 +656,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				`recorded in the "backup.yaml" file. Pass ` +
 				`"force" to remove them`
 			logger.Errorf(msg)
-			return InternalError(fmt.Errorf(msg))
+			return response.InternalError(fmt.Errorf(msg))
 		}
 	}
 
@@ -680,7 +681,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				`the "backup.yaml" file. Pass "force" to ` +
 				`remove them`
 			logger.Errorf(msg)
-			return InternalError(fmt.Errorf(msg))
+			return response.InternalError(fmt.Errorf(msg))
 		}
 
 		var err error
@@ -738,7 +739,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				if req.Force {
 					continue
 				}
-				return BadRequest(needForce)
+				return response.BadRequest(needForce)
 			}
 		case "dir":
 			snpMntPt := driver.GetSnapshotMountPoint(projectName, backup.Pool.Name, snap.Name)
@@ -746,7 +747,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				if req.Force {
 					continue
 				}
-				return BadRequest(needForce)
+				return response.BadRequest(needForce)
 			}
 		case "lvm":
 			ctLvmName := containerNameToLVName(snap.Name)
@@ -755,14 +756,14 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				ctLvmName)
 			exists, err := storageLVExists(ctLvName)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			if !exists {
 				if req.Force {
 					continue
 				}
-				return BadRequest(needForce)
+				return response.BadRequest(needForce)
 			}
 		case "ceph":
 			clusterName := "ceph"
@@ -789,7 +790,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				if req.Force {
 					continue
 				}
-				return BadRequest(needForce)
+				return response.BadRequest(needForce)
 			}
 		case "zfs":
 			ctName, csName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name)
@@ -802,7 +803,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 				if req.Force {
 					continue
 				}
-				return BadRequest(needForce)
+				return response.BadRequest(needForce)
 			}
 		}
 
@@ -814,12 +815,12 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		req.Name, storagePoolVolumeTypeContainer, poolID)
 	if ctVolErr != nil {
 		if ctVolErr != db.ErrNoSuchObject {
-			return SmartError(ctVolErr)
+			return response.SmartError(ctVolErr)
 		}
 	}
 	// If a storage volume entry exists only proceed if force was specified.
 	if ctVolErr == nil && !req.Force {
-		return BadRequest(fmt.Errorf(`Storage volume for container `+
+		return response.BadRequest(fmt.Errorf(`Storage volume for container `+
 			`"%s" already exists in the database. Set "force" to `+
 			`overwrite`, req.Name))
 	}
@@ -828,31 +829,31 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	_, containerErr := d.cluster.ContainerID(projectName, req.Name)
 	if containerErr != nil {
 		if containerErr != db.ErrNoSuchObject {
-			return SmartError(containerErr)
+			return response.SmartError(containerErr)
 		}
 	}
 	// If a db entry exists only proceed if force was specified.
 	if containerErr == nil && !req.Force {
-		return BadRequest(fmt.Errorf(`Entry for container "%s" `+
+		return response.BadRequest(fmt.Errorf(`Entry for container "%s" `+
 			`already exists in the database. Set "force" to `+
 			`overwrite`, req.Name))
 	}
 
 	if backup.Volume == nil {
-		return BadRequest(fmt.Errorf(`No storage volume struct in the ` +
+		return response.BadRequest(fmt.Errorf(`No storage volume struct in the ` +
 			`backup file found. The storage volume needs to be ` +
 			`recovered manually`))
 	}
 
 	if ctVolErr == nil {
 		if volume.Name != backup.Volume.Name {
-			return BadRequest(fmt.Errorf(`The name "%s" of the `+
+			return response.BadRequest(fmt.Errorf(`The name "%s" of the `+
 				`storage volume is not identical to the `+
 				`container's name "%s"`, volume.Name, req.Name))
 		}
 
 		if volume.Type != backup.Volume.Type {
-			return BadRequest(fmt.Errorf(`The type "%s" of the `+
+			return response.BadRequest(fmt.Errorf(`The type "%s" of the `+
 				`storage volume is not identical to the `+
 				`container's type "%s"`, volume.Type,
 				backup.Volume.Type))
@@ -863,7 +864,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		err := d.cluster.StoragePoolVolumeDelete("default", req.Name,
 			storagePoolVolumeTypeContainer, poolID)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
@@ -872,7 +873,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		// force was specified.
 		err := d.cluster.ContainerRemove(projectName, req.Name)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
@@ -885,7 +886,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	// Mark the filesystem as going through an import
 	fd, err := os.Create(filepath.Join(containerMntPoint, ".importing"))
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	fd.Close()
 	defer os.Remove(fd.Name())
@@ -913,7 +914,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 
 	arch, err := osarch.ArchitectureId(backup.Container.Architecture)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	_, err = containerCreateInternal(d.State(), db.ContainerArgs{
 		Project:      projectName,
@@ -932,7 +933,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	})
 	if err != nil {
 		err = errors.Wrap(err, "Create container")
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	containerPath := driver.ContainerPath(project.Prefix(projectName, req.Name), false)
@@ -943,7 +944,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 	err = driver.CreateContainerMountpoint(containerMntPoint, containerPath,
 		isPrivileged)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	for _, snap := range existingSnapshots {
@@ -953,13 +954,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		_, snapErr := d.cluster.InstanceSnapshotID(projectName, parts[0], parts[1])
 		if snapErr != nil {
 			if snapErr != db.ErrNoSuchObject {
-				return SmartError(snapErr)
+				return response.SmartError(snapErr)
 			}
 		}
 
 		// If a db entry exists only proceed if force was specified.
 		if snapErr == nil && !req.Force {
-			return BadRequest(fmt.Errorf(`Entry for snapshot "%s" `+
+			return response.BadRequest(fmt.Errorf(`Entry for snapshot "%s" `+
 				`already exists in the database. Set "force" `+
 				`to overwrite`, snap.Name))
 		}
@@ -969,13 +970,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			projectName, snap.Name, storagePoolVolumeTypeContainer, poolID)
 		if csVolErr != nil {
 			if csVolErr != db.ErrNoSuchObject {
-				return SmartError(csVolErr)
+				return response.SmartError(csVolErr)
 			}
 		}
 
 		// If a storage volume entry exists only proceed if force was specified.
 		if csVolErr == nil && !req.Force {
-			return BadRequest(fmt.Errorf(`Storage volume for `+
+			return response.BadRequest(fmt.Errorf(`Storage volume for `+
 				`snapshot "%s" already exists in the `+
 				`database. Set "force" to overwrite`, snap.Name))
 		}
@@ -983,7 +984,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		if snapErr == nil {
 			err := d.cluster.ContainerRemove(projectName, snap.Name)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
 
@@ -991,7 +992,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			err := d.cluster.StoragePoolVolumeDelete(projectName, snap.Name,
 				storagePoolVolumeTypeContainer, poolID)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
 
@@ -999,7 +1000,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 
 		arch, err := osarch.ArchitectureId(snap.Architecture)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Add root device if missing
@@ -1037,7 +1038,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			Stateful:     snap.Stateful,
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Recreate missing mountpoints and symlinks.
@@ -1049,31 +1050,31 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
 		err = driver.CreateSnapshotMountpoint(snapshotMountPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalGC(d *Daemon, r *http.Request) Response {
+func internalGC(d *Daemon, r *http.Request) response.Response {
 	logger.Infof("Started forced garbage collection run")
 	runtime.GC()
 	runtimeDebug.FreeOSMemory()
 	logger.Infof("Completed forced garbage collection run")
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalRAFTSnapshot(d *Daemon, r *http.Request) Response {
+func internalRAFTSnapshot(d *Daemon, r *http.Request) response.Response {
 	logger.Infof("Started forced RAFT snapshot")
 	err := d.gateway.Snapshot()
 	if err != nil {
 		logger.Errorf("Failed forced RAFT snapshot: %v", err)
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	logger.Infof("Completed forced RAFT snapshot")
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
diff --git a/lxd/api_project.go b/lxd/api_project.go
index 4802c1d15c..a0b1eecab2 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -12,6 +12,8 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -35,7 +37,7 @@ var projectCmd = APIEndpoint{
 	Put:    APIEndpointAction{Handler: projectPut, AccessHandler: AllowAuthenticated},
 }
 
-func projectsGet(d *Daemon, r *http.Request) Response {
+func projectsGet(d *Daemon, r *http.Request) response.Response {
 	recursion := util.IsRecursionRequest(r)
 
 	var result interface{}
@@ -80,13 +82,13 @@ func projectsGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
-func projectsPost(d *Daemon, r *http.Request) Response {
+func projectsPost(d *Daemon, r *http.Request) response.Response {
 	// Parse the request
 	project := api.ProjectsPost{}
 
@@ -103,30 +105,30 @@ func projectsPost(d *Daemon, r *http.Request) Response {
 
 	err := json.NewDecoder(r.Body).Decode(&project)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if project.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(project.Name, "/") {
-		return BadRequest(fmt.Errorf("Project names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Project names may not contain slashes"))
 	}
 
 	if project.Name == "*" {
-		return BadRequest(fmt.Errorf("Reserved project name"))
+		return response.BadRequest(fmt.Errorf("Reserved project name"))
 	}
 
 	if shared.StringInSlice(project.Name, []string{".", ".."}) {
-		return BadRequest(fmt.Errorf("Invalid project name '%s'", project.Name))
+		return response.BadRequest(fmt.Errorf("Invalid project name '%s'", project.Name))
 	}
 
 	// Validate the configuration
 	err = projectValidateConfig(project.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	var id int64
@@ -146,17 +148,17 @@ func projectsPost(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(fmt.Errorf("Error inserting %s into database: %s", project.Name, err))
+		return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", project.Name, err))
 	}
 
 	if d.rbac != nil {
 		err = d.rbac.AddProject(id, project.Name)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/projects/%s", version.APIVersion, project.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/projects/%s", version.APIVersion, project.Name))
 }
 
 // Create the default profile of a project.
@@ -174,12 +176,12 @@ func projectCreateDefaultProfile(tx *db.ClusterTx, project string) error {
 	return nil
 }
 
-func projectGet(d *Daemon, r *http.Request) Response {
+func projectGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Check user permissions
 	if !d.userHasPermission(r, name, "view") {
-		return Forbidden(nil)
+		return response.Forbidden(nil)
 	}
 
 	// Get the database entry
@@ -190,7 +192,7 @@ func projectGet(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	etag := []interface{}{
@@ -199,15 +201,15 @@ func projectGet(d *Daemon, r *http.Request) Response {
 		project.Config["features.profiles"],
 	}
 
-	return SyncResponseETag(true, project, etag)
+	return response.SyncResponseETag(true, project, etag)
 }
 
-func projectPut(d *Daemon, r *http.Request) Response {
+func projectPut(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Check user permissions
 	if !d.userHasPermission(r, name, "manage-projects") {
-		return Forbidden(nil)
+		return response.Forbidden(nil)
 	}
 
 	// Get the current data
@@ -218,7 +220,7 @@ func projectPut(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
@@ -229,7 +231,7 @@ func projectPut(d *Daemon, r *http.Request) Response {
 	}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	// Parse the request
@@ -237,18 +239,18 @@ func projectPut(d *Daemon, r *http.Request) Response {
 
 	err = json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return projectChange(d, project, req)
 }
 
-func projectPatch(d *Daemon, r *http.Request) Response {
+func projectPatch(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Check user permissions
 	if !d.userHasPermission(r, name, "manage-projects") {
-		return Forbidden(nil)
+		return response.Forbidden(nil)
 	}
 
 	// Get the current data
@@ -259,7 +261,7 @@ func projectPatch(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
@@ -270,12 +272,12 @@ func projectPatch(d *Daemon, r *http.Request) Response {
 	}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -283,12 +285,12 @@ func projectPatch(d *Daemon, r *http.Request) Response {
 
 	reqRaw := shared.Jmap{}
 	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	req := api.ProjectPut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check what was actually set in the query
@@ -311,23 +313,23 @@ func projectPatch(d *Daemon, r *http.Request) Response {
 }
 
 // Common logic between PUT and PATCH.
-func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) Response {
+func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) response.Response {
 	// Flag indicating if any feature has changed.
 	featuresChanged := req.Config["features.images"] != project.Config["features.images"] || req.Config["features.profiles"] != project.Config["features.profiles"]
 
 	// Sanity checks
 	if project.Name == "default" && featuresChanged {
-		return BadRequest(fmt.Errorf("You can't change the features of the default project"))
+		return response.BadRequest(fmt.Errorf("You can't change the features of the default project"))
 	}
 
 	if !projectIsEmpty(project) && featuresChanged {
-		return BadRequest(fmt.Errorf("Features can only be changed on empty projects"))
+		return response.BadRequest(fmt.Errorf("Features can only be changed on empty projects"))
 	}
 
 	// Validate the configuration
 	err := projectValidateConfig(req.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Update the database entry
@@ -357,13 +359,13 @@ func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) Response
 	})
 
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func projectPost(d *Daemon, r *http.Request) Response {
+func projectPost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Parse the request
@@ -371,16 +373,16 @@ func projectPost(d *Daemon, r *http.Request) Response {
 
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if name == "default" {
-		return Forbidden(fmt.Errorf("The 'default' project cannot be renamed"))
+		return response.Forbidden(fmt.Errorf("The 'default' project cannot be renamed"))
 	}
 
 	// Perform the rename
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		var id int64
 		err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
 			project, err := tx.ProjectGet(req.Name)
@@ -422,20 +424,20 @@ func projectPost(d *Daemon, r *http.Request) Response {
 		return nil
 	}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationProjectRename, nil, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationProjectRename, nil, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func projectDelete(d *Daemon, r *http.Request) Response {
+func projectDelete(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Sanity checks
 	if name == "default" {
-		return Forbidden(fmt.Errorf("The 'default' project cannot be deleted"))
+		return response.Forbidden(fmt.Errorf("The 'default' project cannot be deleted"))
 	}
 
 	var id int64
@@ -457,17 +459,17 @@ func projectDelete(d *Daemon, r *http.Request) Response {
 	})
 
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if d.rbac != nil {
 		err = d.rbac.DeleteProject(id)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Check if a project is empty.
diff --git a/lxd/backup.go b/lxd/backup.go
index 16f6b7c9f6..e536b07361 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -11,10 +11,12 @@ import (
 	"time"
 
 	"context"
+
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/task"
 	"github.com/lxc/lxd/shared"
@@ -445,11 +447,11 @@ func backupCreateTarball(s *state.State, path string, backup backup) error {
 
 func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return pruneExpiredContainerBackups(ctx, d)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
 			return
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 463c04a9d2..bd18e8cee4 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -15,6 +15,7 @@ import (
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -40,7 +41,7 @@ var certificateCmd = APIEndpoint{
 	Put:    APIEndpointAction{Handler: certificatePut},
 }
 
-func certificatesGet(d *Daemon, r *http.Request) Response {
+func certificatesGet(d *Daemon, r *http.Request) response.Response {
 	recursion := util.IsRecursionRequest(r)
 
 	if recursion {
@@ -48,7 +49,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 
 		baseCerts, err := d.cluster.CertificatesGet()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		for _, baseCert := range baseCerts {
 			resp := api.Certificate{}
@@ -62,7 +63,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 			}
 			certResponses = append(certResponses, resp)
 		}
-		return SyncResponse(true, certResponses)
+		return response.SyncResponse(true, certResponses)
 	}
 
 	body := []string{}
@@ -71,7 +72,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 		body = append(body, fingerprint)
 	}
 
-	return SyncResponse(true, body)
+	return response.SyncResponse(true, body)
 }
 
 func readSavedClientCAList(d *Daemon) {
@@ -100,33 +101,33 @@ func readSavedClientCAList(d *Daemon) {
 	}
 }
 
-func certificatesPost(d *Daemon, r *http.Request) Response {
+func certificatesPost(d *Daemon, r *http.Request) response.Response {
 	// Parse the request
 	req := api.CertificatesPost{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Access check
 	secret, err := cluster.ConfigGetString(d.cluster, "core.trust_password")
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	trusted, _, protocol, err := d.Authenticate(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if (!trusted || (protocol == "candid" && !d.userIsAdmin(r))) && util.PasswordCheck(secret, req.Password) != nil {
 		if req.Password != "" {
 			logger.Warn("Bad trust password", log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
 		}
-		return Forbidden(nil)
+		return response.Forbidden(nil)
 	}
 
 	if req.Type != "client" {
-		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
+		return response.BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
 	}
 
 	// Extract the certificate
@@ -135,28 +136,28 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 	if req.Certificate != "" {
 		data, err := base64.StdEncoding.DecodeString(req.Certificate)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		cert, err = x509.ParseCertificate(data)
 		if err != nil {
-			return BadRequest(errors.Wrap(err, "invalid certificate material"))
+			return response.BadRequest(errors.Wrap(err, "invalid certificate material"))
 		}
 		name = req.Name
 	} else if r.TLS != nil {
 		if len(r.TLS.PeerCertificates) < 1 {
-			return BadRequest(fmt.Errorf("No client certificate provided"))
+			return response.BadRequest(fmt.Errorf("No client certificate provided"))
 		}
 		cert = r.TLS.PeerCertificates[len(r.TLS.PeerCertificates)-1]
 
 		remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		name = remoteHost
 	} else {
-		return BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
+		return response.BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
 	}
 
 	fingerprint := shared.CertFingerprint(cert)
@@ -173,10 +174,10 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 			_, ok := d.clientCerts[fingerprint]
 			if !ok {
 				d.clientCerts[fingerprint] = *cert
-				return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
+				return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
 			}
 
-			return BadRequest(fmt.Errorf("Certificate already in trust store"))
+			return response.BadRequest(fmt.Errorf("Certificate already in trust store"))
 		}
 
 		// Store the certificate in the cluster database
@@ -189,14 +190,14 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 
 		err = d.cluster.CertSave(&dbCert)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Notify other nodes about the new certificate.
 		notifier, err := cluster.NewNotifier(
 			d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		req := api.CertificatesPost{
 			Certificate: base64.StdEncoding.EncodeToString(cert.Raw),
@@ -208,24 +209,24 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
 			return client.CreateCertificate(req)
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	d.clientCerts[shared.CertFingerprint(cert)] = *cert
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
 }
 
-func certificateGet(d *Daemon, r *http.Request) Response {
+func certificateGet(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	cert, err := doCertificateGet(d.cluster, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, cert, cert)
+	return response.SyncResponseETag(true, cert, cert)
 }
 
 func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, error) {
@@ -248,46 +249,46 @@ func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, erro
 	return resp, nil
 }
 
-func certificatePut(d *Daemon, r *http.Request) Response {
+func certificatePut(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	fingerprint = oldEntry.Fingerprint
 
 	err = util.EtagCheck(r, oldEntry)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.CertificatePut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doCertificateUpdate(d, fingerprint, req)
 }
 
-func certificatePatch(d *Daemon, r *http.Request) Response {
+func certificatePatch(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	oldEntry, err := doCertificateGet(d.cluster, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	fingerprint = oldEntry.Fingerprint
 
 	err = util.EtagCheck(r, oldEntry)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := oldEntry
 	reqRaw := shared.Jmap{}
 	if err := json.NewDecoder(r.Body).Decode(&reqRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get name
@@ -305,32 +306,32 @@ func certificatePatch(d *Daemon, r *http.Request) Response {
 	return doCertificateUpdate(d, fingerprint, req.Writable())
 }
 
-func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response {
+func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) response.Response {
 	if req.Type != "client" {
-		return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
+		return response.BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
 	}
 
 	err := d.cluster.CertUpdate(fingerprint, req.Name, 1)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func certificateDelete(d *Daemon, r *http.Request) Response {
+func certificateDelete(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	certInfo, err := d.cluster.CertificateGet(fingerprint)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	err = d.cluster.CertDelete(certInfo.Fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	readSavedClientCAList(d)
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
diff --git a/lxd/container.go b/lxd/container.go
index 722db5fcef..74ffe568ed 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -22,6 +22,7 @@ import (
 	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/events"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/task"
@@ -1249,11 +1250,11 @@ func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
 			return
 		}
 
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return autoCreateContainerSnapshots(ctx, d, instances)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationSnapshotCreate, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationSnapshotCreate, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start create snapshot operation", log.Ctx{"err": err})
 			return
@@ -1370,11 +1371,11 @@ func pruneExpiredContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
 			return
 		}
 
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return pruneExpiredContainerSnapshots(ctx, d, expiredSnapshots)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationSnapshotsExpire, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationSnapshotsExpire, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start expired snapshots operation", log.Ctx{"err": err})
 			return
diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index 040e2f9443..4f8b8fdd30 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -11,40 +11,42 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
-func containerBackupsGet(d *Daemon, r *http.Request) Response {
+func containerBackupsGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	cname := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	recursion := util.IsRecursionRequest(r)
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, cname)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	backups, err := c.Backups()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	resultString := []string{}
@@ -62,39 +64,39 @@ func containerBackupsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
-func containerBackupsPost(d *Daemon, r *http.Request) Response {
+func containerBackupsPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	rj := shared.Jmap{}
 	err = json.NewDecoder(r.Body).Decode(&rj)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	expiry, _ := rj.GetString("expiry")
@@ -106,21 +108,21 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
 	// Create body with correct expiry
 	body, err := json.Marshal(rj)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	req := api.InstanceBackupsPost{}
 
 	err = json.Unmarshal(body, &req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" {
 		// come up with a name
 		backups, err := c.Backups()
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		base := name + shared.SnapshotDelimiter + "backup"
@@ -149,13 +151,13 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
 
 	// Validate the name
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Backup names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Backup names may not contain slashes"))
 	}
 
 	fullName := name + shared.SnapshotDelimiter + req.Name
 	instanceOnly := req.InstanceOnly || req.ContainerOnly
 
-	backup := func(op *operation) error {
+	backup := func(op *operations.Operation) error {
 		args := db.ContainerBackupArgs{
 			Name:             fullName,
 			ContainerID:      c.Id(),
@@ -177,19 +179,19 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
 	resources["containers"] = []string{name}
 	resources["backups"] = []string{req.Name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask,
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
 		db.OperationBackupCreate, resources, nil, backup, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containerBackupGet(d *Daemon, r *http.Request) Response {
+func containerBackupGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -197,27 +199,27 @@ func containerBackupGet(d *Daemon, r *http.Request) Response {
 	backupName := mux.Vars(r)["backupName"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	fullName := name + shared.SnapshotDelimiter + backupName
 	backup, err := backupLoadByName(d.State(), project, fullName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, backup.Render())
+	return response.SyncResponse(true, backup.Render())
 }
 
-func containerBackupPost(d *Daemon, r *http.Request) Response {
+func containerBackupPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -225,34 +227,34 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
 	backupName := mux.Vars(r)["backupName"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	req := api.InstanceBackupPost{}
 	err = json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Validate the name
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Backup names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Backup names may not contain slashes"))
 	}
 
 	oldName := name + shared.SnapshotDelimiter + backupName
 	backup, err := backupLoadByName(d.State(), project, oldName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	newName := name + shared.SnapshotDelimiter + req.Name
 
-	rename := func(op *operation) error {
+	rename := func(op *operations.Operation) error {
 		err := backup.Rename(newName)
 		if err != nil {
 			return err
@@ -264,19 +266,19 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask,
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
 		db.OperationBackupRename, resources, nil, rename, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containerBackupDelete(d *Daemon, r *http.Request) Response {
+func containerBackupDelete(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -284,21 +286,21 @@ func containerBackupDelete(d *Daemon, r *http.Request) Response {
 	backupName := mux.Vars(r)["backupName"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	fullName := name + shared.SnapshotDelimiter + backupName
 	backup, err := backupLoadByName(d.State(), project, fullName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	remove := func(op *operation) error {
+	remove := func(op *operations.Operation) error {
 		err := backup.Delete()
 		if err != nil {
 			return err
@@ -310,19 +312,19 @@ func containerBackupDelete(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["container"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask,
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
 		db.OperationBackupRemove, resources, nil, remove, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containerBackupExportGet(d *Daemon, r *http.Request) Response {
+func containerBackupExportGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -330,23 +332,23 @@ func containerBackupExportGet(d *Daemon, r *http.Request) Response {
 	backupName := mux.Vars(r)["backupName"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	fullName := name + shared.SnapshotDelimiter + backupName
 	backup, err := backupLoadByName(d.State(), project, fullName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	ent := fileResponseEntry{
-		path: shared.VarPath("backups", backup.name),
+	ent := response.FileResponseEntry{
+		Path: shared.VarPath("backups", backup.name),
 	}
 
-	return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+	return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
 }
diff --git a/lxd/container_console.go b/lxd/container_console.go
index 9aa73fea41..9e94ec9bdb 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -19,6 +19,8 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -70,7 +72,7 @@ func (s *consoleWs) Metadata() interface{} {
 	return shared.Jmap{"fds": fds}
 }
 
-func (s *consoleWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *consoleWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -111,7 +113,7 @@ func (s *consoleWs) Connect(op *operation, r *http.Request, w http.ResponseWrite
 	return os.ErrPermission
 }
 
-func (s *consoleWs) Do(op *operation) error {
+func (s *consoleWs) Do(op *operations.Operation) error {
 	<-s.allConnected
 
 	var err error
@@ -254,10 +256,10 @@ func (s *consoleWs) Do(op *operation) error {
 	return finisher(err)
 }
 
-func containerConsolePost(d *Daemon, r *http.Request) Response {
+func containerConsolePost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -266,45 +268,45 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 	post := api.InstanceConsolePost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = json.Unmarshal(buf, &post)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Forward the request if the container is remote.
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if client != nil {
 		url := fmt.Sprintf("/containers/%s/console?project=%s", name, project)
 		op, _, err := client.RawOperation("POST", url, post, "")
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		opAPI := op.Get()
-		return ForwardedOperationResponse(project, &opAPI)
+		return operations.ForwardedOperationResponse(project, &opAPI)
 	}
 
 	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = fmt.Errorf("Container is not running")
 	if !inst.IsRunning() {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = fmt.Errorf("Container is frozen")
 	if inst.IsFrozen() {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	ws := &consoleWs{}
@@ -315,7 +317,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 		c := inst.(container)
 		idmapset, err := c.CurrentIdmap()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		if idmapset != nil {
@@ -329,7 +331,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 	for i := -1; i < len(ws.conns)-1; i++ {
 		ws.fds[i], err = shared.RandomCryptoString()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
@@ -342,54 +344,54 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{ws.instance.Name()}
 
-	op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationConsoleShow,
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationConsoleShow,
 		resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containerConsoleLogGet(d *Daemon, r *http.Request) Response {
+func containerConsoleLogGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Forward the request if the container is remote.
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	if !util.RuntimeLiblxcVersionAtLeast(3, 0, 0) {
-		return BadRequest(fmt.Errorf("Querying the console buffer requires liblxc >= 3.0"))
+		return response.BadRequest(fmt.Errorf("Querying the console buffer requires liblxc >= 3.0"))
 	}
 
 	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance is not container type"))
+		return response.SmartError(fmt.Errorf("Instance is not container type"))
 	}
 
 	c := inst.(container)
-	ent := fileResponseEntry{}
+	ent := response.FileResponseEntry{}
 	if !c.IsRunning() {
 		// Hand back the contents of the console ringbuffer logfile.
 		consoleBufferLogPath := c.ConsoleBufferLogPath()
-		ent.path = consoleBufferLogPath
-		ent.filename = consoleBufferLogPath
-		return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+		ent.Path = consoleBufferLogPath
+		ent.Filename = consoleBufferLogPath
+		return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
 	}
 
 	// Query the container's console ringbuffer.
@@ -405,23 +407,23 @@ func containerConsoleLogGet(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		errno, isErrno := shared.GetErrno(err)
 		if !isErrno {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if errno == unix.ENODATA {
-			return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+			return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
 		}
 
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	ent.buffer = []byte(logContents)
-	return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+	ent.Buffer = []byte(logContents)
+	return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
 }
 
-func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
+func containerConsoleLogDelete(d *Daemon, r *http.Request) response.Response {
 	if !util.RuntimeLiblxcVersionAtLeast(3, 0, 0) {
-		return BadRequest(fmt.Errorf("Clearing the console buffer requires liblxc >= 3.0"))
+		return response.BadRequest(fmt.Errorf("Clearing the console buffer requires liblxc >= 3.0"))
 	}
 
 	name := mux.Vars(r)["name"]
@@ -429,11 +431,11 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
 
 	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance is not container type"))
+		return response.SmartError(fmt.Errorf("Instance is not container type"))
 	}
 
 	c := inst.(container)
@@ -459,7 +461,7 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
 
 	if !inst.IsRunning() {
 		consoleLogpath := c.ConsoleBufferLogPath()
-		return SmartError(truncateConsoleLogFile(consoleLogpath))
+		return response.SmartError(truncateConsoleLogFile(consoleLogpath))
 	}
 
 	// Send a ringbuffer request to the container.
@@ -474,15 +476,15 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		errno, isErrno := shared.GetErrno(err)
 		if !isErrno {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if errno == unix.ENODATA {
-			return SmartError(nil)
+			return response.SmartError(nil)
 		}
 
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SmartError(nil)
+	return response.SmartError(nil)
 }
diff --git a/lxd/container_delete.go b/lxd/container_delete.go
index 2885588d1e..cc0a380e5f 100644
--- a/lxd/container_delete.go
+++ b/lxd/container_delete.go
@@ -6,45 +6,47 @@ import (
 
 	"github.com/gorilla/mux"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 )
 
-func containerDelete(d *Daemon, r *http.Request) Response {
+func containerDelete(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if c.IsRunning() {
-		return BadRequest(fmt.Errorf("container is running"))
+		return response.BadRequest(fmt.Errorf("container is running"))
 	}
 
-	rmct := func(op *operation) error {
+	rmct := func(op *operations.Operation) error {
 		return c.Delete()
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerDelete, resources, nil, rmct, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerDelete, resources, nil, rmct, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 3c871fed37..2c861a911c 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -20,6 +20,8 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	log "github.com/lxc/lxd/shared/log15"
@@ -66,7 +68,7 @@ func (s *execWs) Metadata() interface{} {
 	}
 }
 
-func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -107,7 +109,7 @@ func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter)
 	return os.ErrPermission
 }
 
-func (s *execWs) Do(op *operation) error {
+func (s *execWs) Do(op *operations.Operation) error {
 	<-s.allConnected
 
 	var err error
@@ -341,10 +343,10 @@ func (s *execWs) Do(op *operation) error {
 	return finisher(-1, nil)
 }
 
-func containerExecPost(d *Daemon, r *http.Request) Response {
+func containerExecPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -353,42 +355,42 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	post := api.InstanceExecPost{}
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if err := json.Unmarshal(buf, &post); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Forward the request if the container is remote.
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if client != nil {
 		url := fmt.Sprintf("/containers/%s/exec?project=%s", name, project)
 		op, _, err := client.RawOperation("POST", url, post, "")
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		opAPI := op.Get()
-		return ForwardedOperationResponse(project, &opAPI)
+		return operations.ForwardedOperationResponse(project, &opAPI)
 	}
 
 	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if !inst.IsRunning() {
-		return BadRequest(fmt.Errorf("Container is not running"))
+		return response.BadRequest(fmt.Errorf("Container is not running"))
 	}
 
 	if inst.IsFrozen() {
-		return BadRequest(fmt.Errorf("Container is frozen"))
+		return response.BadRequest(fmt.Errorf("Container is frozen"))
 	}
 
 	env := map[string]string{}
@@ -443,7 +445,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 			c := inst.(container)
 			idmapset, err := c.CurrentIdmap()
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			if idmapset != nil {
@@ -464,7 +466,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		for i := -1; i < len(ws.conns)-1; i++ {
 			ws.fds[i], err = shared.RandomCryptoString()
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 		}
 
@@ -482,28 +484,28 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		resources := map[string][]string{}
 		resources["containers"] = []string{ws.instance.Name()}
 
-		op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return operations.OperationResponse(op)
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		var cmdErr error
 		var cmdResult int
 		metadata := shared.Jmap{}
 
 		if post.RecordOutput {
 			// Prepare stdout and stderr recording
-			stdout, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stdout", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			stdout, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stdout", op.ID())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if err != nil {
 				return err
 			}
 			defer stdout.Close()
 
-			stderr, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stderr", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			stderr, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stderr", op.ID())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if err != nil {
 				return err
 			}
@@ -534,10 +536,10 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationCommandExec, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationCommandExec, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7946fe4444..ce87766ed7 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -10,34 +10,35 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 )
 
-func containerFileHandler(d *Daemon, r *http.Request) Response {
+func containerFileHandler(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	path := r.FormValue("path")
 	if path == "" {
-		return BadRequest(fmt.Errorf("missing path argument"))
+		return response.BadRequest(fmt.Errorf("missing path argument"))
 	}
 
 	switch r.Method {
@@ -48,11 +49,11 @@ func containerFileHandler(d *Daemon, r *http.Request) Response {
 	case "DELETE":
 		return containerFileDelete(c, path, r)
 	default:
-		return NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+		return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
 	}
 }
 
-func containerFileGet(c Instance, path string, r *http.Request) Response {
+func containerFileGet(c Instance, path string, r *http.Request) response.Response {
 	/*
 	 * Copy out of the ns to a temporary file, and then use that to serve
 	 * the request from. This prevents us from having to worry about stuff
@@ -62,7 +63,7 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
 	 */
 	temp, err := ioutil.TempFile("", "lxd_forkgetfile_")
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	defer temp.Close()
 
@@ -70,7 +71,7 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
 	uid, gid, mode, type_, dirEnts, err := c.FilePull(path, temp.Name())
 	if err != nil {
 		os.Remove(temp.Name())
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	headers := map[string]string{
@@ -82,34 +83,34 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
 
 	if type_ == "file" || type_ == "symlink" {
 		// Make a file response struct
-		files := make([]fileResponseEntry, 1)
-		files[0].identifier = filepath.Base(path)
-		files[0].path = temp.Name()
-		files[0].filename = filepath.Base(path)
+		files := make([]response.FileResponseEntry, 1)
+		files[0].Identifier = filepath.Base(path)
+		files[0].Path = temp.Name()
+		files[0].Filename = filepath.Base(path)
 
-		return FileResponse(r, files, headers, true)
+		return response.FileResponse(r, files, headers, true)
 	} else if type_ == "directory" {
 		os.Remove(temp.Name())
-		return SyncResponseHeaders(true, dirEnts, headers)
+		return response.SyncResponseHeaders(true, dirEnts, headers)
 	} else {
 		os.Remove(temp.Name())
-		return InternalError(fmt.Errorf("bad file type %s", type_))
+		return response.InternalError(fmt.Errorf("bad file type %s", type_))
 	}
 }
 
-func containerFilePost(c Instance, path string, r *http.Request) Response {
+func containerFilePost(c Instance, path string, r *http.Request) response.Response {
 	// Extract file ownership and mode from headers
 	uid, gid, mode, type_, write := shared.ParseLXDFileHeaders(r.Header)
 
 	if !shared.StringInSlice(write, []string{"overwrite", "append"}) {
-		return BadRequest(fmt.Errorf("Bad file write mode: %s", write))
+		return response.BadRequest(fmt.Errorf("Bad file write mode: %s", write))
 	}
 
 	if type_ == "file" {
 		// Write file content to a tempfile
 		temp, err := ioutil.TempFile("", "lxd_forkputfile_")
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 		defer func() {
 			temp.Close()
@@ -118,43 +119,43 @@ func containerFilePost(c Instance, path string, r *http.Request) Response {
 
 		_, err = io.Copy(temp, r.Body)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		// Transfer the file into the container
 		err = c.FilePush("file", temp.Name(), path, uid, gid, mode, write)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	} else if type_ == "symlink" {
 		target, err := ioutil.ReadAll(r.Body)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		err = c.FilePush("symlink", string(target), path, uid, gid, mode, write)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	} else if type_ == "directory" {
 		err := c.FilePush("directory", "", path, uid, gid, mode, write)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	} else {
-		return BadRequest(fmt.Errorf("Bad file type: %s", type_))
+		return response.BadRequest(fmt.Errorf("Bad file type: %s", type_))
 	}
 }
 
-func containerFileDelete(c Instance, path string, r *http.Request) Response {
+func containerFileDelete(c Instance, path string, r *http.Request) response.Response {
 	err := c.FileRemove(path)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
diff --git a/lxd/container_get.go b/lxd/container_get.go
index b76a85baa0..2ebf879d85 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -4,35 +4,36 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
+	"github.com/lxc/lxd/lxd/response"
 )
 
-func containerGet(d *Daemon, r *http.Request) Response {
+func containerGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	state, etag, err := c.Render()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, state, etag)
+	return response.SyncResponseETag(true, state, etag)
 }
diff --git a/lxd/container_instance_types.go b/lxd/container_instance_types.go
index 93beb809e0..085e844779 100644
--- a/lxd/container_instance_types.go
+++ b/lxd/container_instance_types.go
@@ -11,6 +11,7 @@ import (
 	"gopkg.in/yaml.v2"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/task"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -74,7 +75,7 @@ func instanceRefreshTypesTask(d *Daemon) (task.Func, task.Schedule) {
 	// returning in case the context expires.
 	_, hasCancellationSupport := interface{}(&http.Request{}).(util.ContextAwareRequest)
 	f := func(ctx context.Context) {
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			if hasCancellationSupport {
 				return instanceRefreshTypes(ctx, d)
 			}
@@ -91,7 +92,7 @@ func instanceRefreshTypesTask(d *Daemon) (task.Func, task.Schedule) {
 			}
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationInstanceTypesUpdate, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationInstanceTypesUpdate, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start instance types update operation", log.Ctx{"err": err})
 			return
diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index 8c68c47e67..e3f7d861fd 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/version"
 )
@@ -30,7 +31,7 @@ var instanceLogsCmd = APIEndpoint{
 	Get: APIEndpointAction{Handler: containerLogsGet, AccessHandler: AllowProjectPermission("containers", "view")},
 }
 
-func containerLogsGet(d *Daemon, r *http.Request) Response {
+func containerLogsGet(d *Daemon, r *http.Request) response.Response {
 	/* Let's explicitly *not* try to do a containerLoadByName here. In some
 	 * cases (e.g. when container creation failed), the container won't
 	 * exist in the DB but it does have some log files on disk.
@@ -41,30 +42,30 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	if err := containerValidName(name); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	result := []string{}
 
 	dents, err := ioutil.ReadDir(shared.LogPath(name))
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	for _, f := range dents {
@@ -75,7 +76,7 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 		result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", version.APIVersion, name, f.Name()))
 	}
 
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
 func validLogFileName(fname string) bool {
@@ -89,73 +90,73 @@ func validLogFileName(fname string) bool {
 		strings.HasPrefix(fname, "exec_")
 }
 
-func containerLogGet(d *Daemon, r *http.Request) Response {
+func containerLogGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	file := mux.Vars(r)["file"]
 
 	if err := containerValidName(name); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if !validLogFileName(file) {
-		return BadRequest(fmt.Errorf("log file name %s not valid", file))
+		return response.BadRequest(fmt.Errorf("log file name %s not valid", file))
 	}
 
-	ent := fileResponseEntry{
-		path:     shared.LogPath(name, file),
-		filename: file,
+	ent := response.FileResponseEntry{
+		Path:     shared.LogPath(name, file),
+		Filename: file,
 	}
 
-	return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+	return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
 }
 
-func containerLogDelete(d *Daemon, r *http.Request) Response {
+func containerLogDelete(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	file := mux.Vars(r)["file"]
 
 	if err := containerValidName(name); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if !validLogFileName(file) {
-		return BadRequest(fmt.Errorf("log file name %s not valid", file))
+		return response.BadRequest(fmt.Errorf("log file name %s not valid", file))
 	}
 
 	if file == "lxc.log" || file == "lxc.conf" {
-		return BadRequest(fmt.Errorf("lxc.log and lxc.conf may not be deleted"))
+		return response.BadRequest(fmt.Errorf("lxc.log and lxc.conf may not be deleted"))
 	}
 
-	return SmartError(os.Remove(shared.LogPath(name, file)))
+	return response.SmartError(os.Remove(shared.LogPath(name, file)))
 }
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c75d4774da..8021c75bbe 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -33,6 +33,7 @@ import (
 	"github.com/lxc/lxd/lxd/events"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/maas"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/state"
 	driver "github.com/lxc/lxd/lxd/storage"
@@ -629,7 +630,7 @@ type containerLXC struct {
 	node string
 
 	// Progress tracking
-	op *operation
+	op *operations.Operation
 
 	expiryDate time.Time
 }
@@ -6906,7 +6907,7 @@ func (c *containerLXC) StoragePool() (string, error) {
 }
 
 // Progress tracking
-func (c *containerLXC) SetOperation(op *operation) {
+func (c *containerLXC) SetOperation(op *operations.Operation) {
 	c.op = op
 }
 
@@ -6924,7 +6925,7 @@ func (c *containerLXC) updateProgress(progress string) {
 		return
 	}
 
-	meta := c.op.metadata
+	meta := c.op.Metadata()
 	if meta == nil {
 		meta = make(map[string]interface{})
 	}
diff --git a/lxd/container_metadata.go b/lxd/container_metadata.go
index bd4bdc712f..0f6ecf828c 100644
--- a/lxd/container_metadata.go
+++ b/lxd/container_metadata.go
@@ -13,39 +13,40 @@ import (
 	"github.com/gorilla/mux"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
-func containerMetadataGet(d *Daemon, r *http.Request) Response {
+func containerMetadataGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	// Load the container
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	metadataPath := filepath.Join(c.Path(), "metadata.yaml")
 
 	// Start the storage if needed
 	ourStart, err := c.StorageStart()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourStart {
 		defer c.StorageStop()
@@ -53,60 +54,60 @@ func containerMetadataGet(d *Daemon, r *http.Request) Response {
 
 	// If missing, just return empty result
 	if !shared.PathExists(metadataPath) {
-		return SyncResponse(true, api.ImageMetadata{})
+		return response.SyncResponse(true, api.ImageMetadata{})
 	}
 
 	// Read the metadata
 	metadataFile, err := os.Open(metadataPath)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	defer metadataFile.Close()
 
 	data, err := ioutil.ReadAll(metadataFile)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Parse into the API struct
 	metadata := api.ImageMetadata{}
 	err = yaml.Unmarshal(data, &metadata)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, metadata)
+	return response.SyncResponse(true, metadata)
 }
 
-func containerMetadataPut(d *Daemon, r *http.Request) Response {
+func containerMetadataPut(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	// Load the container
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	metadataPath := filepath.Join(c.Path(), "metadata.yaml")
 
 	// Start the storage if needed
 	ourStart, err := c.StorageStart()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourStart {
 		defer c.StorageStop()
@@ -115,51 +116,51 @@ func containerMetadataPut(d *Daemon, r *http.Request) Response {
 	// Read the new metadata
 	metadata := api.ImageMetadata{}
 	if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Write as YAML
 	data, err := yaml.Marshal(metadata)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if err := ioutil.WriteFile(metadataPath, data, 0644); err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Return a list of templates used in a container or the content of a template
-func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	// Load the container
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Start the storage if needed
 	ourStart, err := c.StorageStart()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourStart {
 		defer c.StorageStop()
@@ -170,14 +171,14 @@ func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
 	if templateName == "" {
 		templates := []string{}
 		if !shared.PathExists(filepath.Join(c.Path(), "templates")) {
-			return SyncResponse(true, templates)
+			return response.SyncResponse(true, templates)
 		}
 
 		// List templates
 		templatesPath := filepath.Join(c.Path(), "templates")
 		filesInfo, err := ioutil.ReadDir(templatesPath)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		for _, info := range filesInfo {
@@ -186,74 +187,74 @@ func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
 			}
 		}
 
-		return SyncResponse(true, templates)
+		return response.SyncResponse(true, templates)
 	}
 
 	// Check if the template exists
 	templatePath, err := getContainerTemplatePath(c, templateName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if !shared.PathExists(templatePath) {
-		return NotFound(fmt.Errorf("Template '%s' not found", templateName))
+		return response.NotFound(fmt.Errorf("Template '%s' not found", templateName))
 	}
 
 	// Create a temporary file with the template content (since the container
 	// storage might not be available when the file is read from FileResponse)
 	template, err := os.Open(templatePath)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	defer template.Close()
 
 	tempfile, err := ioutil.TempFile("", "lxd_template")
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	defer tempfile.Close()
 
 	_, err = io.Copy(tempfile, template)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	files := make([]fileResponseEntry, 1)
-	files[0].identifier = templateName
-	files[0].path = tempfile.Name()
-	files[0].filename = templateName
-	return FileResponse(r, files, nil, true)
+	files := make([]response.FileResponseEntry, 1)
+	files[0].Identifier = templateName
+	files[0].Path = tempfile.Name()
+	files[0].Filename = templateName
+	return response.FileResponse(r, files, nil, true)
 }
 
 // Add a container template file
-func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	// Load the container
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Start the storage if needed
 	ourStart, err := c.StorageStart()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourStart {
 		defer c.StorageStop()
@@ -262,46 +263,46 @@ func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response {
 	// Look at the request
 	templateName := r.FormValue("path")
 	if templateName == "" {
-		return BadRequest(fmt.Errorf("missing path argument"))
+		return response.BadRequest(fmt.Errorf("missing path argument"))
 	}
 
 	if !shared.PathExists(filepath.Join(c.Path(), "templates")) {
 		err := os.MkdirAll(filepath.Join(c.Path(), "templates"), 0711)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	// Check if the template already exists
 	templatePath, err := getContainerTemplatePath(c, templateName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if r.Method == "POST" && shared.PathExists(templatePath) {
-		return BadRequest(fmt.Errorf("Template already exists"))
+		return response.BadRequest(fmt.Errorf("Template already exists"))
 	}
 
 	// Write the new template
 	template, err := os.OpenFile(templatePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	defer template.Close()
 
 	_, err = io.Copy(template, r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Delete a container template
-func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -309,24 +310,24 @@ func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	// Load the container
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Start the storage if needed
 	ourStart, err := c.StorageStart()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourStart {
 		defer c.StorageStop()
@@ -335,25 +336,25 @@ func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
 	// Look at the request
 	templateName := r.FormValue("path")
 	if templateName == "" {
-		return BadRequest(fmt.Errorf("missing path argument"))
+		return response.BadRequest(fmt.Errorf("missing path argument"))
 	}
 
 	templatePath, err := getContainerTemplatePath(c, templateName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if !shared.PathExists(templatePath) {
-		return NotFound(fmt.Errorf("Template '%s' not found", templateName))
+		return response.NotFound(fmt.Errorf("Template '%s' not found", templateName))
 	}
 
 	// Delete the template
 	err = os.Remove(templatePath)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Return the full path of a container template.
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index 16b8bf3743..44824bcc5a 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -11,16 +11,17 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-func containerPatch(d *Daemon, r *http.Request) Response {
+func containerPatch(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -29,29 +30,29 @@ func containerPatch(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -59,16 +60,16 @@ func containerPatch(d *Daemon, r *http.Request) Response {
 
 	reqRaw := shared.Jmap{}
 	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	req := api.InstancePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Restore != "" {
-		return BadRequest(fmt.Errorf("Can't call PATCH in restore mode"))
+		return response.BadRequest(fmt.Errorf("Can't call PATCH in restore mode"))
 	}
 
 	// Check if architecture was passed
@@ -131,8 +132,8 @@ func containerPatch(d *Daemon, r *http.Request) Response {
 
 	err = c.Update(args, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
diff --git a/lxd/container_post.go b/lxd/container_post.go
index da7e960938..50a137cea3 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -15,6 +15,8 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -27,10 +29,10 @@ var internalClusterContainerMovedCmd = APIEndpoint{
 	Post: APIEndpointAction{Handler: internalClusterContainerMovedPost},
 }
 
-func containerPost(d *Daemon, r *http.Request) Response {
+func containerPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -94,12 +96,12 @@ func containerPost(d *Daemon, r *http.Request) Response {
 			return nil
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	if targetNode != "" && targetNodeOffline {
-		return BadRequest(fmt.Errorf("Target node is offline"))
+		return response.BadRequest(fmt.Errorf("Target node is offline"))
 	}
 
 	var inst Instance
@@ -127,23 +129,23 @@ func containerPost(d *Daemon, r *http.Request) Response {
 	// and we'll either forward the request or load the container.
 	if targetNode == "" || !sourceNodeOffline {
 		// Handle requests targeted to a container on a different node
-		response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+		resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		if response != nil {
-			return response
+		if resp != nil {
+			return resp
 		}
 
 		inst, err = instanceLoadByProjectAndName(d.State(), project, name)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -152,13 +154,13 @@ func containerPost(d *Daemon, r *http.Request) Response {
 	reqRaw := shared.Jmap{}
 	err = json.NewDecoder(rdr1).Decode(&reqRaw)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	req := api.InstancePost{}
 	err = json.NewDecoder(rdr2).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check if stateful (backward compatibility)
@@ -172,19 +174,19 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		if targetNode != "" {
 			// Check whether the container is running.
 			if inst != nil && inst.IsRunning() {
-				return BadRequest(fmt.Errorf("Container is running"))
+				return response.BadRequest(fmt.Errorf("Container is running"))
 			}
 
 			// Check if we are migrating a ceph-based container.
 			poolName, err := d.cluster.ContainerPool(project, name)
 			if err != nil {
 				err = errors.Wrap(err, "Failed to fetch container's pool name")
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 			_, pool, err := d.cluster.StoragePoolGet(poolName)
 			if err != nil {
 				err = errors.Wrap(err, "Failed to fetch container's pool info")
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 			if pool.Driver == "ceph" {
 				return containerPostClusteringMigrateWithCeph(d, inst, project, name, req.Name, targetNode, instanceType)
@@ -196,7 +198,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
 			// ceph-based.
 			if sourceNodeOffline {
 				err := fmt.Errorf("The cluster member hosting the container is offline")
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
 			return containerPostClusteringMigrate(d, inst, name, req.Name, targetNode)
@@ -205,13 +207,13 @@ func containerPost(d *Daemon, r *http.Request) Response {
 		instanceOnly := req.InstanceOnly || req.ContainerOnly
 
 		if inst.Type() != instance.TypeContainer {
-			return SmartError(fmt.Errorf("Instance is not container type"))
+			return response.SmartError(fmt.Errorf("Instance is not container type"))
 		}
 
 		c := inst.(container)
 		ws, err := NewMigrationSource(c, stateful, instanceOnly)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		resources := map[string][]string{}
@@ -221,49 +223,49 @@ func containerPost(d *Daemon, r *http.Request) Response {
 			// Push mode
 			err := ws.ConnectContainerTarget(*req.Target)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerMigrate, resources, nil, ws.Do, nil, nil)
+			op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, ws.Do, nil, nil)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			return OperationResponse(op)
+			return operations.OperationResponse(op)
 		}
 
 		// Pull mode
-		op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationContainerMigrate, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationContainerMigrate, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return operations.OperationResponse(op)
 	}
 
 	// Check that the name isn't already in use
 	id, _ := d.cluster.ContainerID(project, req.Name)
 	if id > 0 {
-		return Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
+		return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
 	}
 
-	run := func(*operation) error {
+	run := func(*operations.Operation) error {
 		return inst.Rename(req.Name)
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerRename, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerRename, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // Move a non-ceph container to another cluster node.
-func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, newNode string) Response {
+func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, newNode string) response.Response {
 	cert := d.endpoints.NetworkCert()
 
 	var sourceAddress string
@@ -290,10 +292,10 @@ func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, new
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	run := func(*operation) error {
+	run := func(*operations.Operation) error {
 		// Connect to the source host, i.e. ourselves (the node the container is running on).
 		source, err := cluster.Connect(sourceAddress, cert, false)
 		if err != nil {
@@ -397,17 +399,17 @@ func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, new
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{oldName}
-	op, err := operationCreate(d.cluster, c.Project(), operationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, c.Project(), operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // Special case migrating a container backed by ceph across two cluster nodes.
-func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldName, newName, newNode string, instanceType instance.Type) Response {
-	run := func(*operation) error {
+func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldName, newName, newNode string, instanceType instance.Type) response.Response {
+	run := func(*operations.Operation) error {
 		// If source node is online (i.e. we're serving the request on
 		// it, and c != nil), let's unmap the RBD volume locally
 		if c != nil {
@@ -505,26 +507,26 @@ func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldN
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{oldName}
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // Notification that a container was moved.
 //
 // At the moment it's used for ceph-based containers, where the target node needs
 // to create the appropriate mount points.
-func internalClusterContainerMovedPost(d *Daemon, r *http.Request) Response {
+func internalClusterContainerMovedPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	containerName := mux.Vars(r)["name"]
 	err := containerPostCreateContainerMountPoint(d, project, containerName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // Used after to create the appropriate mounts point after a container has been
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 93eb4132ca..efc0c27a3b 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -9,6 +9,8 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -20,10 +22,10 @@ import (
  * Update configuration, or, if 'restore:snapshot-name' is present, restore
  * the named snapshot
  */
-func containerPut(d *Daemon, r *http.Request) Response {
+func containerPut(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -32,29 +34,29 @@ func containerPut(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	configRaw := api.InstancePut{}
 	if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	architecture, err := osarch.ArchitectureId(configRaw.Architecture)
@@ -62,11 +64,11 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		architecture = 0
 	}
 
-	var do func(*operation) error
+	var do func(*operations.Operation) error
 	var opType db.OperationType
 	if configRaw.Restore == "" {
 		// Update container configuration
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			args := db.ContainerArgs{
 				Architecture: architecture,
 				Config:       configRaw.Config,
@@ -89,7 +91,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		opType = db.OperationContainerUpdate
 	} else {
 		// Snapshot Restore
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			return containerSnapRestore(d.State(), project, name, configRaw.Restore, configRaw.Stateful)
 		}
 
@@ -99,12 +101,12 @@ func containerPut(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, opType, resources, nil, do, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, opType, resources, nil, do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 func containerSnapRestore(s *state.State, project, name, snap string, stateful bool) error {
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 51629b4d51..64c60181c2 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -14,28 +14,30 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/version"
 )
 
-func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
+func containerSnapshotsGet(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	cname := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	recursion := util.IsRecursionRequest(r)
@@ -45,7 +47,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	if !recursion {
 		snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		for _, snap := range snaps {
@@ -61,12 +63,12 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	} else {
 		c, err := instanceLoadByProjectAndName(d.State(), project, cname)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		snaps, err := c.Snapshots()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		for _, snap := range snaps {
@@ -80,28 +82,28 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
-func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
+func containerSnapshotsPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	/*
@@ -112,24 +114,24 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 	 */
 	inst, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	req := api.InstanceSnapshotsPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" {
 		req.Name, err = containerDetermineNextSnapshotName(d, inst, "snap%d")
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	// Validate the name
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
 	}
 
 	fullName := name +
@@ -142,11 +144,11 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 	} else {
 		expiry, err = shared.GetSnapshotExpiry(time.Now(), inst.LocalConfig()["snapshots.expiry"])
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 	}
 
-	snapshot := func(op *operation) error {
+	snapshot := func(op *operations.Operation) error {
 		args := db.ContainerArgs{
 			Project:      inst.Project(),
 			Architecture: inst.Architecture(),
@@ -178,35 +180,35 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
+func containerSnapshotHandler(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	containerName := mux.Vars(r)["name"]
 	snapshotName := mux.Vars(r)["snapshotName"]
 
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	snapshotName, err = url.QueryUnescape(snapshotName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	inst, err := instanceLoadByProjectAndName(
 		d.State(),
@@ -214,11 +216,11 @@ func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
 			shared.SnapshotDelimiter+
 			snapshotName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if inst.Type() != instance.TypeContainer {
-		return SmartError(fmt.Errorf("Instance not container type"))
+		return response.SmartError(fmt.Errorf("Instance not container type"))
 	}
 
 	sc := inst.(container)
@@ -233,48 +235,48 @@ func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
 	case "PUT":
 		return snapshotPut(d, r, sc, snapshotName)
 	default:
-		return NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+		return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
 	}
 }
 
-func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response {
+func snapshotPut(d *Daemon, r *http.Request, sc container, name string) response.Response {
 	// Validate the ETag
 	etag := []interface{}{sc.ExpiryDate()}
 	err := util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	rj := shared.Jmap{}
 
 	err = json.NewDecoder(r.Body).Decode(&rj)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	var do func(op *operation) error
+	var do func(op *operations.Operation) error
 
 	_, err = rj.GetString("expires_at")
 	if err != nil {
 		// Skip updating the snapshot since the requested key wasn't provided
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			return nil
 		}
 	} else {
 		body, err := json.Marshal(rj)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		configRaw := api.InstanceSnapshotPut{}
 
 		err = json.Unmarshal(body, &configRaw)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		// Update container configuration
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			args := db.ContainerArgs{
 				Architecture: sc.Architecture(),
 				Config:       sc.LocalConfig(),
@@ -302,35 +304,35 @@ func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, opType, resources, nil,
+	op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, opType, resources, nil,
 		do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func snapshotGet(sc container, name string) Response {
+func snapshotGet(sc container, name string) response.Response {
 	render, _, err := sc.Render()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, render.(*api.InstanceSnapshot))
+	return response.SyncResponse(true, render.(*api.InstanceSnapshot))
 }
 
-func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
+func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) response.Response {
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
 
 	raw := shared.Jmap{}
 	if err := json.NewDecoder(rdr1).Decode(&raw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	migration, err := raw.GetBool("migration")
@@ -341,24 +343,24 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 		req := api.InstancePost{}
 		err = json.NewDecoder(rdr2).Decode(&req)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		reqNew := api.InstanceSnapshotPost{}
 		err = json.NewDecoder(rdr3).Decode(&reqNew)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		if reqNew.Name == "" {
-			return BadRequest(fmt.Errorf(`A new name for the ` +
+			return response.BadRequest(fmt.Errorf(`A new name for the ` +
 				`container must be provided`))
 		}
 
 		if reqNew.Live {
 			sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(containerName)
 			if sourceName != reqNew.Name {
-				return BadRequest(fmt.Errorf(`Copying `+
+				return response.BadRequest(fmt.Errorf(`Copying `+
 					`stateful containers requires that `+
 					`source "%s" and `+`target "%s" name `+
 					`be identical`, sourceName, reqNew.Name))
@@ -367,7 +369,7 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 
 		ws, err := NewMigrationSource(sc, reqNew.Live, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		resources := map[string][]string{}
@@ -377,34 +379,34 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 			// Push mode
 			err := ws.ConnectContainerTarget(*req.Target)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
+			op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			return OperationResponse(op)
+			return operations.OperationResponse(op)
 		}
 
 		// Pull mode
-		op, err := operationCreate(d.cluster, sc.Project(), operationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return operations.OperationResponse(op)
 	}
 
 	newName, err := raw.GetString("name")
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Validate the name
 	if strings.Contains(newName, "/") {
-		return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
 	}
 
 	fullName := containerName + shared.SnapshotDelimiter + newName
@@ -412,36 +414,36 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 	// Check that the name isn't already in use
 	id, _ := d.cluster.InstanceSnapshotID(sc.Project(), containerName, newName)
 	if id > 0 {
-		return Conflict(fmt.Errorf("Name '%s' already in use", fullName))
+		return response.Conflict(fmt.Errorf("Name '%s' already in use", fullName))
 	}
 
-	rename := func(op *operation) error {
+	rename := func(op *operations.Operation) error {
 		return sc.Rename(fullName)
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{containerName}
 
-	op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func snapshotDelete(sc container, name string) Response {
-	remove := func(op *operation) error {
+func snapshotDelete(sc container, name string) response.Response {
+	remove := func(op *operations.Operation) error {
 		return sc.Delete()
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{sc.Name()}
 
-	op, err := operationCreate(sc.DaemonState().Cluster, sc.Project(), operationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
+	op, err := operations.OperationCreate(sc.DaemonState().Cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
diff --git a/lxd/container_state.go b/lxd/container_state.go
index b5198977ca..9995803153 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -9,56 +9,58 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 )
 
-func containerState(d *Daemon, r *http.Request) Response {
+func containerState(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	state, err := c.RenderState()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponse(true, state)
+	return response.SyncResponse(true, state)
 }
 
-func containerStatePut(d *Daemon, r *http.Request) Response {
+func containerStatePut(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	// Handle requests targeted to a container on a different node
-	response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+	resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	if response != nil {
-		return response
+	if resp != nil {
+		return resp
 	}
 
 	raw := api.InstanceStatePut{}
@@ -68,7 +70,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 	raw.Timeout = -1
 
 	if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Don't mess with containers while in setup mode
@@ -76,15 +78,15 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 
 	c, err := instanceLoadByProjectAndName(d.State(), project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	var opType db.OperationType
-	var do func(*operation) error
+	var do func(*operations.Operation) error
 	switch shared.ContainerAction(raw.Action) {
 	case shared.Start:
 		opType = db.OperationContainerStart
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			c.SetOperation(op)
 			if err = c.Start(raw.Stateful); err != nil {
 				return err
@@ -94,7 +96,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 	case shared.Stop:
 		opType = db.OperationContainerStop
 		if raw.Stateful {
-			do = func(op *operation) error {
+			do = func(op *operations.Operation) error {
 				c.SetOperation(op)
 				err := c.Stop(raw.Stateful)
 				if err != nil {
@@ -104,7 +106,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 				return nil
 			}
 		} else if raw.Timeout == 0 || raw.Force {
-			do = func(op *operation) error {
+			do = func(op *operations.Operation) error {
 				c.SetOperation(op)
 				err = c.Stop(false)
 				if err != nil {
@@ -114,7 +116,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 				return nil
 			}
 		} else {
-			do = func(op *operation) error {
+			do = func(op *operations.Operation) error {
 				c.SetOperation(op)
 				if c.IsFrozen() {
 					err := c.Unfreeze()
@@ -133,7 +135,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 		}
 	case shared.Restart:
 		opType = db.OperationContainerRestart
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			c.SetOperation(op)
 			ephemeral := c.IsEphemeral()
 
@@ -188,35 +190,35 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 		}
 	case shared.Freeze:
 		if !d.os.CGroupFreezerController {
-			return BadRequest(fmt.Errorf("This system doesn't support freezing containers"))
+			return response.BadRequest(fmt.Errorf("This system doesn't support freezing containers"))
 		}
 
 		opType = db.OperationContainerFreeze
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			c.SetOperation(op)
 			return c.Freeze()
 		}
 	case shared.Unfreeze:
 		if !d.os.CGroupFreezerController {
-			return BadRequest(fmt.Errorf("This system doesn't support unfreezing containers"))
+			return response.BadRequest(fmt.Errorf("This system doesn't support unfreezing containers"))
 		}
 
 		opType = db.OperationContainerUnfreeze
-		do = func(op *operation) error {
+		do = func(op *operations.Operation) error {
 			c.SetOperation(op)
 			return c.Unfreeze()
 		}
 	default:
-		return BadRequest(fmt.Errorf("unknown action %s", raw.Action))
+		return response.BadRequest(fmt.Errorf("unknown action %s", raw.Action))
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, opType, resources, nil, do, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, opType, resources, nil, do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 2d4d147920..0a315d57d6 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -16,6 +16,7 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/db/query"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -39,15 +40,15 @@ func urlInstanceTypeDetect(r *http.Request) (instance.Type, error) {
 	return instance.TypeAny, nil
 }
 
-func containersGet(d *Daemon, r *http.Request) Response {
+func containersGet(d *Daemon, r *http.Request) response.Response {
 	for i := 0; i < 100; i++ {
 		result, err := doContainersGet(d, r)
 		if err == nil {
-			return SyncResponse(true, result)
+			return response.SyncResponse(true, result)
 		}
 		if !query.IsRetriableError(err) {
 			logger.Debugf("DBERR: containersGet: error %q", err)
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		// 100 ms may seem drastic, but we really don't want to thrash
 		// perhaps we should use a random amount
@@ -56,7 +57,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 
 	logger.Debugf("DBERR: containersGet, db is locked")
 	logger.Debugf(logger.GetStack())
-	return InternalError(fmt.Errorf("DB is locked"))
+	return response.InternalError(fmt.Errorf("DB is locked"))
 }
 
 func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 822af02534..b10c77e175 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -22,6 +22,8 @@ import (
 	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
@@ -30,7 +32,7 @@ import (
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromImage(d *Daemon, project string, req *api.InstancesPost) response.Response {
 	var hash string
 	var err error
 
@@ -42,19 +44,19 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
 		} else {
 			_, alias, err := d.cluster.ImageAliasGet(project, req.Source.Alias, true)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
 			hash = alias.Target
 		}
 	} else if req.Source.Properties != nil {
 		if req.Source.Server != "" {
-			return BadRequest(fmt.Errorf("Property match is only supported for local images"))
+			return response.BadRequest(fmt.Errorf("Property match is only supported for local images"))
 		}
 
 		hashes, err := d.cluster.ImagesGet(project, false)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		var image *api.Image
@@ -85,20 +87,20 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
 		}
 
 		if image == nil {
-			return BadRequest(fmt.Errorf("No matching image could be found"))
+			return response.BadRequest(fmt.Errorf("No matching image could be found"))
 		}
 
 		hash = image.Fingerprint
 	} else {
-		return BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image"))
+		return response.BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image"))
 	}
 
 	dbType, err := instance.New(string(req.Type))
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		args := db.ContainerArgs{
 			Project:     project,
 			Config:      req.Config,
@@ -146,18 +148,18 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromNone(d *Daemon, project string, req *api.InstancesPost) response.Response {
 	dbType, err := instance.New(string(req.Type))
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	args := db.ContainerArgs{
@@ -174,12 +176,12 @@ func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response
 	if req.Architecture != "" {
 		architecture, err := osarch.ArchitectureId(req.Architecture)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 		args.Architecture = architecture
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		_, err := containerCreateAsEmpty(d, args)
 		return err
 	}
@@ -187,18 +189,18 @@ func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromMigration(d *Daemon, project string, req *api.InstancesPost) response.Response {
 	// Validate migration mode
 	if req.Source.Mode != "pull" && req.Source.Mode != "push" {
-		return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
+		return response.NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
 	}
 
 	var c container
@@ -206,7 +208,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 	// Parse the architecture name
 	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Pre-fill default profile
@@ -216,7 +218,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 
 	dbType, err := instance.New(string(req.Type))
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Prepare the container creation request
@@ -237,12 +239,12 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 	// Early profile validation
 	profiles, err := d.cluster.Profiles(project)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	for _, profile := range args.Profiles {
 		if !shared.StringInSlice(profile, profiles) {
-			return BadRequest(fmt.Errorf("Requested profile '%s' doesn't exist", profile))
+			return response.BadRequest(fmt.Errorf("Requested profile '%s' doesn't exist", profile))
 		}
 	}
 
@@ -252,7 +254,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 	}
 
 	if storagePool == "" {
-		return BadRequest(fmt.Errorf("Can't find a storage pool for the container to use"))
+		return response.BadRequest(fmt.Errorf("Can't find a storage pool for the container to use"))
 	}
 
 	if localRootDiskDeviceKey == "" && storagePoolProfile == "" {
@@ -289,11 +291,11 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 		if err != nil {
 			req.Source.Refresh = false
 		} else if inst.IsRunning() {
-			return BadRequest(fmt.Errorf("Cannot refresh a running container"))
+			return response.BadRequest(fmt.Errorf("Cannot refresh a running container"))
 		}
 
 		if inst.Type() != instance.TypeContainer {
-			return BadRequest(fmt.Errorf("Instance type not container"))
+			return response.BadRequest(fmt.Errorf("Instance type not container"))
 		}
 
 		c = inst.(container)
@@ -317,40 +319,40 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 		if err != nil {
 			c, err = containerCreateAsEmpty(d, args)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 		} else {
 			// Retrieve the future storage pool
 			cM, err := containerLXCLoad(d.State(), args, nil)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			_, rootDiskDevice, err := shared.GetRootDiskDevice(cM.ExpandedDevices().CloneNative())
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			if rootDiskDevice["pool"] == "" {
-				return BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
+				return response.BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
 			}
 
 			storagePool = rootDiskDevice["pool"]
 
 			ps, err := storagePoolInit(d.State(), storagePool)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			if ps.MigrationType() == migration.MigrationFSType_RSYNC {
 				c, err = containerCreateFromImage(d, args, req.Source.BaseImage, nil)
 				if err != nil {
-					return InternalError(err)
+					return response.InternalError(err)
 				}
 			} else {
 				c, err = containerCreateAsEmpty(d, args)
 				if err != nil {
-					return InternalError(err)
+					return response.InternalError(err)
 				}
 			}
 		}
@@ -363,7 +365,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 			if !req.Source.Refresh {
 				c.Delete()
 			}
-			return InternalError(fmt.Errorf("Invalid certificate"))
+			return response.InternalError(fmt.Errorf("Invalid certificate"))
 		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
@@ -371,7 +373,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 			if !req.Source.Refresh {
 				c.Delete()
 			}
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
@@ -380,7 +382,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 		if !req.Source.Refresh {
 			c.Delete()
 		}
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	push := false
@@ -405,10 +407,10 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 	sink, err := NewMigrationSink(&migrationArgs)
 	if err != nil {
 		c.Delete()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		// And finally run the migration.
 		err = sink.Do(op)
 		if err != nil {
@@ -433,25 +435,25 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	var op *operation
+	var op *operations.Operation
 	if push {
-		op, err = operationCreate(d.cluster, project, operationClassWebsocket, db.OperationContainerCreate, resources, sink.Metadata(), run, nil, sink.Connect)
+		op, err = operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationContainerCreate, resources, sink.Metadata(), run, nil, sink.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	} else {
-		op, err = operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+		op, err = operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromCopy(d *Daemon, project string, req *api.InstancesPost) response.Response {
 	if req.Source.Source == "" {
-		return BadRequest(fmt.Errorf("must specify a source container"))
+		return response.BadRequest(fmt.Errorf("must specify a source container"))
 	}
 
 	sourceProject := req.Source.Project
@@ -462,13 +464,13 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 
 	source, err := instanceLoadByProjectAndName(d.State(), sourceProject, req.Source.Source)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Check if we need to redirect to migration
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// When clustered, use the node name, otherwise use the hostname.
@@ -479,7 +481,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if serverName != source.Location() {
@@ -500,7 +502,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 			_, pool, err := d.cluster.StoragePoolGet(sourcePoolName)
 			if err != nil {
 				err = errors.Wrap(err, "Failed to fetch container's pool info")
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
 			if pool.Driver != "ceph" {
@@ -555,7 +557,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 	if req.Stateful {
 		sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(source.Name())
 		if sourceName != req.Name {
-			return BadRequest(fmt.Errorf(`Copying stateful `+
+			return response.BadRequest(fmt.Errorf(`Copying stateful `+
 				`containers requires that source "%s" and `+
 				`target "%s" name be identical`, sourceName,
 				req.Name))
@@ -569,17 +571,17 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 		if err != nil {
 			req.Source.Refresh = false
 		} else if c.IsRunning() {
-			return BadRequest(fmt.Errorf("Cannot refresh a running container"))
+			return response.BadRequest(fmt.Errorf("Cannot refresh a running container"))
 		}
 	}
 
 	dbType, err := instance.New(string(req.Type))
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if dbType != instance.TypeAny && dbType != source.Type() {
-		return BadRequest(fmt.Errorf("Instance type should not be specified or should match source type"))
+		return response.BadRequest(fmt.Errorf("Instance type should not be specified or should match source type"))
 	}
 
 	args := db.ContainerArgs{
@@ -596,7 +598,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 		Stateful:     req.Stateful,
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		instanceOnly := req.Source.InstanceOnly || req.Source.ContainerOnly
 		_, err := containerCreateAsCopy(d.State(), args, source, instanceOnly, req.Source.Refresh)
 		if err != nil {
@@ -608,26 +610,26 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name, req.Source.Source}
 
-	op, err := operationCreate(d.cluster, targetProject, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, targetProject, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Response {
+func createFromBackup(d *Daemon, project string, data io.Reader, pool string) response.Response {
 	// Write the data to a temp file
 	f, err := ioutil.TempFile("", "lxd_backup_")
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 	defer os.Remove(f.Name())
 
 	_, err = io.Copy(f, data)
 	if err != nil {
 		f.Close()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Parse the backup information
@@ -635,7 +637,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
 	bInfo, err := backupGetInfo(f)
 	if err != nil {
 		f.Close()
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	bInfo.Project = project
 
@@ -644,7 +646,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
 		bInfo.Pool = pool
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		defer f.Close()
 
 		// Dump tarball to storage
@@ -692,16 +694,16 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
 	resources := map[string][]string{}
 	resources["containers"] = []string{bInfo.Name}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationBackupRestore,
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationBackupRestore,
 		resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func containersPost(d *Daemon, r *http.Request) Response {
+func containersPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	logger.Debugf("Responding to container create")
 
@@ -713,7 +715,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	// Parse the request
 	req := api.InstancesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	targetNode := queryParam(r, "target")
@@ -729,20 +731,20 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	if targetNode != "" {
 		address, err := cluster.ResolveTarget(d.cluster, targetNode)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		if address != "" {
 			cert := d.endpoints.NetworkCert()
 			client, err := cluster.Connect(address, cert, false)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
 			client = client.UseProject(project)
@@ -751,18 +753,18 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			logger.Debugf("Forward instance post request to %s", address)
 			op, err := client.CreateInstance(req)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
 			opAPI := op.Get()
-			return ForwardedOperationResponse(project, &opAPI)
+			return operations.ForwardedOperationResponse(project, &opAPI)
 		}
 	}
 
 	// If no storage pool is found, error out.
 	pools, err := d.cluster.StoragePools()
 	if err != nil || len(pools) == 0 {
-		return BadRequest(fmt.Errorf("No storage pool found. Please create a new storage pool"))
+		return response.BadRequest(fmt.Errorf("No storage pool found. Please create a new storage pool"))
 	}
 
 	if req.Name == "" {
@@ -773,7 +775,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			return err
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		i := 0
@@ -785,7 +787,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 			}
 
 			if i > 100 {
-				return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
+				return response.InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
 			}
 		}
 		logger.Debugf("No name provided, creating %s", req.Name)
@@ -802,7 +804,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	if req.InstanceType != "" {
 		conf, err := instanceParseType(req.InstanceType)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		for k, v := range conf {
@@ -813,7 +815,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	}
 
 	if strings.Contains(req.Name, shared.SnapshotDelimiter) {
-		return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter))
+		return response.BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter))
 	}
 
 	switch req.Source.Type {
@@ -826,11 +828,11 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	case "copy":
 		return createFromCopy(d, project, &req)
 	default:
-		return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+		return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
 	}
 }
 
-func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, Response) {
+func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, response.Response) {
 	// Grab the container's root device if one is specified
 	storagePool := ""
 	storagePoolProfile := ""
@@ -856,7 +858,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
 		for _, pName := range req.Profiles {
 			_, p, err := d.cluster.ProfileGet(project, pName)
 			if err != nil {
-				return "", "", "", nil, SmartError(err)
+				return "", "", "", nil, response.SmartError(err)
 			}
 
 			k, v, _ := shared.GetRootDiskDevice(p.Devices)
@@ -874,9 +876,9 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
 		pools, err := d.cluster.StoragePools()
 		if err != nil {
 			if err == db.ErrNoSuchObject {
-				return "", "", "", nil, BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
+				return "", "", "", nil, response.BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
 			}
-			return "", "", "", nil, SmartError(err)
+			return "", "", "", nil, response.SmartError(err)
 		}
 
 		if len(pools) == 1 {
@@ -887,7 +889,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
 	return storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, nil
 }
 
-func clusterCopyContainerInternal(d *Daemon, source Instance, project string, req *api.InstancesPost) Response {
+func clusterCopyContainerInternal(d *Daemon, source Instance, project string, req *api.InstancesPost) response.Response {
 	name := req.Source.Source
 
 	// Locate the source of the container
@@ -904,17 +906,17 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if nodeAddress == "" {
-		return BadRequest(fmt.Errorf("The container source is currently offline"))
+		return response.BadRequest(fmt.Errorf("The container source is currently offline"))
 	}
 
 	// Connect to the container source
 	client, err := cluster.Connect(nodeAddress, d.endpoints.NetworkCert(), false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	client = client.UseProject(source.Project())
@@ -932,7 +934,7 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
 
 		op, err := client.MigrateInstanceSnapshot(cName, sName, pullReq)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		opAPI = op.Get()
@@ -948,7 +950,7 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
 
 		op, err := client.MigrateInstance(req.Source.Source, pullReq)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		opAPI = op.Get()
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 2d2d433ee5..b483125ae8 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -36,6 +36,7 @@ import (
 	"github.com/lxc/lxd/lxd/maas"
 	"github.com/lxc/lxd/lxd/node"
 	"github.com/lxc/lxd/lxd/rbac"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/task"
@@ -174,22 +175,22 @@ type APIEndpointAlias struct {
 
 // APIEndpointAction represents an action on an API endpoint.
 type APIEndpointAction struct {
-	Handler        func(d *Daemon, r *http.Request) Response
-	AccessHandler  func(d *Daemon, r *http.Request) Response
+	Handler        func(d *Daemon, r *http.Request) response.Response
+	AccessHandler  func(d *Daemon, r *http.Request) response.Response
 	AllowUntrusted bool
 }
 
 // AllowAuthenticated is a AccessHandler which allows all requests
-func AllowAuthenticated(d *Daemon, r *http.Request) Response {
-	return EmptySyncResponse
+func AllowAuthenticated(d *Daemon, r *http.Request) response.Response {
+	return response.EmptySyncResponse
 }
 
 // AllowProjectPermission is a wrapper to check access against the project, its features and RBAC permission
-func AllowProjectPermission(feature string, permission string) func(d *Daemon, r *http.Request) Response {
-	return func(d *Daemon, r *http.Request) Response {
+func AllowProjectPermission(feature string, permission string) func(d *Daemon, r *http.Request) response.Response {
+	return func(d *Daemon, r *http.Request) response.Response {
 		// Shortcut for speed
 		if d.userIsAdmin(r) {
-			return EmptySyncResponse
+			return response.EmptySyncResponse
 		}
 
 		// Get the project
@@ -197,10 +198,10 @@ func AllowProjectPermission(feature string, permission string) func(d *Daemon, r
 
 		// Validate whether the user has the needed permission
 		if !d.userHasPermission(r, project, permission) {
-			return Forbidden(nil)
+			return response.Forbidden(nil)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 }
 
@@ -302,7 +303,7 @@ func writeMacaroonsRequiredResponse(b *identchecker.Bakery, r *http.Request, w h
 	m, err := b.Oven.NewMacaroon(
 		ctx, httpbakery.RequestVersion(r), caveats, derr.Ops...)
 	if err != nil {
-		resp := errorResponse{http.StatusInternalServerError, err.Error()}
+		resp := response.ErrorResponse(http.StatusInternalServerError, err.Error())
 		resp.Render(w)
 		return
 	}
@@ -361,7 +362,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 		select {
 		case <-d.setupChan:
 		default:
-			response := Unavailable(fmt.Errorf("LXD daemon setup in progress"))
+			response := response.Unavailable(fmt.Errorf("LXD daemon setup in progress"))
 			response.Render(w)
 			return
 		}
@@ -372,7 +373,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 			// If not a macaroon discharge request, return the error
 			_, ok := err.(*bakery.DischargeRequiredError)
 			if !ok {
-				InternalError(err).Render(w)
+				response.InternalError(err).Render(w)
 				return
 			}
 		}
@@ -382,7 +383,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 			// Except for the initial cluster accept request (done over trusted TLS)
 			if !trusted || c.Path != "cluster/accept" || protocol != "tls" {
 				logger.Warn("Rejecting remote internal API request", log.Ctx{"ip": r.RemoteAddr})
-				Forbidden(nil).Render(w)
+				response.Forbidden(nil).Render(w)
 				return
 			}
 		}
@@ -398,7 +399,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 			return
 		} else {
 			logger.Warn("Rejecting request from untrusted client", log.Ctx{"ip": r.RemoteAddr})
-			Forbidden(nil).Render(w)
+			response.Forbidden(nil).Render(w)
 			return
 		}
 
@@ -408,7 +409,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 			captured := &bytes.Buffer{}
 			multiW := io.MultiWriter(newBody, captured)
 			if _, err := io.Copy(multiW, r.Body); err != nil {
-				InternalError(err).Render(w)
+				response.InternalError(err).Render(w)
 				return
 			}
 
@@ -417,24 +418,24 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 		}
 
 		// Actually process the request
-		var resp Response
-		resp = NotImplemented(nil)
+		var resp response.Response
+		resp = response.NotImplemented(nil)
 
-		handleRequest := func(action APIEndpointAction) Response {
+		handleRequest := func(action APIEndpointAction) response.Response {
 			if action.Handler == nil {
-				return NotImplemented(nil)
+				return response.NotImplemented(nil)
 			}
 
 			if action.AccessHandler != nil {
 				// Defer access control to custom handler
 				resp := action.AccessHandler(d, r)
-				if resp != EmptySyncResponse {
+				if resp != response.EmptySyncResponse {
 					return resp
 				}
 			} else if !action.AllowUntrusted {
 				// Require admin privileges
 				if !d.userIsAdmin(r) {
-					return Forbidden(nil)
+					return response.Forbidden(nil)
 				}
 			}
 
@@ -453,12 +454,12 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
 		case "PATCH":
 			resp = handleRequest(c.Patch)
 		default:
-			resp = NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+			resp = response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
 		}
 
 		// Handle errors
 		if err := resp.Render(w); err != nil {
-			err := InternalError(err).Render(w)
+			err := response.InternalError(err).Render(w)
 			if err != nil {
 				logger.Errorf("Failed writing error for error, giving up")
 			}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index d5a0f1e9a0..51e126d4d1 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -17,6 +17,7 @@ import (
 	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/sys"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -79,7 +80,7 @@ func imageGetStreamCache(d *Daemon) (map[string]*imageStreamCacheEntry, error) {
 }
 
 // ImageDownload resolves the image fingerprint and if not in the database, downloads it
-func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, imageType string, forContainer bool, autoUpdate bool, storagePool string, preferCached bool, project string) (*api.Image, error) {
+func (d *Daemon) ImageDownload(op *operations.Operation, server string, protocol string, certificate string, secret string, alias string, imageType string, forContainer bool, autoUpdate bool, storagePool string, preferCached bool, project string) (*api.Image, error) {
 	var err error
 	var ctxMap log.Ctx
 
@@ -356,7 +357,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	if op == nil {
 		ctxMap = log.Ctx{"alias": alias, "server": server}
 	} else {
-		ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
+		ctxMap = log.Ctx{"trigger": op.URL(), "image": fp, "operation": op.ID(), "alias": alias, "server": server}
 	}
 	logger.Info("Downloading image", ctxMap)
 
@@ -379,7 +380,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			return
 		}
 
-		meta := op.metadata
+		meta := op.Metadata()
 		if meta == nil {
 			meta = make(map[string]interface{})
 		}
@@ -393,7 +394,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 	var canceler *cancel.Canceler
 	if op != nil {
 		canceler = cancel.NewCanceler()
-		op.canceler = canceler
+		op.SetCanceler(canceler)
 	}
 
 	if protocol == "lxd" || protocol == "simplestreams" {
diff --git a/lxd/events.go b/lxd/events.go
index 471e56c1c4..215a6646f7 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -6,6 +6,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
 )
@@ -68,6 +69,6 @@ func eventsSocket(d *Daemon, r *http.Request, w http.ResponseWriter) error {
 	return nil
 }
 
-func eventsGet(d *Daemon, r *http.Request) Response {
+func eventsGet(d *Daemon, r *http.Request) response.Response {
 	return &eventsServe{req: r, d: d}
 }
diff --git a/lxd/images.go b/lxd/images.go
index 330c6afbd3..c922995d88 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -30,6 +30,8 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/task"
 	"github.com/lxc/lxd/lxd/util"
@@ -154,7 +156,7 @@ func compressFile(compress string, infile io.Reader, outfile io.Writer) error {
  * This function takes a container or snapshot from the local image server and
  * exports it as an image.
  */
-func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operation, builddir string) (*api.Image, error) {
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operations.Operation, builddir string) (*api.Image, error) {
 	info := api.Image{}
 	info.Type = "container"
 	info.Properties = map[string]string{}
@@ -315,7 +317,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operati
 	return &info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation, project string) (*api.Image, error) {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, project string) (*api.Image, error) {
 	var err error
 	var hash string
 
@@ -353,7 +355,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation, project str
 	return info, nil
 }
 
-func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation, project string) (*api.Image, error) {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, project string) (*api.Image, error) {
 	var err error
 
 	if req.Source.URL == "" {
@@ -640,10 +642,10 @@ func imageCreateInPool(d *Daemon, info *api.Image, storagePool string) error {
 	return nil
 }
 
-func imagesPost(d *Daemon, r *http.Request) Response {
+func imagesPost(d *Daemon, r *http.Request) response.Response {
 	instanceType, err := urlInstanceTypeDetect(r)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	project := projectParam(r)
@@ -651,7 +653,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	// create a directory under which we keep everything while building
 	builddir, err := ioutil.TempDir(shared.VarPath("images"), "lxd_build_")
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	cleanup := func(path string, fd *os.File) {
@@ -669,13 +671,13 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	post, err := ioutil.TempFile(builddir, "lxd_post_")
 	if err != nil {
 		cleanup(builddir, nil)
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	_, err = io.Copy(post, r.Body)
 	if err != nil {
 		cleanup(builddir, post)
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Is this a container request?
@@ -688,7 +690,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	if err != nil {
 		if r.Header.Get("Content-Type") == "application/json" {
 			cleanup(builddir, post)
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		imageUpload = true
@@ -696,7 +698,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 
 	if !imageUpload && !shared.StringInSlice(req.Source.Type, []string{"container", "snapshot", "image", "url"}) {
 		cleanup(builddir, post)
-		return InternalError(fmt.Errorf("Invalid images JSON"))
+		return response.InternalError(fmt.Errorf("Invalid images JSON"))
 	}
 
 	/* Forward requests for containers on other nodes */
@@ -705,21 +707,21 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		if name != "" {
 			post.Seek(0, 0)
 			r.Body = post
-			response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+			resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
 			if err != nil {
 				cleanup(builddir, post)
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 
-			if response != nil {
+			if resp != nil {
 				cleanup(builddir, nil)
-				return response
+				return resp
 			}
 		}
 	}
 
 	// Begin background operation
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		var err error
 		var info *api.Image
 
@@ -785,13 +787,13 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		return nil
 	}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageDownload, nil, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageDownload, nil, nil, run, nil, nil)
 	if err != nil {
 		cleanup(builddir, post)
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 func getImageMetadata(fname string) (*api.ImageMetadata, string, error) {
@@ -926,24 +928,24 @@ func doImagesGet(d *Daemon, recursion bool, project string, public bool) (interf
 	return resultMap, nil
 }
 
-func imagesGet(d *Daemon, r *http.Request) Response {
+func imagesGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
-	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 
 	result, err := doImagesGet(d, util.IsRecursionRequest(r), project, public)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
 func autoUpdateImagesTask(d *Daemon) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return autoUpdateImages(ctx, d)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesUpdate, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesUpdate, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start image update operation", log.Ctx{"err": err})
 			return
@@ -1040,7 +1042,7 @@ func autoUpdateImagesInProject(ctx context.Context, d *Daemon, project string) e
 
 // Update a single image.  The operation can be nil, if no progress tracking is needed.
 // Returns whether the image has been updated.
-func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image, project string) error {
+func autoUpdateImage(d *Daemon, op *operations.Operation, id int, info *api.Image, project string) error {
 	fingerprint := info.Fingerprint
 	_, source, err := d.cluster.ImageSourceGet(id)
 	if err != nil {
@@ -1167,11 +1169,11 @@ func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image, project
 
 func pruneExpiredImagesTask(d *Daemon) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return pruneExpiredImages(ctx, d)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesExpire, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesExpire, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start expired image operation", log.Ctx{"err": err})
 			return
@@ -1220,7 +1222,7 @@ func pruneExpiredImagesTask(d *Daemon) (task.Func, task.Schedule) {
 }
 
 func pruneLeftoverImages(d *Daemon) {
-	opRun := func(op *operation) error {
+	opRun := func(op *operations.Operation) error {
 		// Get all images
 		images, err := d.cluster.ImagesGet("default", false)
 		if err != nil {
@@ -1249,7 +1251,7 @@ func pruneLeftoverImages(d *Daemon) {
 		return nil
 	}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesPruneLeftover, nil, nil, opRun, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesPruneLeftover, nil, nil, opRun, nil, nil)
 	if err != nil {
 		logger.Error("Failed to start image leftover cleanup operation", log.Ctx{"err": err})
 		return
@@ -1355,7 +1357,7 @@ func doDeleteImageFromPool(state *state.State, fingerprint string, storagePool s
 	return nil
 }
 
-func imageDelete(d *Daemon, r *http.Request) Response {
+func imageDelete(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 
@@ -1451,7 +1453,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 		return nil
 	}
 
-	rmimg := func(op *operation) error {
+	rmimg := func(op *operations.Operation) error {
 		if isClusterNotification(r) {
 			return deleteFromDisk()
 		}
@@ -1462,12 +1464,12 @@ func imageDelete(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["images"] = []string{fingerprint}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageDelete, resources, nil, rmimg, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageDelete, resources, nil, rmimg, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // Helper to delete an image file from the local images directory.
@@ -1491,22 +1493,24 @@ func imageDeleteFromDisk(fingerprint string) {
 	}
 }
 
-func doImageGet(db *db.Cluster, project, fingerprint string, public bool) (*api.Image, Response) {
+func doImageGet(db *db.Cluster, project, fingerprint string, public bool) (*api.Image, response.Response) {
 	_, imgInfo, err := db.ImageGet(project, fingerprint, public, false)
 	if err != nil {
-		return nil, SmartError(err)
+		return nil, response.SmartError(err)
 	}
 
 	return imgInfo, nil
 }
 
 func imageValidSecret(fingerprint string, secret string) bool {
-	for _, op := range operations {
-		if op.resources == nil {
+	for _, op := range operations.Operations() {
+		resources := op.Resources()
+
+		if resources == nil {
 			continue
 		}
 
-		opImages, ok := op.resources["images"]
+		opImages, ok := resources["images"]
 		if !ok {
 			continue
 		}
@@ -1515,7 +1519,7 @@ func imageValidSecret(fingerprint string, secret string) bool {
 			continue
 		}
 
-		opSecret, ok := op.metadata["secret"]
+		opSecret, ok := op.Metadata()["secret"]
 		if !ok {
 			continue
 		}
@@ -1530,73 +1534,73 @@ func imageValidSecret(fingerprint string, secret string) bool {
 	return false
 }
 
-func imageGet(d *Daemon, r *http.Request) Response {
+func imageGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
-	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 	secret := r.FormValue("secret")
 
-	info, response := doImageGet(d.cluster, project, fingerprint, false)
-	if response != nil {
-		return response
+	info, resp := doImageGet(d.cluster, project, fingerprint, false)
+	if resp != nil {
+		return resp
 	}
 
 	if !info.Public && public && !imageValidSecret(info.Fingerprint, secret) {
-		return NotFound(fmt.Errorf("Image '%s' not found", info.Fingerprint))
+		return response.NotFound(fmt.Errorf("Image '%s' not found", info.Fingerprint))
 	}
 
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
-	return SyncResponseETag(true, info, etag)
+	return response.SyncResponseETag(true, info, etag)
 }
 
-func imagePut(d *Daemon, r *http.Request) Response {
+func imagePut(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 	id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.ImagePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func imagePatch(d *Daemon, r *http.Request) Response {
+func imagePatch(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 	id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -1604,12 +1608,12 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 
 	reqRaw := shared.Jmap{}
 	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	req := api.ImagePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get AutoUpdate
@@ -1639,53 +1643,53 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 
 	err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func imageAliasesPost(d *Daemon, r *http.Request) Response {
+func imageAliasesPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	req := api.ImageAliasesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" || req.Target == "" {
-		return BadRequest(fmt.Errorf("name and target are required"))
+		return response.BadRequest(fmt.Errorf("name and target are required"))
 	}
 
 	// This is just to see if the alias name already exists.
 	_, _, err := d.cluster.ImageAliasGet(project, req.Name, true)
 	if err != db.ErrNoSuchObject {
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return Conflict(fmt.Errorf("Alias '%s' already exists", req.Name))
+		return response.Conflict(fmt.Errorf("Alias '%s' already exists", req.Name))
 	}
 
 	id, _, err := d.cluster.ImageGet(project, req.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = d.cluster.ImageAliasAdd(project, req.Name, id, req.Description)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
 }
 
-func imageAliasesGet(d *Daemon, r *http.Request) Response {
+func imageAliasesGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	recursion := util.IsRecursionRequest(r)
 
 	names, err := d.cluster.ImageAliasesGet(project)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	responseStr := []string{}
 	responseMap := []api.ImageAliasesEntry{}
@@ -1704,103 +1708,103 @@ func imageAliasesGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, responseStr)
+		return response.SyncResponse(true, responseStr)
 	}
 
-	return SyncResponse(true, responseMap)
+	return response.SyncResponse(true, responseMap)
 }
 
-func imageAliasGet(d *Daemon, r *http.Request) Response {
+func imageAliasGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
-	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 
 	_, alias, err := d.cluster.ImageAliasGet(project, name, !public)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, alias, alias)
+	return response.SyncResponseETag(true, alias, alias)
 }
 
-func imageAliasDelete(d *Daemon, r *http.Request) Response {
+func imageAliasDelete(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 	_, _, err := d.cluster.ImageAliasGet(project, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = d.cluster.ImageAliasDelete(project, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func imageAliasPut(d *Daemon, r *http.Request) Response {
+func imageAliasPut(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 	id, alias, err := d.cluster.ImageAliasGet(project, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	err = util.EtagCheck(r, alias)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.ImageAliasesEntryPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Target == "" {
-		return BadRequest(fmt.Errorf("The target field is required"))
+		return response.BadRequest(fmt.Errorf("The target field is required"))
 	}
 
 	imageId, _, err := d.cluster.ImageGet(project, req.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = d.cluster.ImageAliasUpdate(id, imageId, req.Description)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func imageAliasPatch(d *Daemon, r *http.Request) Response {
+func imageAliasPatch(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 	id, alias, err := d.cluster.ImageAliasGet(project, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	err = util.EtagCheck(r, alias)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := shared.Jmap{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	_, ok := req["target"]
 	if ok {
 		target, err := req.GetString("target")
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		alias.Target = target
@@ -1810,7 +1814,7 @@ func imageAliasPatch(d *Daemon, r *http.Request) Response {
 	if ok {
 		description, err := req.GetString("description")
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		alias.Description = description
@@ -1818,50 +1822,50 @@ func imageAliasPatch(d *Daemon, r *http.Request) Response {
 
 	imageId, _, err := d.cluster.ImageGet(project, alias.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = d.cluster.ImageAliasUpdate(id, imageId, alias.Description)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func imageAliasPost(d *Daemon, r *http.Request) Response {
+func imageAliasPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	req := api.ImageAliasesEntryPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the name isn't already in use
 	id, _, _ := d.cluster.ImageAliasGet(project, req.Name, true)
 	if id > 0 {
-		return Conflict(fmt.Errorf("Alias '%s' already in use", req.Name))
+		return response.Conflict(fmt.Errorf("Alias '%s' already in use", req.Name))
 	}
 
 	id, _, err := d.cluster.ImageAliasGet(project, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = d.cluster.ImageAliasRename(id, req.Name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
 }
 
-func imageExport(d *Daemon, r *http.Request) Response {
+func imageExport(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+	public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
 	secret := r.FormValue("secret")
 
 	var imgInfo *api.Image
@@ -1870,36 +1874,36 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		// /dev/lxd API requires exact match
 		_, imgInfo, err = d.cluster.ImageGet(project, fingerprint, false, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if !imgInfo.Public && !imgInfo.Cached {
-			return NotFound(fmt.Errorf("Image '%s' not found", fingerprint))
+			return response.NotFound(fmt.Errorf("Image '%s' not found", fingerprint))
 		}
 	} else {
 		_, imgInfo, err = d.cluster.ImageGet(project, fingerprint, false, false)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if !imgInfo.Public && public && !imageValidSecret(imgInfo.Fingerprint, secret) {
-			return NotFound(fmt.Errorf("Image '%s' not found", imgInfo.Fingerprint))
+			return response.NotFound(fmt.Errorf("Image '%s' not found", imgInfo.Fingerprint))
 		}
 	}
 
 	// Check if the image is only available on another node.
 	address, err := d.cluster.ImageLocate(imgInfo.Fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if address != "" {
 		// Forward the request to the other node
 		cert := d.endpoints.NetworkCert()
 		client, err := cluster.Connect(address, cert, false)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return ForwardedResponse(client, r)
+		return response.ForwardedResponse(client, r)
 	}
 
 	imagePath := shared.VarPath("images", imgInfo.Fingerprint)
@@ -1912,11 +1916,11 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	filename := fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
 
 	if shared.PathExists(rootfsPath) {
-		files := make([]fileResponseEntry, 2)
+		files := make([]response.FileResponseEntry, 2)
 
-		files[0].identifier = "metadata"
-		files[0].path = imagePath
-		files[0].filename = "meta-" + filename
+		files[0].Identifier = "metadata"
+		files[0].Path = imagePath
+		files[0].Filename = "meta-" + filename
 
 		// Recompute the extension for the root filesystem, it may use a different
 		// compression algorithm than the metadata.
@@ -1926,33 +1930,33 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		}
 		filename = fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
 
-		files[1].identifier = "rootfs"
-		files[1].path = rootfsPath
-		files[1].filename = filename
+		files[1].Identifier = "rootfs"
+		files[1].Path = rootfsPath
+		files[1].Filename = filename
 
-		return FileResponse(r, files, nil, false)
+		return response.FileResponse(r, files, nil, false)
 	}
 
-	files := make([]fileResponseEntry, 1)
-	files[0].identifier = filename
-	files[0].path = imagePath
-	files[0].filename = filename
+	files := make([]response.FileResponseEntry, 1)
+	files[0].Identifier = filename
+	files[0].Path = imagePath
+	files[0].Filename = filename
 
-	return FileResponse(r, files, nil, false)
+	return response.FileResponse(r, files, nil, false)
 }
 
-func imageSecret(d *Daemon, r *http.Request) Response {
+func imageSecret(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 	_, imgInfo, err := d.cluster.ImageGet(project, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	secret, err := shared.RandomCryptoString()
 
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	meta := shared.Jmap{}
@@ -1961,12 +1965,12 @@ func imageSecret(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["images"] = []string{imgInfo.Fingerprint}
 
-	op, err := operationCreate(d.cluster, project, operationClassToken, db.OperationImageToken, resources, meta, nil, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassToken, db.OperationImageToken, resources, meta, nil, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprint string) error {
@@ -2025,25 +2029,25 @@ func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprin
 	return nil
 }
 
-func imageRefresh(d *Daemon, r *http.Request) Response {
+func imageRefresh(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	fingerprint := mux.Vars(r)["fingerprint"]
 	imageId, imageInfo, err := d.cluster.ImageGet(project, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Begin background operation
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		return autoUpdateImage(d, op, imageId, imageInfo, project)
 	}
 
-	op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageRefresh, nil, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageRefresh, nil, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 func autoSyncImagesTask(d *Daemon) (task.Func, task.Schedule) {
@@ -2067,11 +2071,11 @@ func autoSyncImagesTask(d *Daemon) (task.Func, task.Schedule) {
 			return
 		}
 
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return autoSyncImages(ctx, d)
 		}
 
-		op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesSynchronize, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesSynchronize, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start image synchronization operation", log.Ctx{"err": err})
 			return
diff --git a/lxd/instance_interface.go b/lxd/instance_interface.go
index dcdc4c2198..c2dec6b900 100644
--- a/lxd/instance_interface.go
+++ b/lxd/instance_interface.go
@@ -10,6 +10,7 @@ import (
 	"github.com/lxc/lxd/lxd/device"
 	"github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared/api"
 )
@@ -106,7 +107,7 @@ type Instance interface {
 
 	// Progress reporting
 
-	SetOperation(op *operation)
+	SetOperation(op *operations.Operation)
 
 	// FIXME: Those should be internal functions
 	// Needed for migration for now.
diff --git a/lxd/logging.go b/lxd/logging.go
index 162ad51a3d..35b5855ca4 100644
--- a/lxd/logging.go
+++ b/lxd/logging.go
@@ -8,6 +8,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/task"
 	"github.com/lxc/lxd/shared"
@@ -20,11 +21,11 @@ import (
 // and will run once every 24h.
 func expireLogsTask(state *state.State) (task.Func, task.Schedule) {
 	f := func(ctx context.Context) {
-		opRun := func(op *operation) error {
+		opRun := func(op *operations.Operation) error {
 			return expireLogs(ctx, state)
 		}
 
-		op, err := operationCreate(state.Cluster, "", operationClassTask, db.OperationLogsExpire, nil, nil, opRun, nil, nil)
+		op, err := operations.OperationCreate(state.Cluster, "", operations.OperationClassTask, db.OperationLogsExpire, nil, nil, opRun, nil, nil)
 		if err != nil {
 			logger.Error("Failed to start log expiry operation", log.Ctx{"err": err})
 			return
diff --git a/lxd/main.go b/lxd/main.go
index 476ad9ecb4..ce49cd5dc6 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -8,6 +8,8 @@ import (
 	"github.com/spf13/cobra"
 
 	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/version"
@@ -43,6 +45,12 @@ func (c *cmdGlobal) Run(cmd *cobra.Command, args []string) error {
 	// Set debug and verbose for the events package
 	events.Init(debug, verbose)
 
+	// Set debug for the operations package
+	operations.Init(debug)
+
+	// Set debug for the response package
+	response.Init(debug)
+
 	// Setup logger
 	syslog := ""
 	if c.flagLogSyslog {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6f5ead8459..92963b05f5 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -19,6 +19,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/idmap"
 	"github.com/lxc/lxd/shared/logger"
@@ -145,7 +146,7 @@ func (s *migrationSourceWs) Metadata() interface{} {
 	return secrets
 }
 
-func (s *migrationSourceWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *migrationSourceWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -317,7 +318,7 @@ func (s *migrationSink) Metadata() interface{} {
 	return secrets
 }
 
-func (s *migrationSink) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *migrationSink) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 4d893b87c7..ee31a6fe93 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -18,6 +18,7 @@ import (
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -328,7 +329,7 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) {
 	return final, nil
 }
 
-func (s *migrationSourceWs) Do(migrateOp *operation) error {
+func (s *migrationSourceWs) Do(migrateOp *operations.Operation) error {
 	<-s.allConnected
 
 	criuType := migration.CRIUType_CRIU_RSYNC.Enum()
@@ -543,14 +544,14 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			}
 
 			state := s.instance.DaemonState()
-			actionScriptOp, err := operationCreate(
+			actionScriptOp, err := operations.OperationCreate(
 				state.Cluster,
 				s.instance.Project(),
-				operationClassWebsocket,
+				operations.OperationClassWebsocket,
 				db.OperationContainerLiveMigrate,
 				nil,
 				nil,
-				func(op *operation) error {
+				func(op *operations.Operation) error {
 					result := <-restoreSuccess
 					if !result {
 						return fmt.Errorf("restore failed, failing CRIU")
@@ -558,7 +559,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 					return nil
 				},
 				nil,
-				func(op *operation, r *http.Request, w http.ResponseWriter) error {
+				func(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
 					secret := r.FormValue("secret")
 					if secret == "" {
 						return fmt.Errorf("missing secret")
@@ -584,7 +585,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			}
 
-			err = writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret, state.OS.ExecPath)
+			err = writeActionScript(checkpointDir, actionScriptOp.URL(), actionScriptOpSecret, state.OS.ExecPath)
 			if err != nil {
 				os.RemoveAll(checkpointDir)
 				return abort(err)
@@ -775,7 +776,7 @@ func NewMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
 	return &sink, nil
 }
 
-func (c *migrationSink) Do(migrateOp *operation) error {
+func (c *migrationSink) Do(migrateOp *operations.Operation) error {
 	if c.src.instance.Type() != instance.TypeContainer {
 		return fmt.Errorf("Instance not container type")
 	}
diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go
index 027fe08367..05ba8e7186 100644
--- a/lxd/migrate_storage_volumes.go
+++ b/lxd/migrate_storage_volumes.go
@@ -7,6 +7,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/logger"
@@ -32,7 +33,7 @@ func NewStorageMigrationSource(storage storage, volumeOnly bool) (*migrationSour
 	return &ret, nil
 }
 
-func (s *migrationSourceWs) DoStorage(migrateOp *operation) error {
+func (s *migrationSourceWs) DoStorage(migrateOp *operations.Operation) error {
 	<-s.allConnected
 
 	// Storage needs to start unconditionally now, since we need to
@@ -223,7 +224,7 @@ func NewStorageMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
 	return &sink, nil
 }
 
-func (c *migrationSink) DoStorage(migrateOp *operation) error {
+func (c *migrationSink) DoStorage(migrateOp *operations.Operation) error {
 	var err error
 
 	if c.push {
diff --git a/lxd/networks.go b/lxd/networks.go
index 4058127e7a..f2c8fab7e6 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -25,6 +25,7 @@ import (
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/iptables"
 	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -66,12 +67,12 @@ var networkStateCmd = APIEndpoint{
 }
 
 // API endpoints
-func networksGet(d *Daemon, r *http.Request) Response {
+func networksGet(d *Daemon, r *http.Request) response.Response {
 	recursion := util.IsRecursionRequest(r)
 
 	ifs, err := networkGetInterfaces(d.cluster)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	resultString := []string{}
@@ -89,13 +90,13 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
-func networksPost(d *Daemon, r *http.Request) Response {
+func networksPost(d *Daemon, r *http.Request) response.Response {
 	networkCreateLock.Lock()
 	defer networkCreateLock.Unlock()
 
@@ -104,21 +105,21 @@ func networksPost(d *Daemon, r *http.Request) Response {
 	// Parse the request
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	err = networkValidName(req.Name)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Type != "" && req.Type != "bridge" {
-		return BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
+		return response.BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
 	}
 
 	if req.Config == nil {
@@ -127,11 +128,11 @@ func networksPost(d *Daemon, r *http.Request) Response {
 
 	err = networkValidateConfig(req.Name, req.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	url := fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name)
-	response := SyncResponseLocation(true, nil, url)
+	resp := response.SyncResponseLocation(true, nil, url)
 
 	if isClusterNotification(r) {
 		// This is an internal request which triggers the actual
@@ -139,9 +140,9 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		// been previously defined.
 		err = doNetworksCreate(d, req, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return response
+		return resp
 	}
 
 	targetNode := queryParam(r, "target")
@@ -151,7 +152,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		// value for the storage config is 'bridge.external_interfaces'.
 		for key := range req.Config {
 			if !shared.StringInSlice(key, db.NetworkNodeConfigKeys) {
-				return SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
+				return response.SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
 			}
 		}
 		err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -159,30 +160,30 @@ func networksPost(d *Daemon, r *http.Request) Response {
 		})
 		if err != nil {
 			if err == db.ErrAlreadyDefined {
-				return BadRequest(fmt.Errorf("The network already defined on node %s", targetNode))
+				return response.BadRequest(fmt.Errorf("The network already defined on node %s", targetNode))
 			}
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return response
+		return resp
 	}
 
 	err = networkFillConfig(&req)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Check if we're clustered
 	count, err := cluster.Count(d.State())
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if count > 1 {
 		err = networksPostCluster(d, req)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return response
+		return resp
 	}
 
 	// No targetNode was specified and we're either a single-node
@@ -190,25 +191,25 @@ func networksPost(d *Daemon, r *http.Request) Response {
 	// pool immediately.
 	networks, err := networkGetInterfaces(d.cluster)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if shared.StringInSlice(req.Name, networks) {
-		return BadRequest(fmt.Errorf("The network already exists"))
+		return response.BadRequest(fmt.Errorf("The network already exists"))
 	}
 
 	// Create the database entry
 	_, err = d.cluster.NetworkCreate(req.Name, req.Description, req.Config)
 	if err != nil {
-		return SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
+		return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
 	err = doNetworksCreate(d, req, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return response
+	return resp
 }
 
 func networksPostCluster(d *Daemon, req api.NetworksPost) error {
@@ -348,24 +349,24 @@ func doNetworksCreate(d *Daemon, req api.NetworksPost, withDatabase bool) error
 	return nil
 }
 
-func networkGet(d *Daemon, r *http.Request) Response {
+func networkGet(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	name := mux.Vars(r)["name"]
 
 	n, err := doNetworkGet(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	targetNode := queryParam(r, "target")
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// If no target node is specified and the daemon is clustered, we omit
@@ -378,7 +379,7 @@ func networkGet(d *Daemon, r *http.Request) Response {
 
 	etag := []interface{}{n.Name, n.Managed, n.Type, n.Description, n.Config}
 
-	return SyncResponseETag(true, &n, etag)
+	return response.SyncResponseETag(true, &n, etag)
 }
 
 func doNetworkGet(d *Daemon, name string) (api.Network, error) {
@@ -454,7 +455,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
 	return n, nil
 }
 
-func networkDelete(d *Daemon, r *http.Request) Response {
+func networkDelete(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	state := d.State()
 
@@ -462,20 +463,20 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 	// the database.
 	_, network, err := d.cluster.NetworkGet(name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if network.Status == "Pending" {
 		err := d.cluster.NetworkDelete(name)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	// Get the existing network
 	n, err := networkLoadByName(state, name)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	withDatabase := true
@@ -484,26 +485,26 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 	} else {
 		// Sanity checks
 		if n.IsUsed() {
-			return BadRequest(fmt.Errorf("The network is currently in use"))
+			return response.BadRequest(fmt.Errorf("The network is currently in use"))
 		}
 
 		// Notify all other nodes. If any node is down, an error will be returned.
 		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAll)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		err = notifier(func(client lxd.InstanceServer) error {
 			return client.DeleteNetwork(name)
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	// Delete the network
 	err = n.Delete(withDatabase)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Cleanup storage
@@ -511,10 +512,10 @@ func networkDelete(d *Daemon, r *http.Request) Response {
 		os.RemoveAll(shared.VarPath("networks", n.name))
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func networkPost(d *Daemon, r *http.Request) Response {
+func networkPost(d *Daemon, r *http.Request) response.Response {
 	// FIXME: renaming a network is currently not supported in clustering
 	//        mode. The difficulty is that network.Start() depends on the
 	//        network having already been renamed in the database, which is
@@ -524,10 +525,10 @@ func networkPost(d *Daemon, r *http.Request) Response {
 	//        runs network.Start).
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if clustered {
-		return BadRequest(fmt.Errorf("Renaming a network not supported in LXD clusters"))
+		return response.BadRequest(fmt.Errorf("Renaming a network not supported in LXD clusters"))
 	}
 
 	name := mux.Vars(r)["name"]
@@ -537,57 +538,57 @@ func networkPost(d *Daemon, r *http.Request) Response {
 	// Parse the request
 	err = json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get the existing network
 	n, err := networkLoadByName(state, name)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	// Sanity checks
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	err = networkValidName(req.Name)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the name isn't already in use
 	networks, err := networkGetInterfaces(d.cluster)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if shared.StringInSlice(req.Name, networks) {
-		return Conflict(fmt.Errorf("Network '%s' already exists", req.Name))
+		return response.Conflict(fmt.Errorf("Network '%s' already exists", req.Name))
 	}
 
 	// Rename it
 	err = n.Rename(req.Name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name))
 }
 
-func networkPut(d *Daemon, r *http.Request) Response {
+func networkPut(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Get the existing network
 	_, dbInfo, err := d.cluster.NetworkGet(name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	targetNode := queryParam(r, "target")
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// If no target node is specified and the daemon is clustered, we omit
@@ -603,30 +604,30 @@ func networkPut(d *Daemon, r *http.Request) Response {
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.NetworkPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doNetworkUpdate(d, name, dbInfo.Config, req)
 }
 
-func networkPatch(d *Daemon, r *http.Request) Response {
+func networkPatch(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	// Get the existing network
 	_, dbInfo, err := d.cluster.NetworkGet(name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	targetNode := queryParam(r, "target")
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// If no target node is specified and the daemon is clustered, we omit
@@ -642,12 +643,12 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.NetworkPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Config stacking
@@ -665,11 +666,11 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 	return doNetworkUpdate(d, name, dbInfo.Config, req)
 }
 
-func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req api.NetworkPut) Response {
+func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req api.NetworkPut) response.Response {
 	// Validate the configuration
 	err := networkValidateConfig(name, req.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// When switching to a fan bridge, auto-detect the underlay
@@ -682,30 +683,30 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req ap
 	// Load the network
 	n, err := networkLoadByName(d.State(), name)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	err = n.Update(req)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func networkLeasesGet(d *Daemon, r *http.Request) Response {
+func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	project := projectParam(r)
 
 	// Try to get the network
 	n, err := doNetworkGet(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate that we do have leases for it
 	if !n.Managed || n.Type != "bridge" {
-		return NotFound(errors.New("Leases not found"))
+		return response.NotFound(errors.New("Leases not found"))
 	}
 
 	leases := []api.NetworkLease{}
@@ -716,7 +717,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 		// Get all the instances
 		instances, err := instanceLoadByProject(d.State(), project)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		for _, inst := range instances {
@@ -771,18 +772,18 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get dynamic leases
 	leaseFile := shared.VarPath("networks", name, "dnsmasq.leases")
 	if !shared.PathExists(leaseFile) {
-		return SyncResponse(true, leases)
+		return response.SyncResponse(true, leases)
 	}
 
 	content, err := ioutil.ReadFile(leaseFile)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	for _, lease := range strings.Split(string(content), "\n") {
@@ -824,7 +825,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 	if !isClusterNotification(r) {
 		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		err = notifier(func(client lxd.InstanceServer) error {
@@ -837,7 +838,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 			return nil
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Filter based on project
@@ -853,7 +854,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
 		leases = filteredLeases
 	}
 
-	return SyncResponse(true, leases)
+	return response.SyncResponse(true, leases)
 }
 
 // The network structs and functions
@@ -919,11 +920,11 @@ func networkShutdown(s *state.State) error {
 	return nil
 }
 
-func networkStateGet(d *Daemon, r *http.Request) Response {
+func networkStateGet(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	name := mux.Vars(r)["name"]
@@ -933,10 +934,10 @@ func networkStateGet(d *Daemon, r *http.Request) Response {
 
 	// Sanity check
 	if osInfo == nil {
-		return NotFound(fmt.Errorf("Interface '%s' not found", name))
+		return response.NotFound(fmt.Errorf("Interface '%s' not found", name))
 	}
 
-	return SyncResponse(true, networkGetState(*osInfo))
+	return response.SyncResponse(true, networkGetState(*osInfo))
 }
 
 type network struct {
diff --git a/lxd/operations.go b/lxd/operations.go
index c96409b58e..289814150e 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -4,24 +4,18 @@ import (
 	"fmt"
 	"net/http"
 	"strings"
-	"sync"
-	"time"
 
 	"github.com/gorilla/mux"
-	"github.com/gorilla/websocket"
-	"github.com/pborman/uuid"
-	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
-	"github.com/lxc/lxd/lxd/events"
 	"github.com/lxc/lxd/lxd/node"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
-	"github.com/lxc/lxd/shared/cancel"
 	"github.com/lxc/lxd/shared/logger"
-	"github.com/lxc/lxd/shared/version"
 )
 
 var operationCmd = APIEndpoint{
@@ -49,462 +43,21 @@ var operationWebsocket = APIEndpoint{
 	Get: APIEndpointAction{Handler: operationWebsocketGet, AllowUntrusted: true},
 }
 
-var operationsLock sync.Mutex
-var operations map[string]*operation = make(map[string]*operation)
-
-type operationClass int
-
-const (
-	operationClassTask      operationClass = 1
-	operationClassWebsocket operationClass = 2
-	operationClassToken     operationClass = 3
-)
-
-func (t operationClass) String() string {
-	return map[operationClass]string{
-		operationClassTask:      "task",
-		operationClassWebsocket: "websocket",
-		operationClassToken:     "token",
-	}[t]
-}
-
-type operation struct {
-	project     string
-	id          string
-	class       operationClass
-	createdAt   time.Time
-	updatedAt   time.Time
-	status      api.StatusCode
-	url         string
-	resources   map[string][]string
-	metadata    map[string]interface{}
-	err         string
-	readonly    bool
-	canceler    *cancel.Canceler
-	description string
-	permission  string
-
-	// Those functions are called at various points in the operation lifecycle
-	onRun     func(*operation) error
-	onCancel  func(*operation) error
-	onConnect func(*operation, *http.Request, http.ResponseWriter) error
-
-	// Channels used for error reporting and state tracking of background actions
-	chanDone chan error
-
-	// Locking for concurent access to the operation
-	lock sync.Mutex
-
-	cluster *db.Cluster
-}
-
-func (op *operation) done() {
-	if op.readonly {
-		return
-	}
-
-	op.lock.Lock()
-	op.readonly = true
-	op.onRun = nil
-	op.onCancel = nil
-	op.onConnect = nil
-	close(op.chanDone)
-	op.lock.Unlock()
-
-	time.AfterFunc(time.Second*5, func() {
-		operationsLock.Lock()
-		_, ok := operations[op.id]
-		if !ok {
-			operationsLock.Unlock()
-			return
-		}
-
-		delete(operations, op.id)
-		operationsLock.Unlock()
-
-		err := op.cluster.Transaction(func(tx *db.ClusterTx) error {
-			return tx.OperationRemove(op.id)
-		})
-		if err != nil {
-			logger.Warnf("Failed to delete operation %s: %s", op.id, err)
-		}
-	})
-}
-
-func (op *operation) Run() (chan error, error) {
-	if op.status != api.Pending {
-		return nil, fmt.Errorf("Only pending operations can be started")
-	}
-
-	chanRun := make(chan error, 1)
-
-	op.lock.Lock()
-	op.status = api.Running
-
-	if op.onRun != nil {
-		go func(op *operation, chanRun chan error) {
-			err := op.onRun(op)
-			if err != nil {
-				op.lock.Lock()
-				op.status = api.Failure
-				op.err = SmartError(err).String()
-				op.lock.Unlock()
-				op.done()
-				chanRun <- err
-
-				logger.Debugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
-
-				_, md, _ := op.Render()
-				events.Send(op.project, "operation", md)
-				return
-			}
-
-			op.lock.Lock()
-			op.status = api.Success
-			op.lock.Unlock()
-			op.done()
-			chanRun <- nil
-
-			op.lock.Lock()
-			logger.Debugf("Success for %s operation: %s", op.class.String(), op.id)
-			_, md, _ := op.Render()
-			events.Send(op.project, "operation", md)
-			op.lock.Unlock()
-		}(op, chanRun)
-	}
-	op.lock.Unlock()
-
-	logger.Debugf("Started %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	events.Send(op.project, "operation", md)
-
-	return chanRun, nil
-}
-
-func (op *operation) Cancel() (chan error, error) {
-	if op.status != api.Running {
-		return nil, fmt.Errorf("Only running operations can be cancelled")
-	}
-
-	if !op.mayCancel() {
-		return nil, fmt.Errorf("This operation can't be cancelled")
-	}
-
-	chanCancel := make(chan error, 1)
-
-	op.lock.Lock()
-	oldStatus := op.status
-	op.status = api.Cancelling
-	op.lock.Unlock()
-
-	if op.onCancel != nil {
-		go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
-			err := op.onCancel(op)
-			if err != nil {
-				op.lock.Lock()
-				op.status = oldStatus
-				op.lock.Unlock()
-				chanCancel <- err
-
-				logger.Debugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
-				_, md, _ := op.Render()
-				events.Send(op.project, "operation", md)
-				return
-			}
-
-			op.lock.Lock()
-			op.status = api.Cancelled
-			op.lock.Unlock()
-			op.done()
-			chanCancel <- nil
-
-			logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
-			_, md, _ := op.Render()
-			events.Send(op.project, "operation", md)
-		}(op, oldStatus, chanCancel)
-	}
-
-	logger.Debugf("Cancelling %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	events.Send(op.project, "operation", md)
-
-	if op.canceler != nil {
-		err := op.canceler.Cancel()
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	if op.onCancel == nil {
-		op.lock.Lock()
-		op.status = api.Cancelled
-		op.lock.Unlock()
-		op.done()
-		chanCancel <- nil
-	}
-
-	logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
-	_, md, _ = op.Render()
-	events.Send(op.project, "operation", md)
-
-	return chanCancel, nil
-}
-
-func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) {
-	if op.class != operationClassWebsocket {
-		return nil, fmt.Errorf("Only websocket operations can be connected")
-	}
-
-	if op.status != api.Running {
-		return nil, fmt.Errorf("Only running operations can be connected")
-	}
-
-	chanConnect := make(chan error, 1)
-
-	op.lock.Lock()
-
-	go func(op *operation, chanConnect chan error) {
-		err := op.onConnect(op, r, w)
-		if err != nil {
-			chanConnect <- err
-
-			logger.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
-			return
-		}
-
-		chanConnect <- nil
-
-		logger.Debugf("Handled %s operation: %s", op.class.String(), op.id)
-	}(op, chanConnect)
-	op.lock.Unlock()
-
-	logger.Debugf("Connected %s operation: %s", op.class.String(), op.id)
-
-	return chanConnect, nil
-}
-
-func (op *operation) mayCancel() bool {
-	if op.class == operationClassToken {
-		return true
-	}
-
-	if op.onCancel != nil {
-		return true
-	}
-
-	if op.canceler != nil && op.canceler.Cancelable() {
-		return true
-	}
-
-	return false
-}
-
-func (op *operation) Render() (string, *api.Operation, error) {
-	// Setup the resource URLs
-	resources := op.resources
-	if resources != nil {
-		tmpResources := make(map[string][]string)
-		for key, value := range resources {
-			var values []string
-			for _, c := range value {
-				values = append(values, fmt.Sprintf("/%s/%s/%s", version.APIVersion, key, c))
-			}
-			tmpResources[key] = values
-		}
-		resources = tmpResources
-	}
-
-	// Local server name
-	var err error
-	var serverName string
-	err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
-		serverName, err = tx.NodeName()
-		return err
-	})
-	if err != nil {
-		return "", nil, err
-	}
-
-	return op.url, &api.Operation{
-		ID:          op.id,
-		Class:       op.class.String(),
-		Description: op.description,
-		CreatedAt:   op.createdAt,
-		UpdatedAt:   op.updatedAt,
-		Status:      op.status.String(),
-		StatusCode:  op.status,
-		Resources:   resources,
-		Metadata:    op.metadata,
-		MayCancel:   op.mayCancel(),
-		Err:         op.err,
-		Location:    serverName,
-	}, nil
-}
-
-func (op *operation) WaitFinal(timeout int) (bool, error) {
-	// Check current state
-	if op.status.IsFinal() {
-		return true, nil
-	}
-
-	// Wait indefinitely
-	if timeout == -1 {
-		<-op.chanDone
-		return true, nil
-	}
-
-	// Wait until timeout
-	if timeout > 0 {
-		timer := time.NewTimer(time.Duration(timeout) * time.Second)
-		select {
-		case <-op.chanDone:
-			return true, nil
-
-		case <-timer.C:
-			return false, nil
-		}
-	}
-
-	return false, nil
-}
-
-func (op *operation) UpdateResources(opResources map[string][]string) error {
-	if op.status != api.Pending && op.status != api.Running {
-		return fmt.Errorf("Only pending or running operations can be updated")
-	}
-
-	if op.readonly {
-		return fmt.Errorf("Read-only operations can't be updated")
-	}
-
-	op.lock.Lock()
-	op.updatedAt = time.Now()
-	op.resources = opResources
-	op.lock.Unlock()
-
-	logger.Debugf("Updated resources for %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	events.Send(op.project, "operation", md)
-
-	return nil
-}
-
-func (op *operation) UpdateMetadata(opMetadata interface{}) error {
-	if op.status != api.Pending && op.status != api.Running {
-		return fmt.Errorf("Only pending or running operations can be updated")
-	}
-
-	if op.readonly {
-		return fmt.Errorf("Read-only operations can't be updated")
-	}
-
-	newMetadata, err := shared.ParseMetadata(opMetadata)
-	if err != nil {
-		return err
-	}
-
-	op.lock.Lock()
-	op.updatedAt = time.Now()
-	op.metadata = newMetadata
-	op.lock.Unlock()
-
-	logger.Debugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	events.Send(op.project, "operation", md)
-
-	return nil
-}
-
-func operationCreate(cluster *db.Cluster, project string, opClass operationClass, opType db.OperationType, opResources map[string][]string, opMetadata interface{}, onRun func(*operation) error, onCancel func(*operation) error, onConnect func(*operation, *http.Request, http.ResponseWriter) error) (*operation, error) {
-	// Main attributes
-	op := operation{}
-	op.project = project
-	op.id = uuid.NewRandom().String()
-	op.description = opType.Description()
-	op.permission = opType.Permission()
-	op.class = opClass
-	op.createdAt = time.Now()
-	op.updatedAt = op.createdAt
-	op.status = api.Pending
-	op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
-	op.resources = opResources
-	op.chanDone = make(chan error)
-	op.cluster = cluster
-
-	newMetadata, err := shared.ParseMetadata(opMetadata)
-	if err != nil {
-		return nil, err
-	}
-	op.metadata = newMetadata
-
-	// Callback functions
-	op.onRun = onRun
-	op.onCancel = onCancel
-	op.onConnect = onConnect
-
-	// Sanity check
-	if op.class != operationClassWebsocket && op.onConnect != nil {
-		return nil, fmt.Errorf("Only websocket operations can have a Connect hook")
-	}
-
-	if op.class == operationClassWebsocket && op.onConnect == nil {
-		return nil, fmt.Errorf("Websocket operations must have a Connect hook")
-	}
-
-	if op.class == operationClassToken && op.onRun != nil {
-		return nil, fmt.Errorf("Token operations can't have a Run hook")
-	}
-
-	if op.class == operationClassToken && op.onCancel != nil {
-		return nil, fmt.Errorf("Token operations can't have a Cancel hook")
-	}
-
-	operationsLock.Lock()
-	operations[op.id] = &op
-	operationsLock.Unlock()
-
-	err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
-		_, err := tx.OperationAdd(project, op.id, opType)
-		return err
-	})
-	if err != nil {
-		return nil, errors.Wrapf(err, "failed to add operation %s to database", op.id)
-	}
-
-	logger.Debugf("New %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	events.Send(op.project, "operation", md)
-
-	return &op, nil
-}
-
-func operationGetInternal(id string) (*operation, error) {
-	operationsLock.Lock()
-	op, ok := operations[id]
-	operationsLock.Unlock()
-
-	if !ok {
-		return nil, fmt.Errorf("Operation '%s' doesn't exist", id)
-	}
-
-	return op, nil
-}
-
 // API functions
-func operationGet(d *Daemon, r *http.Request) Response {
+func operationGet(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
 	var body *api.Operation
 
 	// First check if the query is for a local operation from this node
-	op, err := operationGetInternal(id)
+	op, err := operations.OperationGetInternal(id)
 	if err == nil {
 		_, body, err = op.Render()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
-		return SyncResponse(true, body)
+		return response.SyncResponse(true, body)
 	}
 
 	// Then check if the query is from an operation on another node, and, if so, forward it
@@ -519,46 +72,46 @@ func operationGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.Connect(address, cert, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	body, _, err = client.GetOperation(id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, body)
+	return response.SyncResponse(true, body)
 }
 
-func operationDelete(d *Daemon, r *http.Request) Response {
+func operationDelete(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
 	// First check if the query is for a local operation from this node
-	op, err := operationGetInternal(id)
+	op, err := operations.OperationGetInternal(id)
 	if err == nil {
-		if op.permission != "" {
-			project := op.project
+		if op.Permission() != "" {
+			project := op.Project()
 			if project == "" {
 				project = "default"
 			}
 
-			if !d.userHasPermission(r, project, op.permission) {
-				return Forbidden(nil)
+			if !d.userHasPermission(r, project, op.Permission()) {
+				return response.Forbidden(nil)
 			}
 		}
 
 		_, err = op.Cancel()
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	// Then check if the query is from an operation on another node, and, if so, forward it
@@ -573,47 +126,47 @@ func operationDelete(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.Connect(address, cert, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = client.DeleteOperation(id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func operationsGet(d *Daemon, r *http.Request) Response {
+func operationsGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	recursion := util.IsRecursionRequest(r)
 
 	localOperationURLs := func() (shared.Jmap, error) {
 		// Get all the operations
-		operationsLock.Lock()
-		ops := operations
-		operationsLock.Unlock()
+		operations.Lock()
+		ops := operations.Operations()
+		operations.Unlock()
 
 		// Build a list of URLs
 		body := shared.Jmap{}
 
 		for _, v := range ops {
-			if v.project != "" && v.project != project {
+			if v.Project() != "" && v.Project() != project {
 				continue
 			}
-			status := strings.ToLower(v.status.String())
+			status := strings.ToLower(v.Status().String())
 			_, ok := body[status]
 			if !ok {
 				body[status] = make([]string, 0)
 			}
 
-			body[status] = append(body[status].([]string), v.url)
+			body[status] = append(body[status].([]string), v.URL())
 		}
 
 		return body, nil
@@ -621,18 +174,18 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 
 	localOperations := func() (shared.Jmap, error) {
 		// Get all the operations
-		operationsLock.Lock()
-		ops := operations
-		operationsLock.Unlock()
+		operations.Lock()
+		ops := operations.Operations()
+		operations.Unlock()
 
 		// Build a list of operations
 		body := shared.Jmap{}
 
 		for _, v := range ops {
-			if v.project != "" && v.project != project {
+			if v.Project() != "" && v.Project() != project {
 				continue
 			}
-			status := strings.ToLower(v.status.String())
+			status := strings.ToLower(v.Status().String())
 			_, ok := body[status]
 			if !ok {
 				body[status] = make([]*api.Operation, 0)
@@ -656,19 +209,19 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 			// Recursive queries
 			body, err := localOperations()
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			return SyncResponse(true, body)
+			return response.SyncResponse(true, body)
 		}
 
 		// Normal queries
 		body, err := localOperationURLs()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return SyncResponse(true, body)
+		return response.SyncResponse(true, body)
 	}
 
 	// Start with local operations
@@ -678,24 +231,24 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 	if recursion {
 		md, err = localOperations()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	} else {
 		md, err = localOperationURLs()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
 	// Check if clustered
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Return now if not clustered
 	if !clustered {
-		return SyncResponse(true, md)
+		return response.SyncResponse(true, md)
 	}
 
 	// Get all nodes with running operations in this project.
@@ -711,13 +264,13 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get local address
 	localAddress, err := node.HTTPSAddress(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	cert := d.endpoints.NetworkCert()
@@ -729,13 +282,13 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 		// Connect to the remote server
 		client, err := cluster.Connect(node, cert, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Get operation data
 		ops, err := client.GetOperations()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		// Merge with existing data
@@ -759,31 +312,31 @@ func operationsGet(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	return SyncResponse(true, md)
+	return response.SyncResponse(true, md)
 }
 
-func operationWaitGet(d *Daemon, r *http.Request) Response {
+func operationWaitGet(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
 	timeout, err := shared.AtoiEmptyDefault(r.FormValue("timeout"), -1)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// First check if the query is for a local operation from this node
-	op, err := operationGetInternal(id)
+	op, err := operations.OperationGetInternal(id)
 	if err == nil {
 		_, err = op.WaitFinal(timeout)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		_, body, err := op.Render()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
-		return SyncResponse(true, body)
+		return response.SyncResponse(true, body)
 	}
 
 	// Then check if the query is from an operation on another node, and, if so, forward it
@@ -798,79 +351,36 @@ func operationWaitGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.Connect(address, cert, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	_, body, err := client.GetOperationWait(id, timeout)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, body)
-}
-
-type operationWebSocket struct {
-	req *http.Request
-	op  *operation
-}
-
-func (r *operationWebSocket) Render(w http.ResponseWriter) error {
-	chanErr, err := r.op.Connect(r.req, w)
-	if err != nil {
-		return err
-	}
-
-	err = <-chanErr
-	return err
-}
-
-func (r *operationWebSocket) String() string {
-	_, md, err := r.op.Render()
-	if err != nil {
-		return fmt.Sprintf("error: %s", err)
-	}
-
-	return md.ID
-}
-
-type forwardedOperationWebSocket struct {
-	req    *http.Request
-	id     string
-	source *websocket.Conn // Connection to the node were the operation is running
-}
-
-func (r *forwardedOperationWebSocket) Render(w http.ResponseWriter) error {
-	target, err := shared.WebsocketUpgrader.Upgrade(w, r.req, nil)
-	if err != nil {
-		return err
-	}
-	<-shared.WebsocketProxy(r.source, target)
-	return nil
-}
-
-func (r *forwardedOperationWebSocket) String() string {
-	return r.id
+	return response.SyncResponse(true, body)
 }
 
-func operationWebsocketGet(d *Daemon, r *http.Request) Response {
+func operationWebsocketGet(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
 	// First check if the query is for a local operation from this node
-	op, err := operationGetInternal(id)
+	op, err := operations.OperationGetInternal(id)
 	if err == nil {
-		return &operationWebSocket{r, op}
+		return operations.OperationWebSocket(r, op)
 	}
 
 	// Then check if the query is from an operation on another node, and, if so, forward it
 	secret := r.FormValue("secret")
 	if secret == "" {
-		return BadRequest(fmt.Errorf("missing secret"))
+		return response.BadRequest(fmt.Errorf("missing secret"))
 	}
 
 	var address string
@@ -884,20 +394,20 @@ func operationWebsocketGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.Connect(address, cert, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	logger.Debugf("Forward operation websocket from node %s", address)
 	source, err := client.GetOperationWebsocket(id, secret)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return &forwardedOperationWebSocket{req: r, id: id, source: source}
+	return operations.ForwardedOperationWebSocket(r, id, source)
 }
diff --git a/lxd/operations/operations.go b/lxd/operations/operations.go
new file mode 100644
index 0000000000..b1432b1989
--- /dev/null
+++ b/lxd/operations/operations.go
@@ -0,0 +1,543 @@
+package operations
+
+import (
+	"fmt"
+	"net/http"
+	"sync"
+	"time"
+
+	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/cancel"
+	"github.com/lxc/lxd/shared/logger"
+	"github.com/lxc/lxd/shared/version"
+	"github.com/pborman/uuid"
+	"github.com/pkg/errors"
+)
+
+var debug bool
+
+var operationsLock sync.Mutex
+var operations map[string]*Operation = make(map[string]*Operation)
+
+type operationClass int
+
+const (
+	// OperationClassTask represents the Task OperationClass
+	OperationClassTask operationClass = 1
+	// OperationClassWebsocket represents the Websocket OperationClass
+	OperationClassWebsocket operationClass = 2
+	// OperationClassToken represents the Token OperationClass
+	OperationClassToken operationClass = 3
+)
+
+func (t operationClass) String() string {
+	return map[operationClass]string{
+		OperationClassTask:      "task",
+		OperationClassWebsocket: "websocket",
+		OperationClassToken:     "token",
+	}[t]
+}
+
+// Init sets the debug value for the operations package.
+func Init(d bool) {
+	debug = d
+}
+
+// Lock locks the operations mutex.
+func Lock() {
+	operationsLock.Lock()
+}
+
+// Unlock unlocks the operations mutex.
+func Unlock() {
+	operationsLock.Unlock()
+}
+
+// Operations returns a map of operations.
+func Operations() map[string]*Operation {
+	return operations
+}
+
+// OperationGetInternal returns the operation with the given id. It returns an
+// error if it doesn't exist.
+func OperationGetInternal(id string) (*Operation, error) {
+	operationsLock.Lock()
+	op, ok := operations[id]
+	operationsLock.Unlock()
+
+	if !ok {
+		return nil, fmt.Errorf("Operation '%s' doesn't exist", id)
+	}
+
+	return op, nil
+}
+
+// Operation represents an operation.
+type Operation struct {
+	project     string
+	id          string
+	class       operationClass
+	createdAt   time.Time
+	updatedAt   time.Time
+	status      api.StatusCode
+	url         string
+	resources   map[string][]string
+	metadata    map[string]interface{}
+	err         string
+	readonly    bool
+	canceler    *cancel.Canceler
+	description string
+	permission  string
+
+	// Those functions are called at various points in the Operation lifecycle
+	onRun     func(*Operation) error
+	onCancel  func(*Operation) error
+	onConnect func(*Operation, *http.Request, http.ResponseWriter) error
+
+	// Channels used for error reporting and state tracking of background actions
+	chanDone chan error
+
+	// Locking for concurent access to the Operation
+	lock sync.Mutex
+
+	cluster *db.Cluster
+}
+
+func (op *Operation) done() {
+	if op.readonly {
+		return
+	}
+
+	op.lock.Lock()
+	op.readonly = true
+	op.onRun = nil
+	op.onCancel = nil
+	op.onConnect = nil
+	close(op.chanDone)
+	op.lock.Unlock()
+
+	time.AfterFunc(time.Second*5, func() {
+		operationsLock.Lock()
+		_, ok := operations[op.id]
+		if !ok {
+			operationsLock.Unlock()
+			return
+		}
+
+		delete(operations, op.id)
+		operationsLock.Unlock()
+
+		err := op.cluster.Transaction(func(tx *db.ClusterTx) error {
+			return tx.OperationRemove(op.id)
+		})
+		if err != nil {
+			logger.Warnf("Failed to delete Operation %s: %s", op.id, err)
+		}
+	})
+}
+
+// Run runs a pending operation. It returns an error if the operation cannot
+// be started.
+func (op *Operation) Run() (chan error, error) {
+	if op.status != api.Pending {
+		return nil, fmt.Errorf("Only pending operations can be started")
+	}
+
+	chanRun := make(chan error, 1)
+
+	op.lock.Lock()
+	op.status = api.Running
+
+	if op.onRun != nil {
+		go func(op *Operation, chanRun chan error) {
+			err := op.onRun(op)
+			if err != nil {
+				op.lock.Lock()
+				op.status = api.Failure
+				op.err = response.SmartError(err).String()
+				op.lock.Unlock()
+				op.done()
+				chanRun <- err
+
+				logger.Debugf("Failure for %s Operation: %s: %s", op.class.String(), op.id, err)
+
+				_, md, _ := op.Render()
+				events.Send(op.project, "Operation", md)
+				return
+			}
+
+			op.lock.Lock()
+			op.status = api.Success
+			op.lock.Unlock()
+			op.done()
+			chanRun <- nil
+
+			op.lock.Lock()
+			logger.Debugf("Success for %s Operation: %s", op.class.String(), op.id)
+			_, md, _ := op.Render()
+			events.Send(op.project, "Operation", md)
+			op.lock.Unlock()
+		}(op, chanRun)
+	}
+	op.lock.Unlock()
+
+	logger.Debugf("Started %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send(op.project, "Operation", md)
+
+	return chanRun, nil
+}
+
+// Cancel cancels a running operation. If the operation cannot be cancelled, it
+// returns an error.
+func (op *Operation) Cancel() (chan error, error) {
+	if op.status != api.Running {
+		return nil, fmt.Errorf("Only running operations can be cancelled")
+	}
+
+	if !op.mayCancel() {
+		return nil, fmt.Errorf("This Operation can't be cancelled")
+	}
+
+	chanCancel := make(chan error, 1)
+
+	op.lock.Lock()
+	oldStatus := op.status
+	op.status = api.Cancelling
+	op.lock.Unlock()
+
+	if op.onCancel != nil {
+		go func(op *Operation, oldStatus api.StatusCode, chanCancel chan error) {
+			err := op.onCancel(op)
+			if err != nil {
+				op.lock.Lock()
+				op.status = oldStatus
+				op.lock.Unlock()
+				chanCancel <- err
+
+				logger.Debugf("Failed to cancel %s Operation: %s: %s", op.class.String(), op.id, err)
+				_, md, _ := op.Render()
+				events.Send(op.project, "Operation", md)
+				return
+			}
+
+			op.lock.Lock()
+			op.status = api.Cancelled
+			op.lock.Unlock()
+			op.done()
+			chanCancel <- nil
+
+			logger.Debugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+			_, md, _ := op.Render()
+			events.Send(op.project, "Operation", md)
+		}(op, oldStatus, chanCancel)
+	}
+
+	logger.Debugf("Cancelling %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send(op.project, "Operation", md)
+
+	if op.canceler != nil {
+		err := op.canceler.Cancel()
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	if op.onCancel == nil {
+		op.lock.Lock()
+		op.status = api.Cancelled
+		op.lock.Unlock()
+		op.done()
+		chanCancel <- nil
+	}
+
+	logger.Debugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+	_, md, _ = op.Render()
+	events.Send(op.project, "Operation", md)
+
+	return chanCancel, nil
+}
+
+// Connect connects a websocket operation. If the operation is not a websocket
+// operation or the operation is not running, it returns an error.
+func (op *Operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) {
+	if op.class != OperationClassWebsocket {
+		return nil, fmt.Errorf("Only websocket operations can be connected")
+	}
+
+	if op.status != api.Running {
+		return nil, fmt.Errorf("Only running operations can be connected")
+	}
+
+	chanConnect := make(chan error, 1)
+
+	op.lock.Lock()
+
+	go func(op *Operation, chanConnect chan error) {
+		err := op.onConnect(op, r, w)
+		if err != nil {
+			chanConnect <- err
+
+			logger.Debugf("Failed to handle %s Operation: %s: %s", op.class.String(), op.id, err)
+			return
+		}
+
+		chanConnect <- nil
+
+		logger.Debugf("Handled %s Operation: %s", op.class.String(), op.id)
+	}(op, chanConnect)
+	op.lock.Unlock()
+
+	logger.Debugf("Connected %s Operation: %s", op.class.String(), op.id)
+
+	return chanConnect, nil
+}
+
+func (op *Operation) mayCancel() bool {
+	if op.class == OperationClassToken {
+		return true
+	}
+
+	if op.onCancel != nil {
+		return true
+	}
+
+	if op.canceler != nil && op.canceler.Cancelable() {
+		return true
+	}
+
+	return false
+}
+
+// Render renders the operation structure.
+func (op *Operation) Render() (string, *api.Operation, error) {
+	// Setup the resource URLs
+	resources := op.resources
+	if resources != nil {
+		tmpResources := make(map[string][]string)
+		for key, value := range resources {
+			var values []string
+			for _, c := range value {
+				values = append(values, fmt.Sprintf("/%s/%s/%s", version.APIVersion, key, c))
+			}
+			tmpResources[key] = values
+		}
+		resources = tmpResources
+	}
+
+	// Local server name
+	var err error
+	var serverName string
+	err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
+		serverName, err = tx.NodeName()
+		return err
+	})
+	if err != nil {
+		return "", nil, err
+	}
+
+	return op.url, &api.Operation{
+		ID:          op.id,
+		Class:       op.class.String(),
+		Description: op.description,
+		CreatedAt:   op.createdAt,
+		UpdatedAt:   op.updatedAt,
+		Status:      op.status.String(),
+		StatusCode:  op.status,
+		Resources:   resources,
+		Metadata:    op.metadata,
+		MayCancel:   op.mayCancel(),
+		Err:         op.err,
+		Location:    serverName,
+	}, nil
+}
+
+// WaitFinal waits for the operation to be done. If timeout is -1, it will wait
+// indefinitely otherwise it will timeout after {timeout} seconds.
+func (op *Operation) WaitFinal(timeout int) (bool, error) {
+	// Check current state
+	if op.status.IsFinal() {
+		return true, nil
+	}
+
+	// Wait indefinitely
+	if timeout == -1 {
+		<-op.chanDone
+		return true, nil
+	}
+
+	// Wait until timeout
+	if timeout > 0 {
+		timer := time.NewTimer(time.Duration(timeout) * time.Second)
+		select {
+		case <-op.chanDone:
+			return true, nil
+
+		case <-timer.C:
+			return false, nil
+		}
+	}
+
+	return false, nil
+}
+
+// UpdateResources updates the resources of the operation. It returns an error
+// if the operation is not pending or running, or the operation is read-only.
+func (op *Operation) UpdateResources(opResources map[string][]string) error {
+	if op.status != api.Pending && op.status != api.Running {
+		return fmt.Errorf("Only pending or running operations can be updated")
+	}
+
+	if op.readonly {
+		return fmt.Errorf("Read-only operations can't be updated")
+	}
+
+	op.lock.Lock()
+	op.updatedAt = time.Now()
+	op.resources = opResources
+	op.lock.Unlock()
+
+	logger.Debugf("Updated resources for %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send(op.project, "Operation", md)
+
+	return nil
+}
+
+// UpdateMetadata updates the metadata of the operation. It returns an error
+// if the operation is not pending or running, or the operation is read-only.
+func (op *Operation) UpdateMetadata(opMetadata interface{}) error {
+	if op.status != api.Pending && op.status != api.Running {
+		return fmt.Errorf("Only pending or running operations can be updated")
+	}
+
+	if op.readonly {
+		return fmt.Errorf("Read-only operations can't be updated")
+	}
+
+	newMetadata, err := shared.ParseMetadata(opMetadata)
+	if err != nil {
+		return err
+	}
+
+	op.lock.Lock()
+	op.updatedAt = time.Now()
+	op.metadata = newMetadata
+	op.lock.Unlock()
+
+	logger.Debugf("Updated metadata for %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send(op.project, "Operation", md)
+
+	return nil
+}
+
+// ID returns the operation ID.
+func (op *Operation) ID() string {
+	return op.id
+}
+
+// Metadata returns the operation Metadata.
+func (op *Operation) Metadata() map[string]interface{} {
+	return op.metadata
+}
+
+// URL returns the operation URL.
+func (op *Operation) URL() string {
+	return op.url
+}
+
+// Resources returns the operation resources.
+func (op *Operation) Resources() map[string][]string {
+	return op.resources
+}
+
+// SetCanceler sets a canceler.
+func (op *Operation) SetCanceler(canceler *cancel.Canceler) {
+	op.canceler = canceler
+}
+
+// Permission returns the operation permission.
+func (op *Operation) Permission() string {
+	return op.permission
+}
+
+// Project returns the operation project.
+func (op *Operation) Project() string {
+	return op.project
+}
+
+// Status returns the operation status.
+func (op *Operation) Status() api.StatusCode {
+	return op.status
+}
+
+// OperationCreate creates a new operation and returns it. If it cannot be
+// created, it returns an error.
+func OperationCreate(cluster *db.Cluster, project string, opClass operationClass, opType db.OperationType, opResources map[string][]string, opMetadata interface{}, onRun func(*Operation) error, onCancel func(*Operation) error, onConnect func(*Operation, *http.Request, http.ResponseWriter) error) (*Operation, error) {
+	// Main attributes
+	op := Operation{}
+	op.project = project
+	op.id = uuid.NewRandom().String()
+	op.description = opType.Description()
+	op.permission = opType.Permission()
+	op.class = opClass
+	op.createdAt = time.Now()
+	op.updatedAt = op.createdAt
+	op.status = api.Pending
+	op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
+	op.resources = opResources
+	op.chanDone = make(chan error)
+	op.cluster = cluster
+
+	newMetadata, err := shared.ParseMetadata(opMetadata)
+	if err != nil {
+		return nil, err
+	}
+	op.metadata = newMetadata
+
+	// Callback functions
+	op.onRun = onRun
+	op.onCancel = onCancel
+	op.onConnect = onConnect
+
+	// Sanity check
+	if op.class != OperationClassWebsocket && op.onConnect != nil {
+		return nil, fmt.Errorf("Only websocket operations can have a Connect hook")
+	}
+
+	if op.class == OperationClassWebsocket && op.onConnect == nil {
+		return nil, fmt.Errorf("Websocket operations must have a Connect hook")
+	}
+
+	if op.class == OperationClassToken && op.onRun != nil {
+		return nil, fmt.Errorf("Token operations can't have a Run hook")
+	}
+
+	if op.class == OperationClassToken && op.onCancel != nil {
+		return nil, fmt.Errorf("Token operations can't have a Cancel hook")
+	}
+
+	operationsLock.Lock()
+	operations[op.id] = &op
+	operationsLock.Unlock()
+
+	err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
+		_, err := tx.OperationAdd(project, op.id, opType)
+		return err
+	})
+	if err != nil {
+		return nil, errors.Wrapf(err, "failed to add Operation %s to database", op.id)
+	}
+
+	logger.Debugf("New %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send(op.project, "Operation", md)
+
+	return &op, nil
+}
diff --git a/lxd/operations/response.go b/lxd/operations/response.go
new file mode 100644
index 0000000000..d22c5b9f6a
--- /dev/null
+++ b/lxd/operations/response.go
@@ -0,0 +1,96 @@
+package operations
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/lxc/lxd/shared/version"
+)
+
+// Operation response
+type operationResponse struct {
+	op *Operation
+}
+
+// OperationResponse returns an operation response.
+func OperationResponse(op *Operation) response.Response {
+	return &operationResponse{op}
+}
+
+func (r *operationResponse) Render(w http.ResponseWriter) error {
+	_, err := r.op.Run()
+	if err != nil {
+		return err
+	}
+
+	url, md, err := r.op.Render()
+	if err != nil {
+		return err
+	}
+
+	body := api.ResponseRaw{
+		Type:       api.AsyncResponse,
+		Status:     api.OperationCreated.String(),
+		StatusCode: int(api.OperationCreated),
+		Operation:  url,
+		Metadata:   md,
+	}
+
+	w.Header().Set("Location", url)
+	w.WriteHeader(202)
+
+	return util.WriteJSON(w, body, debug)
+}
+
+func (r *operationResponse) String() string {
+	_, md, err := r.op.Render()
+	if err != nil {
+		return fmt.Sprintf("error: %s", err)
+	}
+
+	return md.ID
+}
+
+// Forwarded operation response.
+//
+// Returned when the operation has been created on another node
+type forwardedOperationResponse struct {
+	op      *api.Operation
+	project string
+}
+
+// ForwardedOperationResponse creates a response that forwards the metadata of
+// an operation created on another node.
+func ForwardedOperationResponse(project string, op *api.Operation) response.Response {
+	return &forwardedOperationResponse{
+		op:      op,
+		project: project,
+	}
+}
+
+func (r *forwardedOperationResponse) Render(w http.ResponseWriter) error {
+	url := fmt.Sprintf("/%s/operations/%s", version.APIVersion, r.op.ID)
+	if r.project != "" {
+		url += fmt.Sprintf("?project=%s", r.project)
+	}
+
+	body := api.ResponseRaw{
+		Type:       api.AsyncResponse,
+		Status:     api.OperationCreated.String(),
+		StatusCode: int(api.OperationCreated),
+		Operation:  url,
+		Metadata:   r.op,
+	}
+
+	w.Header().Set("Location", url)
+	w.WriteHeader(202)
+
+	return util.WriteJSON(w, body, debug)
+}
+
+func (r *forwardedOperationResponse) String() string {
+	return r.op.ID
+}
diff --git a/lxd/operations/websocket.go b/lxd/operations/websocket.go
new file mode 100644
index 0000000000..f16005da85
--- /dev/null
+++ b/lxd/operations/websocket.go
@@ -0,0 +1,63 @@
+package operations
+
+import (
+	"fmt"
+	"net/http"
+
+	"github.com/gorilla/websocket"
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/shared"
+)
+
+type operationWebSocket struct {
+	req *http.Request
+	op  *Operation
+}
+
+// OperationWebSocket returns a new websocket operation.
+func OperationWebSocket(req *http.Request, op *Operation) response.Response {
+	return &operationWebSocket{req, op}
+}
+
+func (r *operationWebSocket) Render(w http.ResponseWriter) error {
+	chanErr, err := r.op.Connect(r.req, w)
+	if err != nil {
+		return err
+	}
+
+	err = <-chanErr
+	return err
+}
+
+func (r *operationWebSocket) String() string {
+	_, md, err := r.op.Render()
+	if err != nil {
+		return fmt.Sprintf("error: %s", err)
+	}
+
+	return md.ID
+}
+
+type forwardedOperationWebSocket struct {
+	req    *http.Request
+	id     string
+	source *websocket.Conn // Connection to the node were the operation is running
+}
+
+// ForwardedOperationWebSocket returns a new forwarted websocket operation.
+func ForwardedOperationWebSocket(req *http.Request, id string, source *websocket.Conn) response.Response {
+	return &forwardedOperationWebSocket{req, id, source}
+}
+
+func (r *forwardedOperationWebSocket) Render(w http.ResponseWriter) error {
+	target, err := shared.WebsocketUpgrader.Upgrade(w, r.req, nil)
+	if err != nil {
+		return err
+	}
+	<-shared.WebsocketProxy(r.source, target)
+	return nil
+}
+
+func (r *forwardedOperationWebSocket) String() string {
+	return r.id
+}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 6c6e7c6c17..424a9e0b47 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -15,6 +15,7 @@ import (
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -39,7 +40,7 @@ var profileCmd = APIEndpoint{
 }
 
 /* This is used for both profiles post and profile put */
-func profilesGet(d *Daemon, r *http.Request) Response {
+func profilesGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 
 	recursion := util.IsRecursionRequest(r)
@@ -75,41 +76,41 @@ func profilesGet(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
-func profilesPost(d *Daemon, r *http.Request) Response {
+func profilesPost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	req := api.ProfilesPost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Profile names may not contain slashes"))
 	}
 
 	if shared.StringInSlice(req.Name, []string{".", ".."}) {
-		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+		return response.BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
 	}
 
 	err := containerValidConfig(d.os, req.Config, true, false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Validate container devices with an empty instanceName to indicate profile validation.
 	err = containerValidDevices(d.State(), d.cluster, "", deviceConfig.NewDevices(req.Devices), false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Update DB entry
@@ -139,14 +140,14 @@ func profilesPost(d *Daemon, r *http.Request) Response {
 		return err
 	})
 	if err != nil {
-		return SmartError(
+		return response.SmartError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
 }
 
-func profileGet(d *Daemon, r *http.Request) Response {
+func profileGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
@@ -172,7 +173,7 @@ func profileGet(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// For backward-compatibility, we strip the "?project" query parameter
@@ -185,10 +186,10 @@ func profileGet(d *Daemon, r *http.Request) Response {
 	}
 
 	etag := []interface{}{resp.Config, resp.Description, resp.Devices}
-	return SyncResponseETag(true, resp, etag)
+	return response.SyncResponseETag(true, resp, etag)
 }
 
-func profilePut(d *Daemon, r *http.Request) Response {
+func profilePut(d *Daemon, r *http.Request) response.Response {
 	// Get the project
 	project := projectParam(r)
 
@@ -202,11 +203,11 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		old := api.ProfilePut{}
 		err := json.NewDecoder(r.Body).Decode(&old)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		err = doProfileUpdateCluster(d, project, name, old)
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	var id int64
@@ -233,19 +234,19 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.ProfilePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = doProfileUpdate(d, project, name, id, profile, req)
@@ -254,21 +255,21 @@ func profilePut(d *Daemon, r *http.Request) Response {
 		// Notify all other nodes. If a node is down, it will be ignored.
 		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UseProject(project).UpdateProfile(name, profile.ProfilePut, "")
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return SmartError(err)
+	return response.SmartError(err)
 }
 
-func profilePatch(d *Daemon, r *http.Request) Response {
+func profilePatch(d *Daemon, r *http.Request) response.Response {
 	// Get the project
 	project := projectParam(r)
 
@@ -299,19 +300,19 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 		return nil
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{profile.Config, profile.Description, profile.Devices}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	body, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -319,12 +320,12 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 
 	reqRaw := shared.Jmap{}
 	if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	req := api.ProfilePut{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get Description
@@ -357,34 +358,34 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 		}
 	}
 
-	return SmartError(doProfileUpdate(d, project, name, id, profile, req))
+	return response.SmartError(doProfileUpdate(d, project, name, id, profile, req))
 }
 
 // The handler for the post operation.
-func profilePost(d *Daemon, r *http.Request) Response {
+func profilePost(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	if name == "default" {
-		return Forbidden(errors.New("The 'default' profile cannot be renamed"))
+		return response.Forbidden(errors.New("The 'default' profile cannot be renamed"))
 	}
 
 	req := api.ProfilePost{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Profile names may not contain slashes"))
 	}
 
 	if shared.StringInSlice(req.Name, []string{".", ".."}) {
-		return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+		return response.BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
 	}
 
 	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -406,19 +407,19 @@ func profilePost(d *Daemon, r *http.Request) Response {
 		return tx.ProfileRename(project, name, req.Name)
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
 }
 
 // The handler for the delete operation.
-func profileDelete(d *Daemon, r *http.Request) Response {
+func profileDelete(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	name := mux.Vars(r)["name"]
 
 	if name == "default" {
-		return Forbidden(errors.New("The 'default' profile cannot be deleted"))
+		return response.Forbidden(errors.New("The 'default' profile cannot be deleted"))
 	}
 
 	err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -442,8 +443,8 @@ func profileDelete(d *Daemon, r *http.Request) Response {
 		return tx.ProfileDelete(project, name)
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
diff --git a/lxd/resources.go b/lxd/resources.go
index fba15ac86f..32ddee2d50 100644
--- a/lxd/resources.go
+++ b/lxd/resources.go
@@ -6,6 +6,7 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/resources"
+	"github.com/lxc/lxd/lxd/response"
 )
 
 var api10ResourcesCmd = APIEndpoint{
@@ -22,47 +23,47 @@ var storagePoolResourcesCmd = APIEndpoint{
 
 // /1.0/resources
 // Get system resources
-func api10ResourcesGet(d *Daemon, r *http.Request) Response {
+func api10ResourcesGet(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	// Get the local resource usage
 	res, err := resources.GetResources()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponse(true, res)
+	return response.SyncResponse(true, res)
 }
 
 // /1.0/storage-pools/{name}/resources
 // Get resources for a specific storage pool
-func storagePoolResourcesGet(d *Daemon, r *http.Request) Response {
+func storagePoolResourcesGet(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	// Get the existing storage pool
 	poolName := mux.Vars(r)["name"]
 	s, err := storagePoolInit(d.State(), poolName)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	err = s.StoragePoolCheck()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	res, err := s.StoragePoolResources()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponse(true, &res)
+	return response.SyncResponse(true, &res)
 }
diff --git a/lxd/response.go b/lxd/response.go
index 7bca1b4e17..6e80cb7c01 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -1,169 +1,17 @@
 package main
 
 import (
-	"bytes"
-	"database/sql"
-	"encoding/json"
-	"fmt"
-	"io"
-	"mime/multipart"
 	"net/http"
-	"os"
-	"time"
 
-	"github.com/canonical/go-dqlite/driver"
-	sqlite3 "github.com/mattn/go-sqlite3"
-	"github.com/pkg/errors"
-
-	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/instance"
-	"github.com/lxc/lxd/lxd/util"
-	"github.com/lxc/lxd/shared"
-	"github.com/lxc/lxd/shared/api"
-	"github.com/lxc/lxd/shared/version"
+	"github.com/lxc/lxd/lxd/response"
 )
 
-type Response interface {
-	Render(w http.ResponseWriter) error
-	String() string
-}
-
-// Sync response
-type syncResponse struct {
-	success  bool
-	etag     interface{}
-	metadata interface{}
-	location string
-	code     int
-	headers  map[string]string
-}
-
-func (r *syncResponse) Render(w http.ResponseWriter) error {
-	// Set an appropriate ETag header
-	if r.etag != nil {
-		etag, err := util.EtagHash(r.etag)
-		if err == nil {
-			w.Header().Set("ETag", etag)
-		}
-	}
-
-	// Prepare the JSON response
-	status := api.Success
-	if !r.success {
-		status = api.Failure
-	}
-
-	if r.headers != nil {
-		for h, v := range r.headers {
-			w.Header().Set(h, v)
-		}
-	}
-
-	if r.location != "" {
-		w.Header().Set("Location", r.location)
-		code := r.code
-		if code == 0 {
-			code = 201
-		}
-		w.WriteHeader(code)
-	}
-
-	resp := api.ResponseRaw{
-		Type:       api.SyncResponse,
-		Status:     status.String(),
-		StatusCode: int(status),
-		Metadata:   r.metadata,
-	}
-
-	return util.WriteJSON(w, resp, debug)
-}
-
-func (r *syncResponse) String() string {
-	if r.success {
-		return "success"
-	}
-
-	return "failure"
-}
-
-func SyncResponse(success bool, metadata interface{}) Response {
-	return &syncResponse{success: success, metadata: metadata}
-}
-
-func SyncResponseETag(success bool, metadata interface{}, etag interface{}) Response {
-	return &syncResponse{success: success, metadata: metadata, etag: etag}
-}
-
-func SyncResponseLocation(success bool, metadata interface{}, location string) Response {
-	return &syncResponse{success: success, metadata: metadata, location: location}
-}
-
-func SyncResponseRedirect(address string) Response {
-	return &syncResponse{success: true, location: address, code: http.StatusPermanentRedirect}
-}
-
-func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]string) Response {
-	return &syncResponse{success: success, metadata: metadata, headers: headers}
-}
-
-var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
-
-type forwardedResponse struct {
-	client  lxd.InstanceServer
-	request *http.Request
-}
-
-func (r *forwardedResponse) Render(w http.ResponseWriter) error {
-	info, err := r.client.GetConnectionInfo()
-	if err != nil {
-		return err
-	}
-
-	url := fmt.Sprintf("%s%s", info.Addresses[0], r.request.URL.RequestURI())
-	forwarded, err := http.NewRequest(r.request.Method, url, r.request.Body)
-	if err != nil {
-		return err
-	}
-	for key := range r.request.Header {
-		forwarded.Header.Set(key, r.request.Header.Get(key))
-	}
-
-	httpClient, err := r.client.GetHTTPClient()
-	if err != nil {
-		return err
-	}
-	response, err := httpClient.Do(forwarded)
-	if err != nil {
-		return err
-	}
-
-	for key := range response.Header {
-		w.Header().Set(key, response.Header.Get(key))
-	}
-
-	w.WriteHeader(response.StatusCode)
-	_, err = io.Copy(w, response.Body)
-	return err
-}
-
-func (r *forwardedResponse) String() string {
-	return fmt.Sprintf("request to %s", r.request.URL)
-}
-
-// ForwardedResponse takes a request directed to a node and forwards it to
-// another node, writing back the response it gegs.
-func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
-	return &forwardedResponse{
-		client:  client,
-		request: request,
-	}
-}
-
 // ForwardedResponseIfTargetIsRemote redirects a request to the request has a
 // targetNode parameter pointing to a node which is not the local one.
-func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Response {
+func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) response.Response {
 	targetNode := queryParam(request, "target")
 	if targetNode == "" {
 		return nil
@@ -173,7 +21,7 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
 	// this very same node).
 	address, err := cluster.ResolveTarget(d.cluster, targetNode)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if address != "" {
@@ -181,9 +29,9 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
 		cert := d.endpoints.NetworkCert()
 		client, err := cluster.Connect(address, cert, false)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return ForwardedResponse(client, request)
+		return response.ForwardedResponse(client, request)
 	}
 
 	return nil
@@ -192,7 +40,7 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
 // ForwardedResponseIfContainerIsRemote redirects a request to the node running
 // the container with the given name. If the container is local, nothing gets
 // done and nil is returned.
-func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string, instanceType instance.Type) (Response, error) {
+func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string, instanceType instance.Type) (response.Response, error) {
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
 	if err != nil {
@@ -201,7 +49,7 @@ func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, n
 	if client == nil {
 		return nil, nil
 	}
-	return ForwardedResponse(client, r), nil
+	return response.ForwardedResponse(client, r), nil
 }
 
 // ForwardedResponseIfVolumeIsRemote redirects a request to the node hosting
@@ -211,7 +59,7 @@ func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, n
 //
 // This is used when no targetNode is specified, and saves users some typing
 // when the volume name/type is unique to a node.
-func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64, volumeName string, volumeType int) Response {
+func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64, volumeName string, volumeType int) response.Response {
 	if queryParam(r, "target") != "" {
 		return nil
 	}
@@ -219,332 +67,10 @@ func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64,
 	cert := d.endpoints.NetworkCert()
 	client, err := cluster.ConnectIfVolumeIsRemote(d.cluster, poolID, volumeName, volumeType, cert)
 	if err != nil && err != db.ErrNoSuchObject {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if client == nil {
 		return nil
 	}
-	return ForwardedResponse(client, r)
-}
-
-// File transfer response
-type fileResponseEntry struct {
-	identifier string
-	path       string
-	filename   string
-	buffer     []byte /* either a path or a buffer must be provided */
-}
-
-type fileResponse struct {
-	req              *http.Request
-	files            []fileResponseEntry
-	headers          map[string]string
-	removeAfterServe bool
-}
-
-func (r *fileResponse) Render(w http.ResponseWriter) error {
-	if r.headers != nil {
-		for k, v := range r.headers {
-			w.Header().Set(k, v)
-		}
-	}
-
-	// No file, well, it's easy then
-	if len(r.files) == 0 {
-		return nil
-	}
-
-	// For a single file, return it inline
-	if len(r.files) == 1 {
-		var rs io.ReadSeeker
-		var mt time.Time
-		var sz int64
-
-		if r.files[0].path == "" {
-			rs = bytes.NewReader(r.files[0].buffer)
-			mt = time.Now()
-			sz = int64(len(r.files[0].buffer))
-		} else {
-			f, err := os.Open(r.files[0].path)
-			if err != nil {
-				return err
-			}
-			defer f.Close()
-
-			fi, err := f.Stat()
-			if err != nil {
-				return err
-			}
-
-			mt = fi.ModTime()
-			sz = fi.Size()
-			rs = f
-		}
-
-		w.Header().Set("Content-Type", "application/octet-stream")
-		w.Header().Set("Content-Length", fmt.Sprintf("%d", sz))
-		w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].filename))
-
-		http.ServeContent(w, r.req, r.files[0].filename, mt, rs)
-		if r.files[0].path != "" && r.removeAfterServe {
-			err := os.Remove(r.files[0].path)
-			if err != nil {
-				return err
-			}
-		}
-
-		return nil
-	}
-
-	// Now the complex multipart answer
-	body := &bytes.Buffer{}
-	mw := multipart.NewWriter(body)
-
-	for _, entry := range r.files {
-		var rd io.Reader
-		if entry.path != "" {
-			fd, err := os.Open(entry.path)
-			if err != nil {
-				return err
-			}
-			defer fd.Close()
-			rd = fd
-		} else {
-			rd = bytes.NewReader(entry.buffer)
-		}
-
-		fw, err := mw.CreateFormFile(entry.identifier, entry.filename)
-		if err != nil {
-			return err
-		}
-
-		_, err = io.Copy(fw, rd)
-		if err != nil {
-			return err
-		}
-	}
-	mw.Close()
-
-	w.Header().Set("Content-Type", mw.FormDataContentType())
-	w.Header().Set("Content-Length", fmt.Sprintf("%d", body.Len()))
-
-	_, err := io.Copy(w, body)
-	return err
-}
-
-func (r *fileResponse) String() string {
-	return fmt.Sprintf("%d files", len(r.files))
-}
-
-func FileResponse(r *http.Request, files []fileResponseEntry, headers map[string]string, removeAfterServe bool) Response {
-	return &fileResponse{r, files, headers, removeAfterServe}
-}
-
-// Operation response
-type operationResponse struct {
-	op *operation
-}
-
-func (r *operationResponse) Render(w http.ResponseWriter) error {
-	_, err := r.op.Run()
-	if err != nil {
-		return err
-	}
-
-	url, md, err := r.op.Render()
-	if err != nil {
-		return err
-	}
-
-	body := api.ResponseRaw{
-		Type:       api.AsyncResponse,
-		Status:     api.OperationCreated.String(),
-		StatusCode: int(api.OperationCreated),
-		Operation:  url,
-		Metadata:   md,
-	}
-
-	w.Header().Set("Location", url)
-	w.WriteHeader(202)
-
-	return util.WriteJSON(w, body, debug)
-}
-
-func (r *operationResponse) String() string {
-	_, md, err := r.op.Render()
-	if err != nil {
-		return fmt.Sprintf("error: %s", err)
-	}
-
-	return md.ID
-}
-
-func OperationResponse(op *operation) Response {
-	return &operationResponse{op}
-}
-
-// Forwarded operation response.
-//
-// Returned when the operation has been created on another node
-type forwardedOperationResponse struct {
-	op      *api.Operation
-	project string
-}
-
-func (r *forwardedOperationResponse) Render(w http.ResponseWriter) error {
-	url := fmt.Sprintf("/%s/operations/%s", version.APIVersion, r.op.ID)
-	if r.project != "" {
-		url += fmt.Sprintf("?project=%s", r.project)
-	}
-
-	body := api.ResponseRaw{
-		Type:       api.AsyncResponse,
-		Status:     api.OperationCreated.String(),
-		StatusCode: int(api.OperationCreated),
-		Operation:  url,
-		Metadata:   r.op,
-	}
-
-	w.Header().Set("Location", url)
-	w.WriteHeader(202)
-
-	return util.WriteJSON(w, body, debug)
-}
-
-func (r *forwardedOperationResponse) String() string {
-	return r.op.ID
-}
-
-// ForwardedOperationResponse creates a response that forwards the metadata of
-// an operation created on another node.
-func ForwardedOperationResponse(project string, op *api.Operation) Response {
-	return &forwardedOperationResponse{
-		op:      op,
-		project: project,
-	}
-}
-
-// Error response
-type errorResponse struct {
-	code int
-	msg  string
-}
-
-func (r *errorResponse) String() string {
-	return r.msg
-}
-
-func (r *errorResponse) Render(w http.ResponseWriter) error {
-	var output io.Writer
-
-	buf := &bytes.Buffer{}
-	output = buf
-	var captured *bytes.Buffer
-	if debug {
-		captured = &bytes.Buffer{}
-		output = io.MultiWriter(buf, captured)
-	}
-
-	err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
-
-	if err != nil {
-		return err
-	}
-
-	if debug {
-		shared.DebugJson(captured)
-	}
-
-	w.Header().Set("Content-Type", "application/json")
-	w.Header().Set("X-Content-Type-Options", "nosniff")
-	w.WriteHeader(r.code)
-	fmt.Fprintln(w, buf.String())
-
-	return nil
-}
-
-func NotImplemented(err error) Response {
-	message := "not implemented"
-	if err != nil {
-		message = err.Error()
-	}
-	return &errorResponse{http.StatusNotImplemented, message}
-}
-
-func NotFound(err error) Response {
-	message := "not found"
-	if err != nil {
-		message = err.Error()
-	}
-	return &errorResponse{http.StatusNotFound, message}
-}
-
-func Forbidden(err error) Response {
-	message := "not authorized"
-	if err != nil {
-		message = err.Error()
-	}
-	return &errorResponse{http.StatusForbidden, message}
-}
-
-func Conflict(err error) Response {
-	message := "already exists"
-	if err != nil {
-		message = err.Error()
-	}
-	return &errorResponse{http.StatusConflict, message}
-}
-
-func Unavailable(err error) Response {
-	message := "unavailable"
-	if err != nil {
-		message = err.Error()
-	}
-	return &errorResponse{http.StatusServiceUnavailable, message}
-}
-
-func BadRequest(err error) Response {
-	return &errorResponse{http.StatusBadRequest, err.Error()}
-}
-
-func InternalError(err error) Response {
-	return &errorResponse{http.StatusInternalServerError, err.Error()}
-}
-
-func PreconditionFailed(err error) Response {
-	return &errorResponse{http.StatusPreconditionFailed, err.Error()}
-}
-
-/*
- * SmartError returns the right error message based on err.
- */
-func SmartError(err error) Response {
-	if err == nil {
-		return EmptySyncResponse
-	}
-
-	switch errors.Cause(err) {
-	case os.ErrNotExist, sql.ErrNoRows, db.ErrNoSuchObject:
-		if errors.Cause(err) != err {
-			return NotFound(err)
-		}
-
-		return NotFound(nil)
-	case os.ErrPermission:
-		if errors.Cause(err) != err {
-			return Forbidden(err)
-		}
-
-		return Forbidden(nil)
-	case db.ErrAlreadyDefined, sqlite3.ErrConstraintUnique:
-		if errors.Cause(err) != err {
-			return Conflict(err)
-		}
-
-		return Conflict(nil)
-	case driver.ErrNoAvailableLeader:
-		return Unavailable(err)
-	default:
-		return InternalError(err)
-	}
+	return response.ForwardedResponse(client, r)
 }
diff --git a/lxd/response/response.go b/lxd/response/response.go
new file mode 100644
index 0000000000..713bfa6f1c
--- /dev/null
+++ b/lxd/response/response.go
@@ -0,0 +1,425 @@
+package response
+
+import (
+	"bytes"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+	"time"
+
+	"github.com/canonical/go-dqlite/driver"
+	lxd "github.com/lxc/lxd/client"
+	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/shared/api"
+	"github.com/mattn/go-sqlite3"
+	"github.com/pkg/errors"
+)
+
+var debug bool
+
+// Init sets the debug variable to the provided value.
+func Init(d bool) {
+	debug = d
+}
+
+// Response represents an API response
+type Response interface {
+	Render(w http.ResponseWriter) error
+	String() string
+}
+
+// Sync response
+type syncResponse struct {
+	success  bool
+	etag     interface{}
+	metadata interface{}
+	location string
+	code     int
+	headers  map[string]string
+}
+
+// EmptySyncResponse represents an empty syncResponse.
+var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
+
+// SyncResponse returns a new syncResponse with the success and metadata fields
+// set to the provided values.
+func SyncResponse(success bool, metadata interface{}) Response {
+	return &syncResponse{success: success, metadata: metadata}
+}
+
+// SyncResponseETag returns a new syncResponse with an etag.
+func SyncResponseETag(success bool, metadata interface{}, etag interface{}) Response {
+	return &syncResponse{success: success, metadata: metadata, etag: etag}
+}
+
+// SyncResponseLocation returns a new syncResponse with a location.
+func SyncResponseLocation(success bool, metadata interface{}, location string) Response {
+	return &syncResponse{success: success, metadata: metadata, location: location}
+}
+
+// SyncResponseRedirect returns a new syncResponse with a location, indicating
+// a permanent redirect.
+func SyncResponseRedirect(address string) Response {
+	return &syncResponse{success: true, location: address, code: http.StatusPermanentRedirect}
+}
+
+// SyncResponseHeaders returns a new syncResponse with headers.
+func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]string) Response {
+	return &syncResponse{success: success, metadata: metadata, headers: headers}
+}
+
+func (r *syncResponse) Render(w http.ResponseWriter) error {
+	// Set an appropriate ETag header
+	if r.etag != nil {
+		etag, err := util.EtagHash(r.etag)
+		if err == nil {
+			w.Header().Set("ETag", etag)
+		}
+	}
+
+	// Prepare the JSON response
+	status := api.Success
+	if !r.success {
+		status = api.Failure
+	}
+
+	if r.headers != nil {
+		for h, v := range r.headers {
+			w.Header().Set(h, v)
+		}
+	}
+
+	if r.location != "" {
+		w.Header().Set("Location", r.location)
+		code := r.code
+		if code == 0 {
+			code = 201
+		}
+		w.WriteHeader(code)
+	}
+
+	resp := api.ResponseRaw{
+		Type:       api.SyncResponse,
+		Status:     status.String(),
+		StatusCode: int(status),
+		Metadata:   r.metadata,
+	}
+
+	return util.WriteJSON(w, resp, debug)
+}
+
+func (r *syncResponse) String() string {
+	if r.success {
+		return "success"
+	}
+
+	return "failure"
+}
+
+// Error response
+type errorResponse struct {
+	code int
+	msg  string
+}
+
+// ErrorResponse returns an error response with the given code and msg.
+func ErrorResponse(code int, msg string) Response {
+	return &errorResponse{code, msg}
+}
+
+// BadRequest returns a bad request response (400) with the given error.
+func BadRequest(err error) Response {
+	return &errorResponse{http.StatusBadRequest, err.Error()}
+}
+
+// Conflict returns a conflict response (409) with the given error.
+func Conflict(err error) Response {
+	message := "already exists"
+	if err != nil {
+		message = err.Error()
+	}
+	return &errorResponse{http.StatusConflict, message}
+}
+
+// Forbidden returns a forbidden response (403) with the given error.
+func Forbidden(err error) Response {
+	message := "not authorized"
+	if err != nil {
+		message = err.Error()
+	}
+	return &errorResponse{http.StatusForbidden, message}
+}
+
+// InternalError returns an internal error response (500) with the given error.
+func InternalError(err error) Response {
+	return &errorResponse{http.StatusInternalServerError, err.Error()}
+}
+
+// NotFound returns a not found response (404) with the given error.
+func NotFound(err error) Response {
+	message := "not found"
+	if err != nil {
+		message = err.Error()
+	}
+	return &errorResponse{http.StatusNotFound, message}
+}
+
+// NotImplemented returns a not implemented response (501) with the given error.
+func NotImplemented(err error) Response {
+	message := "not implemented"
+	if err != nil {
+		message = err.Error()
+	}
+	return &errorResponse{http.StatusNotImplemented, message}
+}
+
+// PreconditionFailed returns a precondition failed response (412) with the
+// given error.
+func PreconditionFailed(err error) Response {
+	return &errorResponse{http.StatusPreconditionFailed, err.Error()}
+}
+
+// Unavailable return an unavailable response (503) with the given error.
+func Unavailable(err error) Response {
+	message := "unavailable"
+	if err != nil {
+		message = err.Error()
+	}
+	return &errorResponse{http.StatusServiceUnavailable, message}
+}
+
+// SmartError returns the right error message based on err.
+func SmartError(err error) Response {
+	if err == nil {
+		return EmptySyncResponse
+	}
+
+	switch errors.Cause(err) {
+	case os.ErrNotExist, sql.ErrNoRows, db.ErrNoSuchObject:
+		if errors.Cause(err) != err {
+			return NotFound(err)
+		}
+
+		return NotFound(nil)
+	case os.ErrPermission:
+		if errors.Cause(err) != err {
+			return Forbidden(err)
+		}
+
+		return Forbidden(nil)
+	case db.ErrAlreadyDefined, sqlite3.ErrConstraintUnique:
+		if errors.Cause(err) != err {
+			return Conflict(err)
+		}
+
+		return Conflict(nil)
+	case driver.ErrNoAvailableLeader:
+		return Unavailable(err)
+	default:
+		return InternalError(err)
+	}
+}
+
+func (r *errorResponse) String() string {
+	return r.msg
+}
+
+func (r *errorResponse) Render(w http.ResponseWriter) error {
+	var output io.Writer
+
+	buf := &bytes.Buffer{}
+	output = buf
+	var captured *bytes.Buffer
+	if debug {
+		captured = &bytes.Buffer{}
+		output = io.MultiWriter(buf, captured)
+	}
+
+	err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
+
+	if err != nil {
+		return err
+	}
+
+	if debug {
+		shared.DebugJson(captured)
+	}
+
+	w.Header().Set("Content-Type", "application/json")
+	w.Header().Set("X-Content-Type-Options", "nosniff")
+	w.WriteHeader(r.code)
+	fmt.Fprintln(w, buf.String())
+
+	return nil
+}
+
+// FileResponseEntry represents a file response entry.
+type FileResponseEntry struct {
+	Identifier string
+	Path       string
+	Filename   string
+	Buffer     []byte /* either a path or a buffer must be provided */
+}
+
+type fileResponse struct {
+	req              *http.Request
+	files            []FileResponseEntry
+	headers          map[string]string
+	removeAfterServe bool
+}
+
+// FileResponse returns a new file response.
+func FileResponse(r *http.Request, files []FileResponseEntry, headers map[string]string, removeAfterServe bool) Response {
+	return &fileResponse{r, files, headers, removeAfterServe}
+}
+
+func (r *fileResponse) Render(w http.ResponseWriter) error {
+	if r.headers != nil {
+		for k, v := range r.headers {
+			w.Header().Set(k, v)
+		}
+	}
+
+	// No file, well, it's easy then
+	if len(r.files) == 0 {
+		return nil
+	}
+
+	// For a single file, return it inline
+	if len(r.files) == 1 {
+		var rs io.ReadSeeker
+		var mt time.Time
+		var sz int64
+
+		if r.files[0].Path == "" {
+			rs = bytes.NewReader(r.files[0].Buffer)
+			mt = time.Now()
+			sz = int64(len(r.files[0].Buffer))
+		} else {
+			f, err := os.Open(r.files[0].Path)
+			if err != nil {
+				return err
+			}
+			defer f.Close()
+
+			fi, err := f.Stat()
+			if err != nil {
+				return err
+			}
+
+			mt = fi.ModTime()
+			sz = fi.Size()
+			rs = f
+		}
+
+		w.Header().Set("Content-Type", "application/octet-stream")
+		w.Header().Set("Content-Length", fmt.Sprintf("%d", sz))
+		w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].Filename))
+
+		http.ServeContent(w, r.req, r.files[0].Filename, mt, rs)
+		if r.files[0].Path != "" && r.removeAfterServe {
+			err := os.Remove(r.files[0].Path)
+			if err != nil {
+				return err
+			}
+		}
+
+		return nil
+	}
+
+	// Now the complex multipart answer
+	body := &bytes.Buffer{}
+	mw := multipart.NewWriter(body)
+
+	for _, entry := range r.files {
+		var rd io.Reader
+		if entry.Path != "" {
+			fd, err := os.Open(entry.Path)
+			if err != nil {
+				return err
+			}
+			defer fd.Close()
+			rd = fd
+		} else {
+			rd = bytes.NewReader(entry.Buffer)
+		}
+
+		fw, err := mw.CreateFormFile(entry.Identifier, entry.Filename)
+		if err != nil {
+			return err
+		}
+
+		_, err = io.Copy(fw, rd)
+		if err != nil {
+			return err
+		}
+	}
+	mw.Close()
+
+	w.Header().Set("Content-Type", mw.FormDataContentType())
+	w.Header().Set("Content-Length", fmt.Sprintf("%d", body.Len()))
+
+	_, err := io.Copy(w, body)
+	return err
+}
+
+func (r *fileResponse) String() string {
+	return fmt.Sprintf("%d files", len(r.files))
+}
+
+type forwardedResponse struct {
+	client  lxd.InstanceServer
+	request *http.Request
+}
+
+// ForwardedResponse takes a request directed to a node and forwards it to
+// another node, writing back the response it gegs.
+func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
+	return &forwardedResponse{
+		client:  client,
+		request: request,
+	}
+}
+
+func (r *forwardedResponse) Render(w http.ResponseWriter) error {
+	info, err := r.client.GetConnectionInfo()
+	if err != nil {
+		return err
+	}
+
+	url := fmt.Sprintf("%s%s", info.Addresses[0], r.request.URL.RequestURI())
+	forwarded, err := http.NewRequest(r.request.Method, url, r.request.Body)
+	if err != nil {
+		return err
+	}
+	for key := range r.request.Header {
+		forwarded.Header.Set(key, r.request.Header.Get(key))
+	}
+
+	httpClient, err := r.client.GetHTTPClient()
+	if err != nil {
+		return err
+	}
+	response, err := httpClient.Do(forwarded)
+	if err != nil {
+		return err
+	}
+
+	for key := range response.Header {
+		w.Header().Set(key, response.Header.Get(key))
+	}
+
+	w.WriteHeader(response.StatusCode)
+	_, err = io.Copy(w, response.Body)
+	return err
+}
+
+func (r *forwardedResponse) String() string {
+	return fmt.Sprintf("request to %s", r.request.URL)
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index b0191ee4aa..dd5d5d188b 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -15,6 +15,7 @@ import (
 	"github.com/lxc/lxd/lxd/device"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -248,10 +249,10 @@ type storage interface {
 	// already present on the target instance as an exercise for the
 	// enterprising developer.
 	MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error)
-	MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error
+	MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error
 
 	StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error)
-	StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error
+	StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error
 }
 
 func storageCoreInit(driver string) (storage, error) {
@@ -739,8 +740,8 @@ func resetContainerDiskIdmap(container container, srcIdmap *idmap.IdmapSet) erro
 	return nil
 }
 
-func progressWrapperRender(op *operation, key string, description string, progressInt int64, speedInt int64) {
-	meta := op.metadata
+func progressWrapperRender(op *operations.Operation, key string, description string, progressInt int64, speedInt int64) {
+	meta := op.Metadata()
 	if meta == nil {
 		meta = make(map[string]interface{})
 	}
@@ -757,7 +758,7 @@ func progressWrapperRender(op *operation, key string, description string, progre
 }
 
 // StorageProgressReader reports the read progress.
-func StorageProgressReader(op *operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
+func StorageProgressReader(op *operations.Operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
 	return func(reader io.ReadCloser) io.ReadCloser {
 		if op == nil {
 			return reader
@@ -779,7 +780,7 @@ func StorageProgressReader(op *operation, key string, description string) func(i
 }
 
 // StorageProgressWriter reports the write progress.
-func StorageProgressWriter(op *operation, key string, description string) func(io.WriteCloser) io.WriteCloser {
+func StorageProgressWriter(op *operations.Operation, key string, description string) func(io.WriteCloser) io.WriteCloser {
 	return func(writer io.WriteCloser) io.WriteCloser {
 		if op == nil {
 			return writer
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index f339b710a3..9233d27b60 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -17,6 +17,7 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/lxd/state"
 	driver "github.com/lxc/lxd/lxd/storage"
@@ -2481,7 +2482,7 @@ func (s *storageBtrfs) MigrationSource(args MigrationSourceArgs) (MigrationStora
 	return sourceDriver, nil
 }
 
-func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	if s.s.OS.RunningInUserNS {
 		return rsyncMigrationSink(conn, op, args)
 	}
@@ -2961,7 +2962,7 @@ func (s *storageBtrfs) StorageMigrationSource(args MigrationSourceArgs) (Migrati
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageBtrfs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageBtrfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 0abd661940..8c0282f519 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -16,6 +16,7 @@ import (
 
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -2689,7 +2690,7 @@ func (s *storageCeph) StorageMigrationSource(args MigrationSourceArgs) (Migratio
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageCeph) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCeph) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 
@@ -2874,7 +2875,7 @@ func (s *storageCeph) MigrationSource(args MigrationSourceArgs) (MigrationStorag
 	return &driver, nil
 }
 
-func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	// Check that we received a valid root disk device with a pool property
 	// set.
 	parentStoragePool := ""
diff --git a/lxd/storage_cephfs.go b/lxd/storage_cephfs.go
index 0c3f8b9aa3..ed3f219ece 100644
--- a/lxd/storage_cephfs.go
+++ b/lxd/storage_cephfs.go
@@ -14,6 +14,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -728,7 +729,7 @@ func (s *storageCephFs) MigrationSource(args MigrationSourceArgs) (MigrationStor
 	return rsyncMigrationSource(args)
 }
 
-func (s *storageCephFs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCephFs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncMigrationSink(conn, op, args)
 }
 
@@ -824,7 +825,7 @@ func (s *storageCephFs) StorageMigrationSource(args MigrationSourceArgs) (Migrat
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageCephFs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCephFs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index ff5973a8f7..c24eecaae4 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/lxd/storage/quota"
@@ -1306,7 +1307,7 @@ func (s *storageDir) MigrationSource(args MigrationSourceArgs) (MigrationStorage
 	return rsyncMigrationSource(args)
 }
 
-func (s *storageDir) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageDir) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncMigrationSink(conn, op, args)
 }
 
@@ -1442,7 +1443,7 @@ func (s *storageDir) StorageMigrationSource(args MigrationSourceArgs) (Migration
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageDir) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageDir) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index b3ef0b20eb..d41704a8bf 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -14,6 +14,7 @@ import (
 	"github.com/pkg/errors"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -2068,7 +2069,7 @@ func (s *storageLvm) MigrationSource(args MigrationSourceArgs) (MigrationStorage
 	return rsyncMigrationSource(args)
 }
 
-func (s *storageLvm) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageLvm) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncMigrationSink(conn, op, args)
 }
 
@@ -2265,7 +2266,7 @@ func (s *storageLvm) StorageMigrationSource(args MigrationSourceArgs) (Migration
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageLvm) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageLvm) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 8d329cd5fa..a8f42d06bb 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -10,6 +10,7 @@ import (
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
 	"github.com/lxc/lxd/lxd/instance"
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
@@ -23,7 +24,7 @@ type MigrationStorageSourceDriver interface {
 	/* send any bits of the container/snapshots that are possible while the
 	 * container is still running.
 	 */
-	SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error
+	SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error
 
 	/* send the final bits (e.g. a final delta snapshot for zfs, btrfs, or
 	 * do a final rsync) of the fs after the container has been
@@ -37,7 +38,7 @@ type MigrationStorageSourceDriver interface {
 	 */
 	Cleanup()
 
-	SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error
+	SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error
 }
 
 type rsyncStorageSourceDriver struct {
@@ -46,7 +47,7 @@ type rsyncStorageSourceDriver struct {
 	rsyncFeatures []string
 }
 
-func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
 	ourMount, err := storage.StoragePoolVolumeMount()
 	if err != nil {
 		return err
@@ -90,7 +91,7 @@ func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *op
 	return nil
 }
 
-func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
 	ctName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
 
 	if !containerOnly {
@@ -220,7 +221,7 @@ func snapshotProtobufToContainerArgs(project string, containerName string, snap
 	return args
 }
 
-func rsyncStorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func rsyncStorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	err := args.Storage.StoragePoolVolumeCreate()
 	if err != nil {
 		return err
@@ -281,7 +282,7 @@ func rsyncStorageMigrationSink(conn *websocket.Conn, op *operation, args Migrati
 	return RsyncRecv(path, conn, wrapper, args.RsyncFeatures)
 }
 
-func rsyncMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func rsyncMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	ourStart, err := args.Instance.StorageStart()
 	if err != nil {
 		return err
diff --git a/lxd/storage_migration_btrfs.go b/lxd/storage_migration_btrfs.go
index 166fd8599c..15a3c936c3 100644
--- a/lxd/storage_migration_btrfs.go
+++ b/lxd/storage_migration_btrfs.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operations"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -67,7 +68,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 	return err
 }
 
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
 	_, containerPool, _ := s.container.Storage().GetContainerPoolInfo()
 	containerName := s.container.Name()
 	containersPath := driver.GetContainerMountPoint("default", containerPool, "")
@@ -178,7 +179,7 @@ func (s *btrfsMigrationSourceDriver) Cleanup() {
 	}
 }
 
-func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
 	msg := fmt.Sprintf("Function not implemented")
 	logger.Errorf(msg)
 	return fmt.Errorf(msg)
diff --git a/lxd/storage_migration_ceph.go b/lxd/storage_migration_ceph.go
index 90fa2b80a5..aa13e326d4 100644
--- a/lxd/storage_migration_ceph.go
+++ b/lxd/storage_migration_ceph.go
@@ -9,6 +9,7 @@ import (
 	"github.com/gorilla/websocket"
 	"github.com/pborman/uuid"
 
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -73,7 +74,7 @@ func (s *rbdMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwl
 }
 
 func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
-	op *operation, bwlimit string, containerOnly bool) error {
+	op *operations.Operation, bwlimit string, containerOnly bool) error {
 	containerName := s.container.Name()
 	if s.container.IsSnapshot() {
 		// ContainerSnapshotStart() will create the clone that is
@@ -149,7 +150,7 @@ func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
 	return nil
 }
 
-func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
 	msg := fmt.Sprintf("Function not implemented")
 	logger.Errorf(msg)
 	return fmt.Errorf(msg)
diff --git a/lxd/storage_migration_zfs.go b/lxd/storage_migration_zfs.go
index fe94bf6eab..f3bc582efe 100644
--- a/lxd/storage_migration_zfs.go
+++ b/lxd/storage_migration_zfs.go
@@ -9,6 +9,7 @@ import (
 	"github.com/gorilla/websocket"
 	"github.com/pborman/uuid"
 
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logger"
@@ -78,7 +79,7 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 	return err
 }
 
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
 	if s.instance.IsSnapshot() {
 		_, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(s.instance.Name())
 		snapshotName := fmt.Sprintf("snapshot-%s", snapOnlyName)
@@ -139,7 +140,7 @@ func (s *zfsMigrationSourceDriver) Cleanup() {
 	}
 }
 
-func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
 	msg := fmt.Sprintf("Function not implemented")
 	logger.Errorf(msg)
 	return fmt.Errorf(msg)
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 3b9520928e..d3ab28b093 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -6,6 +6,7 @@ import (
 	"github.com/gorilla/websocket"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared/api"
 	"github.com/lxc/lxd/shared/ioprogress"
@@ -211,7 +212,7 @@ func (s *storageMock) MigrationSource(args MigrationSourceArgs) (MigrationStorag
 	return nil, nil
 }
 
-func (s *storageMock) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageMock) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return nil
 }
 
@@ -231,7 +232,7 @@ func (s *storageMock) StorageMigrationSource(args MigrationSourceArgs) (Migratio
 	return nil, nil
 }
 
-func (s *storageMock) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageMock) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return nil
 }
 
diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index a9c5e60b10..6634c4c240 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -13,6 +13,7 @@ import (
 	lxd "github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/cluster"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/response"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -41,12 +42,12 @@ var storagePoolCmd = APIEndpoint{
 
 // /1.0/storage-pools
 // List all storage pools.
-func storagePoolsGet(d *Daemon, r *http.Request) Response {
+func storagePoolsGet(d *Daemon, r *http.Request) response.Response {
 	recursion := util.IsRecursionRequest(r)
 
 	pools, err := d.cluster.StoragePools()
 	if err != nil && err != db.ErrNoSuchObject {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	resultString := []string{}
@@ -63,7 +64,7 @@ func storagePoolsGet(d *Daemon, r *http.Request) Response {
 			// Get all users of the storage pool.
 			poolUsedBy, err := storagePoolUsedByGet(d.State(), plID, pool)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 			pl.UsedBy = poolUsedBy
 
@@ -72,15 +73,15 @@ func storagePoolsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
 // /1.0/storage-pools
 // Create a storage pool.
-func storagePoolsPost(d *Daemon, r *http.Request) Response {
+func storagePoolsPost(d *Daemon, r *http.Request) response.Response {
 	storagePoolCreateLock.Lock()
 	defer storagePoolCreateLock.Unlock()
 
@@ -89,24 +90,24 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks.
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Storage pool names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Storage pool names may not contain slashes"))
 	}
 
 	if req.Driver == "" {
-		return BadRequest(fmt.Errorf("No driver provided"))
+		return response.BadRequest(fmt.Errorf("No driver provided"))
 	}
 
 	url := fmt.Sprintf("/%s/storage-pools/%s", version.APIVersion, req.Name)
-	response := SyncResponseLocation(true, nil, url)
+	resp := response.SyncResponseLocation(true, nil, url)
 
 	if isClusterNotification(r) {
 		// This is an internal request which triggers the actual
@@ -114,21 +115,21 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
 		// previously defined.
 		err = storagePoolValidate(req.Name, req.Driver, req.Config)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 		err = doStoragePoolCreateInternal(
 			d.State(), req.Name, req.Description, req.Driver, req.Config, true)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return response
+		return resp
 	}
 
 	targetNode := queryParam(r, "target")
 	if targetNode == "" {
 		count, err := cluster.Count(d.State())
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		if count == 1 {
@@ -143,9 +144,9 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
 			err = storagePoolsPostCluster(d, req)
 		}
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
-		return response
+		return resp
 
 	}
 
@@ -154,13 +155,13 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
 	// storage config are the ones in StoragePoolNodeConfigKeys.
 	for key := range req.Config {
 		if !shared.StringInSlice(key, db.StoragePoolNodeConfigKeys) {
-			return SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
+			return response.SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
 		}
 	}
 
 	err = storagePoolValidate(req.Name, req.Driver, req.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -168,12 +169,12 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
 	})
 	if err != nil {
 		if err == db.ErrAlreadyDefined {
-			return BadRequest(fmt.Errorf("The storage pool already defined on node %s", targetNode))
+			return response.BadRequest(fmt.Errorf("The storage pool already defined on node %s", targetNode))
 		}
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return response
+	return resp
 }
 
 func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
@@ -269,11 +270,11 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
 
 // /1.0/storage-pools/{name}
 // Get a single storage pool.
-func storagePoolGet(d *Daemon, r *http.Request) Response {
+func storagePoolGet(d *Daemon, r *http.Request) response.Response {
 	// If a target was specified, forward the request to the relevant node.
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolName := mux.Vars(r)["name"]
@@ -281,13 +282,13 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
 	// Get the existing storage pool.
 	poolID, pool, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get all users of the storage pool.
 	poolUsedBy, err := storagePoolUsedByGet(d.State(), poolID, poolName)
 	if err != nil && err != db.ErrNoSuchObject {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	pool.UsedBy = poolUsedBy
 
@@ -295,7 +296,7 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
 
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// If no target node is specified and the daemon is clustered, we omit
@@ -308,35 +309,35 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
 
 	etag := []interface{}{pool.Name, pool.Driver, pool.Config}
 
-	return SyncResponseETag(true, &pool, etag)
+	return response.SyncResponseETag(true, &pool, etag)
 }
 
 // /1.0/storage-pools/{name}
 // Replace pool properties.
-func storagePoolPut(d *Daemon, r *http.Request) Response {
+func storagePoolPut(d *Daemon, r *http.Request) response.Response {
 	poolName := mux.Vars(r)["name"]
 
 	// Get the existing storage pool.
 	_, dbInfo, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	req := api.StoragePoolPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	config := dbInfo.Config
 	if clustered {
 		err := storagePoolValidateClusterConfig(req.Config)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 		config = storagePoolClusterConfigForEtag(config)
 	}
@@ -346,13 +347,13 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	// Validate the configuration
 	err = storagePoolValidateConfig(poolName, dbInfo.Driver, req.Config, dbInfo.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	config = req.Config
@@ -367,51 +368,51 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
 		cert := d.endpoints.NetworkCert()
 		notifier, err := cluster.NewNotifier(d.State(), cert, cluster.NotifyAll)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	withDB := !isClusterNotification(r)
 	err = storagePoolUpdate(d.State(), poolName, req.Description, config, withDB)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // /1.0/storage-pools/{name}
 // Change pool properties.
-func storagePoolPatch(d *Daemon, r *http.Request) Response {
+func storagePoolPatch(d *Daemon, r *http.Request) response.Response {
 	poolName := mux.Vars(r)["name"]
 
 	// Get the existing network
 	_, dbInfo, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	req := api.StoragePoolPut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	config := dbInfo.Config
 	if clustered {
 		err := storagePoolValidateClusterConfig(req.Config)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 		config = storagePoolClusterConfigForEtag(config)
 	}
@@ -421,7 +422,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	// Config stacking
@@ -439,7 +440,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
 	// Validate the configuration
 	err = storagePoolValidateConfig(poolName, dbInfo.Driver, req.Config, dbInfo.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	config = req.Config
@@ -454,23 +455,23 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
 		cert := d.endpoints.NetworkCert()
 		notifier, err := cluster.NewNotifier(d.State(), cert, cluster.NotifyAll)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		err = notifier(func(client lxd.InstanceServer) error {
 			return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	withDB := !isClusterNotification(r)
 	err = storagePoolUpdate(d.State(), poolName, req.Description, config, withDB)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // This helper makes sure that, when clustered, we're not changing
@@ -510,12 +511,12 @@ func storagePoolClusterFillWithNodeConfig(dbConfig, reqConfig map[string]string)
 
 // /1.0/storage-pools/{name}
 // Delete storage pool.
-func storagePoolDelete(d *Daemon, r *http.Request) Response {
+func storagePoolDelete(d *Daemon, r *http.Request) response.Response {
 	poolName := mux.Vars(r)["name"]
 
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	// If this is not an internal cluster request, check if the storage
@@ -531,19 +532,19 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
 	// the database.
 	_, pool, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if pool.Status == "Pending" {
 		_, err := d.cluster.StoragePoolDelete(poolName)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	s, err := storagePoolInit(d.State(), poolName)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// If this is a notification for a ceph pool deletion, we don't want to
@@ -555,49 +556,49 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
 		if shared.PathExists(poolMntPoint) {
 			err := os.RemoveAll(poolMntPoint)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	volumeNames, err := d.cluster.StoragePoolVolumesGetNames(poolID)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	for _, volume := range volumeNames {
 		_, imgInfo, err := d.cluster.ImageGet("default", volume, false, false)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		err = doDeleteImageFromPool(d.State(), imgInfo.Fingerprint, poolName)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
 	err = s.StoragePoolDelete()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// If this is a cluster notification, we're done, any database work
 	// will be done by the node that is originally serving the request.
 	if isClusterNotification(r) {
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	// If we are clustered, also notify all other nodes, if any.
 	clustered, err := cluster.Enabled(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if clustered {
 		notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAll)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		err = notifier(func(client lxd.InstanceServer) error {
 			_, _, err := client.GetServer()
@@ -607,33 +608,33 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
 			return client.DeleteStoragePool(poolName)
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
 	err = dbStoragePoolDeleteAndUpdateCache(d.cluster, poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, poolID int64) Response {
+func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, poolID int64) response.Response {
 	volumeNames, err := cluster.StoragePoolVolumesGetNames(poolID)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if len(volumeNames) > 0 {
 		volumes, err := cluster.StoragePoolVolumesGet("default", poolID, supportedVolumeTypes)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		for _, volume := range volumes {
 			if volume.Type != "image" {
-				return BadRequest(fmt.Errorf("storage pool \"%s\" has volumes attached to it", poolName))
+				return response.BadRequest(fmt.Errorf("storage pool \"%s\" has volumes attached to it", poolName))
 			}
 		}
 	}
@@ -641,11 +642,11 @@ func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, p
 	// Check if the storage pool is still referenced in any profiles.
 	profiles, err := profilesUsingPoolGetNames(cluster, poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if len(profiles) > 0 {
-		return BadRequest(fmt.Errorf("Storage pool \"%s\" has profiles using it:\n%s", poolName, strings.Join(profiles, "\n")))
+		return response.BadRequest(fmt.Errorf("Storage pool \"%s\" has profiles using it:\n%s", poolName, strings.Join(profiles, "\n")))
 	}
 
 	return nil
diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index e9baf04748..6b4378bf0d 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -12,6 +12,8 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/websocket"
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/api"
@@ -67,7 +69,7 @@ var storagePoolVolumeTypeImageCmd = APIEndpoint{
 
 // /1.0/storage-pools/{name}/volumes
 // List all storage volumes attached to a given storage pool.
-func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 	poolName := mux.Vars(r)["name"]
 
@@ -77,7 +79,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
 	// exists).
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get all volumes currently attached to the storage pool by ID of the
@@ -91,17 +93,17 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
 	// by the project.
 	volumes, err := d.cluster.StoragePoolVolumesGet(project, poolID, supportedVolumeTypesExceptImages)
 	if err != nil && err != db.ErrNoSuchObject {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	imageVolumes, err := d.cluster.StoragePoolVolumesGet("default", poolID, []int{storagePoolVolumeTypeImage})
 	if err != nil && err != db.ErrNoSuchObject {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	projectImages, err := d.cluster.ImagesGet(project, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	for _, volume := range imageVolumes {
 		if shared.StringInSlice(volume.Name, projectImages) {
@@ -113,7 +115,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
 	for _, volume := range volumes {
 		apiEndpoint, err := storagePoolVolumeTypeNameToAPIEndpoint(volume.Type)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		if apiEndpoint == storagePoolVolumeAPIEndpointContainers {
@@ -136,22 +138,22 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
 		} else {
 			volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volume.Name, volume.Type)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			volume.UsedBy = volumeUsedBy
 		}
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, volumes)
+	return response.SyncResponse(true, volumes)
 }
 
 // /1.0/storage-pools/{name}/volumes/{type}
 // List all storage volumes of a given volume type for a given storage pool.
-func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) response.Response {
 	project := projectParam(r)
 
 	// Get the name of the pool the storage volume is supposed to be
@@ -166,25 +168,25 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
 	// Retrieve ID of the storage pool (and check if the storage pool
 	// exists).
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get the names of all storage volumes of a given volume type currently
 	// attached to the storage pool.
 	volumes, err := d.cluster.StoragePoolNodeVolumesGetType(volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	resultString := []string{}
@@ -193,7 +195,7 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
 		if !recursion {
 			apiEndpoint, err := storagePoolVolumeTypeToAPIEndpoint(volumeType)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			if apiEndpoint == storagePoolVolumeAPIEndpointContainers {
@@ -211,7 +213,7 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
 
 			volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, vol.Name, vol.Type)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 			vol.UsedBy = volumeUsedBy
 
@@ -220,18 +222,18 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
 // /1.0/storage-pools/{name}/volumes/{type}
 // Create a storage volume in a given storage pool.
-func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+func storagePoolVolumesTypePost(d *Daemon, r *http.Request) response.Response {
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	req := api.StorageVolumesPost{}
@@ -239,16 +241,16 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks.
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
 	}
 
 	req.Type = mux.Vars(r)["type"]
@@ -257,7 +259,7 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
 	// storagePoolVolumeTypeCustom. So check, that nothing else was
 	// requested.
 	if req.Type != storagePoolVolumeTypeNameCustom {
-		return BadRequest(fmt.Errorf(`Currently not allowed to create `+
+		return response.BadRequest(fmt.Errorf(`Currently not allowed to create `+
 			`storage volumes of type %s`, req.Type))
 	}
 
@@ -271,11 +273,11 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
 	case "migration":
 		return doVolumeMigration(d, poolName, &req)
 	default:
-		return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+		return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
 	}
 }
 
-func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPost) Response {
+func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPost) response.Response {
 	doWork := func() error {
 		return storagePoolVolumeCreateInternal(d.State(), poolName, req)
 	}
@@ -283,31 +285,31 @@ func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPos
 	if req.Source.Name == "" {
 		err := doWork()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		return doWork()
 	}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeCopy, nil, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeCopy, nil, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 
 }
 
 // /1.0/storage-pools/{name}/volumes/{type}
 // Create a storage volume of a given volume type in a given storage pool.
-func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+func storagePoolVolumesPost(d *Daemon, r *http.Request) response.Response {
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	req := api.StorageVolumesPost{}
@@ -315,29 +317,29 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks.
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
 	}
 
 	// Check that the user gave use a storage volume type for the storage
 	// volume we are about to create.
 	if req.Type == "" {
-		return BadRequest(fmt.Errorf("You must provide a storage volume type of the storage volume"))
+		return response.BadRequest(fmt.Errorf("You must provide a storage volume type of the storage volume"))
 	}
 
 	// We currently only allow to create storage volumes of type
 	// storagePoolVolumeTypeCustom. So check, that nothing else was
 	// requested.
 	if req.Type != storagePoolVolumeTypeNameCustom {
-		return BadRequest(fmt.Errorf(`Currently not allowed to create `+
+		return response.BadRequest(fmt.Errorf(`Currently not allowed to create `+
 			`storage volumes of type %s`, req.Type))
 	}
 
@@ -351,19 +353,19 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
 	case "migration":
 		return doVolumeMigration(d, poolName, &req)
 	default:
-		return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+		return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
 	}
 }
 
-func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost) Response {
+func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost) response.Response {
 	// Validate migration mode
 	if req.Source.Mode != "pull" && req.Source.Mode != "push" {
-		return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
+		return response.NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
 	}
 
 	storage, err := storagePoolVolumeDBCreateInternal(d.State(), poolName, req)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// create new certificate
@@ -371,18 +373,18 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
 	if req.Source.Certificate != "" {
 		certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
 		if certBlock == nil {
-			return InternalError(fmt.Errorf("Invalid certificate"))
+			return response.InternalError(fmt.Errorf("Invalid certificate"))
 		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
 	config, err := shared.GetTLSConfig("", "", "", cert)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	push := false
@@ -403,13 +405,13 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
 
 	sink, err := NewStorageMigrationSink(&migrationArgs)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	resources := map[string][]string{}
 	resources["storage_volumes"] = []string{fmt.Sprintf("%s/volumes/custom/%s", poolName, req.Name)}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		// And finally run the migration.
 		err = sink.DoStorage(op)
 		if err != nil {
@@ -420,25 +422,25 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
 		return nil
 	}
 
-	var op *operation
+	var op *operations.Operation
 	if push {
-		op, err = operationCreate(d.cluster, "", operationClassWebsocket, db.OperationVolumeCreate, resources, sink.Metadata(), run, nil, sink.Connect)
+		op, err = operations.OperationCreate(d.cluster, "", operations.OperationClassWebsocket, db.OperationVolumeCreate, resources, sink.Metadata(), run, nil, sink.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	} else {
-		op, err = operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeCopy, resources, nil, run, nil, nil)
+		op, err = operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeCopy, resources, nil, run, nil, nil)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
 // /1.0/storage-pools/{name}/volumes/{type}/{name}
 // Rename a storage volume of a given volume type in a given storage pool.
-func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
 	// Get the name of the storage volume.
 	var volumeName string
 	fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -452,7 +454,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 		// Handle volume
 		volumeName = fields[0]
 	} else {
-		return BadRequest(fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"]))
+		return response.BadRequest(fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"]))
 	}
 
 	// Get the name of the storage pool the volume is supposed to be
@@ -464,23 +466,23 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks.
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
 	}
 
 	// We currently only allow to create storage volumes of type
 	// storagePoolVolumeTypeCustom. So check, that nothing else was
 	// requested.
 	if volumeTypeName != storagePoolVolumeTypeNameCustom {
-		return BadRequest(fmt.Errorf("Renaming storage volumes of type %s is not allowed", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("Renaming storage volumes of type %s is not allowed", volumeTypeName))
 	}
 
 	// Retrieve ID of the storage pool (and check if the storage pool
@@ -492,7 +494,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 		poolID, err = d.cluster.StoragePoolGetID(poolName)
 	}
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// We need to restore the body of the request since it has already been
@@ -500,36 +502,36 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 	buf := bytes.Buffer{}
 	err = json.NewEncoder(&buf).Encode(req)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	r.Body = shared.BytesReadCloser{Buf: &buf}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	s, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, storagePoolVolumeTypeCustom)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// This is a migration request so send back requested secrets
 	if req.Migration {
 		ws, err := NewStorageMigrationSource(s, req.VolumeOnly)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		resources := map[string][]string{}
@@ -539,34 +541,34 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 			// Push mode
 			err := ws.ConnectStorageTarget(*req.Target)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeMigrate, resources, nil, ws.DoStorage, nil, nil)
+			op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeMigrate, resources, nil, ws.DoStorage, nil, nil)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
-			return OperationResponse(op)
+			return operations.OperationResponse(op)
 		}
 
 		// Pull mode
-		op, err := operationCreate(d.cluster, "", operationClassWebsocket, db.OperationVolumeMigrate, resources, ws.Metadata(), ws.DoStorage, nil, ws.Connect)
+		op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassWebsocket, db.OperationVolumeMigrate, resources, ws.Metadata(), ws.DoStorage, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return operations.OperationResponse(op)
 	}
 
 	// Check that the name isn't already in use.
 	_, err = d.cluster.StoragePoolNodeVolumeGetTypeID(req.Name, storagePoolVolumeTypeCustom, poolID)
 	if err != db.ErrNoSuchObject {
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
+		return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
 	}
 
 	doWork := func() error {
@@ -625,39 +627,39 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
 	if req.Pool == "" {
 		err = doWork()
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
-		return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s", version.APIVersion, poolName, storagePoolVolumeAPIEndpointCustom))
+		return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s", version.APIVersion, poolName, storagePoolVolumeAPIEndpointCustom))
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operations.Operation) error {
 		return doWork()
 	}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeMove, nil, nil, run, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeMove, nil, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func storagePoolVolumeTypeContainerPost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPost(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePost(d, r, "container")
 }
 
-func storagePoolVolumeTypeCustomPost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPost(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePost(d, r, "custom")
 }
 
-func storagePoolVolumeTypeImagePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePost(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePost(d, r, "image")
 }
 
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
 // Get storage volume of a given volume type on a given storage pool.
-func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
 	project := projectParam(r)
 
 	// Get the name of the storage volume.
@@ -673,7 +675,7 @@ func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string)
 		// Handle volume
 		volumeName = fields[0]
 	} else {
-		return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+		return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
 	}
 
 	// Get the name of the storage pool the volume is supposed to be
@@ -683,61 +685,61 @@ func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string)
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
 	// Get the ID of the storage pool the storage volume is supposed to be
 	// attached to.
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	// Get the storage volume.
 	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volume.Name, volume.Type)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	volume.UsedBy = volumeUsedBy
 
 	etag := []interface{}{volumeName, volume.Type, volume.Config}
 
-	return SyncResponseETag(true, volume, etag)
+	return response.SyncResponseETag(true, volume, etag)
 }
 
-func storagePoolVolumeTypeContainerGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerGet(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeGet(d, r, "container")
 }
 
-func storagePoolVolumeTypeCustomGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomGet(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeGet(d, r, "custom")
 }
 
-func storagePoolVolumeTypeImageGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImageGet(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeGet(d, r, "image")
 }
 
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
 	// Get the name of the storage volume.
 	var volumeName string
 	fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -751,7 +753,7 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
 		// Handle volume
 		volumeName = fields[0]
 	} else {
-		return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+		return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
 	}
 
 	// Get the name of the storage pool the volume is supposed to be
@@ -761,32 +763,32 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
 	poolID, pool, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	// Get the existing storage volume.
 	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
@@ -794,58 +796,58 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.StorageVolumePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Restore != "" {
 		ctsUsingVolume, err := storagePoolVolumeUsedByRunningContainersWithProfilesGet(d.State(), poolName, volume.Name, storagePoolVolumeTypeNameCustom, true)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		if len(ctsUsingVolume) != 0 {
-			return BadRequest(fmt.Errorf("Cannot restore custom volume used by running containers"))
+			return response.BadRequest(fmt.Errorf("Cannot restore custom volume used by running containers"))
 		}
 
 		err = storagePoolVolumeRestore(d.State(), poolName, volumeName, volumeType, req.Restore)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	} else {
 		// Validate the configuration
 		err = storageVolumeValidateConfig(volumeName, req.Config, pool)
 		if err != nil {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 
 		err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, volumeType, req.Description, req.Config)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func storagePoolVolumeTypeContainerPut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPut(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePut(d, r, "container")
 }
 
-func storagePoolVolumeTypeCustomPut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPut(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePut(d, r, "custom")
 }
 
-func storagePoolVolumeTypeImagePut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePut(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePut(d, r, "image")
 }
 
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
 	// Get the name of the storage volume.
 	var volumeName string
 	fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -859,7 +861,7 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
 		// Handle volume
 		volumeName = fields[0]
 	} else {
-		return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+		return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
 	}
 
 	// Get the name of the storage pool the volume is supposed to be
@@ -869,34 +871,34 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
 	// Get the ID of the storage pool the storage volume is supposed to be
 	// attached to.
 	poolID, pool, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	// Get the existing storage volume.
 	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
@@ -904,12 +906,12 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
 
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.StorageVolumePut{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Config == nil {
@@ -926,31 +928,31 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
 	// Validate the configuration
 	err = storageVolumeValidateConfig(volumeName, req.Config, pool)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, volumeType, req.Description, req.Config)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func storagePoolVolumeTypeContainerPatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPatch(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePatch(d, r, "container")
 }
 
-func storagePoolVolumeTypeCustomPatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPatch(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePatch(d, r, "custom")
 }
 
-func storagePoolVolumeTypeImagePatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePatch(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypePatch(d, r, "image")
 }
 
 // /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
 	project := projectParam(r)
 
 	// Get the name of the storage volume.
@@ -964,7 +966,7 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 		// Handle volume
 		volumeName = fields[0]
 	} else {
-		return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+		return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
 	}
 
 	// Get the name of the storage pool the volume is supposed to be
@@ -974,26 +976,26 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolID, _, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	switch volumeType {
@@ -1002,12 +1004,12 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 	case storagePoolVolumeTypeImage:
 		// allowed
 	default:
-		return BadRequest(fmt.Errorf("storage volumes of type \"%s\" cannot be deleted with the storage api", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("storage volumes of type \"%s\" cannot be deleted with the storage api", volumeTypeName))
 	}
 
 	volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volumeName, volumeTypeName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if len(volumeUsedBy) > 0 {
@@ -1017,13 +1019,13 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 				"/%s/images/%s",
 				version.APIVersion,
 				volumeName) {
-			return BadRequest(fmt.Errorf("The storage volume is still in use"))
+			return response.BadRequest(fmt.Errorf("The storage volume is still in use"))
 		}
 	}
 
 	s, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, volumeType)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
 	switch volumeType {
@@ -1033,18 +1035,18 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 		// Delete storage volume snapshots
 		snapshots, err = d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName, volumeType, poolID)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		for _, snapshot := range snapshots {
 			s, err := storagePoolVolumeInit(d.State(), project, poolName, snapshot, volumeType)
 			if err != nil {
-				return NotFound(err)
+				return response.NotFound(err)
 			}
 
 			err = s.StoragePoolVolumeSnapshotDelete()
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 		}
 
@@ -1052,25 +1054,25 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
 	case storagePoolVolumeTypeImage:
 		err = s.ImageDelete(volumeName)
 	default:
-		return BadRequest(fmt.Errorf(`Storage volumes of type "%s" `+
+		return response.BadRequest(fmt.Errorf(`Storage volumes of type "%s" `+
 			`cannot be deleted with the storage api`,
 			volumeTypeName))
 	}
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func storagePoolVolumeTypeContainerDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerDelete(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeDelete(d, r, "container")
 }
 
-func storagePoolVolumeTypeCustomDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomDelete(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeDelete(d, r, "custom")
 }
 
-func storagePoolVolumeTypeImageDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImageDelete(d *Daemon, r *http.Request) response.Response {
 	return storagePoolVolumeTypeDelete(d, r, "image")
 }
diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 7a5ff87ee7..db8f50fc81 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -9,6 +9,8 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/lxd/db"
+	"github.com/lxc/lxd/lxd/operations"
+	"github.com/lxc/lxd/lxd/response"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
@@ -32,7 +34,7 @@ var storagePoolVolumeSnapshotTypeCmd = APIEndpoint{
 	Put:    APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut},
 }
 
-func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the pool.
 	poolName := mux.Vars(r)["pool"]
 
@@ -46,18 +48,18 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 	req := api.StorageVolumeSnapshotsPost{}
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("Invalid storage volume type \"%d\"", volumeType))
+		return response.BadRequest(fmt.Errorf("Invalid storage volume type \"%d\"", volumeType))
 	}
 
 	// Get a snapshot name.
@@ -69,56 +71,56 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 	// Validate the name
 	err = driver.ValidName(req.Name)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that this isn't a restricted volume
 	used, err := daemonStorageUsed(d.State(), poolName, volumeName)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if used {
-		return BadRequest(fmt.Errorf("Volumes used by LXD itself cannot have snapshots"))
+		return response.BadRequest(fmt.Errorf("Volumes used by LXD itself cannot have snapshots"))
 	}
 
 	// Retrieve ID of the storage pool (and check if the storage pool
 	// exists).
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	// Ensure that the storage volume exists.
 	storage, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, volumeType)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Ensure that the snapshot doens't already exist
 	_, _, err = d.cluster.StoragePoolNodeVolumeGetType(fmt.Sprintf("%s/%s", volumeName, req.Name), volumeType, poolID)
 	if err != db.ErrNoSuchObject {
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
-		return Conflict(fmt.Errorf("Snapshot '%s' already in use", req.Name))
+		return response.Conflict(fmt.Errorf("Snapshot '%s' already in use", req.Name))
 	}
 
 	// Start the storage.
 	ourMount, err := storage.StoragePoolVolumeMount()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	if ourMount {
 		defer storage.StoragePoolVolumeUmount()
@@ -127,7 +129,7 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 	volWritable := storage.GetStoragePoolVolumeWritable()
 	fullSnapName := fmt.Sprintf("%s%s%s", volumeName, shared.SnapshotDelimiter, req.Name)
 	req.Name = fullSnapName
-	snapshot := func(op *operation) error {
+	snapshot := func(op *operations.Operation) error {
 		dbArgs := &db.StorageVolumeArgs{
 			Name:        fullSnapName,
 			PoolName:    poolName,
@@ -152,15 +154,15 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["storage_volumes"] = []string{volumeName}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotCreate, resources, nil, snapshot, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotCreate, resources, nil, snapshot, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the pool the storage volume is supposed to be
 	// attached to.
 	poolName := mux.Vars(r)["pool"]
@@ -176,24 +178,24 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	// Check that the storage volume type is valid.
 	if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
 	// Retrieve ID of the storage pool (and check if the storage pool
 	// exists).
 	poolID, err := d.cluster.StoragePoolGetID(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Get the names of all storage volume snapshots of a given volume
 	volumes, err := d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	resultString := []string{}
@@ -204,7 +206,7 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
 		if !recursion {
 			apiEndpoint, err := storagePoolVolumeTypeToAPIEndpoint(volumeType)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			resultString = append(resultString, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s/%s/snapshots/%s", version.APIVersion, poolName, apiEndpoint, volumeName, snapshotName))
 		} else {
@@ -215,7 +217,7 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
 
 			volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), "default", poolName, vol.Name, vol.Type)
 			if err != nil {
-				return SmartError(err)
+				return response.SmartError(err)
 			}
 			vol.UsedBy = volumeUsedBy
 
@@ -229,13 +231,13 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if !recursion {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
-func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the storage pool the volume is supposed to be
 	// attached to.
 	poolName := mux.Vars(r)["pool"]
@@ -254,51 +256,51 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
 	// Parse the request.
 	err := json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Sanity checks.
 	if req.Name == "" {
-		return BadRequest(fmt.Errorf("No name provided"))
+		return response.BadRequest(fmt.Errorf("No name provided"))
 	}
 
 	if strings.Contains(req.Name, "/") {
-		return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+		return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
 	}
 
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the storage volume type is valid.
 	if volumeType != storagePoolVolumeTypeCustom {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolID, _, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	s, err := storagePoolVolumeInit(d.State(), "default", poolName, fullSnapshotName, volumeType)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
-	snapshotRename := func(op *operation) error {
+	snapshotRename := func(op *operations.Operation) error {
 		err = s.StoragePoolVolumeSnapshotRename(req.Name)
 		if err != nil {
 			return err
@@ -310,15 +312,15 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["storage_volume_snapshots"] = []string{volumeName}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotRename, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotRename, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the storage pool the volume is supposed to be
 	// attached to.
 	poolName := mux.Vars(r)["pool"]
@@ -335,33 +337,33 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the storage volume type is valid.
 	if volumeType != storagePoolVolumeTypeCustom {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolID, _, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(fullSnapshotName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	snapshot := api.StorageVolumeSnapshot{}
@@ -371,10 +373,10 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
 
 	etag := []interface{}{snapshot.Name, snapshot.Description, snapshot.Config}
 
-	return SyncResponseETag(true, &snapshot, etag)
+	return response.SyncResponseETag(true, &snapshot, etag)
 }
 
-func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the storage pool the volume is supposed to be
 	// attached to.
 	poolName := mux.Vars(r)["pool"]
@@ -391,51 +393,51 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the storage volume type is valid.
 	if volumeType != storagePoolVolumeTypeCustom {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolID, _, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(fullSnapshotName, volumeType, poolID)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{snapshotName, volume.Description, volume.Config}
 	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := api.StorageVolumeSnapshotPut{}
 	err = json.NewDecoder(r.Body).Decode(&req)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
-	var do func(*operation) error
+	var do func(*operations.Operation) error
 	var opDescription db.OperationType
-	do = func(op *operation) error {
+	do = func(op *operations.Operation) error {
 		err = storagePoolVolumeSnapshotUpdate(d.State(), poolName, volume.Name, volumeType, req.Description)
 		if err != nil {
 			return err
@@ -448,15 +450,15 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["storage_volume_snapshots"] = []string{volumeName}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, opDescription, resources, nil, do, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, opDescription, resources, nil, do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
 
-func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) response.Response {
 	// Get the name of the storage pool the volume is supposed to be
 	// attached to.
 	poolName := mux.Vars(r)["pool"]
@@ -473,36 +475,36 @@ func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
 	// Convert the volume type name to our internal integer representation.
 	volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Check that the storage volume type is valid.
 	if volumeType != storagePoolVolumeTypeCustom {
-		return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+		return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
 	}
 
-	response := ForwardedResponseIfTargetIsRemote(d, r)
-	if response != nil {
-		return response
+	resp := ForwardedResponseIfTargetIsRemote(d, r)
+	if resp != nil {
+		return resp
 	}
 
 	poolID, _, err := d.cluster.StoragePoolGet(poolName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
-	response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
-	if response != nil {
-		return response
+	resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+	if resp != nil {
+		return resp
 	}
 
 	s, err := storagePoolVolumeInit(d.State(), "default", poolName, fullSnapshotName, volumeType)
 	if err != nil {
-		return NotFound(err)
+		return response.NotFound(err)
 	}
 
-	snapshotDelete := func(op *operation) error {
+	snapshotDelete := func(op *operations.Operation) error {
 		err = s.StoragePoolVolumeSnapshotDelete()
 		if err != nil {
 			return err
@@ -514,10 +516,10 @@ func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["storage_volume_snapshots"] = []string{volumeName}
 
-	op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotDelete, nil, nil)
+	op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotDelete, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return operations.OperationResponse(op)
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index aff449d39a..8476d7a05e 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -15,6 +15,7 @@ import (
 	"golang.org/x/sys/unix"
 
 	"github.com/lxc/lxd/lxd/migration"
+	"github.com/lxc/lxd/lxd/operations"
 	"github.com/lxc/lxd/lxd/project"
 	driver "github.com/lxc/lxd/lxd/storage"
 	"github.com/lxc/lxd/lxd/util"
@@ -2566,7 +2567,7 @@ func (s *storageZfs) MigrationSource(args MigrationSourceArgs) (MigrationStorage
 	return &driver, nil
 }
 
-func (s *storageZfs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageZfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	poolName := s.getOnDiskPoolName()
 	zfsName := fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name()))
 	zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
@@ -3204,7 +3205,7 @@ func (s *storageZfs) StorageMigrationSource(args MigrationSourceArgs) (Migration
 	return rsyncStorageMigrationSource(args)
 }
 
-func (s *storageZfs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageZfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
 	return rsyncStorageMigrationSink(conn, op, args)
 }
 

From ed945f29bc2ef833950e6fc115bb71efcda1e960 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 26 Sep 2019 14:38:36 +0200
Subject: [PATCH 2/2] test: Add response and operations to static analysis

Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
 test/suites/static_analysis.sh | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 21291beeb6..1edbb3d6b5 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -82,6 +82,8 @@ test_static_analysis() {
       golint -set_exit_status lxd/maas
       #golint -set_exit_status lxd/migration
       golint -set_exit_status lxd/node
+      golint -set_exit_status lxd/operations
+      golint -set_exit_status lxd/response
       golint -set_exit_status lxd/state
       golint -set_exit_status lxd/storage
       golint -set_exit_status lxd/sys


More information about the lxc-devel mailing list