[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