[lxc-devel] [lxd/master] [RFC] move response, operations, events, to their own packages

tych0 on Github lxc-bot at linuxcontainers.org
Fri Dec 16 19:48:20 UTC 2016


A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 573 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20161216/4e641706/attachment.bin>
-------------- next part --------------
From 146e801584ef7f0f393344530556d64dcd758c85 Mon Sep 17 00:00:00 2001
From: Tycho Andersen <tycho.andersen at canonical.com>
Date: Fri, 16 Dec 2016 12:42:18 -0700
Subject: [PATCH] move response, operations, events, to their own packages

Signed-off-by: Tycho Andersen <tycho.andersen at canonical.com>
---
 lxd/api_1.0.go              |  44 +++--
 lxd/api_internal.go         |  55 +++---
 lxd/certificates.go         |  76 +++----
 lxd/container.go            |   3 +-
 lxd/container_delete.go     |  17 +-
 lxd/container_exec.go       |  40 ++--
 lxd/container_file.go       |  45 ++---
 lxd/container_get.go        |  10 +-
 lxd/container_logs.go       |  31 +--
 lxd/container_lxc.go        |  40 ++--
 lxd/container_patch.go      |  22 ++-
 lxd/container_post.go       |  29 +--
 lxd/container_put.go        |  25 +--
 lxd/container_snapshot.go   |  73 +++----
 lxd/container_state.go      |  41 ++--
 lxd/containers_get.go       |  10 +-
 lxd/containers_post.go      |  88 +++++----
 lxd/daemon.go               |  30 +--
 lxd/daemon_images.go        |   7 +-
 lxd/db.go                   |  15 --
 lxd/db_containers.go        |   5 +-
 lxd/db_images.go            |   5 +-
 lxd/db_networks.go          |   3 +-
 lxd/db_profiles.go          |   3 +-
 lxd/db_test.go              |   4 +-
 lxd/devlxd.go               |   3 +-
 lxd/events.go               | 139 +------------
 lxd/events/events.go        | 135 +++++++++++++
 lxd/images.go               | 225 ++++++++++-----------
 lxd/main.go                 |  18 +-
 lxd/main_init.go            |   3 +-
 lxd/migrate.go              |  30 +--
 lxd/networks.go             |  98 ++++-----
 lxd/operation/operations.go | 458 ++++++++++++++++++++++++++++++++++++++++++
 lxd/operations.go           | 469 +++-----------------------------------------
 lxd/profiles.go             | 112 +++++------
 lxd/response.go             | 317 ------------------------------
 lxd/response/response.go    | 320 ++++++++++++++++++++++++++++++
 lxd/rsync.go                |   3 +-
 lxd/state/vars.go           |  18 ++
 lxd/storage.go              |  22 ++-
 lxd/storage_btrfs.go        |   5 +-
 lxd/storage_dir.go          |   3 +-
 lxd/storage_lvm.go          |   3 +-
 lxd/storage_zfs.go          |   8 +-
 lxd/util.go                 |  67 -------
 lxd/util/errors.go          |  20 ++
 lxd/util/util.go            |  68 +++++++
 48 files changed, 1697 insertions(+), 1568 deletions(-)
 create mode 100644 lxd/events/events.go
 create mode 100644 lxd/operation/operations.go
 delete mode 100644 lxd/response.go
 create mode 100644 lxd/response/response.go
 create mode 100644 lxd/state/vars.go
 delete mode 100644 lxd/util.go
 create mode 100644 lxd/util/errors.go
 create mode 100644 lxd/util/util.go

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 3117106..4f1d1ce 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -10,6 +10,8 @@ import (
 
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 )
@@ -44,7 +46,7 @@ var api10 = []Command{
 	profileCmd,
 }
 
-func api10Get(d *Daemon, r *http.Request) Response {
+func api10Get(d *Daemon, r *http.Request) response.Response {
 	body := shared.Jmap{
 		/* List of API extensions in the order they were added.
 		 *
@@ -100,7 +102,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		 */
 		uname := syscall.Utsname{}
 		if err := syscall.Uname(&uname); err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		kernel := ""
@@ -129,7 +131,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 
 		addresses, err := d.ListenAddresses()
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		var certificate string
@@ -142,7 +144,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		for _, architecture := range d.architectures {
 			architectureName, err := osarch.ArchitectureName(architecture)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 			architectures = append(architectures, architectureName)
 		}
@@ -170,50 +172,50 @@ func api10Get(d *Daemon, r *http.Request) Response {
 		body["public"] = false
 	}
 
-	return SyncResponseETag(true, body, body["config"])
+	return response.SyncResponseETag(true, body, body["config"])
 }
 
 type apiPut struct {
 	Config shared.Jmap `json:"config"`
 }
 
-func api10Put(d *Daemon, r *http.Request) Response {
+func api10Put(d *Daemon, r *http.Request) response.Response {
 	oldConfig, err := dbConfigValuesGet(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	err = etagCheck(r, oldConfig)
+	err = util.EtagCheck(r, oldConfig)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := apiPut{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doApi10Update(d, oldConfig, req)
 }
 
-func api10Patch(d *Daemon, r *http.Request) Response {
+func api10Patch(d *Daemon, r *http.Request) response.Response {
 	oldConfig, err := dbConfigValuesGet(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	err = etagCheck(r, oldConfig)
+	err = util.EtagCheck(r, oldConfig)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := apiPut{}
 	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
 	}
 
 	for k, v := range oldConfig {
@@ -226,7 +228,7 @@ func api10Patch(d *Daemon, r *http.Request) Response {
 	return doApi10Update(d, oldConfig, req)
 }
 
-func doApi10Update(d *Daemon, oldConfig map[string]string, req apiPut) Response {
+func doApi10Update(d *Daemon, oldConfig map[string]string, req apiPut) response.Response {
 	// Deal with special keys
 	for k, v := range req.Config {
 		config := daemonConfig[k]
@@ -256,23 +258,23 @@ func doApi10Update(d *Daemon, oldConfig map[string]string, req apiPut) Response
 
 		s := reflect.ValueOf(valueRaw)
 		if !s.IsValid() || s.Kind() != reflect.String {
-			return BadRequest(fmt.Errorf("Invalid value type for '%s'", key))
+			return response.BadRequest(fmt.Errorf("Invalid value type for '%s'", key))
 		}
 
 		value := valueRaw.(string)
 
 		confKey, ok := daemonConfig[key]
 		if !ok {
-			return BadRequest(fmt.Errorf("Bad server config key: '%s'", key))
+			return response.BadRequest(fmt.Errorf("Bad server config key: '%s'", key))
 		}
 
 		err := confKey.Set(d, value)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var api10Cmd = Command{name: "", untrustedGet: true, get: api10Get, put: api10Put, patch: api10Patch}
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 7fa2205..3e790ec 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -10,6 +10,7 @@ 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/osarch"
 
@@ -24,57 +25,57 @@ var apiInternal = []Command{
 	internalContainersCmd,
 }
 
-func internalReady(d *Daemon, r *http.Request) Response {
+func internalReady(d *Daemon, r *http.Request) response.Response {
 	if !d.SetupMode {
-		return InternalError(fmt.Errorf("The server isn't currently in setup mode"))
+		return response.InternalError(fmt.Errorf("The server isn't currently in setup mode"))
 	}
 
 	err := d.Ready()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	d.SetupMode = false
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalWaitReady(d *Daemon, r *http.Request) Response {
+func internalWaitReady(d *Daemon, r *http.Request) response.Response {
 	<-d.readyChan
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func internalShutdown(d *Daemon, r *http.Request) Response {
+func internalShutdown(d *Daemon, r *http.Request) response.Response {
 	d.shutdownChan <- true
 
-	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)
 	}
 
 	c, err := containerLoadById(d, id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = c.OnStart()
 	if err != nil {
 		shared.Log.Error("start 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 := r.FormValue("target")
@@ -84,16 +85,16 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
 
 	c, err := containerLoadById(d, id)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = c.OnStop(target)
 	if err != nil {
 		shared.Log.Error("stop hook failed", log.Ctx{"container": c.Name(), "err": err})
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var internalShutdownCmd = Command{name: "shutdown", put: internalShutdown}
@@ -116,23 +117,23 @@ func slurpBackupFile(path string) (*backupFile, error) {
 	return &sf, nil
 }
 
-func internalImport(d *Daemon, r *http.Request) Response {
+func internalImport(d *Daemon, r *http.Request) response.Response {
 	name := r.FormValue("target")
 	if name == "" {
-		return BadRequest(fmt.Errorf("target is required"))
+		return response.BadRequest(fmt.Errorf("target is required"))
 	}
 
 	path := containerPath(name, false)
 	err := d.Storage.ContainerStart(name, path)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	defer d.Storage.ContainerStop(name, path)
 
 	sf, err := slurpBackupFile(shared.VarPath("containers", name, "backup.yaml"))
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	baseImage := sf.Container.Config["volatile.base_image"]
@@ -144,7 +145,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 
 	arch, err := osarch.ArchitectureId(sf.Container.Architecture)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	_, err = containerCreateInternal(d, containerArgs{
 		Architecture: arch,
@@ -160,7 +161,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
 		Stateful:     sf.Container.Stateful,
 	})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	for _, snap := range sf.Snapshots {
@@ -173,7 +174,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)
 		}
 
 		_, err = containerCreateInternal(d, containerArgs{
@@ -190,11 +191,11 @@ func internalImport(d *Daemon, r *http.Request) Response {
 			Stateful:     snap.Stateful,
 		})
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var internalContainersCmd = Command{name: "containers", post: internalImport}
diff --git a/lxd/certificates.go b/lxd/certificates.go
index ea75222..1063d06 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -11,10 +11,12 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
-func certificatesGet(d *Daemon, r *http.Request) Response {
+func certificatesGet(d *Daemon, r *http.Request) response.Response {
 	recursion := d.isRecursionRequest(r)
 
 	if recursion {
@@ -22,7 +24,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 
 		baseCerts, err := dbCertsGet(d.db)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 		for _, baseCert := range baseCerts {
 			resp := shared.CertInfo{}
@@ -35,7 +37,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 			}
 			certResponses = append(certResponses, resp)
 		}
-		return SyncResponse(true, certResponses)
+		return response.SyncResponse(true, certResponses)
 	}
 
 	body := []string{}
@@ -44,7 +46,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
 		body = append(body, fingerprint)
 	}
 
-	return SyncResponse(true, body)
+	return response.SyncResponse(true, body)
 }
 
 type certificatesPostBody struct {
@@ -91,20 +93,20 @@ func saveCert(d *Daemon, host string, cert *x509.Certificate) error {
 	return dbCertSave(d.db, baseCert)
 }
 
-func certificatesPost(d *Daemon, r *http.Request) Response {
+func certificatesPost(d *Daemon, r *http.Request) response.Response {
 	// Parse the request
 	req := certificatesPostBody{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Access check
 	if !d.isTrustedClient(r) && d.PasswordCheck(req.Password) != nil {
-		return Forbidden
+		return response.Forbidden
 	}
 
 	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
@@ -113,58 +115,58 @@ 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(err)
+			return response.BadRequest(err)
 		}
 		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)
 	for _, existingCert := range d.clientCerts {
 		if fingerprint == shared.CertFingerprint(&existingCert) {
-			return BadRequest(fmt.Errorf("Certificate already in trust store"))
+			return response.BadRequest(fmt.Errorf("Certificate already in trust store"))
 		}
 	}
 
 	err := saveCert(d, name, cert)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	d.clientCerts = append(d.clientCerts, *cert)
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, fingerprint))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", shared.APIVersion, fingerprint))
 }
 
 var certificatesCmd = Command{name: "certificates", untrustedPost: true, get: certificatesGet, post: certificatesPost}
 
-func certificateFingerprintGet(d *Daemon, r *http.Request) Response {
+func certificateFingerprintGet(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	cert, err := doCertificateGet(d, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, cert, cert)
+	return response.SyncResponseETag(true, cert, cert)
 }
 
 func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) {
@@ -187,46 +189,46 @@ func doCertificateGet(d *Daemon, fingerprint string) (shared.CertInfo, error) {
 	return resp, nil
 }
 
-func certificateFingerprintPut(d *Daemon, r *http.Request) Response {
+func certificateFingerprintPut(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	oldEntry, err := doCertificateGet(d, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	fingerprint = oldEntry.Fingerprint
 
-	err = etagCheck(r, oldEntry)
+	err = util.EtagCheck(r, oldEntry)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := shared.CertInfo{}
 	if err := shared.ReadToJSON(r.Body, &req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doCertificateUpdate(d, fingerprint, req)
 }
 
-func certificateFingerprintPatch(d *Daemon, r *http.Request) Response {
+func certificateFingerprintPatch(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	oldEntry, err := doCertificateGet(d, fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	fingerprint = oldEntry.Fingerprint
 
-	err = etagCheck(r, oldEntry)
+	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
@@ -244,34 +246,34 @@ func certificateFingerprintPatch(d *Daemon, r *http.Request) Response {
 	return doCertificateUpdate(d, fingerprint, req)
 }
 
-func doCertificateUpdate(d *Daemon, fingerprint string, req shared.CertInfo) Response {
+func doCertificateUpdate(d *Daemon, fingerprint string, req shared.CertInfo) 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 := dbCertUpdate(d.db, fingerprint, req.Name, 1)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func certificateFingerprintDelete(d *Daemon, r *http.Request) Response {
+func certificateFingerprintDelete(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	certInfo, err := dbCertGet(d.db, fingerprint)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	err = dbCertDelete(d.db, certInfo.Fingerprint)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 	readSavedClientCAList(d)
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var certificateFingerprintCmd = Command{name: "certificates/{fingerprint}", get: certificateFingerprintGet, delete: certificateFingerprintDelete, put: certificateFingerprintPut, patch: certificateFingerprintPatch}
diff --git a/lxd/container.go b/lxd/container.go
index 6b43e07..06f1dc3 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -9,6 +9,7 @@ import (
 
 	"gopkg.in/lxc/go-lxc.v2"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 )
@@ -644,7 +645,7 @@ func containerCreateInternal(d *Daemon, args containerArgs) (container, error) {
 	// Create the container entry
 	id, err := dbContainerCreate(d.db, args)
 	if err != nil {
-		if err == DbErrAlreadyDefined {
+		if err == util.DbErrAlreadyDefined {
 			thing := "Container"
 			if shared.IsSnapshot(args.Name) {
 				thing = "Snapshot"
diff --git a/lxd/container_delete.go b/lxd/container_delete.go
index 34a9a82..8a5659c 100644
--- a/lxd/container_delete.go
+++ b/lxd/container_delete.go
@@ -5,30 +5,33 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
+
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 )
 
-func containerDelete(d *Daemon, r *http.Request) Response {
+func containerDelete(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, 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 *operation.Operation) error {
 		return c.Delete()
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, rmct, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, rmct, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 9dead34..2bfc627 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -15,6 +15,8 @@ import (
 	"github.com/gorilla/mux"
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -60,7 +62,7 @@ func (s *execWs) Metadata() interface{} {
 	return shared.Jmap{"fds": fds}
 }
 
-func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *execWs) Connect(op *operation.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -97,11 +99,11 @@ func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter)
 	}
 
 	/* If we didn't find the right secret, the user provided a bad one,
-	 * which 403, not 404, since this operation actually exists */
+	 * which 403, not 404, since this operation.Operation actually exists */
 	return os.ErrPermission
 }
 
-func (s *execWs) Do(op *operation) error {
+func (s *execWs) Do(op *operation.Operation) error {
 	<-s.allConnected
 
 	var err error
@@ -302,29 +304,29 @@ 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 {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	if !c.IsRunning() {
-		return BadRequest(fmt.Errorf("Container is not running."))
+		return response.BadRequest(fmt.Errorf("Container is not running."))
 	}
 
 	if c.IsFrozen() {
-		return BadRequest(fmt.Errorf("Container is frozen."))
+		return response.BadRequest(fmt.Errorf("Container is frozen."))
 	}
 
 	post := commandPostContent{}
 	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)
 	}
 
 	env := map[string]string{}
@@ -369,7 +371,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)
 			}
 		}
 
@@ -383,28 +385,28 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 		resources := map[string][]string{}
 		resources["containers"] = []string{ws.container.Name()}
 
-		op, err := operationCreate(operationClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operation.Create(operation.ClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return response.OperationResponse(op)
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operation.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(c.LogPath(), fmt.Sprintf("exec_%s.stdout", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			stdout, err := os.OpenFile(filepath.Join(c.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(c.LogPath(), fmt.Sprintf("exec_%s.stderr", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+			stderr, err := os.OpenFile(filepath.Join(c.LogPath(), fmt.Sprintf("exec_%s.stderr", op.Id())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
 			if err != nil {
 				return err
 			}
@@ -435,10 +437,10 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7e9d20b..7a8b237 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -10,19 +10,20 @@ 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 {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, 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 {
@@ -31,11 +32,11 @@ func containerFileHandler(d *Daemon, r *http.Request) Response {
 	case "POST":
 		return containerFilePut(c, path, r)
 	default:
-		return NotFound
+		return response.NotFound
 	}
 }
 
-func containerFileGet(c container, path string, r *http.Request) Response {
+func containerFileGet(c container, 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
@@ -45,7 +46,7 @@ func containerFileGet(c container, 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()
 
@@ -53,7 +54,7 @@ func containerFileGet(c container, 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{
@@ -65,22 +66,22 @@ func containerFileGet(c container, path string, r *http.Request) Response {
 
 	if type_ == "file" {
 		// 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 containerFilePut(c container, path string, r *http.Request) Response {
+func containerFilePut(c container, path string, r *http.Request) response.Response {
 	// Extract file ownership and mode from headers
 	uid, gid, mode, type_ := shared.ParseLXDFileHeaders(r.Header)
 
@@ -88,7 +89,7 @@ func containerFilePut(c container, path string, r *http.Request) Response {
 		// 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()
@@ -97,23 +98,23 @@ func containerFilePut(c container, 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(temp.Name(), path, uid, gid, mode)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	} else if type_ == "directory" {
 		err := c.FilePush("", path, uid, gid, mode)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	} else {
-		return InternalError(fmt.Errorf("bad file type %s", type_))
+		return response.InternalError(fmt.Errorf("bad file type %s", type_))
 	}
 }
diff --git a/lxd/container_get.go b/lxd/container_get.go
index a1db3a4..7be6cd7 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -4,19 +4,21 @@ 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 {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	state, etag, err := c.Render()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponseETag(true, state, etag)
+	return response.SyncResponseETag(true, state, etag)
 }
diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index e86df94..e98b776 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -9,10 +9,11 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 )
 
-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.
@@ -23,14 +24,14 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 	name := mux.Vars(r)["name"]
 
 	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 {
@@ -41,7 +42,7 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
 		result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", shared.APIVersion, name, f.Name()))
 	}
 
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
 var containerLogsCmd = Command{
@@ -60,39 +61,39 @@ 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 {
 	name := mux.Vars(r)["name"]
 	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 {
 	name := mux.Vars(r)["name"]
 	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))
 	}
 
-	return SmartError(os.Remove(shared.LogPath(name, file)))
+	return response.SmartError(os.Remove(shared.LogPath(name, file)))
 }
 
 var containerLogCmd = Command{
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index 95232bc..4a4f083 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -24,6 +24,8 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -760,7 +762,7 @@ func (c *containerLXC) initLXC() error {
 		"/sys/firmware/efi/efivars",
 		"/sys/fs/fuse/connections",
 		"/sys/fs/pstore",
-		"/sys/kernel/debug",
+		"/sys/kernel/state.Debug",
 		"/sys/kernel/security"}
 
 	if c.IsPrivileged() && !runningInUserns {
@@ -859,9 +861,9 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	logLevel := "warn"
-	if debug {
+	if state.Debug {
 		logLevel = "trace"
-	} else if verbose {
+	} else if state.Verbose {
 		logLevel = "info"
 	}
 
@@ -885,12 +887,12 @@ func (c *containerLXC) initLXC() error {
 	}
 
 	// Setup the hooks
-	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.pre-start", fmt.Sprintf("%s callhook %s %d start", state.ExecPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
 
-	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", execPath, shared.VarPath(""), c.id))
+	err = lxcSetConfigItem(cc, "lxc.hook.post-stop", fmt.Sprintf("%s callhook %s %d stop", state.ExecPath, shared.VarPath(""), c.id))
 	if err != nil {
 		return err
 	}
@@ -1481,7 +1483,7 @@ func (c *containerLXC) startCommon() (string, error) {
 	if kernelModules != "" {
 		for _, module := range strings.Split(kernelModules, ",") {
 			module = strings.TrimPrefix(module, " ")
-			err := loadModule(module)
+			err := util.LoadModule(module)
 			if err != nil {
 				return "", fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 			}
@@ -1890,13 +1892,13 @@ func (c *containerLXC) Start(stateful bool) error {
 
 	// Start the LXC container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkstart",
 		c.name,
 		c.daemon.lxcpath,
 		configPath).CombinedOutput()
 
-	// Capture debug output
+	// Capture state.Debug output
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
 			shared.LogDebugf("forkstart: %s", line)
@@ -3132,7 +3134,7 @@ func (c *containerLXC) Update(args containerArgs, userRequested bool) error {
 			} else if key == "linux.kernel_modules" && value != "" {
 				for _, module := range strings.Split(value, ",") {
 					module = strings.TrimPrefix(module, " ")
-					err := loadModule(module)
+					err := util.LoadModule(module)
 					if err != nil {
 						return fmt.Errorf("Failed to load kernel module '%s': %s", module, err)
 					}
@@ -3942,7 +3944,7 @@ func (c *containerLXC) Migrate(cmd uint, stateDir string, function string, stop
 
 		var out []byte
 		out, migrateErr = exec.Command(
-			execPath,
+			state.ExecPath,
 			"forkmigrate",
 			c.name,
 			c.daemon.lxcpath,
@@ -4177,7 +4179,7 @@ func (c *containerLXC) FileExists(path string) error {
 
 	// Check if the file exists in the container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkcheckfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -4226,7 +4228,7 @@ func (c *containerLXC) FilePull(srcpath string, dstpath string) (int, int, os.Fi
 
 	// Get the file from the container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkgetfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -4368,7 +4370,7 @@ func (c *containerLXC) FilePush(srcpath string, dstpath string, uid int, gid int
 
 	// Push the file to the container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkputfile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -4442,7 +4444,7 @@ func (c *containerLXC) FileRemove(path string) error {
 
 	// Remove the file from the container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkremovefile",
 		c.RootfsPath(),
 		fmt.Sprintf("%d", c.InitPID()),
@@ -4487,7 +4489,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 		envSlice = append(envSlice, fmt.Sprintf("%s=%s", k, v))
 	}
 
-	args := []string{execPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
+	args := []string{state.ExecPath, "forkexec", c.name, c.daemon.lxcpath, filepath.Join(c.LogPath(), "lxc.conf")}
 
 	args = append(args, "--")
 	args = append(args, "env")
@@ -4498,7 +4500,7 @@ func (c *containerLXC) Exec(command []string, env map[string]string, stdin *os.F
 	args = append(args, command...)
 
 	cmd := exec.Cmd{}
-	cmd.Path = execPath
+	cmd.Path = state.ExecPath
 	cmd.Args = args
 	cmd.Stdin = stdin
 	cmd.Stdout = stdout
@@ -4644,7 +4646,7 @@ func (c *containerLXC) networkState() map[string]shared.ContainerStateNetwork {
 
 	// Get the network state from the container
 	out, err := exec.Command(
-		execPath,
+		state.ExecPath,
 		"forkgetnet",
 		fmt.Sprintf("%d", pid)).CombinedOutput()
 
@@ -4846,7 +4848,7 @@ func (c *containerLXC) insertMount(source, target, fstype string, flags int) err
 	mntsrc := filepath.Join("/dev/.lxd-mounts", filepath.Base(tmpMount))
 	pidStr := fmt.Sprintf("%d", pid)
 
-	out, err := exec.Command(execPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
+	out, err := exec.Command(state.ExecPath, "forkmount", pidStr, mntsrc, target).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
@@ -4876,7 +4878,7 @@ func (c *containerLXC) removeMount(mount string) error {
 
 	// Remove the mount from the container
 	pidStr := fmt.Sprintf("%d", pid)
-	out, err := exec.Command(execPath, "forkumount", pidStr, mount).CombinedOutput()
+	out, err := exec.Command(state.ExecPath, "forkumount", pidStr, mount).CombinedOutput()
 
 	if string(out) != "" {
 		for _, line := range strings.Split(strings.TrimRight(string(out), "\n"), "\n") {
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index f7752bb..dfc1c3b 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -9,28 +9,30 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 )
 
-func containerPatch(d *Daemon, r *http.Request) Response {
+func containerPatch(d *Daemon, r *http.Request) response.Response {
 	// Get the container
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	// Validate the ETag
 	etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
-	err = etagCheck(r, etag)
+	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))
@@ -38,16 +40,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 := containerPutReq{}
 	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
@@ -107,8 +109,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 4139dd7..e946c7f 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -6,6 +6,9 @@ import (
 	"net/http"
 
 	"github.com/gorilla/mux"
+
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 )
 
 type containerPostBody struct {
@@ -13,57 +16,57 @@ type containerPostBody struct {
 	Name      string `json:"name"`
 }
 
-func containerPost(d *Daemon, r *http.Request) Response {
+func containerPost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	buf, err := ioutil.ReadAll(r.Body)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	body := containerPostBody{}
 	if err := json.Unmarshal(buf, &body); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if body.Migration {
 		ws, err := NewMigrationSource(c)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		resources := map[string][]string{}
 		resources["containers"] = []string{name}
 
-		op, err := operationCreate(operationClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operation.Create(operation.ClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return response.OperationResponse(op)
 	}
 
 	// Check that the name isn't already in use
 	id, _ := dbContainerId(d.db, body.Name)
 	if id > 0 {
-		return Conflict
+		return response.Conflict
 	}
 
-	run := func(*operation) error {
+	run := func(*operation.Operation) error {
 		return c.Rename(body.Name)
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 471b022..56fd93b 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -8,6 +8,9 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -27,24 +30,24 @@ type containerPutReq struct {
  * 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 {
 	// Get the container
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	// Validate the ETag
 	etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
-	err = etagCheck(r, etag)
+	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	configRaw := containerPutReq{}
 	if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	architecture, err := osarch.ArchitectureId(configRaw.Architecture)
@@ -52,11 +55,11 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		architecture = 0
 	}
 
-	var do = func(*operation) error { return nil }
+	var do = func(*operation.Operation) error { return nil }
 
 	if configRaw.Restore == "" {
 		// Update container configuration
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			args := containerArgs{
 				Architecture: architecture,
 				Config:       configRaw.Config,
@@ -74,7 +77,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
 		}
 	} else {
 		// Snapshot Restore
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			return containerSnapRestore(d, name, configRaw.Restore)
 		}
 	}
@@ -82,12 +85,12 @@ func containerPut(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, do, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
 func containerSnapRestore(d *Daemon, name string, snap string) error {
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index a2c1ca6..b68dbbe 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -10,6 +10,9 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 )
 
 type containerSnapshotPostReq struct {
@@ -17,7 +20,7 @@ type containerSnapshotPostReq struct {
 	Stateful bool   `json:"stateful"`
 }
 
-func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
+func containerSnapshotsGet(d *Daemon, r *http.Request) response.Response {
 	recursionStr := r.FormValue("recursion")
 	recursion, err := strconv.Atoi(recursionStr)
 	if err != nil {
@@ -27,12 +30,12 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	cname := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, 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)
 	}
 
 	resultString := []string{}
@@ -54,10 +57,10 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if recursion == 0 {
-		return SyncResponse(true, resultString)
+		return response.SyncResponse(true, resultString)
 	}
 
-	return SyncResponse(true, resultMap)
+	return response.SyncResponse(true, resultMap)
 }
 
 /*
@@ -96,7 +99,7 @@ func nextSnapshot(d *Daemon, name string) int {
 	return max
 }
 
-func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
+func containerSnapshotsPost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	/*
@@ -107,12 +110,12 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 	 */
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	req := containerSnapshotPostReq{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" {
@@ -125,7 +128,7 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 		shared.SnapshotDelimiter +
 		req.Name
 
-	snapshot := func(op *operation) error {
+	snapshot := func(op *operation.Operation) error {
 		args := containerArgs{
 			Name:         fullName,
 			Ctype:        cTypeSnapshot,
@@ -149,15 +152,15 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, snapshot, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, snapshot, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func snapshotHandler(d *Daemon, r *http.Request) Response {
+func snapshotHandler(d *Daemon, r *http.Request) response.Response {
 	containerName := mux.Vars(r)["name"]
 	snapshotName := mux.Vars(r)["snapshotName"]
 
@@ -167,7 +170,7 @@ func snapshotHandler(d *Daemon, r *http.Request) Response {
 			shared.SnapshotDelimiter+
 			snapshotName)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	switch r.Method {
@@ -178,46 +181,46 @@ func snapshotHandler(d *Daemon, r *http.Request) Response {
 	case "DELETE":
 		return snapshotDelete(sc, snapshotName)
 	default:
-		return NotFound
+		return response.NotFound
 	}
 }
 
-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.(*shared.SnapshotInfo))
+	return response.SyncResponse(true, render.(*shared.SnapshotInfo))
 }
 
-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 {
 	raw := shared.Jmap{}
 	if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	migration, err := raw.GetBool("migration")
 	if err == nil && migration {
 		ws, err := NewMigrationSource(sc)
 		if err != nil {
-			return SmartError(err)
+			return response.SmartError(err)
 		}
 
 		resources := map[string][]string{}
 		resources["containers"] = []string{containerName}
 
-		op, err := operationCreate(operationClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+		op, err := operation.Create(operation.ClassWebsocket, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return OperationResponse(op)
+		return response.OperationResponse(op)
 	}
 
 	newName, err := raw.GetString("name")
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	fullName := containerName + shared.SnapshotDelimiter + newName
@@ -225,36 +228,36 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
 	// Check that the name isn't already in use
 	id, _ := dbContainerId(d.db, fullName)
 	if id > 0 {
-		return Conflict
+		return response.Conflict
 	}
 
-	rename := func(op *operation) error {
+	rename := func(op *operation.Operation) error {
 		return sc.Rename(fullName)
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{containerName}
 
-	op, err := operationCreate(operationClassTask, resources, nil, rename, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, rename, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.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 *operation.Operation) error {
 		return sc.Delete()
 	}
 
 	resources := map[string][]string{}
 	resources["containers"] = []string{sc.Name()}
 
-	op, err := operationCreate(operationClassTask, resources, nil, remove, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, remove, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
diff --git a/lxd/container_state.go b/lxd/container_state.go
index 1a4ca8c..1da444b 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -9,6 +9,9 @@ import (
 	"github.com/gorilla/mux"
 
 	"github.com/lxc/lxd/shared"
+
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 )
 
 type containerStatePutReq struct {
@@ -18,22 +21,22 @@ type containerStatePutReq struct {
 	Stateful bool   `json:"stateful"`
 }
 
-func containerState(d *Daemon, r *http.Request) Response {
+func containerState(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	c, err := containerLoadByName(d, 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 {
 	name := mux.Vars(r)["name"]
 
 	raw := containerStatePutReq{}
@@ -43,7 +46,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
@@ -51,13 +54,13 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 
 	c, err := containerLoadByName(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	var do func(*operation) error
+	var do func(*operation.Operation) error
 	switch shared.ContainerAction(raw.Action) {
 	case shared.Start:
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			if err = c.Start(raw.Stateful); err != nil {
 				return err
 			}
@@ -65,7 +68,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 		}
 	case shared.Stop:
 		if raw.Stateful {
-			do = func(op *operation) error {
+			do = func(op *operation.Operation) error {
 				err := c.Stop(raw.Stateful)
 				if err != nil {
 					return err
@@ -74,7 +77,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 *operation.Operation) error {
 				err = c.Stop(false)
 				if err != nil {
 					return err
@@ -83,7 +86,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 				return nil
 			}
 		} else {
-			do = func(op *operation) error {
+			do = func(op *operation.Operation) error {
 				if c.IsFrozen() {
 					err := c.Unfreeze()
 					if err != nil {
@@ -100,7 +103,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 			}
 		}
 	case shared.Restart:
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			ephemeral := c.IsEphemeral()
 
 			if ephemeral {
@@ -149,24 +152,24 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
 			return nil
 		}
 	case shared.Freeze:
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			return c.Freeze()
 		}
 	case shared.Unfreeze:
-		do = func(op *operation) error {
+		do = func(op *operation.Operation) error {
 			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(operationClassTask, resources, nil, do, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, do, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 55d9fd5..dc4993b 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -6,17 +6,19 @@ import (
 	"time"
 
 	"github.com/lxc/lxd/shared"
+
+	"github.com/lxc/lxd/lxd/response"
 )
 
-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, d.isRecursionRequest(r))
 		if err == nil {
-			return SyncResponse(true, result)
+			return response.SyncResponse(true, result)
 		}
 		if !isDbLockedError(err) {
 			shared.LogDebugf("DBERR: containersGet: error %q", err)
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 		// 1 s may seem drastic, but we really don't want to thrash
 		// perhaps we should use a random amount
@@ -25,7 +27,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
 
 	shared.LogDebugf("DBERR: containersGet, db is locked")
 	shared.PrintStack()
-	return InternalError(fmt.Errorf("DB is locked"))
+	return response.InternalError(fmt.Errorf("DB is locked"))
 }
 
 func doContainersGet(d *Daemon, recursion bool) (interface{}, error) {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 93b21a4..a730c96 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -11,6 +11,8 @@ import (
 	"github.com/dustinkirkland/golang-petname"
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 
@@ -59,7 +61,7 @@ type containerPostReq struct {
 	Source       containerImageSource `json:"source"`
 }
 
-func createFromImage(d *Daemon, req *containerPostReq) Response {
+func createFromImage(d *Daemon, req *containerPostReq) response.Response {
 	var hash string
 	var err error
 
@@ -71,7 +73,7 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 		} else {
 			_, alias, err := dbImageAliasGet(d.db, req.Source.Alias, true)
 			if err != nil {
-				return InternalError(err)
+				return response.InternalError(err)
 			}
 
 			hash = alias.Target
@@ -80,12 +82,12 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 		hash = req.Source.Fingerprint
 	} 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 := dbImagesGet(d.db, false)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		var image *shared.ImageInfo
@@ -116,15 +118,15 @@ func createFromImage(d *Daemon, req *containerPostReq) 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"))
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operation.Operation) error {
 		if req.Source.Server != "" {
 			hash, err = d.ImageDownload(
 				op, req.Source.Server, req.Source.Protocol, req.Source.Certificate, req.Source.Secret,
@@ -164,15 +166,15 @@ func createFromImage(d *Daemon, req *containerPostReq) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func createFromNone(d *Daemon, req *containerPostReq) Response {
+func createFromNone(d *Daemon, req *containerPostReq) response.Response {
 	architecture, err := osarch.ArchitectureId(req.Architecture)
 	if err != nil {
 		architecture = 0
@@ -188,7 +190,7 @@ func createFromNone(d *Daemon, req *containerPostReq) Response {
 		Profiles:     req.Profiles,
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operation.Operation) error {
 		_, err := containerCreateAsEmpty(d, args)
 		return err
 	}
@@ -196,17 +198,17 @@ func createFromNone(d *Daemon, req *containerPostReq) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func createFromMigration(d *Daemon, req *containerPostReq) Response {
+func createFromMigration(d *Daemon, req *containerPostReq) response.Response {
 	if req.Source.Mode != "pull" && req.Source.Mode != "push" {
-		return NotImplemented
+		return response.NotImplemented
 	}
 
 	architecture, err := osarch.ArchitectureId(req.Architecture)
@@ -244,12 +246,12 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	if err == nil && d.Storage.MigrationType() == MigrationFSType_RSYNC {
 		c, err = containerCreateFromImage(d, args, req.Source.BaseImage)
 		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)
 		}
 	}
 
@@ -258,20 +260,20 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 		certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
 		if certBlock == nil {
 			c.Delete()
-			return InternalError(fmt.Errorf("Invalid certificate"))
+			return response.InternalError(fmt.Errorf("Invalid certificate"))
 		}
 
 		cert, err = x509.ParseCertificate(certBlock.Bytes)
 		if err != nil {
 			c.Delete()
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
 	config, err := shared.GetTLSConfig("", "", "", cert)
 	if err != nil {
 		c.Delete()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	push := false
@@ -293,10 +295,10 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	sink, err := NewMigrationSink(&migrationArgs)
 	if err != nil {
 		c.Delete()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operation.Operation) error {
 		// And finaly run the migration.
 		err = sink.Do(op)
 		if err != nil {
@@ -317,30 +319,30 @@ func createFromMigration(d *Daemon, req *containerPostReq) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name}
 
-	var op *operation
+	var op *operation.Operation
 	if push {
-		op, err = operationCreate(operationClassWebsocket, resources, sink.Metadata(), run, nil, sink.Connect)
+		op, err = operation.Create(operation.ClassWebsocket, resources, sink.Metadata(), run, nil, sink.Connect)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	} else {
-		op, err = operationCreate(operationClassTask, resources, nil, run, nil, nil)
+		op, err = operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func createFromCopy(d *Daemon, req *containerPostReq) Response {
+func createFromCopy(d *Daemon, req *containerPostReq) 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"))
 	}
 
 	source, err := containerLoadByName(d, req.Source.Source)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Config override
@@ -381,7 +383,7 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 		Profiles:     req.Profiles,
 	}
 
-	run := func(op *operation) error {
+	run := func(op *operation.Operation) error {
 		_, err := containerCreateAsCopy(d, args, source)
 		if err != nil {
 			return err
@@ -393,26 +395,26 @@ func createFromCopy(d *Daemon, req *containerPostReq) Response {
 	resources := map[string][]string{}
 	resources["containers"] = []string{req.Name, req.Source.Source}
 
-	op, err := operationCreate(operationClassTask, resources, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func containersPost(d *Daemon, r *http.Request) Response {
+func containersPost(d *Daemon, r *http.Request) response.Response {
 	shared.LogDebugf("Responding to container create")
 
 	req := containerPostReq{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	if req.Name == "" {
 		cs, err := dbContainersList(d.db, cTypeRegular)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
 		i := 0
@@ -424,7 +426,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"))
 			}
 		}
 		shared.LogDebugf("No name provided, creating %s", req.Name)
@@ -439,7 +441,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 {
@@ -452,6 +454,6 @@ func containersPost(d *Daemon, r *http.Request) Response {
 	case "copy":
 		return createFromCopy(d, &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))
 	}
 }
diff --git a/lxd/daemon.go b/lxd/daemon.go
index d2dcd31..3511f73 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -30,6 +30,8 @@ import (
 	"gopkg.in/tomb.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
@@ -101,11 +103,11 @@ type Command struct {
 	name          string
 	untrustedGet  bool
 	untrustedPost bool
-	get           func(d *Daemon, r *http.Request) Response
-	put           func(d *Daemon, r *http.Request) Response
-	post          func(d *Daemon, r *http.Request) Response
-	delete        func(d *Daemon, r *http.Request) Response
-	patch         func(d *Daemon, r *http.Request) Response
+	get           func(d *Daemon, r *http.Request) response.Response
+	put           func(d *Daemon, r *http.Request) response.Response
+	post          func(d *Daemon, r *http.Request) response.Response
+	delete        func(d *Daemon, r *http.Request) response.Response
+	patch         func(d *Daemon, r *http.Request) response.Response
 }
 
 func (d *Daemon) httpClient(certificate string) (*http.Client, error) {
@@ -284,16 +286,16 @@ func (d *Daemon) createCmd(version string, c Command) {
 			shared.LogWarn(
 				"rejecting request from untrusted client",
 				log.Ctx{"ip": r.RemoteAddr})
-			Forbidden.Render(w)
+			response.Forbidden.Render(w)
 			return
 		}
 
-		if debug && r.Method != "GET" && isJSONRequest(r) {
+		if state.Debug && r.Method != "GET" && isJSONRequest(r) {
 			newBody := &bytes.Buffer{}
 			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
 			}
 
@@ -301,8 +303,8 @@ func (d *Daemon) createCmd(version string, c Command) {
 			shared.DebugJson(captured)
 		}
 
-		var resp Response
-		resp = NotImplemented
+		var resp response.Response
+		resp = response.NotImplemented
 
 		switch r.Method {
 		case "GET":
@@ -326,11 +328,11 @@ func (d *Daemon) createCmd(version string, c Command) {
 				resp = c.patch(d, r)
 			}
 		default:
-			resp = NotFound
+			resp = response.NotFound
 		}
 
 		if err := resp.Render(w); err != nil {
-			err := InternalError(err).Render(w)
+			err := response.InternalError(err).Render(w)
 			if err != nil {
 				shared.LogErrorf("Failed writing error for error, giving up")
 			}
@@ -907,7 +909,7 @@ func (d *Daemon) Init() error {
 
 	d.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 _, c := range api10 {
@@ -921,7 +923,7 @@ func (d *Daemon) Init() error {
 	d.mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 		shared.LogInfo("Sending top level 404", log.Ctx{"url": r.URL})
 		w.Header().Set("Content-Type", "application/json")
-		NotFound.Render(w)
+		response.NotFound.Render(w)
 	})
 
 	listeners := d.GetListeners()
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index c2dd4f6..606a315 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -15,6 +15,7 @@ import (
 
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/operation"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/simplestreams"
@@ -86,7 +87,7 @@ func imageLoadStreamCache(d *Daemon) error {
 
 // ImageDownload checks if we have that Image Fingerprint else
 // downloads the image from a remote server.
-func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
+func (d *Daemon) ImageDownload(op *operation.Operation, server string, protocol string, certificate string, secret string, alias string, forContainer bool, autoUpdate bool) (string, error) {
 	var err error
 	var ss *simplestreams.SimpleStreams
 	var ctxMap log.Ctx
@@ -228,7 +229,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}
 	}
 
 	shared.LogInfo("Downloading image", ctxMap)
@@ -264,7 +265,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{})
 		}
diff --git a/lxd/db.go b/lxd/db.go
index a76397b..cae06ed 100644
--- a/lxd/db.go
+++ b/lxd/db.go
@@ -10,21 +10,6 @@ import (
 	"github.com/lxc/lxd/shared"
 )
 
-var (
-	// DbErrAlreadyDefined hapens when the given entry already exists,
-	// for example a container.
-	DbErrAlreadyDefined = fmt.Errorf("The container/snapshot already exists")
-
-	/* NoSuchObjectError is in the case of joins (and probably other) queries,
-	 * we don't get back sql.ErrNoRows when no rows are returned, even though we do
-	 * on selects without joins. Instead, you can use this error to
-	 * propagate up and generate proper 404s to the client when something
-	 * isn't found so we don't abuse sql.ErrNoRows any more than we
-	 * already do.
-	 */
-	NoSuchObjectError = fmt.Errorf("No such object")
-)
-
 // Profile is here to order Profiles.
 type Profile struct {
 	name  string
diff --git a/lxd/db_containers.go b/lxd/db_containers.go
index eec6e9c..d3097ca 100644
--- a/lxd/db_containers.go
+++ b/lxd/db_containers.go
@@ -5,6 +5,7 @@ import (
 	"fmt"
 	"time"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -124,7 +125,7 @@ func dbContainerGet(db *sql.DB, name string) (containerArgs, error) {
 func dbContainerCreate(db *sql.DB, args containerArgs) (int, error) {
 	id, err := dbContainerId(db, args.Name)
 	if err == nil {
-		return 0, DbErrAlreadyDefined
+		return 0, util.DbErrAlreadyDefined
 	}
 
 	tx, err := dbBegin(db)
@@ -308,7 +309,7 @@ func dbContainerConfig(db *sql.DB, containerId int) (map[string]string, error) {
 	// Results is already a slice here, not db Rows anymore.
 	results, err := dbQueryScan(db, q, inargs, outfmt)
 	if err != nil {
-		return nil, err //SmartError will wrap this and make "not found" errors pretty
+		return nil, err //response.SmartError will wrap this and make "not found" errors pretty
 	}
 
 	config := map[string]string{}
diff --git a/lxd/db_images.go b/lxd/db_images.go
index ac515a3..2e9a1d9 100644
--- a/lxd/db_images.go
+++ b/lxd/db_images.go
@@ -7,6 +7,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/osarch"
 )
@@ -88,7 +89,7 @@ func dbImageSourceGet(db *sql.DB, imageId int) (int, shared.ImageSource, error)
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageSource{}, NoSuchObjectError
+			return -1, shared.ImageSource{}, util.NoSuchObjectError
 		}
 
 		return -1, shared.ImageSource{}, err
@@ -263,7 +264,7 @@ func dbImageAliasGet(db *sql.DB, name string, isTrustedClient bool) (int, shared
 	err := dbQueryRowScan(db, q, arg1, arg2)
 	if err != nil {
 		if err == sql.ErrNoRows {
-			return -1, shared.ImageAliasesEntry{}, NoSuchObjectError
+			return -1, shared.ImageAliasesEntry{}, util.NoSuchObjectError
 		}
 
 		return -1, shared.ImageAliasesEntry{}, err
diff --git a/lxd/db_networks.go b/lxd/db_networks.go
index 6ee23a3..654c649 100644
--- a/lxd/db_networks.go
+++ b/lxd/db_networks.go
@@ -7,6 +7,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -120,7 +121,7 @@ func dbNetworkConfigGet(db *sql.DB, id int64) (map[string]string, error) {
 		}
 
 		if len(results) == 0 {
-			return nil, NoSuchObjectError
+			return nil, util.NoSuchObjectError
 		}
 	}
 
diff --git a/lxd/db_profiles.go b/lxd/db_profiles.go
index 4cbfc9d..eff9b81 100644
--- a/lxd/db_profiles.go
+++ b/lxd/db_profiles.go
@@ -6,6 +6,7 @@ import (
 
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -163,7 +164,7 @@ func dbProfileConfig(db *sql.DB, name string) (map[string]string, error) {
 		}
 
 		if len(results) == 0 {
-			return nil, NoSuchObjectError
+			return nil, util.NoSuchObjectError
 		}
 	}
 
diff --git a/lxd/db_test.go b/lxd/db_test.go
index b2e3984..6cc306a 100644
--- a/lxd/db_test.go
+++ b/lxd/db_test.go
@@ -476,8 +476,8 @@ func Test_dbImageAliasGet_alias_does_not_exists(t *testing.T) {
 
 	_, _, err = dbImageAliasGet(db, "whatever", true)
 
-	if err != NoSuchObjectError {
-		t.Fatal("Error should be NoSuchObjectError")
+	if err != util.NoSuchObjectError {
+		t.Fatal("Error should be util.NoSuchObjectError")
 	}
 }
 
diff --git a/lxd/devlxd.go b/lxd/devlxd.go
index af59000..9b49f54 100644
--- a/lxd/devlxd.go
+++ b/lxd/devlxd.go
@@ -15,6 +15,7 @@ import (
 
 	"github.com/gorilla/mux"
 
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -116,7 +117,7 @@ func hoistReq(f func(container, *http.Request) *devLxdResponse, d *Daemon) func(
 			http.Error(w, fmt.Sprintf("%s", resp.content), resp.code)
 		} else if resp.ctype == "json" {
 			w.Header().Set("Content-Type", "application/json")
-			WriteJSON(w, resp.content)
+			util.WriteJSON(w, resp.content)
 		} else {
 			w.Header().Set("Content-Type", "application/octet-stream")
 			fmt.Fprintf(w, resp.content.(string))
diff --git a/lxd/events.go b/lxd/events.go
index 46b7dc6..573b228 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -1,145 +1,14 @@
 package main
 
 import (
-	"encoding/json"
-	"fmt"
 	"net/http"
-	"strings"
-	"sync"
-	"time"
 
-	"github.com/gorilla/websocket"
-	"github.com/pborman/uuid"
-	log "gopkg.in/inconshreveable/log15.v2"
-
-	"github.com/lxc/lxd/shared"
+	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/response"
 )
 
-type eventsHandler struct {
-}
-
-func logContextMap(ctx []interface{}) map[string]string {
-	var key string
-	ctxMap := map[string]string{}
-
-	for _, entry := range ctx {
-		if key == "" {
-			key = entry.(string)
-		} else {
-			ctxMap[key] = fmt.Sprintf("%s", entry)
-			key = ""
-		}
-	}
-
-	return ctxMap
-}
-
-func (h eventsHandler) Log(r *log.Record) error {
-	eventSend("logging", shared.Jmap{
-		"message": r.Msg,
-		"level":   r.Lvl.String(),
-		"context": logContextMap(r.Ctx)})
-	return nil
-}
-
-var eventsLock sync.Mutex
-var eventListeners map[string]*eventListener = make(map[string]*eventListener)
-
-type eventListener struct {
-	connection   *websocket.Conn
-	messageTypes []string
-	active       chan bool
-	id           string
-	msgLock      sync.Mutex
-}
-
-type eventsServe struct {
-	req *http.Request
-}
-
-func (r *eventsServe) Render(w http.ResponseWriter) error {
-	return eventsSocket(r.req, w)
-}
-
-func (r *eventsServe) String() string {
-	return "event handler"
-}
-
-func eventsSocket(r *http.Request, w http.ResponseWriter) error {
-	listener := eventListener{}
-
-	typeStr := r.FormValue("type")
-	if typeStr == "" {
-		typeStr = "logging,operation"
-	}
-
-	c, err := shared.WebsocketUpgrader.Upgrade(w, r, nil)
-	if err != nil {
-		return err
-	}
-
-	listener.active = make(chan bool, 1)
-	listener.connection = c
-	listener.id = uuid.NewRandom().String()
-	listener.messageTypes = strings.Split(typeStr, ",")
-
-	eventsLock.Lock()
-	eventListeners[listener.id] = &listener
-	eventsLock.Unlock()
-
-	shared.LogDebugf("New events listener: %s", listener.id)
-
-	<-listener.active
-
-	eventsLock.Lock()
-	delete(eventListeners, listener.id)
-	eventsLock.Unlock()
-
-	listener.connection.Close()
-	shared.LogDebugf("Disconnected events listener: %s", listener.id)
-
-	return nil
-}
-
-func eventsGet(d *Daemon, r *http.Request) Response {
-	return &eventsServe{r}
+func eventsGet(d *Daemon, r *http.Request) response.Response {
+	return &events.EventsServe{r}
 }
 
 var eventsCmd = Command{name: "events", get: eventsGet}
-
-func eventSend(eventType string, eventMessage interface{}) error {
-	event := shared.Jmap{}
-	event["type"] = eventType
-	event["timestamp"] = time.Now()
-	event["metadata"] = eventMessage
-
-	body, err := json.Marshal(event)
-	if err != nil {
-		return err
-	}
-
-	eventsLock.Lock()
-	listeners := eventListeners
-	for _, listener := range listeners {
-		if !shared.StringInSlice(eventType, listener.messageTypes) {
-			continue
-		}
-
-		go func(listener *eventListener, body []byte) {
-			if listener == nil {
-				return
-			}
-
-			listener.msgLock.Lock()
-			err = listener.connection.WriteMessage(websocket.TextMessage, body)
-			listener.msgLock.Unlock()
-
-			if err != nil {
-				listener.active <- false
-			}
-		}(listener, body)
-	}
-	eventsLock.Unlock()
-
-	return nil
-}
diff --git a/lxd/events/events.go b/lxd/events/events.go
new file mode 100644
index 0000000..b612186
--- /dev/null
+++ b/lxd/events/events.go
@@ -0,0 +1,135 @@
+package events
+
+import (
+	"encoding/json"
+	"fmt"
+	"net/http"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/gorilla/websocket"
+	"github.com/pborman/uuid"
+	log "gopkg.in/inconshreveable/log15.v2"
+
+	"github.com/lxc/lxd/shared"
+)
+
+type EventsHandler struct {
+}
+
+func logContextMap(ctx []interface{}) map[string]string {
+	var key string
+	ctxMap := map[string]string{}
+
+	for _, entry := range ctx {
+		if key == "" {
+			key = entry.(string)
+		} else {
+			ctxMap[key] = fmt.Sprintf("%s", entry)
+			key = ""
+		}
+	}
+
+	return ctxMap
+}
+
+func (h EventsHandler) Log(r *log.Record) error {
+	Send("logging", shared.Jmap{
+		"message": r.Msg,
+		"level":   r.Lvl.String(),
+		"context": logContextMap(r.Ctx)})
+	return nil
+}
+
+var eventsLock sync.Mutex
+var eventListeners map[string]*eventListener = make(map[string]*eventListener)
+
+type eventListener struct {
+	connection   *websocket.Conn
+	messageTypes []string
+	active       chan bool
+	id           string
+	msgLock      sync.Mutex
+}
+
+type EventsServe struct {
+	Req *http.Request
+}
+
+func (r *EventsServe) Render(w http.ResponseWriter) error {
+	listener := eventListener{}
+
+	typeStr := r.Req.FormValue("type")
+	if typeStr == "" {
+		typeStr = "logging,operation"
+	}
+
+	c, err := shared.WebsocketUpgrader.Upgrade(w, r.Req, nil)
+	if err != nil {
+		return err
+	}
+
+	listener.active = make(chan bool, 1)
+	listener.connection = c
+	listener.id = uuid.NewRandom().String()
+	listener.messageTypes = strings.Split(typeStr, ",")
+
+	eventsLock.Lock()
+	eventListeners[listener.id] = &listener
+	eventsLock.Unlock()
+
+	shared.LogDebugf("New events listener: %s", listener.id)
+
+	<-listener.active
+
+	eventsLock.Lock()
+	delete(eventListeners, listener.id)
+	eventsLock.Unlock()
+
+	listener.connection.Close()
+	shared.LogDebugf("Disconnected events listener: %s", listener.id)
+
+	return nil
+}
+
+func (r *EventsServe) String() string {
+	return "event handler"
+}
+
+func Send(eventType string, eventMessage interface{}) error {
+	event := shared.Jmap{}
+	event["type"] = eventType
+	event["timestamp"] = time.Now()
+	event["metadata"] = eventMessage
+
+	body, err := json.Marshal(event)
+	if err != nil {
+		return err
+	}
+
+	eventsLock.Lock()
+	listeners := eventListeners
+	for _, listener := range listeners {
+		if !shared.StringInSlice(eventType, listener.messageTypes) {
+			continue
+		}
+
+		go func(listener *eventListener, body []byte) {
+			if listener == nil {
+				return
+			}
+
+			listener.msgLock.Lock()
+			err = listener.connection.WriteMessage(websocket.TextMessage, body)
+			listener.msgLock.Unlock()
+
+			if err != nil {
+				listener.active <- false
+			}
+		}(listener, body)
+	}
+	eventsLock.Unlock()
+
+	return nil
+}
diff --git a/lxd/images.go b/lxd/images.go
index c6724e1..8d71c2a 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -22,6 +22,9 @@ import (
 	"github.com/gorilla/mux"
 	"gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/logging"
 	"github.com/lxc/lxd/shared/osarch"
@@ -326,7 +329,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req imagePostReq,
 	return info, nil
 }
 
-func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation.Operation) error {
 	var err error
 	var hash string
 
@@ -369,7 +372,7 @@ func imgPostRemoteInfo(d *Daemon, req imagePostReq, op *operation) error {
 	return nil
 }
 
-func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation) error {
+func imgPostURLInfo(d *Daemon, req imagePostReq, op *operation.Operation) error {
 	var err error
 
 	if req.Source["url"] == "" {
@@ -664,13 +667,13 @@ func imageBuildFromInfo(d *Daemon, info shared.ImageInfo) (metadata map[string]s
 	return metadata, nil
 }
 
-func imagesPost(d *Daemon, r *http.Request) Response {
+func imagesPost(d *Daemon, r *http.Request) response.Response {
 	var err error
 
 	// 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) {
@@ -687,13 +690,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?
@@ -705,18 +708,18 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 	err = decoder.Decode(&req)
 	if err != nil {
 		if r.Header.Get("Content-Type") == "application/json" {
-			return BadRequest(err)
+			return response.BadRequest(err)
 		}
 		imageUpload = true
 	}
 
 	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"))
 	}
 
 	// Begin background operation
-	run := func(op *operation) error {
+	run := func(op *operation.Operation) error {
 		var info shared.ImageInfo
 
 		// Setup the cleanup function
@@ -766,12 +769,12 @@ func imagesPost(d *Daemon, r *http.Request) Response {
 		return nil
 	}
 
-	op, err := operationCreate(operationClassTask, nil, nil, run, nil, nil)
+	op, err := operation.Create(operation.ClassTask, nil, nil, run, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
 func getImageMetadata(fname string) (*imageMetadata, error) {
@@ -848,14 +851,14 @@ func doImagesGet(d *Daemon, recursion bool, public bool) (interface{}, error) {
 	return resultMap, nil
 }
 
-func imagesGet(d *Daemon, r *http.Request) Response {
+func imagesGet(d *Daemon, r *http.Request) response.Response {
 	public := !d.isTrustedClient(r)
 
 	result, err := doImagesGet(d, d.isRecursionRequest(r), public)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
-	return SyncResponse(true, result)
+	return response.SyncResponse(true, result)
 }
 
 var imagesCmd = Command{name: "images", post: imagesPost, untrustedGet: true, get: imagesGet}
@@ -988,40 +991,40 @@ func doDeleteImage(d *Daemon, fingerprint string) error {
 	return nil
 }
 
-func imageDelete(d *Daemon, r *http.Request) Response {
+func imageDelete(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
-	rmimg := func(op *operation) error {
+	rmimg := func(op *operation.Operation) error {
 		return doDeleteImage(d, fingerprint)
 	}
 
 	resources := map[string][]string{}
 	resources["images"] = []string{fingerprint}
 
-	op, err := operationCreate(operationClassTask, resources, nil, rmimg, nil, nil)
+	op, err := operation.Create(operation.ClassTask, resources, nil, rmimg, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
-func doImageGet(d *Daemon, fingerprint string, public bool) (*shared.ImageInfo, Response) {
+func doImageGet(d *Daemon, fingerprint string, public bool) (*shared.ImageInfo, response.Response) {
 	_, imgInfo, err := dbImageGet(d.db, 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 operation.Operations() {
+		if op.Resources == nil {
 			continue
 		}
 
-		opImages, ok := op.resources["images"]
+		opImages, ok := op.Resources["images"]
 		if !ok {
 			continue
 		}
@@ -1030,7 +1033,7 @@ func imageValidSecret(fingerprint string, secret string) bool {
 			continue
 		}
 
-		opSecret, ok := op.metadata["secret"]
+		opSecret, ok := op.Metadata["secret"]
 		if !ok {
 			continue
 		}
@@ -1045,7 +1048,7 @@ 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 {
 	fingerprint := mux.Vars(r)["fingerprint"]
 	public := !d.isTrustedClient(r)
 	secret := r.FormValue("secret")
@@ -1054,13 +1057,13 @@ func imageGet(d *Daemon, r *http.Request) Response {
 		public = false
 	}
 
-	info, response := doImageGet(d, fingerprint, public)
-	if response != nil {
-		return response
+	info, resp := doImageGet(d, fingerprint, public)
+	if resp != nil {
+		return resp
 	}
 
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
-	return SyncResponseETag(true, info, etag)
+	return response.SyncResponseETag(true, info, etag)
 }
 
 type imagePutReq struct {
@@ -1069,52 +1072,52 @@ type imagePutReq struct {
 	AutoUpdate bool              `json:"auto_update"`
 }
 
-func imagePut(d *Daemon, r *http.Request) Response {
+func imagePut(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	fingerprint := mux.Vars(r)["fingerprint"]
 	id, info, err := dbImageGet(d.db, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
-	err = etagCheck(r, etag)
+	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := imagePutReq{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = dbImageUpdate(d.db, id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, 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
 	fingerprint := mux.Vars(r)["fingerprint"]
 	id, info, err := dbImageGet(d.db, fingerprint, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
 	etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
-	err = etagCheck(r, etag)
+	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))
@@ -1122,12 +1125,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 := imagePutReq{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get AutoUpdate
@@ -1157,10 +1160,10 @@ func imagePatch(d *Daemon, r *http.Request) Response {
 
 	err = dbImageUpdate(d.db, id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreationDate, info.ExpiryDate, info.Properties)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var imageCmd = Command{name: "images/{fingerprint}", untrustedGet: true, get: imageGet, put: imagePut, delete: imageDelete, patch: imagePatch}
@@ -1176,36 +1179,36 @@ type aliasPutReq struct {
 	Target      string `json:"target"`
 }
 
-func aliasesPost(d *Daemon, r *http.Request) Response {
+func aliasesPost(d *Daemon, r *http.Request) response.Response {
 	req := aliasPostReq{}
 	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 := dbImageAliasGet(d.db, req.Name, true)
 	if err == nil {
-		return Conflict
+		return response.Conflict
 	}
 
 	id, _, err := dbImageGet(d.db, req.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbImageAliasAdd(d.db, req.Name, id, req.Description)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
 }
 
-func aliasesGet(d *Daemon, r *http.Request) Response {
+func aliasesGet(d *Daemon, r *http.Request) response.Response {
 	recursion := d.isRecursionRequest(r)
 
 	q := "SELECT name FROM images_aliases"
@@ -1214,7 +1217,7 @@ func aliasesGet(d *Daemon, r *http.Request) Response {
 	outfmt := []interface{}{name}
 	results, err := dbQueryScan(d.db, q, inargs, outfmt)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 	responseStr := []string{}
 	responseMap := shared.ImageAliases{}
@@ -1234,98 +1237,98 @@ func aliasesGet(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 aliasGet(d *Daemon, r *http.Request) Response {
+func aliasGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	_, alias, err := dbImageAliasGet(d.db, name, d.isTrustedClient(r))
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, alias, alias)
+	return response.SyncResponseETag(true, alias, alias)
 }
 
-func aliasDelete(d *Daemon, r *http.Request) Response {
+func aliasDelete(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 	_, _, err := dbImageAliasGet(d.db, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbImageAliasDelete(d.db, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func aliasPut(d *Daemon, r *http.Request) Response {
+func aliasPut(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	name := mux.Vars(r)["name"]
 	id, alias, err := dbImageAliasGet(d.db, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
-	err = etagCheck(r, alias)
+	err = util.EtagCheck(r, alias)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := aliasPutReq{}
 	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 := dbImageGet(d.db, req.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbImageAliasUpdate(d.db, id, imageId, req.Description)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func aliasPatch(d *Daemon, r *http.Request) Response {
+func aliasPatch(d *Daemon, r *http.Request) response.Response {
 	// Get current value
 	name := mux.Vars(r)["name"]
 	id, alias, err := dbImageAliasGet(d.db, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate ETag
-	err = etagCheck(r, alias)
+	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
@@ -1335,7 +1338,7 @@ func aliasPatch(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
@@ -1343,45 +1346,45 @@ func aliasPatch(d *Daemon, r *http.Request) Response {
 
 	imageId, _, err := dbImageGet(d.db, alias.Target, false, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbImageAliasUpdate(d.db, id, imageId, alias.Description)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
-func aliasPost(d *Daemon, r *http.Request) Response {
+func aliasPost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	req := aliasPostReq{}
 	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, _, _ := dbImageAliasGet(d.db, req.Name, true)
 	if id > 0 {
-		return Conflict
+		return response.Conflict
 	}
 
 	id, _, err := dbImageAliasGet(d.db, name, true)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbImageAliasRename(d.db, id, req.Name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", shared.APIVersion, req.Name))
 }
 
-func imageExport(d *Daemon, r *http.Request) Response {
+func imageExport(d *Daemon, r *http.Request) response.Response {
 	fingerprint := mux.Vars(r)["fingerprint"]
 
 	public := !d.isTrustedClient(r)
@@ -1393,7 +1396,7 @@ func imageExport(d *Daemon, r *http.Request) Response {
 
 	_, imgInfo, err := dbImageGet(d.db, fingerprint, public, false)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	imagePath := shared.VarPath("images", imgInfo.Fingerprint)
@@ -1406,11 +1409,11 @@ func imageExport(d *Daemon, r *http.Request) Response {
 	filename := fmt.Sprintf("%s%s", 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.
@@ -1420,32 +1423,32 @@ func imageExport(d *Daemon, r *http.Request) Response {
 		}
 		filename = fmt.Sprintf("%s%s", 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 {
 	fingerprint := mux.Vars(r)["fingerprint"]
 	_, _, err := dbImageGet(d.db, 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{}
@@ -1454,12 +1457,12 @@ func imageSecret(d *Daemon, r *http.Request) Response {
 	resources := map[string][]string{}
 	resources["images"] = []string{fingerprint}
 
-	op, err := operationCreate(operationClassToken, resources, meta, nil, nil, nil)
+	op, err := operation.Create(operation.ClassToken, resources, meta, nil, nil, nil)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return OperationResponse(op)
+	return response.OperationResponse(op)
 }
 
 var imagesExportCmd = Command{name: "images/{fingerprint}/export", untrustedGet: true, get: imageExport}
diff --git a/lxd/main.go b/lxd/main.go
index 327df27..103b3a2 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -6,6 +6,8 @@ import (
 	"os"
 	"time"
 
+	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/gnuflag"
 	"github.com/lxc/lxd/shared/logging"
@@ -32,18 +34,8 @@ var argTrustPassword = gnuflag.String("trust-password", "", "")
 var argVerbose = gnuflag.Bool("verbose", false, "")
 var argVersion = gnuflag.Bool("version", false, "")
 
-// Global variables
-var debug bool
-var verbose bool
-var execPath string
-
 func init() {
 	rand.Seed(time.Now().UTC().UnixNano())
-	absPath, err := os.Readlink("/proc/self/exe")
-	if err != nil {
-		absPath = "bad-exec-path"
-	}
-	execPath = absPath
 }
 
 func main() {
@@ -155,8 +147,8 @@ func run() error {
 	gnuflag.Parse(true)
 
 	// Set the global variables
-	debug = *argDebug
-	verbose = *argVerbose
+	state.Debug = *argDebug
+	state.Verbose = *argVerbose
 
 	if *argHelp {
 		// The user asked for help via --help, so we shouldn't print to
@@ -182,7 +174,7 @@ func run() error {
 		syslog = "lxd"
 	}
 
-	handler := eventsHandler{}
+	handler := events.EventsHandler{}
 	var err error
 	shared.Log, err = logging.GetLogger(syslog, *argLogfile, *argVerbose, *argDebug, handler)
 	if err != nil {
diff --git a/lxd/main_init.go b/lxd/main_init.go
index 2d621d8..c2fd50f 100644
--- a/lxd/main_init.go
+++ b/lxd/main_init.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/crypto/ssh/terminal"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -49,7 +50,7 @@ func cmdInit() error {
 	// Detect zfs
 	out, err := exec.LookPath("zfs")
 	if err == nil && len(out) != 0 && !runningInUserns {
-		_ = loadModule("zfs")
+		_ = util.LoadModule("zfs")
 
 		err := shared.RunCommand("zpool", "list")
 		if err == nil {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index e45fb15..a4e59f9 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -20,6 +20,8 @@ import (
 	"gopkg.in/lxc/go-lxc.v2"
 
 	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -193,7 +195,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 *operation.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -210,7 +212,7 @@ func (s *migrationSourceWs) Connect(op *operation, r *http.Request, w http.Respo
 		conn = &s.fsConn
 	default:
 		/* If we didn't find the right secret, the user provided a bad one,
-		 * which 403, not 404, since this operation actually exists */
+		 * which 403, not 404, since this operation.Operation actually exists */
 		return os.ErrPermission
 	}
 
@@ -233,7 +235,7 @@ func writeActionScript(directory string, operation string, secret string) error
 if [ "$CRTOOLS_SCRIPT_ACTION" = "post-dump" ]; then
 	%s migratedumpsuccess %s %s
 fi
-`, execPath, operation, secret)
+`, state.ExecPath, operation, secret)
 
 	f, err := os.Create(filepath.Join(directory, "action.sh"))
 	if err != nil {
@@ -284,7 +286,7 @@ func snapshotToProtobuf(c container) *Snapshot {
 	}
 }
 
-func (s *migrationSourceWs) Do(migrateOp *operation) error {
+func (s *migrationSourceWs) Do(migrateOp *operation.Operation) error {
 	<-s.allConnected
 
 	criuType := CRIUType_CRIU_RSYNC.Enum()
@@ -404,7 +406,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 			 * executable path, so we write a custom action script with the
 			 * real command we want to run.)
 			 *
-			 * This script then hangs until the migration operation either
+			 * This script then hangs until the migration operation.Operation either
 			 * finishes successfully or fails, and exits 1 or 0, which
 			 * causes criu to either leave the container running or kill it
 			 * as we asked.
@@ -416,11 +418,11 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			}
 
-			actionScriptOp, err := operationCreate(
-				operationClassWebsocket,
+			actionScriptOp, err := operation.Create(
+				operation.ClassWebsocket,
 				nil,
 				nil,
-				func(op *operation) error {
+				func(op *operation.Operation) error {
 					result := <-restoreSuccess
 					if !result {
 						return fmt.Errorf("restore failed, failing CRIU")
@@ -428,7 +430,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 					return nil
 				},
 				nil,
-				func(op *operation, r *http.Request, w http.ResponseWriter) error {
+				func(op *operation.Operation, r *http.Request, w http.ResponseWriter) error {
 					secret := r.FormValue("secret")
 					if secret == "" {
 						return fmt.Errorf("missing secret")
@@ -454,7 +456,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
 				return abort(err)
 			}
 
-			if err := writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret); err != nil {
+			if err := writeActionScript(checkpointDir, actionScriptOp.Url(), actionScriptOpSecret); err != nil {
 				os.RemoveAll(checkpointDir)
 				return abort(err)
 			}
@@ -607,7 +609,7 @@ func NewMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
 func (c *migrationSink) connectWithSecret(secret string) (*websocket.Conn, error) {
 	query := url.Values{"secret": []string{secret}}
 
-	// The URL is a https URL to the operation, mangle to be a wss URL to the secret
+	// The URL is a https URL to the operation.Operation, mangle to be a wss URL to the secret
 	wsUrl := fmt.Sprintf("wss://%s/websocket?%s", strings.TrimPrefix(c.url, "https://"), query.Encode())
 
 	return lxd.WebsocketDial(c.dialer, wsUrl)
@@ -626,7 +628,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 *operation.Operation, r *http.Request, w http.ResponseWriter) error {
 	secret := r.FormValue("secret")
 	if secret == "" {
 		return fmt.Errorf("missing secret")
@@ -643,7 +645,7 @@ func (s *migrationSink) Connect(op *operation, r *http.Request, w http.ResponseW
 		conn = &s.dest.fsConn
 	default:
 		/* If we didn't find the right secret, the user provided a bad one,
-		 * which 403, not 404, since this operation actually exists */
+		 * which 403, not 404, since this operation.Operation actually exists */
 		return os.ErrPermission
 	}
 
@@ -661,7 +663,7 @@ func (s *migrationSink) Connect(op *operation, r *http.Request, w http.ResponseW
 	return nil
 }
 
-func (c *migrationSink) Do(migrateOp *operation) error {
+func (c *migrationSink) Do(migrateOp *operation.Operation) error {
 	var err error
 
 	if c.push {
diff --git a/lxd/networks.go b/lxd/networks.go
index 62ed2b9..0e44f0c 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -15,11 +15,13 @@ import (
 	"github.com/gorilla/mux"
 	log "gopkg.in/inconshreveable/log15.v2"
 
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 )
 
 // API endpoints
-func networksGet(d *Daemon, r *http.Request) Response {
+func networksGet(d *Daemon, r *http.Request) response.Response {
 	recursionStr := r.FormValue("recursion")
 	recursion, err := strconv.Atoi(recursionStr)
 	if err != nil {
@@ -28,7 +30,7 @@ func networksGet(d *Daemon, r *http.Request) Response {
 
 	ifs, err := networkGetInterfaces(d)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	resultString := []string{}
@@ -46,42 +48,42 @@ func networksGet(d *Daemon, r *http.Request) Response {
 	}
 
 	if recursion == 0 {
-		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 {
 	req := shared.NetworkConfig{}
 
 	// 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"))
 	}
 
 	networks, err := networkGetInterfaces(d)
 	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"))
 	}
 
 	if req.Config == nil {
@@ -90,7 +92,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
 
 	err = networkValidateConfig(req.Name, req.Config)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Set some default values where needed
@@ -120,44 +122,44 @@ func networksPost(d *Daemon, r *http.Request) Response {
 	// Replace "auto" by actual values
 	err = networkFillAuto(req.Config)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Create the database entry
 	_, err = dbNetworkCreate(d.db, req.Name, req.Config)
 	if err != nil {
-		return InternalError(
+		return response.InternalError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
 	// Start the network
 	n, err := networkLoadByName(d, req.Name)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	err = n.Start()
 	if err != nil {
 		n.Delete()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.APIVersion, req.Name))
 }
 
 var networksCmd = Command{name: "networks", get: networksGet, post: networksPost}
 
-func networkGet(d *Daemon, r *http.Request) Response {
+func networkGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	n, err := doNetworkGet(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	etag := []interface{}{n.Name, n.Managed, n.Type, n.Config}
 
-	return SyncResponseETag(true, &n, etag)
+	return response.SyncResponseETag(true, &n, etag)
 }
 
 func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, error) {
@@ -219,19 +221,19 @@ func doNetworkGet(d *Daemon, name string) (shared.NetworkConfig, 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"]
 
 	// Get the existing network
 	n, err := networkLoadByName(d, name)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	// Attempt to delete the network
 	err = n.Delete()
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Cleanup storage
@@ -239,99 +241,99 @@ 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 {
 	name := mux.Vars(r)["name"]
 	req := shared.NetworkConfig{}
 
 	// 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(d, name)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	// 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)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if shared.StringInSlice(req.Name, networks) {
-		return Conflict
+		return response.Conflict
 	}
 
 	// 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", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", shared.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 := dbNetworkGet(d.db, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Config}
 
-	err = etagCheck(r, etag)
+	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := shared.NetworkConfig{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
 }
 
-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 := dbNetworkGet(d.db, name)
 	if dbInfo != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	// Validate the ETag
 	etag := []interface{}{dbInfo.Name, dbInfo.Managed, dbInfo.Type, dbInfo.Config}
 
-	err = etagCheck(r, etag)
+	err = util.EtagCheck(r, etag)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := shared.NetworkConfig{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Config stacking
@@ -349,11 +351,11 @@ func networkPatch(d *Daemon, r *http.Request) Response {
 	return doNetworkUpdate(d, name, dbInfo.Config, req.Config)
 }
 
-func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) Response {
+func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newConfig map[string]string) response.Response {
 	// Validate the configuration
 	err := networkValidateConfig(name, newConfig)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// When switching to a fan bridge, auto-detect the underlay
@@ -366,15 +368,15 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, newCon
 	// Load the network
 	n, err := networkLoadByName(d, name)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	err = n.Update(shared.NetworkConfig{Config: newConfig})
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var networkCmd = Command{name: "networks/{name}", get: networkGet, delete: networkDelete, post: networkPost, put: networkPut, patch: networkPatch}
diff --git a/lxd/operation/operations.go b/lxd/operation/operations.go
new file mode 100644
index 0000000..148a55f
--- /dev/null
+++ b/lxd/operation/operations.go
@@ -0,0 +1,458 @@
+package operation
+
+import (
+	"database/sql"
+	"fmt"
+	"net/http"
+	"os"
+	"runtime"
+	"sync"
+	"time"
+
+	"github.com/mattn/go-sqlite3"
+	"github.com/pborman/uuid"
+
+	"github.com/lxc/lxd/lxd/events"
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared"
+)
+
+var operationsLock sync.Mutex
+var operations map[string]*Operation = make(map[string]*Operation)
+
+type Class int
+
+const (
+	ClassTask      Class = 1
+	ClassWebsocket Class = 2
+	ClassToken     Class = 3
+)
+
+func (t Class) String() string {
+	return map[Class]string{
+		ClassTask:      "task",
+		ClassWebsocket: "websocket",
+		ClassToken:     "token",
+	}[t]
+}
+
+func Operations() map[string]*Operation {
+	operationsLock.Lock()
+	ops := operations
+	operationsLock.Unlock()
+	return ops
+}
+
+type Operation struct {
+	id        string
+	class     Class
+	createdAt time.Time
+	updatedAt time.Time
+	status    shared.StatusCode
+	url       string
+	Resources map[string][]string
+	Metadata  map[string]interface{}
+	err       string
+	readonly  bool
+
+	// 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
+}
+
+func (op *Operation) Id() string {
+	return op.id
+}
+
+func (op *Operation) Url() string {
+	return op.url
+}
+
+func (op *Operation) Status() shared.StatusCode {
+	return op.status
+}
+
+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()
+
+		/*
+		 * When we create a new lxc.Container, it adds a finalizer (via
+		 * SetFinalizer) that frees the struct. However, it sometimes
+		 * takes the go GC a while to actually free the struct,
+		 * presumably since it is a small amount of memory.
+		 * Unfortunately, the struct also keeps the log fd open, so if
+		 * we leave too many of these around, we end up running out of
+		 * fds. So, let's explicitly do a GC to collect these at the
+		 * end of each request.
+		 */
+		runtime.GC()
+	})
+}
+
+func (op *Operation) Run() (chan error, error) {
+	if op.status != shared.Pending {
+		return nil, fmt.Errorf("Only pending operations can be started")
+	}
+
+	chanRun := make(chan error, 1)
+
+	op.lock.Lock()
+	op.status = shared.Running
+
+	if op.onRun != nil {
+		go func(op *Operation, chanRun chan error) {
+			err := op.onRun(op)
+			if err != nil {
+				op.lock.Lock()
+				op.status = shared.Failure
+				switch err {
+				case os.ErrNotExist:
+					op.err = "not found"
+				case sql.ErrNoRows:
+					op.err = "not found"
+				case util.NoSuchObjectError:
+					op.err = "not found"
+				case os.ErrPermission:
+					op.err = "not authorized"
+				case util.DbErrAlreadyDefined:
+					op.err = "already exists"
+				case sqlite3.ErrConstraintUnique:
+					op.err = "already exists"
+				default:
+					op.err = err.Error()
+				}
+				op.lock.Unlock()
+				op.done()
+				chanRun <- err
+
+				shared.LogDebugf("Failure for %s Operation: %s: %s", op.class.String(), op.id, err)
+
+				_, md, _ := op.Render()
+				events.Send("Operation", md)
+				return
+			}
+
+			op.lock.Lock()
+			op.status = shared.Success
+			op.lock.Unlock()
+			op.done()
+			chanRun <- nil
+
+			op.lock.Lock()
+			shared.LogDebugf("Success for %s Operation: %s", op.class.String(), op.id)
+			_, md, _ := op.Render()
+			events.Send("Operation", md)
+			op.lock.Unlock()
+		}(op, chanRun)
+	}
+	op.lock.Unlock()
+
+	shared.LogDebugf("Started %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send("Operation", md)
+
+	return chanRun, nil
+}
+
+func (op *Operation) Cancel() (chan error, error) {
+	if op.status != shared.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 = shared.Cancelling
+	op.lock.Unlock()
+
+	if op.onCancel != nil {
+		go func(op *Operation, oldStatus shared.StatusCode, chanCancel chan error) {
+			err := op.onCancel(op)
+			if err != nil {
+				op.lock.Lock()
+				op.status = oldStatus
+				op.lock.Unlock()
+				chanCancel <- err
+
+				shared.LogDebugf("Failed to cancel %s Operation: %s: %s", op.class.String(), op.id, err)
+				_, md, _ := op.Render()
+				events.Send("Operation", md)
+				return
+			}
+
+			op.lock.Lock()
+			op.status = shared.Cancelled
+			op.lock.Unlock()
+			op.done()
+			chanCancel <- nil
+
+			shared.LogDebugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+			_, md, _ := op.Render()
+			events.Send("Operation", md)
+		}(op, oldStatus, chanCancel)
+	}
+
+	shared.LogDebugf("Cancelling %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send("Operation", md)
+
+	if op.onCancel == nil {
+		op.lock.Lock()
+		op.status = shared.Cancelled
+		op.lock.Unlock()
+		op.done()
+		chanCancel <- nil
+	}
+
+	shared.LogDebugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+	_, md, _ = op.Render()
+	events.Send("Operation", md)
+
+	return chanCancel, nil
+}
+
+func (op *Operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) {
+	if op.class != ClassWebsocket {
+		return nil, fmt.Errorf("Only websocket operations can be connected")
+	}
+
+	if op.status != shared.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
+
+			shared.LogDebugf("Failed to handle %s Operation: %s: %s", op.class.String(), op.id, err)
+			return
+		}
+
+		chanConnect <- nil
+
+		shared.LogDebugf("Handled %s Operation: %s", op.class.String(), op.id)
+	}(op, chanConnect)
+	op.lock.Unlock()
+
+	shared.LogDebugf("Connected %s Operation: %s", op.class.String(), op.id)
+
+	return chanConnect, nil
+}
+
+func (op *Operation) mayCancel() bool {
+	return op.onCancel != nil || op.class == ClassToken
+}
+
+func (op *Operation) Render() (string, *shared.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", shared.APIVersion, key, c))
+			}
+			tmpResources[key] = values
+		}
+		resources = tmpResources
+	}
+
+	md := shared.Jmap(op.Metadata)
+
+	return op.url, &shared.Operation{
+		Id:         op.id,
+		Class:      op.class.String(),
+		CreatedAt:  op.createdAt,
+		UpdatedAt:  op.updatedAt,
+		Status:     op.status.String(),
+		StatusCode: op.status,
+		Resources:  resources,
+		Metadata:   &md,
+		MayCancel:  op.mayCancel(),
+		Err:        op.err,
+	}, nil
+}
+
+func (op *Operation) WaitFinal(timeout int) (bool, error) {
+	// Check current state
+	if op.status.IsFinal() {
+		return true, nil
+	}
+
+	// Wait indefinitely
+	if timeout == -1 {
+		for {
+			<-op.chanDone
+			return true, nil
+		}
+	}
+
+	// Wait until timeout
+	if timeout > 0 {
+		timer := time.NewTimer(time.Duration(timeout) * time.Second)
+		for {
+			select {
+			case <-op.chanDone:
+				return false, nil
+
+			case <-timer.C:
+				return false, nil
+			}
+		}
+	}
+
+	return false, nil
+}
+
+func (op *Operation) UpdateResources(opResources map[string][]string) error {
+	if op.status != shared.Pending && op.status != shared.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()
+
+	shared.LogDebugf("Updated resources for %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send("Operation", md)
+
+	return nil
+}
+
+func (op *Operation) UpdateMetadata(opMetadata interface{}) error {
+	if op.status != shared.Pending && op.status != shared.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()
+
+	shared.LogDebugf("Updated metadata for %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send("Operation", md)
+
+	return nil
+}
+
+func Create(opClass Class, 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.id = uuid.NewRandom().String()
+	op.class = opClass
+	op.createdAt = time.Now()
+	op.updatedAt = op.createdAt
+	op.status = shared.Pending
+	op.url = fmt.Sprintf("/%s/operations/%s", shared.APIVersion, op.id)
+	op.Resources = opResources
+	op.chanDone = make(chan error)
+
+	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 != ClassWebsocket && op.onConnect != nil {
+		return nil, fmt.Errorf("Only websocket operations can have a Connect hook")
+	}
+
+	if op.class == ClassWebsocket && op.onConnect == nil {
+		return nil, fmt.Errorf("Websocket operations must have a Connect hook")
+	}
+
+	if op.class == ClassToken && op.onRun != nil {
+		return nil, fmt.Errorf("Token operations can't have a Run hook")
+	}
+
+	if op.class == ClassToken && op.onCancel != nil {
+		return nil, fmt.Errorf("Token operations can't have a Cancel hook")
+	}
+
+	operationsLock.Lock()
+	operations[op.id] = &op
+	operationsLock.Unlock()
+
+	shared.LogDebugf("New %s Operation: %s", op.class.String(), op.id)
+	_, md, _ := op.Render()
+	events.Send("Operation", md)
+
+	return &op, nil
+}
+
+func Get(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
+}
diff --git a/lxd/operations.go b/lxd/operations.go
index 8e54263..d040117 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -3,471 +3,58 @@ package main
 import (
 	"fmt"
 	"net/http"
-	"runtime"
 	"strings"
-	"sync"
-	"time"
 
 	"github.com/gorilla/mux"
-	"github.com/pborman/uuid"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/response"
 	"github.com/lxc/lxd/shared"
 )
 
-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 {
-	id        string
-	class     operationClass
-	createdAt time.Time
-	updatedAt time.Time
-	status    shared.StatusCode
-	url       string
-	resources map[string][]string
-	metadata  map[string]interface{}
-	err       string
-	readonly  bool
-
-	// 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
-}
-
-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()
-
-		/*
-		 * When we create a new lxc.Container, it adds a finalizer (via
-		 * SetFinalizer) that frees the struct. However, it sometimes
-		 * takes the go GC a while to actually free the struct,
-		 * presumably since it is a small amount of memory.
-		 * Unfortunately, the struct also keeps the log fd open, so if
-		 * we leave too many of these around, we end up running out of
-		 * fds. So, let's explicitly do a GC to collect these at the
-		 * end of each request.
-		 */
-		runtime.GC()
-	})
-}
-
-func (op *operation) Run() (chan error, error) {
-	if op.status != shared.Pending {
-		return nil, fmt.Errorf("Only pending operations can be started")
-	}
-
-	chanRun := make(chan error, 1)
-
-	op.lock.Lock()
-	op.status = shared.Running
-
-	if op.onRun != nil {
-		go func(op *operation, chanRun chan error) {
-			err := op.onRun(op)
-			if err != nil {
-				op.lock.Lock()
-				op.status = shared.Failure
-				op.err = SmartError(err).String()
-				op.lock.Unlock()
-				op.done()
-				chanRun <- err
-
-				shared.LogDebugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
-
-				_, md, _ := op.Render()
-				eventSend("operation", md)
-				return
-			}
-
-			op.lock.Lock()
-			op.status = shared.Success
-			op.lock.Unlock()
-			op.done()
-			chanRun <- nil
-
-			op.lock.Lock()
-			shared.LogDebugf("Success for %s operation: %s", op.class.String(), op.id)
-			_, md, _ := op.Render()
-			eventSend("operation", md)
-			op.lock.Unlock()
-		}(op, chanRun)
-	}
-	op.lock.Unlock()
-
-	shared.LogDebugf("Started %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
-
-	return chanRun, nil
-}
-
-func (op *operation) Cancel() (chan error, error) {
-	if op.status != shared.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 = shared.Cancelling
-	op.lock.Unlock()
-
-	if op.onCancel != nil {
-		go func(op *operation, oldStatus shared.StatusCode, chanCancel chan error) {
-			err := op.onCancel(op)
-			if err != nil {
-				op.lock.Lock()
-				op.status = oldStatus
-				op.lock.Unlock()
-				chanCancel <- err
-
-				shared.LogDebugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
-				_, md, _ := op.Render()
-				eventSend("operation", md)
-				return
-			}
-
-			op.lock.Lock()
-			op.status = shared.Cancelled
-			op.lock.Unlock()
-			op.done()
-			chanCancel <- nil
-
-			shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
-			_, md, _ := op.Render()
-			eventSend("operation", md)
-		}(op, oldStatus, chanCancel)
-	}
-
-	shared.LogDebugf("Cancelling %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
-
-	if op.onCancel == nil {
-		op.lock.Lock()
-		op.status = shared.Cancelled
-		op.lock.Unlock()
-		op.done()
-		chanCancel <- nil
-	}
-
-	shared.LogDebugf("Cancelled %s operation: %s", op.class.String(), op.id)
-	_, md, _ = op.Render()
-	eventSend("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 != shared.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
-
-			shared.LogDebugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
-			return
-		}
-
-		chanConnect <- nil
-
-		shared.LogDebugf("Handled %s operation: %s", op.class.String(), op.id)
-	}(op, chanConnect)
-	op.lock.Unlock()
-
-	shared.LogDebugf("Connected %s operation: %s", op.class.String(), op.id)
-
-	return chanConnect, nil
-}
-
-func (op *operation) mayCancel() bool {
-	return op.onCancel != nil || op.class == operationClassToken
-}
-
-func (op *operation) Render() (string, *shared.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", shared.APIVersion, key, c))
-			}
-			tmpResources[key] = values
-		}
-		resources = tmpResources
-	}
-
-	md := shared.Jmap(op.metadata)
-
-	return op.url, &shared.Operation{
-		Id:         op.id,
-		Class:      op.class.String(),
-		CreatedAt:  op.createdAt,
-		UpdatedAt:  op.updatedAt,
-		Status:     op.status.String(),
-		StatusCode: op.status,
-		Resources:  resources,
-		Metadata:   &md,
-		MayCancel:  op.mayCancel(),
-		Err:        op.err,
-	}, nil
-}
-
-func (op *operation) WaitFinal(timeout int) (bool, error) {
-	// Check current state
-	if op.status.IsFinal() {
-		return true, nil
-	}
-
-	// Wait indefinitely
-	if timeout == -1 {
-		for {
-			<-op.chanDone
-			return true, nil
-		}
-	}
-
-	// Wait until timeout
-	if timeout > 0 {
-		timer := time.NewTimer(time.Duration(timeout) * time.Second)
-		for {
-			select {
-			case <-op.chanDone:
-				return false, nil
-
-			case <-timer.C:
-				return false, nil
-			}
-		}
-	}
-
-	return false, nil
-}
-
-func (op *operation) UpdateResources(opResources map[string][]string) error {
-	if op.status != shared.Pending && op.status != shared.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()
-
-	shared.LogDebugf("Updated resources for %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
-
-	return nil
-}
-
-func (op *operation) UpdateMetadata(opMetadata interface{}) error {
-	if op.status != shared.Pending && op.status != shared.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()
-
-	shared.LogDebugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
-
-	return nil
-}
-
-func operationCreate(opClass operationClass, 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.id = uuid.NewRandom().String()
-	op.class = opClass
-	op.createdAt = time.Now()
-	op.updatedAt = op.createdAt
-	op.status = shared.Pending
-	op.url = fmt.Sprintf("/%s/operations/%s", shared.APIVersion, op.id)
-	op.resources = opResources
-	op.chanDone = make(chan error)
-
-	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()
-
-	shared.LogDebugf("New %s operation: %s", op.class.String(), op.id)
-	_, md, _ := op.Render()
-	eventSend("operation", md)
-
-	return &op, nil
-}
-
-func operationGet(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 operationAPIGet(d *Daemon, r *http.Request) Response {
+func operationAPIGet(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
-	op, err := operationGet(id)
+	op, err := operation.Get(id)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	_, body, err := op.Render()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponse(true, body)
+	return response.SyncResponse(true, body)
 }
 
-func operationAPIDelete(d *Daemon, r *http.Request) Response {
+func operationAPIDelete(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
 
-	op, err := operationGet(id)
+	op, err := operation.Get(id)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	_, err = op.Cancel()
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var operationCmd = Command{name: "operations/{id}", get: operationAPIGet, delete: operationAPIDelete}
 
-func operationsAPIGet(d *Daemon, r *http.Request) Response {
+func operationsAPIGet(d *Daemon, r *http.Request) response.Response {
 	var md shared.Jmap
 
 	recursion := d.isRecursionRequest(r)
 
 	md = shared.Jmap{}
 
-	operationsLock.Lock()
-	ops := operations
-	operationsLock.Unlock()
-
-	for _, v := range ops {
-		status := strings.ToLower(v.status.String())
+	for _, v := range operation.Operations() {
+		status := strings.ToLower(v.Status().String())
 		_, ok := md[status]
 		if !ok {
 			if recursion {
@@ -478,7 +65,7 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 		}
 
 		if !recursion {
-			md[status] = append(md[status].([]string), v.url)
+			md[status] = append(md[status].([]string), v.Url())
 			continue
 		}
 
@@ -490,41 +77,41 @@ func operationsAPIGet(d *Daemon, r *http.Request) Response {
 		md[status] = append(md[status].([]*shared.Operation), body)
 	}
 
-	return SyncResponse(true, md)
+	return response.SyncResponse(true, md)
 }
 
 var operationsCmd = Command{name: "operations", get: operationsAPIGet}
 
-func operationAPIWaitGet(d *Daemon, r *http.Request) Response {
+func operationAPIWaitGet(d *Daemon, r *http.Request) response.Response {
 	timeout, err := shared.AtoiEmptyDefault(r.FormValue("timeout"), -1)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	id := mux.Vars(r)["id"]
-	op, err := operationGet(id)
+	op, err := operation.Get(id)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	_, err = op.WaitFinal(timeout)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	_, body, err := op.Render()
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponse(true, body)
+	return response.SyncResponse(true, body)
 }
 
 var operationWait = Command{name: "operations/{id}/wait", get: operationAPIWaitGet}
 
 type operationWebSocket struct {
 	req *http.Request
-	op  *operation
+	op  *operation.Operation
 }
 
 func (r *operationWebSocket) Render(w http.ResponseWriter) error {
@@ -546,11 +133,11 @@ func (r *operationWebSocket) String() string {
 	return md.Id
 }
 
-func operationAPIWebsocketGet(d *Daemon, r *http.Request) Response {
+func operationAPIWebsocketGet(d *Daemon, r *http.Request) response.Response {
 	id := mux.Vars(r)["id"]
-	op, err := operationGet(id)
+	op, err := operation.Get(id)
 	if err != nil {
-		return NotFound
+		return response.NotFound
 	}
 
 	return &operationWebSocket{r, op}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 4babe81..1081500 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -12,6 +12,8 @@ import (
 	"github.com/gorilla/mux"
 	_ "github.com/mattn/go-sqlite3"
 
+	"github.com/lxc/lxd/lxd/response"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -25,10 +27,10 @@ type profilesPostReq struct {
 	Devices     shared.Devices    `json:"devices"`
 }
 
-func profilesGet(d *Daemon, r *http.Request) Response {
+func profilesGet(d *Daemon, r *http.Request) response.Response {
 	results, err := dbProfiles(d.db)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	recursion := d.isRecursionRequest(r)
@@ -52,54 +54,54 @@ func profilesGet(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 profilesPost(d *Daemon, r *http.Request) Response {
+func profilesPost(d *Daemon, r *http.Request) response.Response {
 	req := profilesPostReq{}
 	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"))
 	}
 
 	_, profile, _ := dbProfileGet(d.db, req.Name)
 	if profile != nil {
-		return BadRequest(fmt.Errorf("The profile already exists"))
+		return response.BadRequest(fmt.Errorf("The profile already exists"))
 	}
 
 	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, req.Config, true, false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = containerValidDevices(req.Devices, true, false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Update DB entry
 	_, err = dbProfileCreate(d.db, req.Name, req.Description, req.Config, req.Devices)
 	if err != nil {
-		return InternalError(
+		return response.InternalError(
 			fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
 }
 
 var profilesCmd = Command{
@@ -127,15 +129,15 @@ func doProfileGet(d *Daemon, name string) (*shared.ProfileConfig, error) {
 	return profile, nil
 }
 
-func profileGet(d *Daemon, r *http.Request) Response {
+func profileGet(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	resp, err := doProfileGet(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return SyncResponseETag(true, resp, resp)
+	return response.SyncResponseETag(true, resp, resp)
 }
 
 func getContainersWithProfile(d *Daemon, profile string) []container {
@@ -158,45 +160,45 @@ func getContainersWithProfile(d *Daemon, profile string) []container {
 	return results
 }
 
-func profilePut(d *Daemon, r *http.Request) Response {
+func profilePut(d *Daemon, r *http.Request) response.Response {
 	// Get the profile
 	name := mux.Vars(r)["name"]
 	id, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
-		return InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
+		return response.InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
 
 	// Validate the ETag
-	err = etagCheck(r, profile)
+	err = util.EtagCheck(r, profile)
 	if err != nil {
-		return PreconditionFailed(err)
+		return response.PreconditionFailed(err)
 	}
 
 	req := profilesPostReq{}
 	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	return doProfileUpdate(d, name, id, profile, req)
 }
 
-func profilePatch(d *Daemon, r *http.Request) Response {
+func profilePatch(d *Daemon, r *http.Request) response.Response {
 	// Get the profile
 	name := mux.Vars(r)["name"]
 	id, profile, err := dbProfileGet(d.db, name)
 	if err != nil {
-		return InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
+		return response.InternalError(fmt.Errorf("Failed to retrieve profile='%s'", name))
 	}
 
 	// Validate the ETag
-	err = etagCheck(r, profile)
+	err = util.EtagCheck(r, profile)
 	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))
@@ -204,12 +206,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 := profilesPostReq{}
 	if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get Description
@@ -245,16 +247,16 @@ func profilePatch(d *Daemon, r *http.Request) Response {
 	return doProfileUpdate(d, name, id, profile, req)
 }
 
-func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileConfig, req profilesPostReq) Response {
+func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileConfig, req profilesPostReq) response.Response {
 	// Sanity checks
 	err := containerValidConfig(d, req.Config, true, false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	err = containerValidDevices(req.Devices, true, false)
 	if err != nil {
-		return BadRequest(err)
+		return response.BadRequest(err)
 	}
 
 	// Get the container list
@@ -263,14 +265,14 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileCo
 	// Update the database
 	tx, err := dbBegin(d.db)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	if profile.Description != req.Description {
 		err = dbProfileDescriptionUpdate(tx, id, req.Description)
 		if err != nil {
 			tx.Rollback()
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 	}
 
@@ -278,33 +280,33 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileCo
 	if reflect.DeepEqual(profile.Config, req.Config) && reflect.DeepEqual(profile.Devices, req.Devices) {
 		err = txCommit(tx)
 		if err != nil {
-			return InternalError(err)
+			return response.InternalError(err)
 		}
 
-		return EmptySyncResponse
+		return response.EmptySyncResponse
 	}
 
 	err = dbProfileConfigClear(tx, id)
 	if err != nil {
 		tx.Rollback()
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	err = dbProfileConfigAdd(tx, id, req.Config)
 	if err != nil {
 		tx.Rollback()
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = dbDevicesAdd(tx, "profile", id, req.Devices)
 	if err != nil {
 		tx.Rollback()
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	err = txCommit(tx)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
 	// Update all the containers using the profile. Must be done after txCommit due to DB lock.
@@ -327,68 +329,68 @@ func doProfileUpdate(d *Daemon, name string, id int64, profile *shared.ProfileCo
 		for cname, err := range failures {
 			msg += fmt.Sprintf(" - %s: %s\n", cname, err)
 		}
-		return InternalError(fmt.Errorf("%s", msg))
+		return response.InternalError(fmt.Errorf("%s", msg))
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 // The handler for the post operation.
-func profilePost(d *Daemon, r *http.Request) Response {
+func profilePost(d *Daemon, r *http.Request) response.Response {
 	name := mux.Vars(r)["name"]
 
 	req := profilesPostReq{}
 	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"))
 	}
 
 	// Check that the name isn't already in use
 	id, _, _ := dbProfileGet(d.db, req.Name)
 	if id > 0 {
-		return Conflict
+		return response.Conflict
 	}
 
 	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 := dbProfileUpdate(d.db, name, req.Name)
 	if err != nil {
-		return InternalError(err)
+		return response.InternalError(err)
 	}
 
-	return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.APIVersion, req.Name))
+	return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", shared.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 {
 	name := mux.Vars(r)["name"]
 
 	_, err := doProfileGet(d, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
 	clist := getContainersWithProfile(d, name)
 	if len(clist) != 0 {
-		return BadRequest(fmt.Errorf("Profile is currently in use"))
+		return response.BadRequest(fmt.Errorf("Profile is currently in use"))
 	}
 
 	err = dbProfileDelete(d.db, name)
 	if err != nil {
-		return SmartError(err)
+		return response.SmartError(err)
 	}
 
-	return EmptySyncResponse
+	return response.EmptySyncResponse
 }
 
 var profileCmd = Command{name: "profiles/{name}", get: profileGet, put: profilePut, delete: profileDelete, post: profilePost, patch: profilePatch}
diff --git a/lxd/response.go b/lxd/response.go
deleted file mode 100644
index 1116857..0000000
--- a/lxd/response.go
+++ /dev/null
@@ -1,317 +0,0 @@
-package main
-
-import (
-	"bytes"
-	"database/sql"
-	"encoding/json"
-	"fmt"
-	"io"
-	"mime/multipart"
-	"net/http"
-	"os"
-
-	"github.com/mattn/go-sqlite3"
-
-	"github.com/lxc/lxd"
-	"github.com/lxc/lxd/shared"
-)
-
-type syncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
-}
-
-type asyncResp struct {
-	Type       lxd.ResponseType  `json:"type"`
-	Status     string            `json:"status"`
-	StatusCode shared.StatusCode `json:"status_code"`
-	Metadata   interface{}       `json:"metadata"`
-	Operation  string            `json:"operation"`
-}
-
-type Response interface {
-	Render(w http.ResponseWriter) error
-	String() string
-}
-
-// Sync response
-type syncResponse struct {
-	success  bool
-	etag     interface{}
-	metadata interface{}
-	location string
-	headers  map[string]string
-}
-
-func (r *syncResponse) Render(w http.ResponseWriter) error {
-	// Set an appropriate ETag header
-	if r.etag != nil {
-		etag, err := etagHash(r.etag)
-		if err == nil {
-			w.Header().Set("ETag", etag)
-		}
-	}
-
-	// Prepare the JSON response
-	status := shared.Success
-	if !r.success {
-		status = shared.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)
-		w.WriteHeader(201)
-	}
-
-	resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata}
-	return WriteJSON(w, resp)
-}
-
-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 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{})}
-
-// File transfer response
-type fileResponseEntry struct {
-	identifier string
-	path       string
-	filename   string
-}
-
-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 {
-		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
-		}
-
-		w.Header().Set("Content-Type", "application/octet-stream")
-		w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
-		w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].filename))
-
-		http.ServeContent(w, r.req, r.files[0].filename, fi.ModTime(), f)
-		if 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 {
-		fd, err := os.Open(entry.path)
-		if err != nil {
-			return err
-		}
-		defer fd.Close()
-
-		fw, err := mw.CreateFormFile(entry.identifier, entry.filename)
-		if err != nil {
-			return err
-		}
-
-		_, err = io.Copy(fw, fd)
-		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 := asyncResp{
-		Type:       lxd.Async,
-		Status:     shared.OperationCreated.String(),
-		StatusCode: shared.OperationCreated,
-		Operation:  url,
-		Metadata:   md}
-
-	w.Header().Set("Location", url)
-	w.WriteHeader(202)
-
-	return WriteJSON(w, body)
-}
-
-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}
-}
-
-// 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": lxd.Error, "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
-}
-
-/* Some standard responses */
-var NotImplemented = &errorResponse{http.StatusNotImplemented, "not implemented"}
-var NotFound = &errorResponse{http.StatusNotFound, "not found"}
-var Forbidden = &errorResponse{http.StatusForbidden, "not authorized"}
-var Conflict = &errorResponse{http.StatusConflict, "already exists"}
-
-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 {
-	switch err {
-	case nil:
-		return EmptySyncResponse
-	case os.ErrNotExist:
-		return NotFound
-	case sql.ErrNoRows:
-		return NotFound
-	case NoSuchObjectError:
-		return NotFound
-	case os.ErrPermission:
-		return Forbidden
-	case DbErrAlreadyDefined:
-		return Conflict
-	case sqlite3.ErrConstraintUnique:
-		return Conflict
-	default:
-		return InternalError(err)
-	}
-}
diff --git a/lxd/response/response.go b/lxd/response/response.go
new file mode 100644
index 0000000..baedf2b
--- /dev/null
+++ b/lxd/response/response.go
@@ -0,0 +1,320 @@
+package response
+
+import (
+	"bytes"
+	"database/sql"
+	"encoding/json"
+	"fmt"
+	"io"
+	"mime/multipart"
+	"net/http"
+	"os"
+
+	"github.com/mattn/go-sqlite3"
+
+	"github.com/lxc/lxd"
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/lxd/util"
+	"github.com/lxc/lxd/shared"
+)
+
+type syncResp struct {
+	Type       lxd.ResponseType  `json:"type"`
+	Status     string            `json:"status"`
+	StatusCode shared.StatusCode `json:"status_code"`
+	Metadata   interface{}       `json:"metadata"`
+}
+
+type asyncResp struct {
+	Type       lxd.ResponseType  `json:"type"`
+	Status     string            `json:"status"`
+	StatusCode shared.StatusCode `json:"status_code"`
+	Metadata   interface{}       `json:"metadata"`
+	Operation  string            `json:"operation"`
+}
+
+type Response interface {
+	Render(w http.ResponseWriter) error
+	String() string
+}
+
+// Sync response
+type syncResponse struct {
+	success  bool
+	etag     interface{}
+	metadata interface{}
+	location string
+	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 := shared.Success
+	if !r.success {
+		status = shared.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)
+		w.WriteHeader(201)
+	}
+
+	resp := syncResp{Type: lxd.Sync, Status: status.String(), StatusCode: status, Metadata: r.metadata}
+	return util.WriteJSON(w, resp)
+}
+
+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 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{})}
+
+// File transfer response
+type FileResponseEntry struct {
+	Identifier string
+	Path       string
+	Filename   string
+}
+
+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 {
+		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
+		}
+
+		w.Header().Set("Content-Type", "application/octet-stream")
+		w.Header().Set("Content-Length", fmt.Sprintf("%d", fi.Size()))
+		w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].Filename))
+
+		http.ServeContent(w, r.req, r.files[0].Filename, fi.ModTime(), f)
+		if 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 {
+		fd, err := os.Open(entry.Path)
+		if err != nil {
+			return err
+		}
+		defer fd.Close()
+
+		fw, err := mw.CreateFormFile(entry.Identifier, entry.Filename)
+		if err != nil {
+			return err
+		}
+
+		_, err = io.Copy(fw, fd)
+		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.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 := asyncResp{
+		Type:       lxd.Async,
+		Status:     shared.OperationCreated.String(),
+		StatusCode: shared.OperationCreated,
+		Operation:  url,
+		Metadata:   md}
+
+	w.Header().Set("Location", url)
+	w.WriteHeader(202)
+
+	return util.WriteJSON(w, body)
+}
+
+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.Operation) Response {
+	return &operationResponse{op}
+}
+
+// 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 state.Debug {
+		captured = &bytes.Buffer{}
+		output = io.MultiWriter(buf, captured)
+	}
+
+	err := json.NewEncoder(output).Encode(shared.Jmap{"type": lxd.Error, "error": r.msg, "error_code": r.code})
+
+	if err != nil {
+		return err
+	}
+
+	if state.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
+}
+
+/* Some standard responses */
+var NotImplemented = &errorResponse{http.StatusNotImplemented, "not implemented"}
+var NotFound = &errorResponse{http.StatusNotFound, "not found"}
+var Forbidden = &errorResponse{http.StatusForbidden, "not authorized"}
+var Conflict = &errorResponse{http.StatusConflict, "already exists"}
+
+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 {
+	switch err {
+	case nil:
+		return EmptySyncResponse
+	case os.ErrNotExist:
+		return NotFound
+	case sql.ErrNoRows:
+		return NotFound
+	case util.NoSuchObjectError:
+		return NotFound
+	case os.ErrPermission:
+		return Forbidden
+	case util.DbErrAlreadyDefined:
+		return Conflict
+	case sqlite3.ErrConstraintUnique:
+		return Conflict
+	default:
+		return InternalError(err)
+	}
+}
diff --git a/lxd/rsync.go b/lxd/rsync.go
index f6a5ff5..265ab34 100644
--- a/lxd/rsync.go
+++ b/lxd/rsync.go
@@ -10,6 +10,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 )
 
@@ -55,7 +56,7 @@ func rsyncSendSetup(path string) (*exec.Cmd, net.Conn, io.ReadCloser, error) {
 	 * command (i.e. the command to run on --server). However, we're
 	 * hardcoding that at the other end, so we can just ignore it.
 	 */
-	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", execPath, f.Name())
+	rsyncCmd := fmt.Sprintf("sh -c \"%s netcat %s\"", state.ExecPath, f.Name())
 	cmd := exec.Command(
 		"rsync",
 		"-arvP",
diff --git a/lxd/state/vars.go b/lxd/state/vars.go
new file mode 100644
index 0000000..b080283
--- /dev/null
+++ b/lxd/state/vars.go
@@ -0,0 +1,18 @@
+package state
+
+import (
+	"os"
+)
+
+// Global variables
+var Debug bool
+var Verbose bool
+var ExecPath string
+
+func init() {
+	absPath, err := os.Readlink("/proc/self/exe")
+	if err != nil {
+		absPath = "bad-exec-path"
+	}
+	ExecPath = absPath
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index 3edf294..75298e4 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -13,6 +13,8 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/state"
 	"github.com/lxc/lxd/shared"
 	"github.com/lxc/lxd/shared/ioprogress"
 	"github.com/lxc/lxd/shared/logging"
@@ -67,7 +69,7 @@ func storageRsyncCopy(source string, dest string) (string, error) {
 	}
 
 	rsyncVerbosity := "-q"
-	if debug {
+	if state.Debug {
 		rsyncVerbosity = "-vi"
 	}
 
@@ -119,7 +121,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) error
+	SendWhileRunning(conn *websocket.Conn, op *operation.Operation) 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
@@ -194,7 +196,7 @@ type storage interface {
 	// already present on the target instance as an exercise for the
 	// enterprising developer.
 	MigrationSource(container container) (MigrationStorageSourceDriver, error)
-	MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error
+	MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error
 }
 
 func newStorage(d *Daemon, sType storageType) (storage, error) {
@@ -558,7 +560,7 @@ func (lw *storageLogWrapper) MigrationSource(container container) (MigrationStor
 	return lw.w.MigrationSource(container)
 }
 
-func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func (lw *storageLogWrapper) MigrationSink(live bool, container container, objects []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	objNames := []string{}
 	for _, obj := range objects {
 		objNames = append(objNames, obj.GetName())
@@ -611,7 +613,7 @@ func (s rsyncStorageSourceDriver) Snapshots() []container {
 	return s.snapshots
 }
 
-func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation.Operation) error {
 	for _, send := range s.snapshots {
 		if err := send.StorageStart(); err != nil {
 			return err
@@ -677,7 +679,7 @@ func snapshotProtobufToContainerArgs(containerName string, snap *Snapshot) conta
 	}
 }
 
-func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func rsyncMigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	isDirBackend := container.Storage().GetStorageType() == storageTypeDir
 
 	if isDirBackend {
@@ -807,8 +809,8 @@ func tryUnmount(path string, flags int) error {
 	return nil
 }
 
-func progressWrapperRender(op *operation, key string, description string, progressInt int64, speedInt int64) {
-	meta := op.metadata
+func progressWrapperRender(op *operation.Operation, key string, description string, progressInt int64, speedInt int64) {
+	meta := op.Metadata
 	if meta == nil {
 		meta = make(map[string]interface{})
 	}
@@ -824,7 +826,7 @@ func progressWrapperRender(op *operation, key string, description string, progre
 	}
 }
 
-func StorageProgressReader(op *operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
+func StorageProgressReader(op *operation.Operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
 	return func(reader io.ReadCloser) io.ReadCloser {
 		if op == nil {
 			return reader
@@ -845,7 +847,7 @@ func StorageProgressReader(op *operation, key string, description string) func(i
 	}
 }
 
-func StorageProgressWriter(op *operation, key string, description string) func(io.WriteCloser) io.WriteCloser {
+func StorageProgressWriter(op *operation.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 cf70b21..1c43242 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -15,6 +15,7 @@ import (
 	"github.com/gorilla/websocket"
 	"github.com/pborman/uuid"
 
+	"github.com/lxc/lxd/lxd/operation"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -870,7 +871,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
 	return err
 }
 
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation.Operation) error {
 	if s.container.IsSnapshot() {
 		tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
 		err := os.MkdirAll(tmpPath, 0700)
@@ -994,7 +995,7 @@ func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDrive
 	return driver, nil
 }
 
-func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	if runningInUserns {
 		return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op)
 	}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index 5adcd45..52c2dee 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -9,6 +9,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -306,6 +307,6 @@ func (s *storageDir) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func (s *storageDir) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op)
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index a3bc02f..e7d9edd 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -12,6 +12,7 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
 	"github.com/lxc/lxd/shared"
 
 	log "gopkg.in/inconshreveable/log15.v2"
@@ -976,6 +977,6 @@ func (s *storageLvm) MigrationSource(container container) (MigrationStorageSourc
 	return rsyncMigrationSource(container)
 }
 
-func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func (s *storageLvm) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	return rsyncMigrationSink(live, container, snapshots, conn, srcIdmap, op)
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index 022c42b..7b99526 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -13,6 +13,8 @@ import (
 
 	"github.com/gorilla/websocket"
 
+	"github.com/lxc/lxd/lxd/operation"
+	"github.com/lxc/lxd/lxd/util"
 	"github.com/lxc/lxd/shared"
 
 	"github.com/pborman/uuid"
@@ -54,7 +56,7 @@ func (s *storageZfs) Init(config map[string]interface{}) (storage, error) {
 	err = s.zfsCheckPool(s.zfsPool)
 	if err != nil {
 		if shared.PathExists(shared.VarPath("zfs.img")) {
-			_ = loadModule("zfs")
+			_ = util.LoadModule("zfs")
 
 			output, err := exec.Command("zpool", "import",
 				"-d", shared.VarPath(), s.zfsPool).CombinedOutput()
@@ -1304,7 +1306,7 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
 	return err
 }
 
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation) error {
+func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation.Operation) error {
 	if s.container.IsSnapshot() {
 		fields := strings.SplitN(s.container.Name(), shared.SnapshotDelimiter, 2)
 		snapshotName := fmt.Sprintf("snapshot-%s", fields[1])
@@ -1419,7 +1421,7 @@ func (s *storageZfs) MigrationSource(ct container) (MigrationStorageSourceDriver
 	return &driver, nil
 }
 
-func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation) error {
+func (s *storageZfs) MigrationSink(live bool, container container, snapshots []*Snapshot, conn *websocket.Conn, srcIdmap *shared.IdmapSet, op *operation.Operation) error {
 	zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
 		zfsFsName := fmt.Sprintf("%s/%s", s.zfsPool, zfsName)
 		args := []string{"receive", "-F", "-u", zfsFsName}
diff --git a/lxd/util.go b/lxd/util.go
deleted file mode 100644
index cce5ffb..0000000
--- a/lxd/util.go
+++ /dev/null
@@ -1,67 +0,0 @@
-package main
-
-import (
-	"bytes"
-	"crypto/sha256"
-	"encoding/json"
-	"fmt"
-	"io"
-	"net/http"
-
-	"github.com/lxc/lxd/shared"
-)
-
-func WriteJSON(w http.ResponseWriter, body interface{}) error {
-	var output io.Writer
-	var captured *bytes.Buffer
-
-	output = w
-	if debug {
-		captured = &bytes.Buffer{}
-		output = io.MultiWriter(w, captured)
-	}
-
-	err := json.NewEncoder(output).Encode(body)
-
-	if captured != nil {
-		shared.DebugJson(captured)
-	}
-
-	return err
-}
-
-func etagHash(data interface{}) (string, error) {
-	etag := sha256.New()
-	err := json.NewEncoder(etag).Encode(data)
-	if err != nil {
-		return "", err
-	}
-
-	return fmt.Sprintf("%x", etag.Sum(nil)), nil
-}
-
-func etagCheck(r *http.Request, data interface{}) error {
-	match := r.Header.Get("If-Match")
-	if match == "" {
-		return nil
-	}
-
-	hash, err := etagHash(data)
-	if err != nil {
-		return err
-	}
-
-	if hash != match {
-		return fmt.Errorf("ETag doesn't match: %s vs %s", hash, match)
-	}
-
-	return nil
-}
-
-func loadModule(module string) error {
-	if shared.PathExists(fmt.Sprintf("/sys/module/%s", module)) {
-		return nil
-	}
-
-	return shared.RunCommand("modprobe", module)
-}
diff --git a/lxd/util/errors.go b/lxd/util/errors.go
new file mode 100644
index 0000000..424df81
--- /dev/null
+++ b/lxd/util/errors.go
@@ -0,0 +1,20 @@
+package util
+
+import (
+	"fmt"
+)
+
+var (
+	// DbErrAlreadyDefined hapens when the given entry already exists,
+	// for example a container.
+	DbErrAlreadyDefined = fmt.Errorf("The container/snapshot already exists")
+
+	/* NoSuchObjectError is in the case of joins (and probably other) queries,
+	 * we don't get back sql.ErrNoRows when no rows are returned, even though we do
+	 * on selects without joins. Instead, you can use this error to
+	 * propagate up and generate proper 404s to the client when something
+	 * isn't found so we don't abuse sql.ErrNoRows any more than we
+	 * already do.
+	 */
+	NoSuchObjectError = fmt.Errorf("No such object")
+)
diff --git a/lxd/util/util.go b/lxd/util/util.go
new file mode 100644
index 0000000..755cb7a
--- /dev/null
+++ b/lxd/util/util.go
@@ -0,0 +1,68 @@
+package util
+
+import (
+	"bytes"
+	"crypto/sha256"
+	"encoding/json"
+	"fmt"
+	"io"
+	"net/http"
+
+	"github.com/lxc/lxd/lxd/state"
+	"github.com/lxc/lxd/shared"
+)
+
+func WriteJSON(w http.ResponseWriter, body interface{}) error {
+	var output io.Writer
+	var captured *bytes.Buffer
+
+	output = w
+	if state.Debug {
+		captured = &bytes.Buffer{}
+		output = io.MultiWriter(w, captured)
+	}
+
+	err := json.NewEncoder(output).Encode(body)
+
+	if captured != nil {
+		shared.DebugJson(captured)
+	}
+
+	return err
+}
+
+func EtagHash(data interface{}) (string, error) {
+	etag := sha256.New()
+	err := json.NewEncoder(etag).Encode(data)
+	if err != nil {
+		return "", err
+	}
+
+	return fmt.Sprintf("%x", etag.Sum(nil)), nil
+}
+
+func EtagCheck(r *http.Request, data interface{}) error {
+	match := r.Header.Get("If-Match")
+	if match == "" {
+		return nil
+	}
+
+	hash, err := EtagHash(data)
+	if err != nil {
+		return err
+	}
+
+	if hash != match {
+		return fmt.Errorf("ETag doesn't match: %s vs %s", hash, match)
+	}
+
+	return nil
+}
+
+func LoadModule(module string) error {
+	if shared.PathExists(fmt.Sprintf("/sys/module/%s", module)) {
+		return nil
+	}
+
+	return shared.RunCommand("modprobe", module)
+}


More information about the lxc-devel mailing list