[lxc-devel] [lxd/master] Move response and operations to their own packages
monstermunchkin on Github
lxc-bot at linuxcontainers.org
Thu Sep 26 12:40:19 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 301 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20190926/0d5662c0/attachment-0001.bin>
-------------- next part --------------
From 3de200988e557e333ae01f0a57fa24cdc9fe9d30 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Wed, 25 Sep 2019 10:02:36 +0200
Subject: [PATCH 1/2] lxd: Move operations and response to separate packages
This moves both operations and response to their own packages.
Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
lxd/api.go | 9 +-
lxd/api_1.0.go | 91 ++---
lxd/api_cluster.go | 170 ++++-----
lxd/api_internal.go | 197 +++++-----
lxd/api_project.go | 96 ++---
lxd/backup.go | 6 +-
lxd/certificates.go | 81 ++---
lxd/container.go | 9 +-
lxd/container_backup.go | 138 +++----
lxd/container_console.go | 90 ++---
lxd/container_delete.go | 26 +-
lxd/container_exec.go | 48 +--
lxd/container_file.go | 69 ++--
lxd/container_get.go | 19 +-
lxd/container_instance_types.go | 5 +-
lxd/container_logs.go | 63 ++--
lxd/container_lxc.go | 7 +-
lxd/container_metadata.go | 149 ++++----
lxd/container_patch.go | 29 +-
lxd/container_post.go | 90 ++---
lxd/container_put.go | 32 +-
lxd/container_snapshot.go | 156 ++++----
lxd/container_state.go | 64 ++--
lxd/containers_get.go | 9 +-
lxd/containers_post.go | 182 +++++-----
lxd/daemon.go | 47 +--
lxd/daemon_images.go | 9 +-
lxd/events.go | 3 +-
lxd/images.go | 284 +++++++--------
lxd/instance_interface.go | 3 +-
lxd/logging.go | 5 +-
lxd/main.go | 8 +
lxd/migrate.go | 5 +-
lxd/migrate_container.go | 15 +-
lxd/migrate_storage_volumes.go | 5 +-
lxd/networks.go | 171 ++++-----
lxd/operations.go | 620 ++++----------------------------
lxd/operations/operations.go | 543 ++++++++++++++++++++++++++++
lxd/operations/response.go | 96 +++++
lxd/operations/websocket.go | 63 ++++
lxd/profiles.go | 87 ++---
lxd/resources.go | 29 +-
lxd/response.go | 494 +------------------------
lxd/response/response.go | 425 ++++++++++++++++++++++
lxd/storage.go | 13 +-
lxd/storage_btrfs.go | 5 +-
lxd/storage_ceph.go | 5 +-
lxd/storage_cephfs.go | 5 +-
lxd/storage_dir.go | 5 +-
lxd/storage_lvm.go | 5 +-
lxd/storage_migration.go | 13 +-
lxd/storage_migration_btrfs.go | 5 +-
lxd/storage_migration_ceph.go | 5 +-
lxd/storage_migration_zfs.go | 5 +-
lxd/storage_mock.go | 5 +-
lxd/storage_pools.go | 153 ++++----
lxd/storage_volumes.go | 356 +++++++++---------
lxd/storage_volumes_snapshot.go | 190 +++++-----
lxd/storage_zfs.go | 5 +-
59 files changed, 2881 insertions(+), 2641 deletions(-)
create mode 100644 lxd/operations/operations.go
create mode 100644 lxd/operations/response.go
create mode 100644 lxd/operations/websocket.go
create mode 100644 lxd/response/response.go
diff --git a/lxd/api.go b/lxd/api.go
index a22297b741..cdca5ae0c7 100644
--- a/lxd/api.go
+++ b/lxd/api.go
@@ -10,6 +10,7 @@ import (
"github.com/gorilla/mux"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared/logger"
)
@@ -22,7 +23,7 @@ func RestServer(d *Daemon) *http.Server {
mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
- SyncResponse(true, []string{"/1.0"}).Render(w)
+ response.SyncResponse(true, []string{"/1.0"}).Render(w)
})
for endpoint, f := range d.gateway.HandlerFuncs(d.NodeRefreshTask) {
@@ -50,7 +51,7 @@ func RestServer(d *Daemon) *http.Server {
mux.NotFoundHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
logger.Info("Sending top level 404", log.Ctx{"url": r.URL})
w.Header().Set("Content-Type", "application/json")
- NotFound(nil).Render(w)
+ response.NotFound(nil).Render(w)
})
return &http.Server{Handler: &lxdHttpServer{r: mux, d: d}}
@@ -74,8 +75,8 @@ func (s *lxdHttpServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
return nil
})
if err != nil {
- response := SmartError(err)
- response.Render(rw)
+ resp := response.SmartError(err)
+ resp.Render(rw)
return
}
}
diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 7339ca26b7..cd7182ba9c 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -13,6 +13,7 @@ import (
"github.com/lxc/lxd/lxd/config"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -83,7 +84,7 @@ var api10 = []APIEndpoint{
storagePoolVolumeTypeImageCmd,
}
-func api10Get(d *Daemon, r *http.Request) Response {
+func api10Get(d *Daemon, r *http.Request) response.Response {
authMethods := []string{"tls"}
err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
config, err := cluster.ConfigLoad(tx)
@@ -100,7 +101,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
srv := api.ServerUntrusted{
APIExtensions: version.APIExtensions,
@@ -113,34 +114,34 @@ func api10Get(d *Daemon, r *http.Request) Response {
// If untrusted, return now
if d.checkTrustedClient(r) != nil {
- return SyncResponseETag(true, srv, nil)
+ return response.SyncResponseETag(true, srv, nil)
}
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
srv.Auth = "trusted"
uname, err := shared.Uname()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
address, err := node.HTTPSAddress(d.db)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
addresses, err := util.ListenAddresses(address)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// When clustered, use the node name, otherwise use the hostname.
@@ -151,12 +152,12 @@ func api10Get(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
} else {
hostname, err := os.Hostname()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
serverName = hostname
}
@@ -166,7 +167,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
if certificate != "" {
certificateFingerprint, err = shared.CertFingerprintStr(certificate)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
@@ -175,7 +176,7 @@ func api10Get(d *Daemon, r *http.Request) Response {
for _, architecture := range d.os.Architectures {
architectureName, err := osarch.ArchitectureName(architecture)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
architectures = append(architectures, architectureName)
}
@@ -238,22 +239,22 @@ func api10Get(d *Daemon, r *http.Request) Response {
fullSrv.Environment = env
fullSrv.Config, err = daemonConfigRender(d.State())
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return SyncResponseETag(true, fullSrv, fullSrv.Config)
+ return response.SyncResponseETag(true, fullSrv, fullSrv.Config)
}
-func api10Put(d *Daemon, r *http.Request) Response {
+func api10Put(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
req := api.ServerPut{}
if err := shared.ReadToJSON(r.Body, &req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// If this is a notification from a cluster node, just run the triggers
@@ -271,56 +272,56 @@ func api10Put(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = doApi10UpdateTriggers(d, nil, changed, nil, config)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
render, err := daemonConfigRender(d.State())
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = util.EtagCheck(r, render)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
return doApi10Update(d, req, false)
}
-func api10Patch(d *Daemon, r *http.Request) Response {
+func api10Patch(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
render, err := daemonConfigRender(d.State())
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
err = util.EtagCheck(r, render)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.ServerPut{}
if err := shared.ReadToJSON(r.Body, &req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Config == nil {
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
return doApi10Update(d, req, true)
}
-func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
+func doApi10Update(d *Daemon, req api.ServerPut, patch bool) response.Response {
s := d.State()
// First deal with config specific to the local daemon
@@ -336,7 +337,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return InternalError(errors.Wrap(err, "Failed to check for cluster state"))
+ return response.InternalError(errors.Wrap(err, "Failed to check for cluster state"))
}
nodeChanged := map[string]string{}
@@ -382,9 +383,9 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
if err != nil {
switch err.(type) {
case config.ErrorList:
- return BadRequest(err)
+ return response.BadRequest(err)
default:
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -403,7 +404,7 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
}
if hasCandid && hasRBAC {
- return BadRequest(fmt.Errorf("RBAC and Candid are mutually exclusive"))
+ return response.BadRequest(fmt.Errorf("RBAC and Candid are mutually exclusive"))
}
}
@@ -426,16 +427,16 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
if err != nil {
switch err.(type) {
case config.ErrorList:
- return BadRequest(err)
+ return response.BadRequest(err)
default:
- return SmartError(err)
+ return response.SmartError(err)
}
}
// Notify the other nodes about changes
notifier, err := cluster.NewNotifier(s, d.endpoints.NetworkCert(), cluster.NotifyAlive)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
server, etag, err := client.GetServer()
@@ -452,15 +453,15 @@ func doApi10Update(d *Daemon, req api.ServerPut, patch bool) Response {
})
if err != nil {
logger.Debugf("Failed to notify other nodes about config change: %v", err)
- return SmartError(err)
+ return response.SmartError(err)
}
err = doApi10UpdateTriggers(d, nodeChanged, clusterChanged, newNodeConfig, newClusterConfig)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
func doApi10UpdateTriggers(d *Daemon, nodeChanged, clusterChanged map[string]string, nodeConfig *node.Config, clusterConfig *cluster.Config) error {
diff --git a/lxd/api_cluster.go b/lxd/api_cluster.go
index 6980defc7b..fef01aa0c5 100644
--- a/lxd/api_cluster.go
+++ b/lxd/api_cluster.go
@@ -20,6 +20,8 @@ import (
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
storagedriver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -68,7 +70,7 @@ var internalClusterPromoteCmd = APIEndpoint{
}
// Return information about the cluster.
-func clusterGet(d *Daemon, r *http.Request) Response {
+func clusterGet(d *Daemon, r *http.Request) response.Response {
name := ""
err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
var err error
@@ -76,7 +78,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// If the name is set to the hard-coded default node name, then
@@ -87,7 +89,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
memberConfig, err := clusterGetMemberConfig(d.cluster)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
cluster := api.Cluster{
@@ -96,7 +98,7 @@ func clusterGet(d *Daemon, r *http.Request) Response {
MemberConfig: memberConfig,
}
- return SyncResponseETag(true, cluster, cluster)
+ return response.SyncResponseETag(true, cluster, cluster)
}
// Fetch information about all node-specific configuration keys set on the
@@ -169,21 +171,21 @@ func clusterGetMemberConfig(cluster *db.Cluster) ([]api.ClusterMemberConfigKey,
// - disable clustering on a node
//
// The client is required to be trusted.
-func clusterPut(d *Daemon, r *http.Request) Response {
+func clusterPut(d *Daemon, r *http.Request) response.Response {
req := api.ClusterPut{}
// Parse the request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if req.ServerName == "" && req.Enabled {
- return BadRequest(fmt.Errorf("ServerName is required when enabling clustering"))
+ return response.BadRequest(fmt.Errorf("ServerName is required when enabling clustering"))
}
if req.ServerName != "" && !req.Enabled {
- return BadRequest(fmt.Errorf("ServerName must be empty when disabling clustering"))
+ return response.BadRequest(fmt.Errorf("ServerName must be empty when disabling clustering"))
}
// Disable clustering.
@@ -201,8 +203,8 @@ func clusterPut(d *Daemon, r *http.Request) Response {
return clusterPutJoin(d, req)
}
-func clusterPutBootstrap(d *Daemon, req api.ClusterPut) Response {
- run := func(op *operation) error {
+func clusterPutBootstrap(d *Daemon, req api.ClusterPut) response.Response {
+ run := func(op *operations.Operation) error {
// The default timeout when non-clustered is one minute, let's
// lower it down now that we'll likely have to make requests
// over the network.
@@ -250,39 +252,39 @@ func clusterPutBootstrap(d *Daemon, req api.ClusterPut) Response {
return nil
})
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationClusterBootstrap, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationClusterBootstrap, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Add the cluster flag from the agent
version.UserAgentFeatures([]string{"cluster"})
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
+func clusterPutJoin(d *Daemon, req api.ClusterPut) response.Response {
// Make sure basic pre-conditions are met.
if len(req.ClusterCertificate) == 0 {
- return BadRequest(fmt.Errorf("No target cluster node certificate provided"))
+ return response.BadRequest(fmt.Errorf("No target cluster node certificate provided"))
}
clusterAddress, err := node.ClusterAddress(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if clusterAddress != "" {
- return BadRequest(fmt.Errorf("This server is already clustered"))
+ return response.BadRequest(fmt.Errorf("This server is already clustered"))
}
address, err := node.HTTPSAddress(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if address == "" {
if req.ServerAddress == "" {
- return BadRequest(fmt.Errorf("No core.https_address config key is set on this node"))
+ return response.BadRequest(fmt.Errorf("No core.https_address config key is set on this node"))
}
// The user has provided a server address, and no networking
@@ -294,7 +296,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
// won't actually update the database config.
err = d.endpoints.NetworkUpdateAddress(req.ServerAddress)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err := d.db.Transaction(func(tx *db.NodeTx) error {
@@ -310,7 +312,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
address = req.ServerAddress
@@ -322,7 +324,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
if !util.IsAddressCovered(req.ServerAddress, address) {
err := d.endpoints.ClusterUpdateAddress(req.ServerAddress)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
address = req.ServerAddress
}
@@ -340,7 +342,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -354,7 +356,7 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
fingerprint := cert.Fingerprint()
// Asynchronously join the cluster.
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
logger.Debug("Running cluster join operation")
// If the user has provided a cluster password, setup the trust
@@ -608,20 +610,20 @@ func clusterPutJoin(d *Daemon, req api.ClusterPut) Response {
resources := map[string][]string{}
resources["cluster"] = []string{}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationClusterJoin, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationClusterJoin, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Disable clustering on a node.
-func clusterPutDisable(d *Daemon) Response {
+func clusterPutDisable(d *Daemon) response.Response {
// Close the cluster database
err := d.cluster.Close()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Update our TLS configuration using our original certificate.
@@ -632,25 +634,25 @@ func clusterPutDisable(d *Daemon) Response {
}
err := os.Remove(path)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
cert, err := util.LoadCert(d.os.VarDir)
if err != nil {
- return InternalError(errors.Wrap(err, "failed to parse node certificate"))
+ return response.InternalError(errors.Wrap(err, "failed to parse node certificate"))
}
// Reset the cluster database and make it local to this node.
d.endpoints.NetworkUpdateCert(cert)
err = d.gateway.Reset(cert)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Re-open the cluster database
address, err := node.HTTPSAddress(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
store := d.gateway.NodeStore()
d.cluster, err = db.OpenCluster(
@@ -661,7 +663,7 @@ func clusterPutDisable(d *Daemon) Response {
dqlitedriver.WithContext(d.gateway.Context()),
)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Stop the clustering tasks
@@ -670,7 +672,7 @@ func clusterPutDisable(d *Daemon) Response {
// Remove the cluster flag from the agent
version.UserAgentFeatures(nil)
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Initialize storage pools and networks on this node.
@@ -814,12 +816,12 @@ func clusterAcceptMember(
return info, nil
}
-func clusterNodesGet(d *Daemon, r *http.Request) Response {
+func clusterNodesGet(d *Daemon, r *http.Request) response.Response {
recursion := util.IsRecursionRequest(r)
nodes, err := cluster.List(d.State())
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
var result interface{}
@@ -834,27 +836,27 @@ func clusterNodesGet(d *Daemon, r *http.Request) Response {
result = urls
}
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
-func clusterNodeGet(d *Daemon, r *http.Request) Response {
+func clusterNodeGet(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
nodes, err := cluster.List(d.State())
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, node := range nodes {
if node.ServerName == name {
- return SyncResponseETag(true, node, node)
+ return response.SyncResponseETag(true, node, node)
}
}
- return NotFound(fmt.Errorf("Node '%s' not found", name))
+ return response.NotFound(fmt.Errorf("Node '%s' not found", name))
}
-func clusterNodePost(d *Daemon, r *http.Request) Response {
+func clusterNodePost(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
req := api.ClusterMemberPost{}
@@ -862,20 +864,20 @@ func clusterNodePost(d *Daemon, r *http.Request) Response {
// Parse the request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
return tx.NodeRename(name, req.ServerName)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func clusterNodeDelete(d *Daemon, r *http.Request) Response {
+func clusterNodeDelete(d *Daemon, r *http.Request) response.Response {
force, err := strconv.Atoi(r.FormValue("force"))
if err != nil {
force = 0
@@ -888,7 +890,7 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
// make it leave the database cluster, if it's part of it.
address, err := cluster.Leave(d.State(), d.gateway, name, force == 1)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if force != 1 {
@@ -897,28 +899,28 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
networks, err := d.cluster.Networks()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, name := range networks {
err := client.DeleteNetwork(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
// Delete all the pools on this node
pools, err := d.cluster.StoragePools()
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, name := range pools {
err := client.DeleteStoragePool(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
}
@@ -926,7 +928,7 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
// Remove node from the database
err = cluster.Purge(d.cluster, name)
if err != nil {
- return SmartError(errors.Wrap(err, "failed to remove node from database"))
+ return response.SmartError(errors.Wrap(err, "failed to remove node from database"))
}
// Try to notify the leader.
err = tryClusterRebalance(d)
@@ -940,17 +942,17 @@ func clusterNodeDelete(d *Daemon, r *http.Request) Response {
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
put := api.ClusterPut{}
put.Enabled = false
_, err = client.UpdateCluster(put, "")
if err != nil {
- return SmartError(errors.Wrap(err, "failed to cleanup the node"))
+ return response.SmartError(errors.Wrap(err, "failed to cleanup the node"))
}
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// This function is used to notify the leader that a node was removed, it will
@@ -973,29 +975,29 @@ func tryClusterRebalance(d *Daemon) error {
return nil
}
-func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
+func internalClusterPostAccept(d *Daemon, r *http.Request) response.Response {
req := internalClusterPostAcceptRequest{}
// Parse the request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
// Redirect all requests to the leader, which is the one with
// knowning what nodes are part of the raft cluster.
address, err := node.ClusterAddress(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
leader, err := d.gateway.LeaderAddress()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if address != leader {
logger.Debugf("Redirect node accept request to %s", leader)
@@ -1004,23 +1006,23 @@ func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
Path: "/internal/cluster/accept",
Host: leader,
}
- return SyncResponseRedirect(url.String())
+ return response.SyncResponseRedirect(url.String())
}
// Check that the pools and networks provided by the joining node have
// configs that match the cluster ones.
err = clusterCheckStoragePoolsMatch(d.cluster, req.StoragePools)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = clusterCheckNetworksMatch(d.cluster, req.Networks)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
nodes, err := cluster.Accept(d.State(), d.gateway, req.Name, req.Address, req.Schema, req.API)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
accepted := internalClusterPostAcceptResponse{
RaftNodes: make([]internalRaftNode, len(nodes)),
@@ -1030,7 +1032,7 @@ func internalClusterPostAccept(d *Daemon, r *http.Request) Response {
accepted.RaftNodes[i].ID = node.ID
accepted.RaftNodes[i].Address = node.Address
}
- return SyncResponse(true, accepted)
+ return response.SyncResponse(true, accepted)
}
// A request for the /internal/cluster/accept endpoint.
@@ -1043,7 +1045,7 @@ type internalClusterPostAcceptRequest struct {
Networks []api.Network `json:"networks" yaml:"networks"`
}
-// A Response for the /internal/cluster/accept endpoint.
+// A response.Response for the /internal/cluster/accept endpoint.
type internalClusterPostAcceptResponse struct {
RaftNodes []internalRaftNode `json:"raft_nodes" yaml:"raft_nodes"`
PrivateKey []byte `json:"private_key" yaml:"private_key"`
@@ -1057,16 +1059,16 @@ type internalRaftNode struct {
// Used to update the cluster after a database node has been removed, and
// possibly promote another one as database node.
-func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
+func internalClusterPostRebalance(d *Daemon, r *http.Request) response.Response {
// Redirect all requests to the leader, which is the one with with
// up-to-date knowledge of what nodes are part of the raft cluster.
localAddress, err := node.ClusterAddress(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
leader, err := d.gateway.LeaderAddress()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if localAddress != leader {
logger.Debugf("Redirect cluster rebalance request to %s", leader)
@@ -1075,7 +1077,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
Path: "/internal/cluster/rebalance",
Host: leader,
}
- return SyncResponseRedirect(url.String())
+ return response.SyncResponseRedirect(url.String())
}
logger.Debugf("Rebalance cluster")
@@ -1083,7 +1085,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
// Check if we have a spare node to promote.
address, nodes, err := cluster.Rebalance(d.State(), d.gateway)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if address == "" {
@@ -1100,7 +1102,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
hbState := &cluster.APIHeartbeat{}
@@ -1108,7 +1110,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
cert, err := util.LoadCert(d.os.VarDir)
if err != nil {
- return InternalError(errors.Wrap(err, "failed to parse cluster certificate"))
+ return response.InternalError(errors.Wrap(err, "failed to parse cluster certificate"))
}
for _, raftNode := range nodes {
@@ -1119,7 +1121,7 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
go cluster.HeartbeatNode(context.Background(), raftNode.Address, cert, hbState)
}
- return SyncResponse(true, nil)
+ return response.SyncResponse(true, nil)
}
// Tell the node to promote itself.
@@ -1134,29 +1136,29 @@ func internalClusterPostRebalance(d *Daemon, r *http.Request) Response {
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
_, _, err = client.RawQuery("POST", "/internal/cluster/promote", post, "")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, nil)
+ return response.SyncResponse(true, nil)
}
// Used to promote the local non-database node to be a database one.
-func internalClusterPostPromote(d *Daemon, r *http.Request) Response {
+func internalClusterPostPromote(d *Daemon, r *http.Request) response.Response {
req := internalClusterPostPromoteRequest{}
// Parse the request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if len(req.RaftNodes) == 0 {
- return BadRequest(fmt.Errorf("No raft nodes provided"))
+ return response.BadRequest(fmt.Errorf("No raft nodes provided"))
}
nodes := make([]db.RaftNode, len(req.RaftNodes))
@@ -1166,10 +1168,10 @@ func internalClusterPostPromote(d *Daemon, r *http.Request) Response {
}
err = cluster.Promote(d.State(), d.gateway, nodes)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, nil)
+ return response.SyncResponse(true, nil)
}
// A request for the /internal/cluster/promote endpoint.
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index e6534ed8ef..fd10613dcb 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -24,6 +24,7 @@ import (
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/project"
+ "github.com/lxc/lxd/lxd/response"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -103,51 +104,51 @@ var internalRAFTSnapshotCmd = APIEndpoint{
Get: APIEndpointAction{Handler: internalRAFTSnapshot},
}
-func internalWaitReady(d *Daemon, r *http.Request) Response {
+func internalWaitReady(d *Daemon, r *http.Request) response.Response {
select {
case <-d.readyChan:
default:
- return Unavailable(fmt.Errorf("LXD daemon not ready yet"))
+ return response.Unavailable(fmt.Errorf("LXD daemon not ready yet"))
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalShutdown(d *Daemon, r *http.Request) Response {
+func internalShutdown(d *Daemon, r *http.Request) response.Response {
d.shutdownChan <- struct{}{}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalContainerOnStart(d *Daemon, r *http.Request) Response {
+func internalContainerOnStart(d *Daemon, r *http.Request) response.Response {
id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
inst, err := instanceLoadById(d.State(), id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
err = c.OnStart()
if err != nil {
logger.Error("The start hook failed", log.Ctx{"container": c.Name(), "err": err})
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalContainerOnStopNS(d *Daemon, r *http.Request) Response {
+func internalContainerOnStopNS(d *Daemon, r *http.Request) response.Response {
id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
target := queryParam(r, "target")
@@ -158,27 +159,27 @@ func internalContainerOnStopNS(d *Daemon, r *http.Request) Response {
inst, err := instanceLoadById(d.State(), id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
err = c.OnStopNS(target, netns)
if err != nil {
logger.Error("The stopns hook failed", log.Ctx{"container": c.Name(), "err": err})
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalContainerOnStop(d *Daemon, r *http.Request) Response {
+func internalContainerOnStop(d *Daemon, r *http.Request) response.Response {
id, err := strconv.Atoi(mux.Vars(r)["id"])
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
target := queryParam(r, "target")
@@ -188,21 +189,21 @@ func internalContainerOnStop(d *Daemon, r *http.Request) Response {
inst, err := instanceLoadById(d.State(), id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
err = c.OnStop(target)
if err != nil {
logger.Error("The stop hook failed", log.Ctx{"container": c.Name(), "err": err})
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
type internalSQLDump struct {
@@ -226,11 +227,11 @@ type internalSQLResult struct {
}
// Perform a database dump.
-func internalSQLGet(d *Daemon, r *http.Request) Response {
+func internalSQLGet(d *Daemon, r *http.Request) response.Response {
database := r.FormValue("database")
if !shared.StringInSlice(database, []string{"local", "global"}) {
- return BadRequest(fmt.Errorf("Invalid database"))
+ return response.BadRequest(fmt.Errorf("Invalid database"))
}
schemaFormValue := r.FormValue("schema")
@@ -251,31 +252,31 @@ func internalSQLGet(d *Daemon, r *http.Request) Response {
tx, err := db.Begin()
if err != nil {
- return SmartError(errors.Wrap(err, "failed to start transaction"))
+ return response.SmartError(errors.Wrap(err, "failed to start transaction"))
}
defer tx.Rollback()
dump, err := query.Dump(tx, schema, schemaOnly == 1)
if err != nil {
- return SmartError(errors.Wrapf(err, "failed dump database %s", database))
+ return response.SmartError(errors.Wrapf(err, "failed dump database %s", database))
}
- return SyncResponse(true, internalSQLDump{Text: dump})
+ return response.SyncResponse(true, internalSQLDump{Text: dump})
}
// Execute queries.
-func internalSQLPost(d *Daemon, r *http.Request) Response {
+func internalSQLPost(d *Daemon, r *http.Request) response.Response {
req := &internalSQLQuery{}
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if !shared.StringInSlice(req.Database, []string{"local", "global"}) {
- return BadRequest(fmt.Errorf("Invalid database"))
+ return response.BadRequest(fmt.Errorf("Invalid database"))
}
if req.Query == "" {
- return BadRequest(fmt.Errorf("No query provided"))
+ return response.BadRequest(fmt.Errorf("No query provided"))
}
var db *sql.DB
@@ -289,7 +290,7 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
if req.Query == ".sync" {
d.gateway.Sync()
- return SyncResponse(true, batch)
+ return response.SyncResponse(true, batch)
}
for _, query := range strings.Split(req.Query, ";") {
@@ -303,7 +304,7 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
tx, err := db.Begin()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if strings.HasPrefix(strings.ToUpper(query), "SELECT") {
@@ -318,13 +319,13 @@ func internalSQLPost(d *Daemon, r *http.Request) Response {
}
}
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
batch.Results = append(batch.Results, result)
}
- return SyncResponse(true, batch)
+ return response.SyncResponse(true, batch)
}
func internalSQLSelect(tx *sql.Tx, query string, result *internalSQLResult) error {
@@ -409,32 +410,32 @@ type internalImportPost struct {
Force bool `json:"force" yaml:"force"`
}
-func internalImport(d *Daemon, r *http.Request) Response {
+func internalImport(d *Daemon, r *http.Request) response.Response {
projectName := projectParam(r)
req := &internalImportPost{}
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Name == "" {
- return BadRequest(fmt.Errorf(`The name of the container ` +
+ return response.BadRequest(fmt.Errorf(`The name of the container ` +
`is required`))
}
storagePoolsPath := shared.VarPath("storage-pools")
storagePoolsDir, err := os.Open(storagePoolsPath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Get a list of all storage pools.
storagePoolNames, err := storagePoolsDir.Readdirnames(-1)
if err != nil {
storagePoolsDir.Close()
- return InternalError(err)
+ return response.InternalError(err)
}
storagePoolsDir.Close()
@@ -451,10 +452,10 @@ func internalImport(d *Daemon, r *http.Request) Response {
// Sanity checks.
if len(containerMntPoints) > 1 {
- return BadRequest(fmt.Errorf(`The container "%s" seems to `+
+ return response.BadRequest(fmt.Errorf(`The container "%s" seems to `+
`exist on multiple storage pools`, req.Name))
} else if len(containerMntPoints) != 1 {
- return BadRequest(fmt.Errorf(`The container "%s" does not `+
+ return response.BadRequest(fmt.Errorf(`The container "%s" does not `+
`seem to exist on any storage pool`, req.Name))
}
@@ -463,11 +464,11 @@ func internalImport(d *Daemon, r *http.Request) Response {
containerMntPoint := containerMntPoints[0]
isEmpty, err := shared.PathIsEmpty(containerMntPoint)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if isEmpty {
- return BadRequest(fmt.Errorf(`The container's directory "%s" `+
+ return response.BadRequest(fmt.Errorf(`The container's directory "%s" `+
`appears to be empty. Please ensure that the `+
`container's storage volume is mounted`,
containerMntPoint))
@@ -477,7 +478,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
backupYamlPath := filepath.Join(containerMntPoint, "backup.yaml")
backup, err := slurpBackupFile(backupYamlPath)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Update snapshot names to include container name (if needed)
@@ -492,13 +493,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
poolID, pool, poolErr := d.cluster.StoragePoolGet(containerPoolName)
if poolErr != nil {
if poolErr != db.ErrNoSuchObject {
- return SmartError(poolErr)
+ return response.SmartError(poolErr)
}
}
if backup.Pool == nil {
// We don't know what kind of storage type the pool is.
- return BadRequest(fmt.Errorf(`No storage pool struct in the ` +
+ return response.BadRequest(fmt.Errorf(`No storage pool struct in the ` +
`backup file found. The storage pool needs to be ` +
`recovered manually`))
}
@@ -509,23 +510,23 @@ func internalImport(d *Daemon, r *http.Request) Response {
backup.Pool.Driver, backup.Pool.Config)
if err != nil {
err = errors.Wrap(err, "Create storage pool database entry")
- return SmartError(err)
+ return response.SmartError(err)
}
poolID, err = d.cluster.StoragePoolGetID(containerPoolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
} else {
if backup.Pool.Name != containerPoolName {
- return BadRequest(fmt.Errorf(`The storage pool "%s" `+
+ return response.BadRequest(fmt.Errorf(`The storage pool "%s" `+
`the container was detected on does not match `+
`the storage pool "%s" specified in the `+
`backup file`, containerPoolName, backup.Pool.Name))
}
if backup.Pool.Driver != pool.Driver {
- return BadRequest(fmt.Errorf(`The storage pool's `+
+ return response.BadRequest(fmt.Errorf(`The storage pool's `+
`"%s" driver "%s" conflicts with the driver `+
`"%s" recorded in the container's backup file`,
containerPoolName, pool.Driver, backup.Pool.Driver))
@@ -535,12 +536,12 @@ func internalImport(d *Daemon, r *http.Request) Response {
initPool, err := storagePoolInit(d.State(), backup.Pool.Name)
if err != nil {
err = errors.Wrap(err, "Initialize storage")
- return InternalError(err)
+ return response.InternalError(err)
}
ourMount, err := initPool.StoragePoolMount()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if ourMount {
defer initPool.StoragePoolUmount()
@@ -553,7 +554,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
// retrieve on-disk pool name
_, _, poolName := initPool.GetContainerPoolInfo()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Retrieve all snapshots that exist on disk.
@@ -564,24 +565,24 @@ func internalImport(d *Daemon, r *http.Request) Response {
snapshotsDirPath := driver.GetSnapshotMountPoint(projectName, poolName, req.Name)
snapshotsDir, err := os.Open(snapshotsDirPath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
if err != nil {
snapshotsDir.Close()
- return InternalError(err)
+ return response.InternalError(err)
}
snapshotsDir.Close()
case "dir":
snapshotsDirPath := driver.GetSnapshotMountPoint(projectName, poolName, req.Name)
snapshotsDir, err := os.Open(snapshotsDirPath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
if err != nil {
snapshotsDir.Close()
- return InternalError(err)
+ return response.InternalError(err)
}
snapshotsDir.Close()
case "lvm":
@@ -589,7 +590,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
msg, err := shared.RunCommand("lvs", "-o", "lv_name",
onDiskPoolName, "--noheadings")
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
snaps := strings.Fields(msg)
@@ -618,7 +619,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
storagePoolVolumeTypeNameContainer, userName)
if err != nil {
if err != db.ErrNoSuchObject {
- return InternalError(err)
+ return response.InternalError(err)
}
}
@@ -634,7 +635,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
snaps, err := zfsPoolListSnapshots(onDiskPoolName,
fmt.Sprintf("containers/%s", req.Name))
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, v := range snaps {
@@ -655,7 +656,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
`recorded in the "backup.yaml" file. Pass ` +
`"force" to remove them`
logger.Errorf(msg)
- return InternalError(fmt.Errorf(msg))
+ return response.InternalError(fmt.Errorf(msg))
}
}
@@ -680,7 +681,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
`the "backup.yaml" file. Pass "force" to ` +
`remove them`
logger.Errorf(msg)
- return InternalError(fmt.Errorf(msg))
+ return response.InternalError(fmt.Errorf(msg))
}
var err error
@@ -738,7 +739,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
if req.Force {
continue
}
- return BadRequest(needForce)
+ return response.BadRequest(needForce)
}
case "dir":
snpMntPt := driver.GetSnapshotMountPoint(projectName, backup.Pool.Name, snap.Name)
@@ -746,7 +747,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
if req.Force {
continue
}
- return BadRequest(needForce)
+ return response.BadRequest(needForce)
}
case "lvm":
ctLvmName := containerNameToLVName(snap.Name)
@@ -755,14 +756,14 @@ func internalImport(d *Daemon, r *http.Request) Response {
ctLvmName)
exists, err := storageLVExists(ctLvName)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if !exists {
if req.Force {
continue
}
- return BadRequest(needForce)
+ return response.BadRequest(needForce)
}
case "ceph":
clusterName := "ceph"
@@ -789,7 +790,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
if req.Force {
continue
}
- return BadRequest(needForce)
+ return response.BadRequest(needForce)
}
case "zfs":
ctName, csName, _ := shared.ContainerGetParentAndSnapshotName(snap.Name)
@@ -802,7 +803,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
if req.Force {
continue
}
- return BadRequest(needForce)
+ return response.BadRequest(needForce)
}
}
@@ -814,12 +815,12 @@ func internalImport(d *Daemon, r *http.Request) Response {
req.Name, storagePoolVolumeTypeContainer, poolID)
if ctVolErr != nil {
if ctVolErr != db.ErrNoSuchObject {
- return SmartError(ctVolErr)
+ return response.SmartError(ctVolErr)
}
}
// If a storage volume entry exists only proceed if force was specified.
if ctVolErr == nil && !req.Force {
- return BadRequest(fmt.Errorf(`Storage volume for container `+
+ return response.BadRequest(fmt.Errorf(`Storage volume for container `+
`"%s" already exists in the database. Set "force" to `+
`overwrite`, req.Name))
}
@@ -828,31 +829,31 @@ func internalImport(d *Daemon, r *http.Request) Response {
_, containerErr := d.cluster.ContainerID(projectName, req.Name)
if containerErr != nil {
if containerErr != db.ErrNoSuchObject {
- return SmartError(containerErr)
+ return response.SmartError(containerErr)
}
}
// If a db entry exists only proceed if force was specified.
if containerErr == nil && !req.Force {
- return BadRequest(fmt.Errorf(`Entry for container "%s" `+
+ return response.BadRequest(fmt.Errorf(`Entry for container "%s" `+
`already exists in the database. Set "force" to `+
`overwrite`, req.Name))
}
if backup.Volume == nil {
- return BadRequest(fmt.Errorf(`No storage volume struct in the ` +
+ return response.BadRequest(fmt.Errorf(`No storage volume struct in the ` +
`backup file found. The storage volume needs to be ` +
`recovered manually`))
}
if ctVolErr == nil {
if volume.Name != backup.Volume.Name {
- return BadRequest(fmt.Errorf(`The name "%s" of the `+
+ return response.BadRequest(fmt.Errorf(`The name "%s" of the `+
`storage volume is not identical to the `+
`container's name "%s"`, volume.Name, req.Name))
}
if volume.Type != backup.Volume.Type {
- return BadRequest(fmt.Errorf(`The type "%s" of the `+
+ return response.BadRequest(fmt.Errorf(`The type "%s" of the `+
`storage volume is not identical to the `+
`container's type "%s"`, volume.Type,
backup.Volume.Type))
@@ -863,7 +864,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
err := d.cluster.StoragePoolVolumeDelete("default", req.Name,
storagePoolVolumeTypeContainer, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -872,7 +873,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
// force was specified.
err := d.cluster.ContainerRemove(projectName, req.Name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -885,7 +886,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
// Mark the filesystem as going through an import
fd, err := os.Create(filepath.Join(containerMntPoint, ".importing"))
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
fd.Close()
defer os.Remove(fd.Name())
@@ -913,7 +914,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
arch, err := osarch.ArchitectureId(backup.Container.Architecture)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
_, err = containerCreateInternal(d.State(), db.ContainerArgs{
Project: projectName,
@@ -932,7 +933,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
})
if err != nil {
err = errors.Wrap(err, "Create container")
- return SmartError(err)
+ return response.SmartError(err)
}
containerPath := driver.ContainerPath(project.Prefix(projectName, req.Name), false)
@@ -943,7 +944,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
err = driver.CreateContainerMountpoint(containerMntPoint, containerPath,
isPrivileged)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, snap := range existingSnapshots {
@@ -953,13 +954,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
_, snapErr := d.cluster.InstanceSnapshotID(projectName, parts[0], parts[1])
if snapErr != nil {
if snapErr != db.ErrNoSuchObject {
- return SmartError(snapErr)
+ return response.SmartError(snapErr)
}
}
// If a db entry exists only proceed if force was specified.
if snapErr == nil && !req.Force {
- return BadRequest(fmt.Errorf(`Entry for snapshot "%s" `+
+ return response.BadRequest(fmt.Errorf(`Entry for snapshot "%s" `+
`already exists in the database. Set "force" `+
`to overwrite`, snap.Name))
}
@@ -969,13 +970,13 @@ func internalImport(d *Daemon, r *http.Request) Response {
projectName, snap.Name, storagePoolVolumeTypeContainer, poolID)
if csVolErr != nil {
if csVolErr != db.ErrNoSuchObject {
- return SmartError(csVolErr)
+ return response.SmartError(csVolErr)
}
}
// If a storage volume entry exists only proceed if force was specified.
if csVolErr == nil && !req.Force {
- return BadRequest(fmt.Errorf(`Storage volume for `+
+ return response.BadRequest(fmt.Errorf(`Storage volume for `+
`snapshot "%s" already exists in the `+
`database. Set "force" to overwrite`, snap.Name))
}
@@ -983,7 +984,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
if snapErr == nil {
err := d.cluster.ContainerRemove(projectName, snap.Name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -991,7 +992,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
err := d.cluster.StoragePoolVolumeDelete(projectName, snap.Name,
storagePoolVolumeTypeContainer, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -999,7 +1000,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
arch, err := osarch.ArchitectureId(snap.Architecture)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Add root device if missing
@@ -1037,7 +1038,7 @@ func internalImport(d *Daemon, r *http.Request) Response {
Stateful: snap.Stateful,
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Recreate missing mountpoints and symlinks.
@@ -1049,31 +1050,31 @@ func internalImport(d *Daemon, r *http.Request) Response {
snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
err = driver.CreateSnapshotMountpoint(snapshotMountPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalGC(d *Daemon, r *http.Request) Response {
+func internalGC(d *Daemon, r *http.Request) response.Response {
logger.Infof("Started forced garbage collection run")
runtime.GC()
runtimeDebug.FreeOSMemory()
logger.Infof("Completed forced garbage collection run")
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func internalRAFTSnapshot(d *Daemon, r *http.Request) Response {
+func internalRAFTSnapshot(d *Daemon, r *http.Request) response.Response {
logger.Infof("Started forced RAFT snapshot")
err := d.gateway.Snapshot()
if err != nil {
logger.Errorf("Failed forced RAFT snapshot: %v", err)
- return InternalError(err)
+ return response.InternalError(err)
}
logger.Infof("Completed forced RAFT snapshot")
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
diff --git a/lxd/api_project.go b/lxd/api_project.go
index 4802c1d15c..a0b1eecab2 100644
--- a/lxd/api_project.go
+++ b/lxd/api_project.go
@@ -12,6 +12,8 @@ import (
"github.com/pkg/errors"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -35,7 +37,7 @@ var projectCmd = APIEndpoint{
Put: APIEndpointAction{Handler: projectPut, AccessHandler: AllowAuthenticated},
}
-func projectsGet(d *Daemon, r *http.Request) Response {
+func projectsGet(d *Daemon, r *http.Request) response.Response {
recursion := util.IsRecursionRequest(r)
var result interface{}
@@ -80,13 +82,13 @@ func projectsGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
-func projectsPost(d *Daemon, r *http.Request) Response {
+func projectsPost(d *Daemon, r *http.Request) response.Response {
// Parse the request
project := api.ProjectsPost{}
@@ -103,30 +105,30 @@ func projectsPost(d *Daemon, r *http.Request) Response {
err := json.NewDecoder(r.Body).Decode(&project)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if project.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(project.Name, "/") {
- return BadRequest(fmt.Errorf("Project names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Project names may not contain slashes"))
}
if project.Name == "*" {
- return BadRequest(fmt.Errorf("Reserved project name"))
+ return response.BadRequest(fmt.Errorf("Reserved project name"))
}
if shared.StringInSlice(project.Name, []string{".", ".."}) {
- return BadRequest(fmt.Errorf("Invalid project name '%s'", project.Name))
+ return response.BadRequest(fmt.Errorf("Invalid project name '%s'", project.Name))
}
// Validate the configuration
err = projectValidateConfig(project.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
var id int64
@@ -146,17 +148,17 @@ func projectsPost(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(fmt.Errorf("Error inserting %s into database: %s", project.Name, err))
+ return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", project.Name, err))
}
if d.rbac != nil {
err = d.rbac.AddProject(id, project.Name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/projects/%s", version.APIVersion, project.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/projects/%s", version.APIVersion, project.Name))
}
// Create the default profile of a project.
@@ -174,12 +176,12 @@ func projectCreateDefaultProfile(tx *db.ClusterTx, project string) error {
return nil
}
-func projectGet(d *Daemon, r *http.Request) Response {
+func projectGet(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Check user permissions
if !d.userHasPermission(r, name, "view") {
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
// Get the database entry
@@ -190,7 +192,7 @@ func projectGet(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
etag := []interface{}{
@@ -199,15 +201,15 @@ func projectGet(d *Daemon, r *http.Request) Response {
project.Config["features.profiles"],
}
- return SyncResponseETag(true, project, etag)
+ return response.SyncResponseETag(true, project, etag)
}
-func projectPut(d *Daemon, r *http.Request) Response {
+func projectPut(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Check user permissions
if !d.userHasPermission(r, name, "manage-projects") {
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
// Get the current data
@@ -218,7 +220,7 @@ func projectPut(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
@@ -229,7 +231,7 @@ func projectPut(d *Daemon, r *http.Request) Response {
}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
// Parse the request
@@ -237,18 +239,18 @@ func projectPut(d *Daemon, r *http.Request) Response {
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
return projectChange(d, project, req)
}
-func projectPatch(d *Daemon, r *http.Request) Response {
+func projectPatch(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Check user permissions
if !d.userHasPermission(r, name, "manage-projects") {
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
// Get the current data
@@ -259,7 +261,7 @@ func projectPatch(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
@@ -270,12 +272,12 @@ func projectPatch(d *Daemon, r *http.Request) Response {
}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -283,12 +285,12 @@ func projectPatch(d *Daemon, r *http.Request) Response {
reqRaw := shared.Jmap{}
if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
req := api.ProjectPut{}
if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check what was actually set in the query
@@ -311,23 +313,23 @@ func projectPatch(d *Daemon, r *http.Request) Response {
}
// Common logic between PUT and PATCH.
-func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) Response {
+func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) response.Response {
// Flag indicating if any feature has changed.
featuresChanged := req.Config["features.images"] != project.Config["features.images"] || req.Config["features.profiles"] != project.Config["features.profiles"]
// Sanity checks
if project.Name == "default" && featuresChanged {
- return BadRequest(fmt.Errorf("You can't change the features of the default project"))
+ return response.BadRequest(fmt.Errorf("You can't change the features of the default project"))
}
if !projectIsEmpty(project) && featuresChanged {
- return BadRequest(fmt.Errorf("Features can only be changed on empty projects"))
+ return response.BadRequest(fmt.Errorf("Features can only be changed on empty projects"))
}
// Validate the configuration
err := projectValidateConfig(req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Update the database entry
@@ -357,13 +359,13 @@ func projectChange(d *Daemon, project *api.Project, req api.ProjectPut) Response
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func projectPost(d *Daemon, r *http.Request) Response {
+func projectPost(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Parse the request
@@ -371,16 +373,16 @@ func projectPost(d *Daemon, r *http.Request) Response {
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if name == "default" {
- return Forbidden(fmt.Errorf("The 'default' project cannot be renamed"))
+ return response.Forbidden(fmt.Errorf("The 'default' project cannot be renamed"))
}
// Perform the rename
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
var id int64
err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
project, err := tx.ProjectGet(req.Name)
@@ -422,20 +424,20 @@ func projectPost(d *Daemon, r *http.Request) Response {
return nil
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationProjectRename, nil, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationProjectRename, nil, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func projectDelete(d *Daemon, r *http.Request) Response {
+func projectDelete(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Sanity checks
if name == "default" {
- return Forbidden(fmt.Errorf("The 'default' project cannot be deleted"))
+ return response.Forbidden(fmt.Errorf("The 'default' project cannot be deleted"))
}
var id int64
@@ -457,17 +459,17 @@ func projectDelete(d *Daemon, r *http.Request) Response {
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if d.rbac != nil {
err = d.rbac.DeleteProject(id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Check if a project is empty.
diff --git a/lxd/backup.go b/lxd/backup.go
index 16f6b7c9f6..e536b07361 100644
--- a/lxd/backup.go
+++ b/lxd/backup.go
@@ -11,10 +11,12 @@ import (
"time"
"context"
+
"gopkg.in/yaml.v2"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/task"
"github.com/lxc/lxd/shared"
@@ -445,11 +447,11 @@ func backupCreateTarball(s *state.State, path string, backup backup) error {
func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
f := func(ctx context.Context) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return pruneExpiredContainerBackups(ctx, d)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
return
diff --git a/lxd/certificates.go b/lxd/certificates.go
index 463c04a9d2..bd18e8cee4 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -15,6 +15,7 @@ import (
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -40,7 +41,7 @@ var certificateCmd = APIEndpoint{
Put: APIEndpointAction{Handler: certificatePut},
}
-func certificatesGet(d *Daemon, r *http.Request) Response {
+func certificatesGet(d *Daemon, r *http.Request) response.Response {
recursion := util.IsRecursionRequest(r)
if recursion {
@@ -48,7 +49,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
baseCerts, err := d.cluster.CertificatesGet()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, baseCert := range baseCerts {
resp := api.Certificate{}
@@ -62,7 +63,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
}
certResponses = append(certResponses, resp)
}
- return SyncResponse(true, certResponses)
+ return response.SyncResponse(true, certResponses)
}
body := []string{}
@@ -71,7 +72,7 @@ func certificatesGet(d *Daemon, r *http.Request) Response {
body = append(body, fingerprint)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
func readSavedClientCAList(d *Daemon) {
@@ -100,33 +101,33 @@ func readSavedClientCAList(d *Daemon) {
}
}
-func certificatesPost(d *Daemon, r *http.Request) Response {
+func certificatesPost(d *Daemon, r *http.Request) response.Response {
// Parse the request
req := api.CertificatesPost{}
if err := shared.ReadToJSON(r.Body, &req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Access check
secret, err := cluster.ConfigGetString(d.cluster, "core.trust_password")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
trusted, _, protocol, err := d.Authenticate(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if (!trusted || (protocol == "candid" && !d.userIsAdmin(r))) && util.PasswordCheck(secret, req.Password) != nil {
if req.Password != "" {
logger.Warn("Bad trust password", log.Ctx{"url": r.URL.RequestURI(), "ip": r.RemoteAddr})
}
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
if req.Type != "client" {
- return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
+ return response.BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
}
// Extract the certificate
@@ -135,28 +136,28 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
if req.Certificate != "" {
data, err := base64.StdEncoding.DecodeString(req.Certificate)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
cert, err = x509.ParseCertificate(data)
if err != nil {
- return BadRequest(errors.Wrap(err, "invalid certificate material"))
+ return response.BadRequest(errors.Wrap(err, "invalid certificate material"))
}
name = req.Name
} else if r.TLS != nil {
if len(r.TLS.PeerCertificates) < 1 {
- return BadRequest(fmt.Errorf("No client certificate provided"))
+ return response.BadRequest(fmt.Errorf("No client certificate provided"))
}
cert = r.TLS.PeerCertificates[len(r.TLS.PeerCertificates)-1]
remoteHost, _, err := net.SplitHostPort(r.RemoteAddr)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
name = remoteHost
} else {
- return BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
+ return response.BadRequest(fmt.Errorf("Can't use TLS data on non-TLS link"))
}
fingerprint := shared.CertFingerprint(cert)
@@ -173,10 +174,10 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
_, ok := d.clientCerts[fingerprint]
if !ok {
d.clientCerts[fingerprint] = *cert
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
}
- return BadRequest(fmt.Errorf("Certificate already in trust store"))
+ return response.BadRequest(fmt.Errorf("Certificate already in trust store"))
}
// Store the certificate in the cluster database
@@ -189,14 +190,14 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
err = d.cluster.CertSave(&dbCert)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Notify other nodes about the new certificate.
notifier, err := cluster.NewNotifier(
d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
req := api.CertificatesPost{
Certificate: base64.StdEncoding.EncodeToString(cert.Raw),
@@ -208,24 +209,24 @@ func certificatesPost(d *Daemon, r *http.Request) Response {
return client.CreateCertificate(req)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
d.clientCerts[shared.CertFingerprint(cert)] = *cert
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/certificates/%s", version.APIVersion, fingerprint))
}
-func certificateGet(d *Daemon, r *http.Request) Response {
+func certificateGet(d *Daemon, r *http.Request) response.Response {
fingerprint := mux.Vars(r)["fingerprint"]
cert, err := doCertificateGet(d.cluster, fingerprint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseETag(true, cert, cert)
+ return response.SyncResponseETag(true, cert, cert)
}
func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, error) {
@@ -248,46 +249,46 @@ func doCertificateGet(db *db.Cluster, fingerprint string) (api.Certificate, erro
return resp, nil
}
-func certificatePut(d *Daemon, r *http.Request) Response {
+func certificatePut(d *Daemon, r *http.Request) response.Response {
fingerprint := mux.Vars(r)["fingerprint"]
oldEntry, err := doCertificateGet(d.cluster, fingerprint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fingerprint = oldEntry.Fingerprint
err = util.EtagCheck(r, oldEntry)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.CertificatePut{}
if err := shared.ReadToJSON(r.Body, &req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
return doCertificateUpdate(d, fingerprint, req)
}
-func certificatePatch(d *Daemon, r *http.Request) Response {
+func certificatePatch(d *Daemon, r *http.Request) response.Response {
fingerprint := mux.Vars(r)["fingerprint"]
oldEntry, err := doCertificateGet(d.cluster, fingerprint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fingerprint = oldEntry.Fingerprint
err = util.EtagCheck(r, oldEntry)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := oldEntry
reqRaw := shared.Jmap{}
if err := json.NewDecoder(r.Body).Decode(&reqRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Get name
@@ -305,32 +306,32 @@ func certificatePatch(d *Daemon, r *http.Request) Response {
return doCertificateUpdate(d, fingerprint, req.Writable())
}
-func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) Response {
+func doCertificateUpdate(d *Daemon, fingerprint string, req api.CertificatePut) response.Response {
if req.Type != "client" {
- return BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
+ return response.BadRequest(fmt.Errorf("Unknown request type %s", req.Type))
}
err := d.cluster.CertUpdate(fingerprint, req.Name, 1)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func certificateDelete(d *Daemon, r *http.Request) Response {
+func certificateDelete(d *Daemon, r *http.Request) response.Response {
fingerprint := mux.Vars(r)["fingerprint"]
certInfo, err := d.cluster.CertificateGet(fingerprint)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
err = d.cluster.CertDelete(certInfo.Fingerprint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
readSavedClientCAList(d)
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
diff --git a/lxd/container.go b/lxd/container.go
index 722db5fcef..74ffe568ed 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -22,6 +22,7 @@ import (
"github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/events"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/sys"
"github.com/lxc/lxd/lxd/task"
@@ -1249,11 +1250,11 @@ func autoCreateContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
return
}
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return autoCreateContainerSnapshots(ctx, d, instances)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationSnapshotCreate, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationSnapshotCreate, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start create snapshot operation", log.Ctx{"err": err})
return
@@ -1370,11 +1371,11 @@ func pruneExpiredContainerSnapshotsTask(d *Daemon) (task.Func, task.Schedule) {
return
}
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return pruneExpiredContainerSnapshots(ctx, d, expiredSnapshots)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationSnapshotsExpire, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationSnapshotsExpire, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start expired snapshots operation", log.Ctx{"err": err})
return
diff --git a/lxd/container_backup.go b/lxd/container_backup.go
index 040e2f9443..4f8b8fdd30 100644
--- a/lxd/container_backup.go
+++ b/lxd/container_backup.go
@@ -11,40 +11,42 @@ import (
"github.com/pkg/errors"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/version"
)
-func containerBackupsGet(d *Daemon, r *http.Request) Response {
+func containerBackupsGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
cname := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
recursion := util.IsRecursionRequest(r)
c, err := instanceLoadByProjectAndName(d.State(), project, cname)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
backups, err := c.Backups()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
resultString := []string{}
@@ -62,39 +64,39 @@ func containerBackupsGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
-func containerBackupsPost(d *Daemon, r *http.Request) Response {
+func containerBackupsPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
rj := shared.Jmap{}
err = json.NewDecoder(r.Body).Decode(&rj)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
expiry, _ := rj.GetString("expiry")
@@ -106,21 +108,21 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
// Create body with correct expiry
body, err := json.Marshal(rj)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
req := api.InstanceBackupsPost{}
err = json.Unmarshal(body, &req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Name == "" {
// come up with a name
backups, err := c.Backups()
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
base := name + shared.SnapshotDelimiter + "backup"
@@ -149,13 +151,13 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
// Validate the name
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Backup names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Backup names may not contain slashes"))
}
fullName := name + shared.SnapshotDelimiter + req.Name
instanceOnly := req.InstanceOnly || req.ContainerOnly
- backup := func(op *operation) error {
+ backup := func(op *operations.Operation) error {
args := db.ContainerBackupArgs{
Name: fullName,
ContainerID: c.Id(),
@@ -177,19 +179,19 @@ func containerBackupsPost(d *Daemon, r *http.Request) Response {
resources["containers"] = []string{name}
resources["backups"] = []string{req.Name}
- op, err := operationCreate(d.cluster, project, operationClassTask,
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
db.OperationBackupCreate, resources, nil, backup, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containerBackupGet(d *Daemon, r *http.Request) Response {
+func containerBackupGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -197,27 +199,27 @@ func containerBackupGet(d *Daemon, r *http.Request) Response {
backupName := mux.Vars(r)["backupName"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
fullName := name + shared.SnapshotDelimiter + backupName
backup, err := backupLoadByName(d.State(), project, fullName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, backup.Render())
+ return response.SyncResponse(true, backup.Render())
}
-func containerBackupPost(d *Daemon, r *http.Request) Response {
+func containerBackupPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -225,34 +227,34 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
backupName := mux.Vars(r)["backupName"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
req := api.InstanceBackupPost{}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Validate the name
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Backup names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Backup names may not contain slashes"))
}
oldName := name + shared.SnapshotDelimiter + backupName
backup, err := backupLoadByName(d.State(), project, oldName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
newName := name + shared.SnapshotDelimiter + req.Name
- rename := func(op *operation) error {
+ rename := func(op *operations.Operation) error {
err := backup.Rename(newName)
if err != nil {
return err
@@ -264,19 +266,19 @@ func containerBackupPost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask,
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
db.OperationBackupRename, resources, nil, rename, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containerBackupDelete(d *Daemon, r *http.Request) Response {
+func containerBackupDelete(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -284,21 +286,21 @@ func containerBackupDelete(d *Daemon, r *http.Request) Response {
backupName := mux.Vars(r)["backupName"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
fullName := name + shared.SnapshotDelimiter + backupName
backup, err := backupLoadByName(d.State(), project, fullName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- remove := func(op *operation) error {
+ remove := func(op *operations.Operation) error {
err := backup.Delete()
if err != nil {
return err
@@ -310,19 +312,19 @@ func containerBackupDelete(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["container"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask,
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask,
db.OperationBackupRemove, resources, nil, remove, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containerBackupExportGet(d *Daemon, r *http.Request) Response {
+func containerBackupExportGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -330,23 +332,23 @@ func containerBackupExportGet(d *Daemon, r *http.Request) Response {
backupName := mux.Vars(r)["backupName"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
fullName := name + shared.SnapshotDelimiter + backupName
backup, err := backupLoadByName(d.State(), project, fullName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- ent := fileResponseEntry{
- path: shared.VarPath("backups", backup.name),
+ ent := response.FileResponseEntry{
+ Path: shared.VarPath("backups", backup.name),
}
- return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+ return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
}
diff --git a/lxd/container_console.go b/lxd/container_console.go
index 9aa73fea41..9e94ec9bdb 100644
--- a/lxd/container_console.go
+++ b/lxd/container_console.go
@@ -19,6 +19,8 @@ import (
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -70,7 +72,7 @@ func (s *consoleWs) Metadata() interface{} {
return shared.Jmap{"fds": fds}
}
-func (s *consoleWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *consoleWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
secret := r.FormValue("secret")
if secret == "" {
return fmt.Errorf("missing secret")
@@ -111,7 +113,7 @@ func (s *consoleWs) Connect(op *operation, r *http.Request, w http.ResponseWrite
return os.ErrPermission
}
-func (s *consoleWs) Do(op *operation) error {
+func (s *consoleWs) Do(op *operations.Operation) error {
<-s.allConnected
var err error
@@ -254,10 +256,10 @@ func (s *consoleWs) Do(op *operation) error {
return finisher(err)
}
-func containerConsolePost(d *Daemon, r *http.Request) Response {
+func containerConsolePost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -266,45 +268,45 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
post := api.InstanceConsolePost{}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = json.Unmarshal(buf, &post)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Forward the request if the container is remote.
cert := d.endpoints.NetworkCert()
client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if client != nil {
url := fmt.Sprintf("/containers/%s/console?project=%s", name, project)
op, _, err := client.RawOperation("POST", url, post, "")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
opAPI := op.Get()
- return ForwardedOperationResponse(project, &opAPI)
+ return operations.ForwardedOperationResponse(project, &opAPI)
}
inst, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = fmt.Errorf("Container is not running")
if !inst.IsRunning() {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = fmt.Errorf("Container is frozen")
if inst.IsFrozen() {
- return BadRequest(err)
+ return response.BadRequest(err)
}
ws := &consoleWs{}
@@ -315,7 +317,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
c := inst.(container)
idmapset, err := c.CurrentIdmap()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if idmapset != nil {
@@ -329,7 +331,7 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
for i := -1; i < len(ws.conns)-1; i++ {
ws.fds[i], err = shared.RandomCryptoString()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
@@ -342,54 +344,54 @@ func containerConsolePost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{ws.instance.Name()}
- op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationConsoleShow,
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationConsoleShow,
resources, ws.Metadata(), ws.Do, nil, ws.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containerConsoleLogGet(d *Daemon, r *http.Request) Response {
+func containerConsoleLogGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Forward the request if the container is remote.
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
if !util.RuntimeLiblxcVersionAtLeast(3, 0, 0) {
- return BadRequest(fmt.Errorf("Querying the console buffer requires liblxc >= 3.0"))
+ return response.BadRequest(fmt.Errorf("Querying the console buffer requires liblxc >= 3.0"))
}
inst, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
- ent := fileResponseEntry{}
+ ent := response.FileResponseEntry{}
if !c.IsRunning() {
// Hand back the contents of the console ringbuffer logfile.
consoleBufferLogPath := c.ConsoleBufferLogPath()
- ent.path = consoleBufferLogPath
- ent.filename = consoleBufferLogPath
- return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+ ent.Path = consoleBufferLogPath
+ ent.Filename = consoleBufferLogPath
+ return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
}
// Query the container's console ringbuffer.
@@ -405,23 +407,23 @@ func containerConsoleLogGet(d *Daemon, r *http.Request) Response {
if err != nil {
errno, isErrno := shared.GetErrno(err)
if !isErrno {
- return SmartError(err)
+ return response.SmartError(err)
}
if errno == unix.ENODATA {
- return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+ return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
}
- return SmartError(err)
+ return response.SmartError(err)
}
- ent.buffer = []byte(logContents)
- return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+ ent.Buffer = []byte(logContents)
+ return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
}
-func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
+func containerConsoleLogDelete(d *Daemon, r *http.Request) response.Response {
if !util.RuntimeLiblxcVersionAtLeast(3, 0, 0) {
- return BadRequest(fmt.Errorf("Clearing the console buffer requires liblxc >= 3.0"))
+ return response.BadRequest(fmt.Errorf("Clearing the console buffer requires liblxc >= 3.0"))
}
name := mux.Vars(r)["name"]
@@ -429,11 +431,11 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
inst, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
@@ -459,7 +461,7 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
if !inst.IsRunning() {
consoleLogpath := c.ConsoleBufferLogPath()
- return SmartError(truncateConsoleLogFile(consoleLogpath))
+ return response.SmartError(truncateConsoleLogFile(consoleLogpath))
}
// Send a ringbuffer request to the container.
@@ -474,15 +476,15 @@ func containerConsoleLogDelete(d *Daemon, r *http.Request) Response {
if err != nil {
errno, isErrno := shared.GetErrno(err)
if !isErrno {
- return SmartError(err)
+ return response.SmartError(err)
}
if errno == unix.ENODATA {
- return SmartError(nil)
+ return response.SmartError(nil)
}
- return SmartError(err)
+ return response.SmartError(err)
}
- return SmartError(nil)
+ return response.SmartError(nil)
}
diff --git a/lxd/container_delete.go b/lxd/container_delete.go
index 2885588d1e..cc0a380e5f 100644
--- a/lxd/container_delete.go
+++ b/lxd/container_delete.go
@@ -6,45 +6,47 @@ import (
"github.com/gorilla/mux"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
)
-func containerDelete(d *Daemon, r *http.Request) Response {
+func containerDelete(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if c.IsRunning() {
- return BadRequest(fmt.Errorf("container is running"))
+ return response.BadRequest(fmt.Errorf("container is running"))
}
- rmct := func(op *operation) error {
+ rmct := func(op *operations.Operation) error {
return c.Delete()
}
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerDelete, resources, nil, rmct, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerDelete, resources, nil, rmct, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
diff --git a/lxd/container_exec.go b/lxd/container_exec.go
index 3c871fed37..2c861a911c 100644
--- a/lxd/container_exec.go
+++ b/lxd/container_exec.go
@@ -20,6 +20,8 @@ import (
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
log "github.com/lxc/lxd/shared/log15"
@@ -66,7 +68,7 @@ func (s *execWs) Metadata() interface{} {
}
}
-func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *execWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
secret := r.FormValue("secret")
if secret == "" {
return fmt.Errorf("missing secret")
@@ -107,7 +109,7 @@ func (s *execWs) Connect(op *operation, r *http.Request, w http.ResponseWriter)
return os.ErrPermission
}
-func (s *execWs) Do(op *operation) error {
+func (s *execWs) Do(op *operations.Operation) error {
<-s.allConnected
var err error
@@ -341,10 +343,10 @@ func (s *execWs) Do(op *operation) error {
return finisher(-1, nil)
}
-func containerExecPost(d *Daemon, r *http.Request) Response {
+func containerExecPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -353,42 +355,42 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
post := api.InstanceExecPost{}
buf, err := ioutil.ReadAll(r.Body)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if err := json.Unmarshal(buf, &post); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Forward the request if the container is remote.
cert := d.endpoints.NetworkCert()
client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if client != nil {
url := fmt.Sprintf("/containers/%s/exec?project=%s", name, project)
op, _, err := client.RawOperation("POST", url, post, "")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
opAPI := op.Get()
- return ForwardedOperationResponse(project, &opAPI)
+ return operations.ForwardedOperationResponse(project, &opAPI)
}
inst, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if !inst.IsRunning() {
- return BadRequest(fmt.Errorf("Container is not running"))
+ return response.BadRequest(fmt.Errorf("Container is not running"))
}
if inst.IsFrozen() {
- return BadRequest(fmt.Errorf("Container is frozen"))
+ return response.BadRequest(fmt.Errorf("Container is frozen"))
}
env := map[string]string{}
@@ -443,7 +445,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
c := inst.(container)
idmapset, err := c.CurrentIdmap()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if idmapset != nil {
@@ -464,7 +466,7 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
for i := -1; i < len(ws.conns)-1; i++ {
ws.fds[i], err = shared.RandomCryptoString()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
@@ -482,28 +484,28 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{ws.instance.Name()}
- op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationCommandExec, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
var cmdErr error
var cmdResult int
metadata := shared.Jmap{}
if post.RecordOutput {
// Prepare stdout and stderr recording
- stdout, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stdout", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ stdout, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stdout", op.ID())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
defer stdout.Close()
- stderr, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stderr", op.id)), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
+ stderr, err := os.OpenFile(filepath.Join(inst.LogPath(), fmt.Sprintf("exec_%s.stderr", op.ID())), os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
if err != nil {
return err
}
@@ -534,10 +536,10 @@ func containerExecPost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationCommandExec, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationCommandExec, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
diff --git a/lxd/container_file.go b/lxd/container_file.go
index 7946fe4444..ce87766ed7 100644
--- a/lxd/container_file.go
+++ b/lxd/container_file.go
@@ -10,34 +10,35 @@ import (
"github.com/gorilla/mux"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
)
-func containerFileHandler(d *Daemon, r *http.Request) Response {
+func containerFileHandler(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
path := r.FormValue("path")
if path == "" {
- return BadRequest(fmt.Errorf("missing path argument"))
+ return response.BadRequest(fmt.Errorf("missing path argument"))
}
switch r.Method {
@@ -48,11 +49,11 @@ func containerFileHandler(d *Daemon, r *http.Request) Response {
case "DELETE":
return containerFileDelete(c, path, r)
default:
- return NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+ return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
}
}
-func containerFileGet(c Instance, path string, r *http.Request) Response {
+func containerFileGet(c Instance, path string, r *http.Request) response.Response {
/*
* Copy out of the ns to a temporary file, and then use that to serve
* the request from. This prevents us from having to worry about stuff
@@ -62,7 +63,7 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
*/
temp, err := ioutil.TempFile("", "lxd_forkgetfile_")
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
defer temp.Close()
@@ -70,7 +71,7 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
uid, gid, mode, type_, dirEnts, err := c.FilePull(path, temp.Name())
if err != nil {
os.Remove(temp.Name())
- return SmartError(err)
+ return response.SmartError(err)
}
headers := map[string]string{
@@ -82,34 +83,34 @@ func containerFileGet(c Instance, path string, r *http.Request) Response {
if type_ == "file" || type_ == "symlink" {
// Make a file response struct
- files := make([]fileResponseEntry, 1)
- files[0].identifier = filepath.Base(path)
- files[0].path = temp.Name()
- files[0].filename = filepath.Base(path)
+ files := make([]response.FileResponseEntry, 1)
+ files[0].Identifier = filepath.Base(path)
+ files[0].Path = temp.Name()
+ files[0].Filename = filepath.Base(path)
- return FileResponse(r, files, headers, true)
+ return response.FileResponse(r, files, headers, true)
} else if type_ == "directory" {
os.Remove(temp.Name())
- return SyncResponseHeaders(true, dirEnts, headers)
+ return response.SyncResponseHeaders(true, dirEnts, headers)
} else {
os.Remove(temp.Name())
- return InternalError(fmt.Errorf("bad file type %s", type_))
+ return response.InternalError(fmt.Errorf("bad file type %s", type_))
}
}
-func containerFilePost(c Instance, path string, r *http.Request) Response {
+func containerFilePost(c Instance, path string, r *http.Request) response.Response {
// Extract file ownership and mode from headers
uid, gid, mode, type_, write := shared.ParseLXDFileHeaders(r.Header)
if !shared.StringInSlice(write, []string{"overwrite", "append"}) {
- return BadRequest(fmt.Errorf("Bad file write mode: %s", write))
+ return response.BadRequest(fmt.Errorf("Bad file write mode: %s", write))
}
if type_ == "file" {
// Write file content to a tempfile
temp, err := ioutil.TempFile("", "lxd_forkputfile_")
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
defer func() {
temp.Close()
@@ -118,43 +119,43 @@ func containerFilePost(c Instance, path string, r *http.Request) Response {
_, err = io.Copy(temp, r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Transfer the file into the container
err = c.FilePush("file", temp.Name(), path, uid, gid, mode, write)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
} else if type_ == "symlink" {
target, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
err = c.FilePush("symlink", string(target), path, uid, gid, mode, write)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
} else if type_ == "directory" {
err := c.FilePush("directory", "", path, uid, gid, mode, write)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
} else {
- return BadRequest(fmt.Errorf("Bad file type: %s", type_))
+ return response.BadRequest(fmt.Errorf("Bad file type: %s", type_))
}
}
-func containerFileDelete(c Instance, path string, r *http.Request) Response {
+func containerFileDelete(c Instance, path string, r *http.Request) response.Response {
err := c.FileRemove(path)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
diff --git a/lxd/container_get.go b/lxd/container_get.go
index b76a85baa0..2ebf879d85 100644
--- a/lxd/container_get.go
+++ b/lxd/container_get.go
@@ -4,35 +4,36 @@ import (
"net/http"
"github.com/gorilla/mux"
+ "github.com/lxc/lxd/lxd/response"
)
-func containerGet(d *Daemon, r *http.Request) Response {
+func containerGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
state, etag, err := c.Render()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseETag(true, state, etag)
+ return response.SyncResponseETag(true, state, etag)
}
diff --git a/lxd/container_instance_types.go b/lxd/container_instance_types.go
index 93beb809e0..085e844779 100644
--- a/lxd/container_instance_types.go
+++ b/lxd/container_instance_types.go
@@ -11,6 +11,7 @@ import (
"gopkg.in/yaml.v2"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/task"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -74,7 +75,7 @@ func instanceRefreshTypesTask(d *Daemon) (task.Func, task.Schedule) {
// returning in case the context expires.
_, hasCancellationSupport := interface{}(&http.Request{}).(util.ContextAwareRequest)
f := func(ctx context.Context) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
if hasCancellationSupport {
return instanceRefreshTypes(ctx, d)
}
@@ -91,7 +92,7 @@ func instanceRefreshTypesTask(d *Daemon) (task.Func, task.Schedule) {
}
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationInstanceTypesUpdate, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationInstanceTypesUpdate, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start instance types update operation", log.Ctx{"err": err})
return
diff --git a/lxd/container_logs.go b/lxd/container_logs.go
index 8c68c47e67..e3f7d861fd 100644
--- a/lxd/container_logs.go
+++ b/lxd/container_logs.go
@@ -9,6 +9,7 @@ import (
"github.com/gorilla/mux"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/version"
)
@@ -30,7 +31,7 @@ var instanceLogsCmd = APIEndpoint{
Get: APIEndpointAction{Handler: containerLogsGet, AccessHandler: AllowProjectPermission("containers", "view")},
}
-func containerLogsGet(d *Daemon, r *http.Request) Response {
+func containerLogsGet(d *Daemon, r *http.Request) response.Response {
/* Let's explicitly *not* try to do a containerLoadByName here. In some
* cases (e.g. when container creation failed), the container won't
* exist in the DB but it does have some log files on disk.
@@ -41,30 +42,30 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
if err := containerValidName(name); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
result := []string{}
dents, err := ioutil.ReadDir(shared.LogPath(name))
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, f := range dents {
@@ -75,7 +76,7 @@ func containerLogsGet(d *Daemon, r *http.Request) Response {
result = append(result, fmt.Sprintf("/%s/containers/%s/logs/%s", version.APIVersion, name, f.Name()))
}
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
func validLogFileName(fname string) bool {
@@ -89,73 +90,73 @@ func validLogFileName(fname string) bool {
strings.HasPrefix(fname, "exec_")
}
-func containerLogGet(d *Daemon, r *http.Request) Response {
+func containerLogGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
file := mux.Vars(r)["file"]
if err := containerValidName(name); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if !validLogFileName(file) {
- return BadRequest(fmt.Errorf("log file name %s not valid", file))
+ return response.BadRequest(fmt.Errorf("log file name %s not valid", file))
}
- ent := fileResponseEntry{
- path: shared.LogPath(name, file),
- filename: file,
+ ent := response.FileResponseEntry{
+ Path: shared.LogPath(name, file),
+ Filename: file,
}
- return FileResponse(r, []fileResponseEntry{ent}, nil, false)
+ return response.FileResponse(r, []response.FileResponseEntry{ent}, nil, false)
}
-func containerLogDelete(d *Daemon, r *http.Request) Response {
+func containerLogDelete(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
file := mux.Vars(r)["file"]
if err := containerValidName(name); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if !validLogFileName(file) {
- return BadRequest(fmt.Errorf("log file name %s not valid", file))
+ return response.BadRequest(fmt.Errorf("log file name %s not valid", file))
}
if file == "lxc.log" || file == "lxc.conf" {
- return BadRequest(fmt.Errorf("lxc.log and lxc.conf may not be deleted"))
+ return response.BadRequest(fmt.Errorf("lxc.log and lxc.conf may not be deleted"))
}
- return SmartError(os.Remove(shared.LogPath(name, file)))
+ return response.SmartError(os.Remove(shared.LogPath(name, file)))
}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index c75d4774da..8021c75bbe 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -33,6 +33,7 @@ import (
"github.com/lxc/lxd/lxd/events"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/maas"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
@@ -629,7 +630,7 @@ type containerLXC struct {
node string
// Progress tracking
- op *operation
+ op *operations.Operation
expiryDate time.Time
}
@@ -6906,7 +6907,7 @@ func (c *containerLXC) StoragePool() (string, error) {
}
// Progress tracking
-func (c *containerLXC) SetOperation(op *operation) {
+func (c *containerLXC) SetOperation(op *operations.Operation) {
c.op = op
}
@@ -6924,7 +6925,7 @@ func (c *containerLXC) updateProgress(progress string) {
return
}
- meta := c.op.metadata
+ meta := c.op.Metadata()
if meta == nil {
meta = make(map[string]interface{})
}
diff --git a/lxd/container_metadata.go b/lxd/container_metadata.go
index bd4bdc712f..0f6ecf828c 100644
--- a/lxd/container_metadata.go
+++ b/lxd/container_metadata.go
@@ -13,39 +13,40 @@ import (
"github.com/gorilla/mux"
"gopkg.in/yaml.v2"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
)
-func containerMetadataGet(d *Daemon, r *http.Request) Response {
+func containerMetadataGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
// Load the container
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
metadataPath := filepath.Join(c.Path(), "metadata.yaml")
// Start the storage if needed
ourStart, err := c.StorageStart()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourStart {
defer c.StorageStop()
@@ -53,60 +54,60 @@ func containerMetadataGet(d *Daemon, r *http.Request) Response {
// If missing, just return empty result
if !shared.PathExists(metadataPath) {
- return SyncResponse(true, api.ImageMetadata{})
+ return response.SyncResponse(true, api.ImageMetadata{})
}
// Read the metadata
metadataFile, err := os.Open(metadataPath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
defer metadataFile.Close()
data, err := ioutil.ReadAll(metadataFile)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Parse into the API struct
metadata := api.ImageMetadata{}
err = yaml.Unmarshal(data, &metadata)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, metadata)
+ return response.SyncResponse(true, metadata)
}
-func containerMetadataPut(d *Daemon, r *http.Request) Response {
+func containerMetadataPut(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
// Load the container
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
metadataPath := filepath.Join(c.Path(), "metadata.yaml")
// Start the storage if needed
ourStart, err := c.StorageStart()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourStart {
defer c.StorageStop()
@@ -115,51 +116,51 @@ func containerMetadataPut(d *Daemon, r *http.Request) Response {
// Read the new metadata
metadata := api.ImageMetadata{}
if err := json.NewDecoder(r.Body).Decode(&metadata); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Write as YAML
data, err := yaml.Marshal(metadata)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if err := ioutil.WriteFile(metadataPath, data, 0644); err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Return a list of templates used in a container or the content of a template
-func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
// Load the container
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Start the storage if needed
ourStart, err := c.StorageStart()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourStart {
defer c.StorageStop()
@@ -170,14 +171,14 @@ func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
if templateName == "" {
templates := []string{}
if !shared.PathExists(filepath.Join(c.Path(), "templates")) {
- return SyncResponse(true, templates)
+ return response.SyncResponse(true, templates)
}
// List templates
templatesPath := filepath.Join(c.Path(), "templates")
filesInfo, err := ioutil.ReadDir(templatesPath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, info := range filesInfo {
@@ -186,74 +187,74 @@ func containerMetadataTemplatesGet(d *Daemon, r *http.Request) Response {
}
}
- return SyncResponse(true, templates)
+ return response.SyncResponse(true, templates)
}
// Check if the template exists
templatePath, err := getContainerTemplatePath(c, templateName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if !shared.PathExists(templatePath) {
- return NotFound(fmt.Errorf("Template '%s' not found", templateName))
+ return response.NotFound(fmt.Errorf("Template '%s' not found", templateName))
}
// Create a temporary file with the template content (since the container
// storage might not be available when the file is read from FileResponse)
template, err := os.Open(templatePath)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
defer template.Close()
tempfile, err := ioutil.TempFile("", "lxd_template")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
defer tempfile.Close()
_, err = io.Copy(tempfile, template)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- files := make([]fileResponseEntry, 1)
- files[0].identifier = templateName
- files[0].path = tempfile.Name()
- files[0].filename = templateName
- return FileResponse(r, files, nil, true)
+ files := make([]response.FileResponseEntry, 1)
+ files[0].Identifier = templateName
+ files[0].Path = tempfile.Name()
+ files[0].Filename = templateName
+ return response.FileResponse(r, files, nil, true)
}
// Add a container template file
-func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
// Load the container
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Start the storage if needed
ourStart, err := c.StorageStart()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourStart {
defer c.StorageStop()
@@ -262,46 +263,46 @@ func containerMetadataTemplatesPostPut(d *Daemon, r *http.Request) Response {
// Look at the request
templateName := r.FormValue("path")
if templateName == "" {
- return BadRequest(fmt.Errorf("missing path argument"))
+ return response.BadRequest(fmt.Errorf("missing path argument"))
}
if !shared.PathExists(filepath.Join(c.Path(), "templates")) {
err := os.MkdirAll(filepath.Join(c.Path(), "templates"), 0711)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
// Check if the template already exists
templatePath, err := getContainerTemplatePath(c, templateName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if r.Method == "POST" && shared.PathExists(templatePath) {
- return BadRequest(fmt.Errorf("Template already exists"))
+ return response.BadRequest(fmt.Errorf("Template already exists"))
}
// Write the new template
template, err := os.OpenFile(templatePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
defer template.Close()
_, err = io.Copy(template, r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Delete a container template
-func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
+func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -309,24 +310,24 @@ func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
// Load the container
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Start the storage if needed
ourStart, err := c.StorageStart()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourStart {
defer c.StorageStop()
@@ -335,25 +336,25 @@ func containerMetadataTemplatesDelete(d *Daemon, r *http.Request) Response {
// Look at the request
templateName := r.FormValue("path")
if templateName == "" {
- return BadRequest(fmt.Errorf("missing path argument"))
+ return response.BadRequest(fmt.Errorf("missing path argument"))
}
templatePath, err := getContainerTemplatePath(c, templateName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if !shared.PathExists(templatePath) {
- return NotFound(fmt.Errorf("Template '%s' not found", templateName))
+ return response.NotFound(fmt.Errorf("Template '%s' not found", templateName))
}
// Delete the template
err = os.Remove(templatePath)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Return the full path of a container template.
diff --git a/lxd/container_patch.go b/lxd/container_patch.go
index 16b8bf3743..44824bcc5a 100644
--- a/lxd/container_patch.go
+++ b/lxd/container_patch.go
@@ -11,16 +11,17 @@ import (
"github.com/lxc/lxd/lxd/db"
deviceConfig "github.com/lxc/lxd/lxd/device/config"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/osarch"
)
-func containerPatch(d *Daemon, r *http.Request) Response {
+func containerPatch(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -29,29 +30,29 @@ func containerPatch(d *Daemon, r *http.Request) Response {
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
// Validate the ETag
etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -59,16 +60,16 @@ func containerPatch(d *Daemon, r *http.Request) Response {
reqRaw := shared.Jmap{}
if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
req := api.InstancePut{}
if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Restore != "" {
- return BadRequest(fmt.Errorf("Can't call PATCH in restore mode"))
+ return response.BadRequest(fmt.Errorf("Can't call PATCH in restore mode"))
}
// Check if architecture was passed
@@ -131,8 +132,8 @@ func containerPatch(d *Daemon, r *http.Request) Response {
err = c.Update(args, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
diff --git a/lxd/container_post.go b/lxd/container_post.go
index da7e960938..50a137cea3 100644
--- a/lxd/container_post.go
+++ b/lxd/container_post.go
@@ -15,6 +15,8 @@ import (
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -27,10 +29,10 @@ var internalClusterContainerMovedCmd = APIEndpoint{
Post: APIEndpointAction{Handler: internalClusterContainerMovedPost},
}
-func containerPost(d *Daemon, r *http.Request) Response {
+func containerPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -94,12 +96,12 @@ func containerPost(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
if targetNode != "" && targetNodeOffline {
- return BadRequest(fmt.Errorf("Target node is offline"))
+ return response.BadRequest(fmt.Errorf("Target node is offline"))
}
var inst Instance
@@ -127,23 +129,23 @@ func containerPost(d *Daemon, r *http.Request) Response {
// and we'll either forward the request or load the container.
if targetNode == "" || !sourceNodeOffline {
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
inst, err = instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -152,13 +154,13 @@ func containerPost(d *Daemon, r *http.Request) Response {
reqRaw := shared.Jmap{}
err = json.NewDecoder(rdr1).Decode(&reqRaw)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
req := api.InstancePost{}
err = json.NewDecoder(rdr2).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check if stateful (backward compatibility)
@@ -172,19 +174,19 @@ func containerPost(d *Daemon, r *http.Request) Response {
if targetNode != "" {
// Check whether the container is running.
if inst != nil && inst.IsRunning() {
- return BadRequest(fmt.Errorf("Container is running"))
+ return response.BadRequest(fmt.Errorf("Container is running"))
}
// Check if we are migrating a ceph-based container.
poolName, err := d.cluster.ContainerPool(project, name)
if err != nil {
err = errors.Wrap(err, "Failed to fetch container's pool name")
- return SmartError(err)
+ return response.SmartError(err)
}
_, pool, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
err = errors.Wrap(err, "Failed to fetch container's pool info")
- return SmartError(err)
+ return response.SmartError(err)
}
if pool.Driver == "ceph" {
return containerPostClusteringMigrateWithCeph(d, inst, project, name, req.Name, targetNode, instanceType)
@@ -196,7 +198,7 @@ func containerPost(d *Daemon, r *http.Request) Response {
// ceph-based.
if sourceNodeOffline {
err := fmt.Errorf("The cluster member hosting the container is offline")
- return SmartError(err)
+ return response.SmartError(err)
}
return containerPostClusteringMigrate(d, inst, name, req.Name, targetNode)
@@ -205,13 +207,13 @@ func containerPost(d *Daemon, r *http.Request) Response {
instanceOnly := req.InstanceOnly || req.ContainerOnly
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance is not container type"))
+ return response.SmartError(fmt.Errorf("Instance is not container type"))
}
c := inst.(container)
ws, err := NewMigrationSource(c, stateful, instanceOnly)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
resources := map[string][]string{}
@@ -221,49 +223,49 @@ func containerPost(d *Daemon, r *http.Request) Response {
// Push mode
err := ws.ConnectContainerTarget(*req.Target)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerMigrate, resources, nil, ws.Do, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, ws.Do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Pull mode
- op, err := operationCreate(d.cluster, project, operationClassWebsocket, db.OperationContainerMigrate, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationContainerMigrate, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Check that the name isn't already in use
id, _ := d.cluster.ContainerID(project, req.Name)
if id > 0 {
- return Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
+ return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
}
- run := func(*operation) error {
+ run := func(*operations.Operation) error {
return inst.Rename(req.Name)
}
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerRename, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerRename, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Move a non-ceph container to another cluster node.
-func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, newNode string) Response {
+func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, newNode string) response.Response {
cert := d.endpoints.NetworkCert()
var sourceAddress string
@@ -290,10 +292,10 @@ func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, new
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- run := func(*operation) error {
+ run := func(*operations.Operation) error {
// Connect to the source host, i.e. ourselves (the node the container is running on).
source, err := cluster.Connect(sourceAddress, cert, false)
if err != nil {
@@ -397,17 +399,17 @@ func containerPostClusteringMigrate(d *Daemon, c Instance, oldName, newName, new
resources := map[string][]string{}
resources["containers"] = []string{oldName}
- op, err := operationCreate(d.cluster, c.Project(), operationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, c.Project(), operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Special case migrating a container backed by ceph across two cluster nodes.
-func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldName, newName, newNode string, instanceType instance.Type) Response {
- run := func(*operation) error {
+func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldName, newName, newNode string, instanceType instance.Type) response.Response {
+ run := func(*operations.Operation) error {
// If source node is online (i.e. we're serving the request on
// it, and c != nil), let's unmap the RBD volume locally
if c != nil {
@@ -505,26 +507,26 @@ func containerPostClusteringMigrateWithCeph(d *Daemon, c Instance, project, oldN
resources := map[string][]string{}
resources["containers"] = []string{oldName}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerMigrate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Notification that a container was moved.
//
// At the moment it's used for ceph-based containers, where the target node needs
// to create the appropriate mount points.
-func internalClusterContainerMovedPost(d *Daemon, r *http.Request) Response {
+func internalClusterContainerMovedPost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
containerName := mux.Vars(r)["name"]
err := containerPostCreateContainerMountPoint(d, project, containerName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Used after to create the appropriate mounts point after a container has been
diff --git a/lxd/container_put.go b/lxd/container_put.go
index 93eb4132ca..efc0c27a3b 100644
--- a/lxd/container_put.go
+++ b/lxd/container_put.go
@@ -9,6 +9,8 @@ import (
"github.com/lxc/lxd/lxd/db"
deviceConfig "github.com/lxc/lxd/lxd/device/config"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -20,10 +22,10 @@ import (
* Update configuration, or, if 'restore:snapshot-name' is present, restore
* the named snapshot
*/
-func containerPut(d *Daemon, r *http.Request) Response {
+func containerPut(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -32,29 +34,29 @@ func containerPut(d *Daemon, r *http.Request) Response {
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
// Validate the ETag
etag := []interface{}{c.Architecture(), c.LocalConfig(), c.LocalDevices(), c.IsEphemeral(), c.Profiles()}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
configRaw := api.InstancePut{}
if err := json.NewDecoder(r.Body).Decode(&configRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
architecture, err := osarch.ArchitectureId(configRaw.Architecture)
@@ -62,11 +64,11 @@ func containerPut(d *Daemon, r *http.Request) Response {
architecture = 0
}
- var do func(*operation) error
+ var do func(*operations.Operation) error
var opType db.OperationType
if configRaw.Restore == "" {
// Update container configuration
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
args := db.ContainerArgs{
Architecture: architecture,
Config: configRaw.Config,
@@ -89,7 +91,7 @@ func containerPut(d *Daemon, r *http.Request) Response {
opType = db.OperationContainerUpdate
} else {
// Snapshot Restore
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
return containerSnapRestore(d.State(), project, name, configRaw.Restore, configRaw.Stateful)
}
@@ -99,12 +101,12 @@ func containerPut(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, opType, resources, nil, do, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, opType, resources, nil, do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
func containerSnapRestore(s *state.State, project, name, snap string, stateful bool) error {
diff --git a/lxd/container_snapshot.go b/lxd/container_snapshot.go
index 51629b4d51..64c60181c2 100644
--- a/lxd/container_snapshot.go
+++ b/lxd/container_snapshot.go
@@ -14,28 +14,30 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/version"
)
-func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
+func containerSnapshotsGet(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
cname := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, cname, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
recursion := util.IsRecursionRequest(r)
@@ -45,7 +47,7 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
if !recursion {
snaps, err := d.cluster.ContainerGetSnapshots(project, cname)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, snap := range snaps {
@@ -61,12 +63,12 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
} else {
c, err := instanceLoadByProjectAndName(d.State(), project, cname)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
snaps, err := c.Snapshots()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, snap := range snaps {
@@ -80,28 +82,28 @@ func containerSnapshotsGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
-func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
+func containerSnapshotsPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
/*
@@ -112,24 +114,24 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
*/
inst, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
req := api.InstanceSnapshotsPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Name == "" {
req.Name, err = containerDetermineNextSnapshotName(d, inst, "snap%d")
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
// Validate the name
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
}
fullName := name +
@@ -142,11 +144,11 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
} else {
expiry, err = shared.GetSnapshotExpiry(time.Now(), inst.LocalConfig()["snapshots.expiry"])
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
}
- snapshot := func(op *operation) error {
+ snapshot := func(op *operations.Operation) error {
args := db.ContainerArgs{
Project: inst.Project(),
Architecture: inst.Architecture(),
@@ -178,35 +180,35 @@ func containerSnapshotsPost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationSnapshotCreate, resources, nil, snapshot, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
+func containerSnapshotHandler(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
containerName := mux.Vars(r)["name"]
snapshotName := mux.Vars(r)["snapshotName"]
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, containerName, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
snapshotName, err = url.QueryUnescape(snapshotName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
inst, err := instanceLoadByProjectAndName(
d.State(),
@@ -214,11 +216,11 @@ func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
shared.SnapshotDelimiter+
snapshotName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if inst.Type() != instance.TypeContainer {
- return SmartError(fmt.Errorf("Instance not container type"))
+ return response.SmartError(fmt.Errorf("Instance not container type"))
}
sc := inst.(container)
@@ -233,48 +235,48 @@ func containerSnapshotHandler(d *Daemon, r *http.Request) Response {
case "PUT":
return snapshotPut(d, r, sc, snapshotName)
default:
- return NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+ return response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
}
}
-func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response {
+func snapshotPut(d *Daemon, r *http.Request, sc container, name string) response.Response {
// Validate the ETag
etag := []interface{}{sc.ExpiryDate()}
err := util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
rj := shared.Jmap{}
err = json.NewDecoder(r.Body).Decode(&rj)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- var do func(op *operation) error
+ var do func(op *operations.Operation) error
_, err = rj.GetString("expires_at")
if err != nil {
// Skip updating the snapshot since the requested key wasn't provided
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
return nil
}
} else {
body, err := json.Marshal(rj)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
configRaw := api.InstanceSnapshotPut{}
err = json.Unmarshal(body, &configRaw)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Update container configuration
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
args := db.ContainerArgs{
Architecture: sc.Architecture(),
Config: sc.LocalConfig(),
@@ -302,35 +304,35 @@ func snapshotPut(d *Daemon, r *http.Request, sc container, name string) Response
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, opType, resources, nil,
+ op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, opType, resources, nil,
do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func snapshotGet(sc container, name string) Response {
+func snapshotGet(sc container, name string) response.Response {
render, _, err := sc.Render()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, render.(*api.InstanceSnapshot))
+ return response.SyncResponse(true, render.(*api.InstanceSnapshot))
}
-func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) Response {
+func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string) response.Response {
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
raw := shared.Jmap{}
if err := json.NewDecoder(rdr1).Decode(&raw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
migration, err := raw.GetBool("migration")
@@ -341,24 +343,24 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
req := api.InstancePost{}
err = json.NewDecoder(rdr2).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
reqNew := api.InstanceSnapshotPost{}
err = json.NewDecoder(rdr3).Decode(&reqNew)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if reqNew.Name == "" {
- return BadRequest(fmt.Errorf(`A new name for the ` +
+ return response.BadRequest(fmt.Errorf(`A new name for the ` +
`container must be provided`))
}
if reqNew.Live {
sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(containerName)
if sourceName != reqNew.Name {
- return BadRequest(fmt.Errorf(`Copying `+
+ return response.BadRequest(fmt.Errorf(`Copying `+
`stateful containers requires that `+
`source "%s" and `+`target "%s" name `+
`be identical`, sourceName, reqNew.Name))
@@ -367,7 +369,7 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
ws, err := NewMigrationSource(sc, reqNew.Live, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
resources := map[string][]string{}
@@ -377,34 +379,34 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
// Push mode
err := ws.ConnectContainerTarget(*req.Target)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotTransfer, resources, nil, ws.Do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Pull mode
- op, err := operationCreate(d.cluster, sc.Project(), operationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
+ op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassWebsocket, db.OperationSnapshotTransfer, resources, ws.Metadata(), ws.Do, nil, ws.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
newName, err := raw.GetString("name")
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Validate the name
if strings.Contains(newName, "/") {
- return BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Snapshot names may not contain slashes"))
}
fullName := containerName + shared.SnapshotDelimiter + newName
@@ -412,36 +414,36 @@ func snapshotPost(d *Daemon, r *http.Request, sc container, containerName string
// Check that the name isn't already in use
id, _ := d.cluster.InstanceSnapshotID(sc.Project(), containerName, newName)
if id > 0 {
- return Conflict(fmt.Errorf("Name '%s' already in use", fullName))
+ return response.Conflict(fmt.Errorf("Name '%s' already in use", fullName))
}
- rename := func(op *operation) error {
+ rename := func(op *operations.Operation) error {
return sc.Rename(fullName)
}
resources := map[string][]string{}
resources["containers"] = []string{containerName}
- op, err := operationCreate(d.cluster, sc.Project(), operationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotRename, resources, nil, rename, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func snapshotDelete(sc container, name string) Response {
- remove := func(op *operation) error {
+func snapshotDelete(sc container, name string) response.Response {
+ remove := func(op *operations.Operation) error {
return sc.Delete()
}
resources := map[string][]string{}
resources["containers"] = []string{sc.Name()}
- op, err := operationCreate(sc.DaemonState().Cluster, sc.Project(), operationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
+ op, err := operations.OperationCreate(sc.DaemonState().Cluster, sc.Project(), operations.OperationClassTask, db.OperationSnapshotDelete, resources, nil, remove, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
diff --git a/lxd/container_state.go b/lxd/container_state.go
index b5198977ca..9995803153 100644
--- a/lxd/container_state.go
+++ b/lxd/container_state.go
@@ -9,56 +9,58 @@ import (
"github.com/gorilla/mux"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
)
-func containerState(d *Daemon, r *http.Request) Response {
+func containerState(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
state, err := c.RenderState()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return SyncResponse(true, state)
+ return response.SyncResponse(true, state)
}
-func containerStatePut(d *Daemon, r *http.Request) Response {
+func containerStatePut(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
name := mux.Vars(r)["name"]
// Handle requests targeted to a container on a different node
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
- return response
+ if resp != nil {
+ return resp
}
raw := api.InstanceStatePut{}
@@ -68,7 +70,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
raw.Timeout = -1
if err := json.NewDecoder(r.Body).Decode(&raw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Don't mess with containers while in setup mode
@@ -76,15 +78,15 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
c, err := instanceLoadByProjectAndName(d.State(), project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
var opType db.OperationType
- var do func(*operation) error
+ var do func(*operations.Operation) error
switch shared.ContainerAction(raw.Action) {
case shared.Start:
opType = db.OperationContainerStart
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
if err = c.Start(raw.Stateful); err != nil {
return err
@@ -94,7 +96,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
case shared.Stop:
opType = db.OperationContainerStop
if raw.Stateful {
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
err := c.Stop(raw.Stateful)
if err != nil {
@@ -104,7 +106,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
return nil
}
} else if raw.Timeout == 0 || raw.Force {
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
err = c.Stop(false)
if err != nil {
@@ -114,7 +116,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
return nil
}
} else {
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
if c.IsFrozen() {
err := c.Unfreeze()
@@ -133,7 +135,7 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
}
case shared.Restart:
opType = db.OperationContainerRestart
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
ephemeral := c.IsEphemeral()
@@ -188,35 +190,35 @@ func containerStatePut(d *Daemon, r *http.Request) Response {
}
case shared.Freeze:
if !d.os.CGroupFreezerController {
- return BadRequest(fmt.Errorf("This system doesn't support freezing containers"))
+ return response.BadRequest(fmt.Errorf("This system doesn't support freezing containers"))
}
opType = db.OperationContainerFreeze
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
return c.Freeze()
}
case shared.Unfreeze:
if !d.os.CGroupFreezerController {
- return BadRequest(fmt.Errorf("This system doesn't support unfreezing containers"))
+ return response.BadRequest(fmt.Errorf("This system doesn't support unfreezing containers"))
}
opType = db.OperationContainerUnfreeze
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
c.SetOperation(op)
return c.Unfreeze()
}
default:
- return BadRequest(fmt.Errorf("unknown action %s", raw.Action))
+ return response.BadRequest(fmt.Errorf("unknown action %s", raw.Action))
}
resources := map[string][]string{}
resources["containers"] = []string{name}
- op, err := operationCreate(d.cluster, project, operationClassTask, opType, resources, nil, do, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, opType, resources, nil, do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
diff --git a/lxd/containers_get.go b/lxd/containers_get.go
index 2d4d147920..0a315d57d6 100644
--- a/lxd/containers_get.go
+++ b/lxd/containers_get.go
@@ -16,6 +16,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/db/query"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/logger"
@@ -39,15 +40,15 @@ func urlInstanceTypeDetect(r *http.Request) (instance.Type, error) {
return instance.TypeAny, nil
}
-func containersGet(d *Daemon, r *http.Request) Response {
+func containersGet(d *Daemon, r *http.Request) response.Response {
for i := 0; i < 100; i++ {
result, err := doContainersGet(d, r)
if err == nil {
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
if !query.IsRetriableError(err) {
logger.Debugf("DBERR: containersGet: error %q", err)
- return SmartError(err)
+ return response.SmartError(err)
}
// 100 ms may seem drastic, but we really don't want to thrash
// perhaps we should use a random amount
@@ -56,7 +57,7 @@ func containersGet(d *Daemon, r *http.Request) Response {
logger.Debugf("DBERR: containersGet, db is locked")
logger.Debugf(logger.GetStack())
- return InternalError(fmt.Errorf("DB is locked"))
+ return response.InternalError(fmt.Errorf("DB is locked"))
}
func doContainersGet(d *Daemon, r *http.Request) (interface{}, error) {
diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index 822af02534..b10c77e175 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -22,6 +22,8 @@ import (
"github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/ioprogress"
@@ -30,7 +32,7 @@ import (
"github.com/lxc/lxd/shared/osarch"
)
-func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromImage(d *Daemon, project string, req *api.InstancesPost) response.Response {
var hash string
var err error
@@ -42,19 +44,19 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
} else {
_, alias, err := d.cluster.ImageAliasGet(project, req.Source.Alias, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
hash = alias.Target
}
} else if req.Source.Properties != nil {
if req.Source.Server != "" {
- return BadRequest(fmt.Errorf("Property match is only supported for local images"))
+ return response.BadRequest(fmt.Errorf("Property match is only supported for local images"))
}
hashes, err := d.cluster.ImagesGet(project, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
var image *api.Image
@@ -85,20 +87,20 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
}
if image == nil {
- return BadRequest(fmt.Errorf("No matching image could be found"))
+ return response.BadRequest(fmt.Errorf("No matching image could be found"))
}
hash = image.Fingerprint
} else {
- return BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image"))
+ return response.BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image"))
}
dbType, err := instance.New(string(req.Type))
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
args := db.ContainerArgs{
Project: project,
Config: req.Config,
@@ -146,18 +148,18 @@ func createFromImage(d *Daemon, project string, req *api.InstancesPost) Response
resources := map[string][]string{}
resources["containers"] = []string{req.Name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromNone(d *Daemon, project string, req *api.InstancesPost) response.Response {
dbType, err := instance.New(string(req.Type))
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
args := db.ContainerArgs{
@@ -174,12 +176,12 @@ func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response
if req.Architecture != "" {
architecture, err := osarch.ArchitectureId(req.Architecture)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
args.Architecture = architecture
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
_, err := containerCreateAsEmpty(d, args)
return err
}
@@ -187,18 +189,18 @@ func createFromNone(d *Daemon, project string, req *api.InstancesPost) Response
resources := map[string][]string{}
resources["containers"] = []string{req.Name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromMigration(d *Daemon, project string, req *api.InstancesPost) response.Response {
// Validate migration mode
if req.Source.Mode != "pull" && req.Source.Mode != "push" {
- return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
+ return response.NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
}
var c container
@@ -206,7 +208,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
// Parse the architecture name
architecture, err := osarch.ArchitectureId(req.Architecture)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Pre-fill default profile
@@ -216,7 +218,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
dbType, err := instance.New(string(req.Type))
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Prepare the container creation request
@@ -237,12 +239,12 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
// Early profile validation
profiles, err := d.cluster.Profiles(project)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, profile := range args.Profiles {
if !shared.StringInSlice(profile, profiles) {
- return BadRequest(fmt.Errorf("Requested profile '%s' doesn't exist", profile))
+ return response.BadRequest(fmt.Errorf("Requested profile '%s' doesn't exist", profile))
}
}
@@ -252,7 +254,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
}
if storagePool == "" {
- return BadRequest(fmt.Errorf("Can't find a storage pool for the container to use"))
+ return response.BadRequest(fmt.Errorf("Can't find a storage pool for the container to use"))
}
if localRootDiskDeviceKey == "" && storagePoolProfile == "" {
@@ -289,11 +291,11 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
if err != nil {
req.Source.Refresh = false
} else if inst.IsRunning() {
- return BadRequest(fmt.Errorf("Cannot refresh a running container"))
+ return response.BadRequest(fmt.Errorf("Cannot refresh a running container"))
}
if inst.Type() != instance.TypeContainer {
- return BadRequest(fmt.Errorf("Instance type not container"))
+ return response.BadRequest(fmt.Errorf("Instance type not container"))
}
c = inst.(container)
@@ -317,40 +319,40 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
if err != nil {
c, err = containerCreateAsEmpty(d, args)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
} else {
// Retrieve the future storage pool
cM, err := containerLXCLoad(d.State(), args, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
_, rootDiskDevice, err := shared.GetRootDiskDevice(cM.ExpandedDevices().CloneNative())
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if rootDiskDevice["pool"] == "" {
- return BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
+ return response.BadRequest(fmt.Errorf("The container's root device is missing the pool property"))
}
storagePool = rootDiskDevice["pool"]
ps, err := storagePoolInit(d.State(), storagePool)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if ps.MigrationType() == migration.MigrationFSType_RSYNC {
c, err = containerCreateFromImage(d, args, req.Source.BaseImage, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
} else {
c, err = containerCreateAsEmpty(d, args)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
}
@@ -363,7 +365,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
if !req.Source.Refresh {
c.Delete()
}
- return InternalError(fmt.Errorf("Invalid certificate"))
+ return response.InternalError(fmt.Errorf("Invalid certificate"))
}
cert, err = x509.ParseCertificate(certBlock.Bytes)
@@ -371,7 +373,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
if !req.Source.Refresh {
c.Delete()
}
- return InternalError(err)
+ return response.InternalError(err)
}
}
@@ -380,7 +382,7 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
if !req.Source.Refresh {
c.Delete()
}
- return InternalError(err)
+ return response.InternalError(err)
}
push := false
@@ -405,10 +407,10 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
sink, err := NewMigrationSink(&migrationArgs)
if err != nil {
c.Delete()
- return InternalError(err)
+ return response.InternalError(err)
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
// And finally run the migration.
err = sink.Do(op)
if err != nil {
@@ -433,25 +435,25 @@ func createFromMigration(d *Daemon, project string, req *api.InstancesPost) Resp
resources := map[string][]string{}
resources["containers"] = []string{req.Name}
- var op *operation
+ var op *operations.Operation
if push {
- op, err = operationCreate(d.cluster, project, operationClassWebsocket, db.OperationContainerCreate, resources, sink.Metadata(), run, nil, sink.Connect)
+ op, err = operations.OperationCreate(d.cluster, project, operations.OperationClassWebsocket, db.OperationContainerCreate, resources, sink.Metadata(), run, nil, sink.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
} else {
- op, err = operationCreate(d.cluster, project, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+ op, err = operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response {
+func createFromCopy(d *Daemon, project string, req *api.InstancesPost) response.Response {
if req.Source.Source == "" {
- return BadRequest(fmt.Errorf("must specify a source container"))
+ return response.BadRequest(fmt.Errorf("must specify a source container"))
}
sourceProject := req.Source.Project
@@ -462,13 +464,13 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
source, err := instanceLoadByProjectAndName(d.State(), sourceProject, req.Source.Source)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Check if we need to redirect to migration
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// When clustered, use the node name, otherwise use the hostname.
@@ -479,7 +481,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if serverName != source.Location() {
@@ -500,7 +502,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
_, pool, err := d.cluster.StoragePoolGet(sourcePoolName)
if err != nil {
err = errors.Wrap(err, "Failed to fetch container's pool info")
- return SmartError(err)
+ return response.SmartError(err)
}
if pool.Driver != "ceph" {
@@ -555,7 +557,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
if req.Stateful {
sourceName, _, _ := shared.ContainerGetParentAndSnapshotName(source.Name())
if sourceName != req.Name {
- return BadRequest(fmt.Errorf(`Copying stateful `+
+ return response.BadRequest(fmt.Errorf(`Copying stateful `+
`containers requires that source "%s" and `+
`target "%s" name be identical`, sourceName,
req.Name))
@@ -569,17 +571,17 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
if err != nil {
req.Source.Refresh = false
} else if c.IsRunning() {
- return BadRequest(fmt.Errorf("Cannot refresh a running container"))
+ return response.BadRequest(fmt.Errorf("Cannot refresh a running container"))
}
}
dbType, err := instance.New(string(req.Type))
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if dbType != instance.TypeAny && dbType != source.Type() {
- return BadRequest(fmt.Errorf("Instance type should not be specified or should match source type"))
+ return response.BadRequest(fmt.Errorf("Instance type should not be specified or should match source type"))
}
args := db.ContainerArgs{
@@ -596,7 +598,7 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
Stateful: req.Stateful,
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
instanceOnly := req.Source.InstanceOnly || req.Source.ContainerOnly
_, err := containerCreateAsCopy(d.State(), args, source, instanceOnly, req.Source.Refresh)
if err != nil {
@@ -608,26 +610,26 @@ func createFromCopy(d *Daemon, project string, req *api.InstancesPost) Response
resources := map[string][]string{}
resources["containers"] = []string{req.Name, req.Source.Source}
- op, err := operationCreate(d.cluster, targetProject, operationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, targetProject, operations.OperationClassTask, db.OperationContainerCreate, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Response {
+func createFromBackup(d *Daemon, project string, data io.Reader, pool string) response.Response {
// Write the data to a temp file
f, err := ioutil.TempFile("", "lxd_backup_")
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
defer os.Remove(f.Name())
_, err = io.Copy(f, data)
if err != nil {
f.Close()
- return InternalError(err)
+ return response.InternalError(err)
}
// Parse the backup information
@@ -635,7 +637,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
bInfo, err := backupGetInfo(f)
if err != nil {
f.Close()
- return BadRequest(err)
+ return response.BadRequest(err)
}
bInfo.Project = project
@@ -644,7 +646,7 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
bInfo.Pool = pool
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
defer f.Close()
// Dump tarball to storage
@@ -692,16 +694,16 @@ func createFromBackup(d *Daemon, project string, data io.Reader, pool string) Re
resources := map[string][]string{}
resources["containers"] = []string{bInfo.Name}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationBackupRestore,
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationBackupRestore,
resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func containersPost(d *Daemon, r *http.Request) Response {
+func containersPost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
logger.Debugf("Responding to container create")
@@ -713,7 +715,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
// Parse the request
req := api.InstancesPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
targetNode := queryParam(r, "target")
@@ -729,20 +731,20 @@ func containersPost(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
if targetNode != "" {
address, err := cluster.ResolveTarget(d.cluster, targetNode)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if address != "" {
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
client = client.UseProject(project)
@@ -751,18 +753,18 @@ func containersPost(d *Daemon, r *http.Request) Response {
logger.Debugf("Forward instance post request to %s", address)
op, err := client.CreateInstance(req)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
opAPI := op.Get()
- return ForwardedOperationResponse(project, &opAPI)
+ return operations.ForwardedOperationResponse(project, &opAPI)
}
}
// If no storage pool is found, error out.
pools, err := d.cluster.StoragePools()
if err != nil || len(pools) == 0 {
- return BadRequest(fmt.Errorf("No storage pool found. Please create a new storage pool"))
+ return response.BadRequest(fmt.Errorf("No storage pool found. Please create a new storage pool"))
}
if req.Name == "" {
@@ -773,7 +775,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
i := 0
@@ -785,7 +787,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
}
if i > 100 {
- return InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
+ return response.InternalError(fmt.Errorf("couldn't generate a new unique name after 100 tries"))
}
}
logger.Debugf("No name provided, creating %s", req.Name)
@@ -802,7 +804,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
if req.InstanceType != "" {
conf, err := instanceParseType(req.InstanceType)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
for k, v := range conf {
@@ -813,7 +815,7 @@ func containersPost(d *Daemon, r *http.Request) Response {
}
if strings.Contains(req.Name, shared.SnapshotDelimiter) {
- return BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter))
+ return response.BadRequest(fmt.Errorf("Invalid container name: '%s' is reserved for snapshots", shared.SnapshotDelimiter))
}
switch req.Source.Type {
@@ -826,11 +828,11 @@ func containersPost(d *Daemon, r *http.Request) Response {
case "copy":
return createFromCopy(d, project, &req)
default:
- return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+ return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
}
}
-func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, Response) {
+func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost) (string, string, string, map[string]string, response.Response) {
// Grab the container's root device if one is specified
storagePool := ""
storagePoolProfile := ""
@@ -856,7 +858,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
for _, pName := range req.Profiles {
_, p, err := d.cluster.ProfileGet(project, pName)
if err != nil {
- return "", "", "", nil, SmartError(err)
+ return "", "", "", nil, response.SmartError(err)
}
k, v, _ := shared.GetRootDiskDevice(p.Devices)
@@ -874,9 +876,9 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
pools, err := d.cluster.StoragePools()
if err != nil {
if err == db.ErrNoSuchObject {
- return "", "", "", nil, BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
+ return "", "", "", nil, response.BadRequest(fmt.Errorf("This LXD instance does not have any storage pools configured"))
}
- return "", "", "", nil, SmartError(err)
+ return "", "", "", nil, response.SmartError(err)
}
if len(pools) == 1 {
@@ -887,7 +889,7 @@ func containerFindStoragePool(d *Daemon, project string, req *api.InstancesPost)
return storagePool, storagePoolProfile, localRootDiskDeviceKey, localRootDiskDevice, nil
}
-func clusterCopyContainerInternal(d *Daemon, source Instance, project string, req *api.InstancesPost) Response {
+func clusterCopyContainerInternal(d *Daemon, source Instance, project string, req *api.InstancesPost) response.Response {
name := req.Source.Source
// Locate the source of the container
@@ -904,17 +906,17 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if nodeAddress == "" {
- return BadRequest(fmt.Errorf("The container source is currently offline"))
+ return response.BadRequest(fmt.Errorf("The container source is currently offline"))
}
// Connect to the container source
client, err := cluster.Connect(nodeAddress, d.endpoints.NetworkCert(), false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
client = client.UseProject(source.Project())
@@ -932,7 +934,7 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
op, err := client.MigrateInstanceSnapshot(cName, sName, pullReq)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
opAPI = op.Get()
@@ -948,7 +950,7 @@ func clusterCopyContainerInternal(d *Daemon, source Instance, project string, re
op, err := client.MigrateInstance(req.Source.Source, pullReq)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
opAPI = op.Get()
diff --git a/lxd/daemon.go b/lxd/daemon.go
index 2d2d433ee5..b483125ae8 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -36,6 +36,7 @@ import (
"github.com/lxc/lxd/lxd/maas"
"github.com/lxc/lxd/lxd/node"
"github.com/lxc/lxd/lxd/rbac"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/sys"
"github.com/lxc/lxd/lxd/task"
@@ -174,22 +175,22 @@ type APIEndpointAlias struct {
// APIEndpointAction represents an action on an API endpoint.
type APIEndpointAction struct {
- Handler func(d *Daemon, r *http.Request) Response
- AccessHandler func(d *Daemon, r *http.Request) Response
+ Handler func(d *Daemon, r *http.Request) response.Response
+ AccessHandler func(d *Daemon, r *http.Request) response.Response
AllowUntrusted bool
}
// AllowAuthenticated is a AccessHandler which allows all requests
-func AllowAuthenticated(d *Daemon, r *http.Request) Response {
- return EmptySyncResponse
+func AllowAuthenticated(d *Daemon, r *http.Request) response.Response {
+ return response.EmptySyncResponse
}
// AllowProjectPermission is a wrapper to check access against the project, its features and RBAC permission
-func AllowProjectPermission(feature string, permission string) func(d *Daemon, r *http.Request) Response {
- return func(d *Daemon, r *http.Request) Response {
+func AllowProjectPermission(feature string, permission string) func(d *Daemon, r *http.Request) response.Response {
+ return func(d *Daemon, r *http.Request) response.Response {
// Shortcut for speed
if d.userIsAdmin(r) {
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Get the project
@@ -197,10 +198,10 @@ func AllowProjectPermission(feature string, permission string) func(d *Daemon, r
// Validate whether the user has the needed permission
if !d.userHasPermission(r, project, permission) {
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
}
@@ -302,7 +303,7 @@ func writeMacaroonsRequiredResponse(b *identchecker.Bakery, r *http.Request, w h
m, err := b.Oven.NewMacaroon(
ctx, httpbakery.RequestVersion(r), caveats, derr.Ops...)
if err != nil {
- resp := errorResponse{http.StatusInternalServerError, err.Error()}
+ resp := response.ErrorResponse(http.StatusInternalServerError, err.Error())
resp.Render(w)
return
}
@@ -361,7 +362,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
select {
case <-d.setupChan:
default:
- response := Unavailable(fmt.Errorf("LXD daemon setup in progress"))
+ response := response.Unavailable(fmt.Errorf("LXD daemon setup in progress"))
response.Render(w)
return
}
@@ -372,7 +373,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
// If not a macaroon discharge request, return the error
_, ok := err.(*bakery.DischargeRequiredError)
if !ok {
- InternalError(err).Render(w)
+ response.InternalError(err).Render(w)
return
}
}
@@ -382,7 +383,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
// Except for the initial cluster accept request (done over trusted TLS)
if !trusted || c.Path != "cluster/accept" || protocol != "tls" {
logger.Warn("Rejecting remote internal API request", log.Ctx{"ip": r.RemoteAddr})
- Forbidden(nil).Render(w)
+ response.Forbidden(nil).Render(w)
return
}
}
@@ -398,7 +399,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
return
} else {
logger.Warn("Rejecting request from untrusted client", log.Ctx{"ip": r.RemoteAddr})
- Forbidden(nil).Render(w)
+ response.Forbidden(nil).Render(w)
return
}
@@ -408,7 +409,7 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
captured := &bytes.Buffer{}
multiW := io.MultiWriter(newBody, captured)
if _, err := io.Copy(multiW, r.Body); err != nil {
- InternalError(err).Render(w)
+ response.InternalError(err).Render(w)
return
}
@@ -417,24 +418,24 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
}
// Actually process the request
- var resp Response
- resp = NotImplemented(nil)
+ var resp response.Response
+ resp = response.NotImplemented(nil)
- handleRequest := func(action APIEndpointAction) Response {
+ handleRequest := func(action APIEndpointAction) response.Response {
if action.Handler == nil {
- return NotImplemented(nil)
+ return response.NotImplemented(nil)
}
if action.AccessHandler != nil {
// Defer access control to custom handler
resp := action.AccessHandler(d, r)
- if resp != EmptySyncResponse {
+ if resp != response.EmptySyncResponse {
return resp
}
} else if !action.AllowUntrusted {
// Require admin privileges
if !d.userIsAdmin(r) {
- return Forbidden(nil)
+ return response.Forbidden(nil)
}
}
@@ -453,12 +454,12 @@ func (d *Daemon) createCmd(restAPI *mux.Router, version string, c APIEndpoint) {
case "PATCH":
resp = handleRequest(c.Patch)
default:
- resp = NotFound(fmt.Errorf("Method '%s' not found", r.Method))
+ resp = response.NotFound(fmt.Errorf("Method '%s' not found", r.Method))
}
// Handle errors
if err := resp.Render(w); err != nil {
- err := InternalError(err).Render(w)
+ err := response.InternalError(err).Render(w)
if err != nil {
logger.Errorf("Failed writing error for error, giving up")
}
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index d5a0f1e9a0..51e126d4d1 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -17,6 +17,7 @@ import (
"github.com/lxc/lxd/client"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/sys"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -79,7 +80,7 @@ func imageGetStreamCache(d *Daemon) (map[string]*imageStreamCacheEntry, error) {
}
// ImageDownload resolves the image fingerprint and if not in the database, downloads it
-func (d *Daemon) ImageDownload(op *operation, server string, protocol string, certificate string, secret string, alias string, imageType string, forContainer bool, autoUpdate bool, storagePool string, preferCached bool, project string) (*api.Image, error) {
+func (d *Daemon) ImageDownload(op *operations.Operation, server string, protocol string, certificate string, secret string, alias string, imageType string, forContainer bool, autoUpdate bool, storagePool string, preferCached bool, project string) (*api.Image, error) {
var err error
var ctxMap log.Ctx
@@ -356,7 +357,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
if op == nil {
ctxMap = log.Ctx{"alias": alias, "server": server}
} else {
- ctxMap = log.Ctx{"trigger": op.url, "image": fp, "operation": op.id, "alias": alias, "server": server}
+ ctxMap = log.Ctx{"trigger": op.URL(), "image": fp, "operation": op.ID(), "alias": alias, "server": server}
}
logger.Info("Downloading image", ctxMap)
@@ -379,7 +380,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
return
}
- meta := op.metadata
+ meta := op.Metadata()
if meta == nil {
meta = make(map[string]interface{})
}
@@ -393,7 +394,7 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
var canceler *cancel.Canceler
if op != nil {
canceler = cancel.NewCanceler()
- op.canceler = canceler
+ op.SetCanceler(canceler)
}
if protocol == "lxd" || protocol == "simplestreams" {
diff --git a/lxd/events.go b/lxd/events.go
index 471e56c1c4..215a6646f7 100644
--- a/lxd/events.go
+++ b/lxd/events.go
@@ -6,6 +6,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/events"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/logger"
)
@@ -68,6 +69,6 @@ func eventsSocket(d *Daemon, r *http.Request, w http.ResponseWriter) error {
return nil
}
-func eventsGet(d *Daemon, r *http.Request) Response {
+func eventsGet(d *Daemon, r *http.Request) response.Response {
return &eventsServe{req: r, d: d}
}
diff --git a/lxd/images.go b/lxd/images.go
index 330c6afbd3..c922995d88 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -30,6 +30,8 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/task"
"github.com/lxc/lxd/lxd/util"
@@ -154,7 +156,7 @@ func compressFile(compress string, infile io.Reader, outfile io.Writer) error {
* This function takes a container or snapshot from the local image server and
* exports it as an image.
*/
-func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operation, builddir string) (*api.Image, error) {
+func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operations.Operation, builddir string) (*api.Image, error) {
info := api.Image{}
info.Type = "container"
info.Properties = map[string]string{}
@@ -315,7 +317,7 @@ func imgPostContInfo(d *Daemon, r *http.Request, req api.ImagesPost, op *operati
return &info, nil
}
-func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation, project string) (*api.Image, error) {
+func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, project string) (*api.Image, error) {
var err error
var hash string
@@ -353,7 +355,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operation, project str
return info, nil
}
-func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operation, project string) (*api.Image, error) {
+func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, project string) (*api.Image, error) {
var err error
if req.Source.URL == "" {
@@ -640,10 +642,10 @@ func imageCreateInPool(d *Daemon, info *api.Image, storagePool string) error {
return nil
}
-func imagesPost(d *Daemon, r *http.Request) Response {
+func imagesPost(d *Daemon, r *http.Request) response.Response {
instanceType, err := urlInstanceTypeDetect(r)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
project := projectParam(r)
@@ -651,7 +653,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
// create a directory under which we keep everything while building
builddir, err := ioutil.TempDir(shared.VarPath("images"), "lxd_build_")
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
cleanup := func(path string, fd *os.File) {
@@ -669,13 +671,13 @@ func imagesPost(d *Daemon, r *http.Request) Response {
post, err := ioutil.TempFile(builddir, "lxd_post_")
if err != nil {
cleanup(builddir, nil)
- return InternalError(err)
+ return response.InternalError(err)
}
_, err = io.Copy(post, r.Body)
if err != nil {
cleanup(builddir, post)
- return InternalError(err)
+ return response.InternalError(err)
}
// Is this a container request?
@@ -688,7 +690,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
if err != nil {
if r.Header.Get("Content-Type") == "application/json" {
cleanup(builddir, post)
- return BadRequest(err)
+ return response.BadRequest(err)
}
imageUpload = true
@@ -696,7 +698,7 @@ func imagesPost(d *Daemon, r *http.Request) Response {
if !imageUpload && !shared.StringInSlice(req.Source.Type, []string{"container", "snapshot", "image", "url"}) {
cleanup(builddir, post)
- return InternalError(fmt.Errorf("Invalid images JSON"))
+ return response.InternalError(fmt.Errorf("Invalid images JSON"))
}
/* Forward requests for containers on other nodes */
@@ -705,21 +707,21 @@ func imagesPost(d *Daemon, r *http.Request) Response {
if name != "" {
post.Seek(0, 0)
r.Body = post
- response, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
+ resp, err := ForwardedResponseIfContainerIsRemote(d, r, project, name, instanceType)
if err != nil {
cleanup(builddir, post)
- return SmartError(err)
+ return response.SmartError(err)
}
- if response != nil {
+ if resp != nil {
cleanup(builddir, nil)
- return response
+ return resp
}
}
}
// Begin background operation
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
var err error
var info *api.Image
@@ -785,13 +787,13 @@ func imagesPost(d *Daemon, r *http.Request) Response {
return nil
}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageDownload, nil, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageDownload, nil, nil, run, nil, nil)
if err != nil {
cleanup(builddir, post)
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
func getImageMetadata(fname string) (*api.ImageMetadata, string, error) {
@@ -926,24 +928,24 @@ func doImagesGet(d *Daemon, recursion bool, project string, public bool) (interf
return resultMap, nil
}
-func imagesGet(d *Daemon, r *http.Request) Response {
+func imagesGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
- public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+ public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
result, err := doImagesGet(d, util.IsRecursionRequest(r), project, public)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
func autoUpdateImagesTask(d *Daemon) (task.Func, task.Schedule) {
f := func(ctx context.Context) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return autoUpdateImages(ctx, d)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesUpdate, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesUpdate, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start image update operation", log.Ctx{"err": err})
return
@@ -1040,7 +1042,7 @@ func autoUpdateImagesInProject(ctx context.Context, d *Daemon, project string) e
// Update a single image. The operation can be nil, if no progress tracking is needed.
// Returns whether the image has been updated.
-func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image, project string) error {
+func autoUpdateImage(d *Daemon, op *operations.Operation, id int, info *api.Image, project string) error {
fingerprint := info.Fingerprint
_, source, err := d.cluster.ImageSourceGet(id)
if err != nil {
@@ -1167,11 +1169,11 @@ func autoUpdateImage(d *Daemon, op *operation, id int, info *api.Image, project
func pruneExpiredImagesTask(d *Daemon) (task.Func, task.Schedule) {
f := func(ctx context.Context) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return pruneExpiredImages(ctx, d)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesExpire, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesExpire, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start expired image operation", log.Ctx{"err": err})
return
@@ -1220,7 +1222,7 @@ func pruneExpiredImagesTask(d *Daemon) (task.Func, task.Schedule) {
}
func pruneLeftoverImages(d *Daemon) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
// Get all images
images, err := d.cluster.ImagesGet("default", false)
if err != nil {
@@ -1249,7 +1251,7 @@ func pruneLeftoverImages(d *Daemon) {
return nil
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesPruneLeftover, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesPruneLeftover, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start image leftover cleanup operation", log.Ctx{"err": err})
return
@@ -1355,7 +1357,7 @@ func doDeleteImageFromPool(state *state.State, fingerprint string, storagePool s
return nil
}
-func imageDelete(d *Daemon, r *http.Request) Response {
+func imageDelete(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
@@ -1451,7 +1453,7 @@ func imageDelete(d *Daemon, r *http.Request) Response {
return nil
}
- rmimg := func(op *operation) error {
+ rmimg := func(op *operations.Operation) error {
if isClusterNotification(r) {
return deleteFromDisk()
}
@@ -1462,12 +1464,12 @@ func imageDelete(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["images"] = []string{fingerprint}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageDelete, resources, nil, rmimg, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageDelete, resources, nil, rmimg, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Helper to delete an image file from the local images directory.
@@ -1491,22 +1493,24 @@ func imageDeleteFromDisk(fingerprint string) {
}
}
-func doImageGet(db *db.Cluster, project, fingerprint string, public bool) (*api.Image, Response) {
+func doImageGet(db *db.Cluster, project, fingerprint string, public bool) (*api.Image, response.Response) {
_, imgInfo, err := db.ImageGet(project, fingerprint, public, false)
if err != nil {
- return nil, SmartError(err)
+ return nil, response.SmartError(err)
}
return imgInfo, nil
}
func imageValidSecret(fingerprint string, secret string) bool {
- for _, op := range operations {
- if op.resources == nil {
+ for _, op := range operations.Operations() {
+ resources := op.Resources()
+
+ if resources == nil {
continue
}
- opImages, ok := op.resources["images"]
+ opImages, ok := resources["images"]
if !ok {
continue
}
@@ -1515,7 +1519,7 @@ func imageValidSecret(fingerprint string, secret string) bool {
continue
}
- opSecret, ok := op.metadata["secret"]
+ opSecret, ok := op.Metadata()["secret"]
if !ok {
continue
}
@@ -1530,73 +1534,73 @@ func imageValidSecret(fingerprint string, secret string) bool {
return false
}
-func imageGet(d *Daemon, r *http.Request) Response {
+func imageGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
- public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+ public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
secret := r.FormValue("secret")
- info, response := doImageGet(d.cluster, project, fingerprint, false)
- if response != nil {
- return response
+ info, resp := doImageGet(d.cluster, project, fingerprint, false)
+ if resp != nil {
+ return resp
}
if !info.Public && public && !imageValidSecret(info.Fingerprint, secret) {
- return NotFound(fmt.Errorf("Image '%s' not found", info.Fingerprint))
+ return response.NotFound(fmt.Errorf("Image '%s' not found", info.Fingerprint))
}
etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
- return SyncResponseETag(true, info, etag)
+ return response.SyncResponseETag(true, info, etag)
}
-func imagePut(d *Daemon, r *http.Request) Response {
+func imagePut(d *Daemon, r *http.Request) response.Response {
// Get current value
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.ImagePut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func imagePatch(d *Daemon, r *http.Request) Response {
+func imagePatch(d *Daemon, r *http.Request) response.Response {
// Get current value
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
id, info, err := d.cluster.ImageGet(project, fingerprint, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
etag := []interface{}{info.Public, info.AutoUpdate, info.Properties}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -1604,12 +1608,12 @@ func imagePatch(d *Daemon, r *http.Request) Response {
reqRaw := shared.Jmap{}
if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
req := api.ImagePut{}
if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Get AutoUpdate
@@ -1639,53 +1643,53 @@ func imagePatch(d *Daemon, r *http.Request) Response {
err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func imageAliasesPost(d *Daemon, r *http.Request) Response {
+func imageAliasesPost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
req := api.ImageAliasesPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Name == "" || req.Target == "" {
- return BadRequest(fmt.Errorf("name and target are required"))
+ return response.BadRequest(fmt.Errorf("name and target are required"))
}
// This is just to see if the alias name already exists.
_, _, err := d.cluster.ImageAliasGet(project, req.Name, true)
if err != db.ErrNoSuchObject {
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return Conflict(fmt.Errorf("Alias '%s' already exists", req.Name))
+ return response.Conflict(fmt.Errorf("Alias '%s' already exists", req.Name))
}
id, _, err := d.cluster.ImageGet(project, req.Target, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = d.cluster.ImageAliasAdd(project, req.Name, id, req.Description)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
}
-func imageAliasesGet(d *Daemon, r *http.Request) Response {
+func imageAliasesGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
recursion := util.IsRecursionRequest(r)
names, err := d.cluster.ImageAliasesGet(project)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
responseStr := []string{}
responseMap := []api.ImageAliasesEntry{}
@@ -1704,103 +1708,103 @@ func imageAliasesGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, responseStr)
+ return response.SyncResponse(true, responseStr)
}
- return SyncResponse(true, responseMap)
+ return response.SyncResponse(true, responseMap)
}
-func imageAliasGet(d *Daemon, r *http.Request) Response {
+func imageAliasGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
- public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+ public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
_, alias, err := d.cluster.ImageAliasGet(project, name, !public)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseETag(true, alias, alias)
+ return response.SyncResponseETag(true, alias, alias)
}
-func imageAliasDelete(d *Daemon, r *http.Request) Response {
+func imageAliasDelete(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
_, _, err := d.cluster.ImageAliasGet(project, name, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = d.cluster.ImageAliasDelete(project, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func imageAliasPut(d *Daemon, r *http.Request) Response {
+func imageAliasPut(d *Daemon, r *http.Request) response.Response {
// Get current value
project := projectParam(r)
name := mux.Vars(r)["name"]
id, alias, err := d.cluster.ImageAliasGet(project, name, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
err = util.EtagCheck(r, alias)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.ImageAliasesEntryPut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Target == "" {
- return BadRequest(fmt.Errorf("The target field is required"))
+ return response.BadRequest(fmt.Errorf("The target field is required"))
}
imageId, _, err := d.cluster.ImageGet(project, req.Target, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = d.cluster.ImageAliasUpdate(id, imageId, req.Description)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func imageAliasPatch(d *Daemon, r *http.Request) Response {
+func imageAliasPatch(d *Daemon, r *http.Request) response.Response {
// Get current value
project := projectParam(r)
name := mux.Vars(r)["name"]
id, alias, err := d.cluster.ImageAliasGet(project, name, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate ETag
err = util.EtagCheck(r, alias)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := shared.Jmap{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
_, ok := req["target"]
if ok {
target, err := req.GetString("target")
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
alias.Target = target
@@ -1810,7 +1814,7 @@ func imageAliasPatch(d *Daemon, r *http.Request) Response {
if ok {
description, err := req.GetString("description")
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
alias.Description = description
@@ -1818,50 +1822,50 @@ func imageAliasPatch(d *Daemon, r *http.Request) Response {
imageId, _, err := d.cluster.ImageGet(project, alias.Target, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = d.cluster.ImageAliasUpdate(id, imageId, alias.Description)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func imageAliasPost(d *Daemon, r *http.Request) Response {
+func imageAliasPost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
req := api.ImageAliasesEntryPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the name isn't already in use
id, _, _ := d.cluster.ImageAliasGet(project, req.Name, true)
if id > 0 {
- return Conflict(fmt.Errorf("Alias '%s' already in use", req.Name))
+ return response.Conflict(fmt.Errorf("Alias '%s' already in use", req.Name))
}
id, _, err := d.cluster.ImageAliasGet(project, name, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = d.cluster.ImageAliasRename(id, req.Name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/images/aliases/%s", version.APIVersion, req.Name))
}
-func imageExport(d *Daemon, r *http.Request) Response {
+func imageExport(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
- public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != EmptySyncResponse
+ public := d.checkTrustedClient(r) != nil || AllowProjectPermission("images", "view")(d, r) != response.EmptySyncResponse
secret := r.FormValue("secret")
var imgInfo *api.Image
@@ -1870,36 +1874,36 @@ func imageExport(d *Daemon, r *http.Request) Response {
// /dev/lxd API requires exact match
_, imgInfo, err = d.cluster.ImageGet(project, fingerprint, false, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if !imgInfo.Public && !imgInfo.Cached {
- return NotFound(fmt.Errorf("Image '%s' not found", fingerprint))
+ return response.NotFound(fmt.Errorf("Image '%s' not found", fingerprint))
}
} else {
_, imgInfo, err = d.cluster.ImageGet(project, fingerprint, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if !imgInfo.Public && public && !imageValidSecret(imgInfo.Fingerprint, secret) {
- return NotFound(fmt.Errorf("Image '%s' not found", imgInfo.Fingerprint))
+ return response.NotFound(fmt.Errorf("Image '%s' not found", imgInfo.Fingerprint))
}
}
// Check if the image is only available on another node.
address, err := d.cluster.ImageLocate(imgInfo.Fingerprint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if address != "" {
// Forward the request to the other node
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return ForwardedResponse(client, r)
+ return response.ForwardedResponse(client, r)
}
imagePath := shared.VarPath("images", imgInfo.Fingerprint)
@@ -1912,11 +1916,11 @@ func imageExport(d *Daemon, r *http.Request) Response {
filename := fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
if shared.PathExists(rootfsPath) {
- files := make([]fileResponseEntry, 2)
+ files := make([]response.FileResponseEntry, 2)
- files[0].identifier = "metadata"
- files[0].path = imagePath
- files[0].filename = "meta-" + filename
+ files[0].Identifier = "metadata"
+ files[0].Path = imagePath
+ files[0].Filename = "meta-" + filename
// Recompute the extension for the root filesystem, it may use a different
// compression algorithm than the metadata.
@@ -1926,33 +1930,33 @@ func imageExport(d *Daemon, r *http.Request) Response {
}
filename = fmt.Sprintf("%s%s", imgInfo.Fingerprint, ext)
- files[1].identifier = "rootfs"
- files[1].path = rootfsPath
- files[1].filename = filename
+ files[1].Identifier = "rootfs"
+ files[1].Path = rootfsPath
+ files[1].Filename = filename
- return FileResponse(r, files, nil, false)
+ return response.FileResponse(r, files, nil, false)
}
- files := make([]fileResponseEntry, 1)
- files[0].identifier = filename
- files[0].path = imagePath
- files[0].filename = filename
+ files := make([]response.FileResponseEntry, 1)
+ files[0].Identifier = filename
+ files[0].Path = imagePath
+ files[0].Filename = filename
- return FileResponse(r, files, nil, false)
+ return response.FileResponse(r, files, nil, false)
}
-func imageSecret(d *Daemon, r *http.Request) Response {
+func imageSecret(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
_, imgInfo, err := d.cluster.ImageGet(project, fingerprint, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
secret, err := shared.RandomCryptoString()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
meta := shared.Jmap{}
@@ -1961,12 +1965,12 @@ func imageSecret(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["images"] = []string{imgInfo.Fingerprint}
- op, err := operationCreate(d.cluster, project, operationClassToken, db.OperationImageToken, resources, meta, nil, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassToken, db.OperationImageToken, resources, meta, nil, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprint string) error {
@@ -2025,25 +2029,25 @@ func imageImportFromNode(imagesDir string, client lxd.InstanceServer, fingerprin
return nil
}
-func imageRefresh(d *Daemon, r *http.Request) Response {
+func imageRefresh(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
fingerprint := mux.Vars(r)["fingerprint"]
imageId, imageInfo, err := d.cluster.ImageGet(project, fingerprint, false, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Begin background operation
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
return autoUpdateImage(d, op, imageId, imageInfo, project)
}
- op, err := operationCreate(d.cluster, project, operationClassTask, db.OperationImageRefresh, nil, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, project, operations.OperationClassTask, db.OperationImageRefresh, nil, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
func autoSyncImagesTask(d *Daemon) (task.Func, task.Schedule) {
@@ -2067,11 +2071,11 @@ func autoSyncImagesTask(d *Daemon) (task.Func, task.Schedule) {
return
}
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return autoSyncImages(ctx, d)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationImagesSynchronize, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationImagesSynchronize, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start image synchronization operation", log.Ctx{"err": err})
return
diff --git a/lxd/instance_interface.go b/lxd/instance_interface.go
index dcdc4c2198..c2dec6b900 100644
--- a/lxd/instance_interface.go
+++ b/lxd/instance_interface.go
@@ -10,6 +10,7 @@ import (
"github.com/lxc/lxd/lxd/device"
"github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/shared/api"
)
@@ -106,7 +107,7 @@ type Instance interface {
// Progress reporting
- SetOperation(op *operation)
+ SetOperation(op *operations.Operation)
// FIXME: Those should be internal functions
// Needed for migration for now.
diff --git a/lxd/logging.go b/lxd/logging.go
index 162ad51a3d..35b5855ca4 100644
--- a/lxd/logging.go
+++ b/lxd/logging.go
@@ -8,6 +8,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/task"
"github.com/lxc/lxd/shared"
@@ -20,11 +21,11 @@ import (
// and will run once every 24h.
func expireLogsTask(state *state.State) (task.Func, task.Schedule) {
f := func(ctx context.Context) {
- opRun := func(op *operation) error {
+ opRun := func(op *operations.Operation) error {
return expireLogs(ctx, state)
}
- op, err := operationCreate(state.Cluster, "", operationClassTask, db.OperationLogsExpire, nil, nil, opRun, nil, nil)
+ op, err := operations.OperationCreate(state.Cluster, "", operations.OperationClassTask, db.OperationLogsExpire, nil, nil, opRun, nil, nil)
if err != nil {
logger.Error("Failed to start log expiry operation", log.Ctx{"err": err})
return
diff --git a/lxd/main.go b/lxd/main.go
index 476ad9ecb4..ce49cd5dc6 100644
--- a/lxd/main.go
+++ b/lxd/main.go
@@ -8,6 +8,8 @@ import (
"github.com/spf13/cobra"
"github.com/lxc/lxd/lxd/events"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/shared/logger"
"github.com/lxc/lxd/shared/logging"
"github.com/lxc/lxd/shared/version"
@@ -43,6 +45,12 @@ func (c *cmdGlobal) Run(cmd *cobra.Command, args []string) error {
// Set debug and verbose for the events package
events.Init(debug, verbose)
+ // Set debug for the operations package
+ operations.Init(debug)
+
+ // Set debug for the response package
+ response.Init(debug)
+
// Setup logger
syslog := ""
if c.flagLogSyslog {
diff --git a/lxd/migrate.go b/lxd/migrate.go
index 6f5ead8459..92963b05f5 100644
--- a/lxd/migrate.go
+++ b/lxd/migrate.go
@@ -19,6 +19,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/idmap"
"github.com/lxc/lxd/shared/logger"
@@ -145,7 +146,7 @@ func (s *migrationSourceWs) Metadata() interface{} {
return secrets
}
-func (s *migrationSourceWs) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *migrationSourceWs) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
secret := r.FormValue("secret")
if secret == "" {
return fmt.Errorf("missing secret")
@@ -317,7 +318,7 @@ func (s *migrationSink) Metadata() interface{} {
return secrets
}
-func (s *migrationSink) Connect(op *operation, r *http.Request, w http.ResponseWriter) error {
+func (s *migrationSink) Connect(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
secret := r.FormValue("secret")
if secret == "" {
return fmt.Errorf("missing secret")
diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 4d893b87c7..ee31a6fe93 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -18,6 +18,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -328,7 +329,7 @@ func (s *migrationSourceWs) preDumpLoop(args *preDumpLoopArgs) (bool, error) {
return final, nil
}
-func (s *migrationSourceWs) Do(migrateOp *operation) error {
+func (s *migrationSourceWs) Do(migrateOp *operations.Operation) error {
<-s.allConnected
criuType := migration.CRIUType_CRIU_RSYNC.Enum()
@@ -543,14 +544,14 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
}
state := s.instance.DaemonState()
- actionScriptOp, err := operationCreate(
+ actionScriptOp, err := operations.OperationCreate(
state.Cluster,
s.instance.Project(),
- operationClassWebsocket,
+ operations.OperationClassWebsocket,
db.OperationContainerLiveMigrate,
nil,
nil,
- func(op *operation) error {
+ func(op *operations.Operation) error {
result := <-restoreSuccess
if !result {
return fmt.Errorf("restore failed, failing CRIU")
@@ -558,7 +559,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
return nil
},
nil,
- func(op *operation, r *http.Request, w http.ResponseWriter) error {
+ func(op *operations.Operation, r *http.Request, w http.ResponseWriter) error {
secret := r.FormValue("secret")
if secret == "" {
return fmt.Errorf("missing secret")
@@ -584,7 +585,7 @@ func (s *migrationSourceWs) Do(migrateOp *operation) error {
return abort(err)
}
- err = writeActionScript(checkpointDir, actionScriptOp.url, actionScriptOpSecret, state.OS.ExecPath)
+ err = writeActionScript(checkpointDir, actionScriptOp.URL(), actionScriptOpSecret, state.OS.ExecPath)
if err != nil {
os.RemoveAll(checkpointDir)
return abort(err)
@@ -775,7 +776,7 @@ func NewMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
return &sink, nil
}
-func (c *migrationSink) Do(migrateOp *operation) error {
+func (c *migrationSink) Do(migrateOp *operations.Operation) error {
if c.src.instance.Type() != instance.TypeContainer {
return fmt.Errorf("Instance not container type")
}
diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go
index 027fe08367..05ba8e7186 100644
--- a/lxd/migrate_storage_volumes.go
+++ b/lxd/migrate_storage_volumes.go
@@ -7,6 +7,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/logger"
@@ -32,7 +33,7 @@ func NewStorageMigrationSource(storage storage, volumeOnly bool) (*migrationSour
return &ret, nil
}
-func (s *migrationSourceWs) DoStorage(migrateOp *operation) error {
+func (s *migrationSourceWs) DoStorage(migrateOp *operations.Operation) error {
<-s.allConnected
// Storage needs to start unconditionally now, since we need to
@@ -223,7 +224,7 @@ func NewStorageMigrationSink(args *MigrationSinkArgs) (*migrationSink, error) {
return &sink, nil
}
-func (c *migrationSink) DoStorage(migrateOp *operation) error {
+func (c *migrationSink) DoStorage(migrateOp *operations.Operation) error {
var err error
if c.push {
diff --git a/lxd/networks.go b/lxd/networks.go
index 4058127e7a..f2c8fab7e6 100644
--- a/lxd/networks.go
+++ b/lxd/networks.go
@@ -25,6 +25,7 @@ import (
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/iptables"
"github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -66,12 +67,12 @@ var networkStateCmd = APIEndpoint{
}
// API endpoints
-func networksGet(d *Daemon, r *http.Request) Response {
+func networksGet(d *Daemon, r *http.Request) response.Response {
recursion := util.IsRecursionRequest(r)
ifs, err := networkGetInterfaces(d.cluster)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
resultString := []string{}
@@ -89,13 +90,13 @@ func networksGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
-func networksPost(d *Daemon, r *http.Request) Response {
+func networksPost(d *Daemon, r *http.Request) response.Response {
networkCreateLock.Lock()
defer networkCreateLock.Unlock()
@@ -104,21 +105,21 @@ func networksPost(d *Daemon, r *http.Request) Response {
// Parse the request
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
err = networkValidName(req.Name)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Type != "" && req.Type != "bridge" {
- return BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
+ return response.BadRequest(fmt.Errorf("Only 'bridge' type networks can be created"))
}
if req.Config == nil {
@@ -127,11 +128,11 @@ func networksPost(d *Daemon, r *http.Request) Response {
err = networkValidateConfig(req.Name, req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
url := fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name)
- response := SyncResponseLocation(true, nil, url)
+ resp := response.SyncResponseLocation(true, nil, url)
if isClusterNotification(r) {
// This is an internal request which triggers the actual
@@ -139,9 +140,9 @@ func networksPost(d *Daemon, r *http.Request) Response {
// been previously defined.
err = doNetworksCreate(d, req, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
targetNode := queryParam(r, "target")
@@ -151,7 +152,7 @@ func networksPost(d *Daemon, r *http.Request) Response {
// value for the storage config is 'bridge.external_interfaces'.
for key := range req.Config {
if !shared.StringInSlice(key, db.NetworkNodeConfigKeys) {
- return SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
+ return response.SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
}
}
err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -159,30 +160,30 @@ func networksPost(d *Daemon, r *http.Request) Response {
})
if err != nil {
if err == db.ErrAlreadyDefined {
- return BadRequest(fmt.Errorf("The network already defined on node %s", targetNode))
+ return response.BadRequest(fmt.Errorf("The network already defined on node %s", targetNode))
}
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
err = networkFillConfig(&req)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Check if we're clustered
count, err := cluster.Count(d.State())
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if count > 1 {
err = networksPostCluster(d, req)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
// No targetNode was specified and we're either a single-node
@@ -190,25 +191,25 @@ func networksPost(d *Daemon, r *http.Request) Response {
// pool immediately.
networks, err := networkGetInterfaces(d.cluster)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if shared.StringInSlice(req.Name, networks) {
- return BadRequest(fmt.Errorf("The network already exists"))
+ return response.BadRequest(fmt.Errorf("The network already exists"))
}
// Create the database entry
_, err = d.cluster.NetworkCreate(req.Name, req.Description, req.Config)
if err != nil {
- return SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
+ return response.SmartError(fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
}
err = doNetworksCreate(d, req, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
func networksPostCluster(d *Daemon, req api.NetworksPost) error {
@@ -348,24 +349,24 @@ func doNetworksCreate(d *Daemon, req api.NetworksPost, withDatabase bool) error
return nil
}
-func networkGet(d *Daemon, r *http.Request) Response {
+func networkGet(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
name := mux.Vars(r)["name"]
n, err := doNetworkGet(d, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
targetNode := queryParam(r, "target")
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// If no target node is specified and the daemon is clustered, we omit
@@ -378,7 +379,7 @@ func networkGet(d *Daemon, r *http.Request) Response {
etag := []interface{}{n.Name, n.Managed, n.Type, n.Description, n.Config}
- return SyncResponseETag(true, &n, etag)
+ return response.SyncResponseETag(true, &n, etag)
}
func doNetworkGet(d *Daemon, name string) (api.Network, error) {
@@ -454,7 +455,7 @@ func doNetworkGet(d *Daemon, name string) (api.Network, error) {
return n, nil
}
-func networkDelete(d *Daemon, r *http.Request) Response {
+func networkDelete(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
state := d.State()
@@ -462,20 +463,20 @@ func networkDelete(d *Daemon, r *http.Request) Response {
// the database.
_, network, err := d.cluster.NetworkGet(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if network.Status == "Pending" {
err := d.cluster.NetworkDelete(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Get the existing network
n, err := networkLoadByName(state, name)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
withDatabase := true
@@ -484,26 +485,26 @@ func networkDelete(d *Daemon, r *http.Request) Response {
} else {
// Sanity checks
if n.IsUsed() {
- return BadRequest(fmt.Errorf("The network is currently in use"))
+ return response.BadRequest(fmt.Errorf("The network is currently in use"))
}
// Notify all other nodes. If any node is down, an error will be returned.
notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAll)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
return client.DeleteNetwork(name)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
// Delete the network
err = n.Delete(withDatabase)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Cleanup storage
@@ -511,10 +512,10 @@ func networkDelete(d *Daemon, r *http.Request) Response {
os.RemoveAll(shared.VarPath("networks", n.name))
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func networkPost(d *Daemon, r *http.Request) Response {
+func networkPost(d *Daemon, r *http.Request) response.Response {
// FIXME: renaming a network is currently not supported in clustering
// mode. The difficulty is that network.Start() depends on the
// network having already been renamed in the database, which is
@@ -524,10 +525,10 @@ func networkPost(d *Daemon, r *http.Request) Response {
// runs network.Start).
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if clustered {
- return BadRequest(fmt.Errorf("Renaming a network not supported in LXD clusters"))
+ return response.BadRequest(fmt.Errorf("Renaming a network not supported in LXD clusters"))
}
name := mux.Vars(r)["name"]
@@ -537,57 +538,57 @@ func networkPost(d *Daemon, r *http.Request) Response {
// Parse the request
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Get the existing network
n, err := networkLoadByName(state, name)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
// Sanity checks
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
err = networkValidName(req.Name)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the name isn't already in use
networks, err := networkGetInterfaces(d.cluster)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if shared.StringInSlice(req.Name, networks) {
- return Conflict(fmt.Errorf("Network '%s' already exists", req.Name))
+ return response.Conflict(fmt.Errorf("Network '%s' already exists", req.Name))
}
// Rename it
err = n.Rename(req.Name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/networks/%s", version.APIVersion, req.Name))
}
-func networkPut(d *Daemon, r *http.Request) Response {
+func networkPut(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Get the existing network
_, dbInfo, err := d.cluster.NetworkGet(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
targetNode := queryParam(r, "target")
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// If no target node is specified and the daemon is clustered, we omit
@@ -603,30 +604,30 @@ func networkPut(d *Daemon, r *http.Request) Response {
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.NetworkPut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
return doNetworkUpdate(d, name, dbInfo.Config, req)
}
-func networkPatch(d *Daemon, r *http.Request) Response {
+func networkPatch(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
// Get the existing network
_, dbInfo, err := d.cluster.NetworkGet(name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
targetNode := queryParam(r, "target")
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// If no target node is specified and the daemon is clustered, we omit
@@ -642,12 +643,12 @@ func networkPatch(d *Daemon, r *http.Request) Response {
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.NetworkPut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Config stacking
@@ -665,11 +666,11 @@ func networkPatch(d *Daemon, r *http.Request) Response {
return doNetworkUpdate(d, name, dbInfo.Config, req)
}
-func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req api.NetworkPut) Response {
+func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req api.NetworkPut) response.Response {
// Validate the configuration
err := networkValidateConfig(name, req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// When switching to a fan bridge, auto-detect the underlay
@@ -682,30 +683,30 @@ func doNetworkUpdate(d *Daemon, name string, oldConfig map[string]string, req ap
// Load the network
n, err := networkLoadByName(d.State(), name)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
err = n.Update(req)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func networkLeasesGet(d *Daemon, r *http.Request) Response {
+func networkLeasesGet(d *Daemon, r *http.Request) response.Response {
name := mux.Vars(r)["name"]
project := projectParam(r)
// Try to get the network
n, err := doNetworkGet(d, name)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate that we do have leases for it
if !n.Managed || n.Type != "bridge" {
- return NotFound(errors.New("Leases not found"))
+ return response.NotFound(errors.New("Leases not found"))
}
leases := []api.NetworkLease{}
@@ -716,7 +717,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
// Get all the instances
instances, err := instanceLoadByProject(d.State(), project)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, inst := range instances {
@@ -771,18 +772,18 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get dynamic leases
leaseFile := shared.VarPath("networks", name, "dnsmasq.leases")
if !shared.PathExists(leaseFile) {
- return SyncResponse(true, leases)
+ return response.SyncResponse(true, leases)
}
content, err := ioutil.ReadFile(leaseFile)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, lease := range strings.Split(string(content), "\n") {
@@ -824,7 +825,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
if !isClusterNotification(r) {
notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
@@ -837,7 +838,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Filter based on project
@@ -853,7 +854,7 @@ func networkLeasesGet(d *Daemon, r *http.Request) Response {
leases = filteredLeases
}
- return SyncResponse(true, leases)
+ return response.SyncResponse(true, leases)
}
// The network structs and functions
@@ -919,11 +920,11 @@ func networkShutdown(s *state.State) error {
return nil
}
-func networkStateGet(d *Daemon, r *http.Request) Response {
+func networkStateGet(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
name := mux.Vars(r)["name"]
@@ -933,10 +934,10 @@ func networkStateGet(d *Daemon, r *http.Request) Response {
// Sanity check
if osInfo == nil {
- return NotFound(fmt.Errorf("Interface '%s' not found", name))
+ return response.NotFound(fmt.Errorf("Interface '%s' not found", name))
}
- return SyncResponse(true, networkGetState(*osInfo))
+ return response.SyncResponse(true, networkGetState(*osInfo))
}
type network struct {
diff --git a/lxd/operations.go b/lxd/operations.go
index c96409b58e..289814150e 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -4,24 +4,18 @@ import (
"fmt"
"net/http"
"strings"
- "sync"
- "time"
"github.com/gorilla/mux"
- "github.com/gorilla/websocket"
- "github.com/pborman/uuid"
- "github.com/pkg/errors"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
- "github.com/lxc/lxd/lxd/events"
"github.com/lxc/lxd/lxd/node"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/cancel"
"github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/version"
)
var operationCmd = APIEndpoint{
@@ -49,462 +43,21 @@ var operationWebsocket = APIEndpoint{
Get: APIEndpointAction{Handler: operationWebsocketGet, AllowUntrusted: true},
}
-var operationsLock sync.Mutex
-var operations map[string]*operation = make(map[string]*operation)
-
-type operationClass int
-
-const (
- operationClassTask operationClass = 1
- operationClassWebsocket operationClass = 2
- operationClassToken operationClass = 3
-)
-
-func (t operationClass) String() string {
- return map[operationClass]string{
- operationClassTask: "task",
- operationClassWebsocket: "websocket",
- operationClassToken: "token",
- }[t]
-}
-
-type operation struct {
- project string
- id string
- class operationClass
- createdAt time.Time
- updatedAt time.Time
- status api.StatusCode
- url string
- resources map[string][]string
- metadata map[string]interface{}
- err string
- readonly bool
- canceler *cancel.Canceler
- description string
- permission string
-
- // Those functions are called at various points in the operation lifecycle
- onRun func(*operation) error
- onCancel func(*operation) error
- onConnect func(*operation, *http.Request, http.ResponseWriter) error
-
- // Channels used for error reporting and state tracking of background actions
- chanDone chan error
-
- // Locking for concurent access to the operation
- lock sync.Mutex
-
- cluster *db.Cluster
-}
-
-func (op *operation) done() {
- if op.readonly {
- return
- }
-
- op.lock.Lock()
- op.readonly = true
- op.onRun = nil
- op.onCancel = nil
- op.onConnect = nil
- close(op.chanDone)
- op.lock.Unlock()
-
- time.AfterFunc(time.Second*5, func() {
- operationsLock.Lock()
- _, ok := operations[op.id]
- if !ok {
- operationsLock.Unlock()
- return
- }
-
- delete(operations, op.id)
- operationsLock.Unlock()
-
- err := op.cluster.Transaction(func(tx *db.ClusterTx) error {
- return tx.OperationRemove(op.id)
- })
- if err != nil {
- logger.Warnf("Failed to delete operation %s: %s", op.id, err)
- }
- })
-}
-
-func (op *operation) Run() (chan error, error) {
- if op.status != api.Pending {
- return nil, fmt.Errorf("Only pending operations can be started")
- }
-
- chanRun := make(chan error, 1)
-
- op.lock.Lock()
- op.status = api.Running
-
- if op.onRun != nil {
- go func(op *operation, chanRun chan error) {
- err := op.onRun(op)
- if err != nil {
- op.lock.Lock()
- op.status = api.Failure
- op.err = SmartError(err).String()
- op.lock.Unlock()
- op.done()
- chanRun <- err
-
- logger.Debugf("Failure for %s operation: %s: %s", op.class.String(), op.id, err)
-
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
- return
- }
-
- op.lock.Lock()
- op.status = api.Success
- op.lock.Unlock()
- op.done()
- chanRun <- nil
-
- op.lock.Lock()
- logger.Debugf("Success for %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
- op.lock.Unlock()
- }(op, chanRun)
- }
- op.lock.Unlock()
-
- logger.Debugf("Started %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
-
- return chanRun, nil
-}
-
-func (op *operation) Cancel() (chan error, error) {
- if op.status != api.Running {
- return nil, fmt.Errorf("Only running operations can be cancelled")
- }
-
- if !op.mayCancel() {
- return nil, fmt.Errorf("This operation can't be cancelled")
- }
-
- chanCancel := make(chan error, 1)
-
- op.lock.Lock()
- oldStatus := op.status
- op.status = api.Cancelling
- op.lock.Unlock()
-
- if op.onCancel != nil {
- go func(op *operation, oldStatus api.StatusCode, chanCancel chan error) {
- err := op.onCancel(op)
- if err != nil {
- op.lock.Lock()
- op.status = oldStatus
- op.lock.Unlock()
- chanCancel <- err
-
- logger.Debugf("Failed to cancel %s operation: %s: %s", op.class.String(), op.id, err)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
- return
- }
-
- op.lock.Lock()
- op.status = api.Cancelled
- op.lock.Unlock()
- op.done()
- chanCancel <- nil
-
- logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
- }(op, oldStatus, chanCancel)
- }
-
- logger.Debugf("Cancelling %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
-
- if op.canceler != nil {
- err := op.canceler.Cancel()
- if err != nil {
- return nil, err
- }
- }
-
- if op.onCancel == nil {
- op.lock.Lock()
- op.status = api.Cancelled
- op.lock.Unlock()
- op.done()
- chanCancel <- nil
- }
-
- logger.Debugf("Cancelled %s operation: %s", op.class.String(), op.id)
- _, md, _ = op.Render()
- events.Send(op.project, "operation", md)
-
- return chanCancel, nil
-}
-
-func (op *operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) {
- if op.class != operationClassWebsocket {
- return nil, fmt.Errorf("Only websocket operations can be connected")
- }
-
- if op.status != api.Running {
- return nil, fmt.Errorf("Only running operations can be connected")
- }
-
- chanConnect := make(chan error, 1)
-
- op.lock.Lock()
-
- go func(op *operation, chanConnect chan error) {
- err := op.onConnect(op, r, w)
- if err != nil {
- chanConnect <- err
-
- logger.Debugf("Failed to handle %s operation: %s: %s", op.class.String(), op.id, err)
- return
- }
-
- chanConnect <- nil
-
- logger.Debugf("Handled %s operation: %s", op.class.String(), op.id)
- }(op, chanConnect)
- op.lock.Unlock()
-
- logger.Debugf("Connected %s operation: %s", op.class.String(), op.id)
-
- return chanConnect, nil
-}
-
-func (op *operation) mayCancel() bool {
- if op.class == operationClassToken {
- return true
- }
-
- if op.onCancel != nil {
- return true
- }
-
- if op.canceler != nil && op.canceler.Cancelable() {
- return true
- }
-
- return false
-}
-
-func (op *operation) Render() (string, *api.Operation, error) {
- // Setup the resource URLs
- resources := op.resources
- if resources != nil {
- tmpResources := make(map[string][]string)
- for key, value := range resources {
- var values []string
- for _, c := range value {
- values = append(values, fmt.Sprintf("/%s/%s/%s", version.APIVersion, key, c))
- }
- tmpResources[key] = values
- }
- resources = tmpResources
- }
-
- // Local server name
- var err error
- var serverName string
- err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
- serverName, err = tx.NodeName()
- return err
- })
- if err != nil {
- return "", nil, err
- }
-
- return op.url, &api.Operation{
- ID: op.id,
- Class: op.class.String(),
- Description: op.description,
- CreatedAt: op.createdAt,
- UpdatedAt: op.updatedAt,
- Status: op.status.String(),
- StatusCode: op.status,
- Resources: resources,
- Metadata: op.metadata,
- MayCancel: op.mayCancel(),
- Err: op.err,
- Location: serverName,
- }, nil
-}
-
-func (op *operation) WaitFinal(timeout int) (bool, error) {
- // Check current state
- if op.status.IsFinal() {
- return true, nil
- }
-
- // Wait indefinitely
- if timeout == -1 {
- <-op.chanDone
- return true, nil
- }
-
- // Wait until timeout
- if timeout > 0 {
- timer := time.NewTimer(time.Duration(timeout) * time.Second)
- select {
- case <-op.chanDone:
- return true, nil
-
- case <-timer.C:
- return false, nil
- }
- }
-
- return false, nil
-}
-
-func (op *operation) UpdateResources(opResources map[string][]string) error {
- if op.status != api.Pending && op.status != api.Running {
- return fmt.Errorf("Only pending or running operations can be updated")
- }
-
- if op.readonly {
- return fmt.Errorf("Read-only operations can't be updated")
- }
-
- op.lock.Lock()
- op.updatedAt = time.Now()
- op.resources = opResources
- op.lock.Unlock()
-
- logger.Debugf("Updated resources for %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
-
- return nil
-}
-
-func (op *operation) UpdateMetadata(opMetadata interface{}) error {
- if op.status != api.Pending && op.status != api.Running {
- return fmt.Errorf("Only pending or running operations can be updated")
- }
-
- if op.readonly {
- return fmt.Errorf("Read-only operations can't be updated")
- }
-
- newMetadata, err := shared.ParseMetadata(opMetadata)
- if err != nil {
- return err
- }
-
- op.lock.Lock()
- op.updatedAt = time.Now()
- op.metadata = newMetadata
- op.lock.Unlock()
-
- logger.Debugf("Updated metadata for %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
-
- return nil
-}
-
-func operationCreate(cluster *db.Cluster, project string, opClass operationClass, opType db.OperationType, opResources map[string][]string, opMetadata interface{}, onRun func(*operation) error, onCancel func(*operation) error, onConnect func(*operation, *http.Request, http.ResponseWriter) error) (*operation, error) {
- // Main attributes
- op := operation{}
- op.project = project
- op.id = uuid.NewRandom().String()
- op.description = opType.Description()
- op.permission = opType.Permission()
- op.class = opClass
- op.createdAt = time.Now()
- op.updatedAt = op.createdAt
- op.status = api.Pending
- op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
- op.resources = opResources
- op.chanDone = make(chan error)
- op.cluster = cluster
-
- newMetadata, err := shared.ParseMetadata(opMetadata)
- if err != nil {
- return nil, err
- }
- op.metadata = newMetadata
-
- // Callback functions
- op.onRun = onRun
- op.onCancel = onCancel
- op.onConnect = onConnect
-
- // Sanity check
- if op.class != operationClassWebsocket && op.onConnect != nil {
- return nil, fmt.Errorf("Only websocket operations can have a Connect hook")
- }
-
- if op.class == operationClassWebsocket && op.onConnect == nil {
- return nil, fmt.Errorf("Websocket operations must have a Connect hook")
- }
-
- if op.class == operationClassToken && op.onRun != nil {
- return nil, fmt.Errorf("Token operations can't have a Run hook")
- }
-
- if op.class == operationClassToken && op.onCancel != nil {
- return nil, fmt.Errorf("Token operations can't have a Cancel hook")
- }
-
- operationsLock.Lock()
- operations[op.id] = &op
- operationsLock.Unlock()
-
- err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
- _, err := tx.OperationAdd(project, op.id, opType)
- return err
- })
- if err != nil {
- return nil, errors.Wrapf(err, "failed to add operation %s to database", op.id)
- }
-
- logger.Debugf("New %s operation: %s", op.class.String(), op.id)
- _, md, _ := op.Render()
- events.Send(op.project, "operation", md)
-
- return &op, nil
-}
-
-func operationGetInternal(id string) (*operation, error) {
- operationsLock.Lock()
- op, ok := operations[id]
- operationsLock.Unlock()
-
- if !ok {
- return nil, fmt.Errorf("Operation '%s' doesn't exist", id)
- }
-
- return op, nil
-}
-
// API functions
-func operationGet(d *Daemon, r *http.Request) Response {
+func operationGet(d *Daemon, r *http.Request) response.Response {
id := mux.Vars(r)["id"]
var body *api.Operation
// First check if the query is for a local operation from this node
- op, err := operationGetInternal(id)
+ op, err := operations.OperationGetInternal(id)
if err == nil {
_, body, err = op.Render()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
// Then check if the query is from an operation on another node, and, if so, forward it
@@ -519,46 +72,46 @@ func operationGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
body, _, err = client.GetOperation(id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
-func operationDelete(d *Daemon, r *http.Request) Response {
+func operationDelete(d *Daemon, r *http.Request) response.Response {
id := mux.Vars(r)["id"]
// First check if the query is for a local operation from this node
- op, err := operationGetInternal(id)
+ op, err := operations.OperationGetInternal(id)
if err == nil {
- if op.permission != "" {
- project := op.project
+ if op.Permission() != "" {
+ project := op.Project()
if project == "" {
project = "default"
}
- if !d.userHasPermission(r, project, op.permission) {
- return Forbidden(nil)
+ if !d.userHasPermission(r, project, op.Permission()) {
+ return response.Forbidden(nil)
}
}
_, err = op.Cancel()
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// Then check if the query is from an operation on another node, and, if so, forward it
@@ -573,47 +126,47 @@ func operationDelete(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = client.DeleteOperation(id)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func operationsGet(d *Daemon, r *http.Request) Response {
+func operationsGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
recursion := util.IsRecursionRequest(r)
localOperationURLs := func() (shared.Jmap, error) {
// Get all the operations
- operationsLock.Lock()
- ops := operations
- operationsLock.Unlock()
+ operations.Lock()
+ ops := operations.Operations()
+ operations.Unlock()
// Build a list of URLs
body := shared.Jmap{}
for _, v := range ops {
- if v.project != "" && v.project != project {
+ if v.Project() != "" && v.Project() != project {
continue
}
- status := strings.ToLower(v.status.String())
+ status := strings.ToLower(v.Status().String())
_, ok := body[status]
if !ok {
body[status] = make([]string, 0)
}
- body[status] = append(body[status].([]string), v.url)
+ body[status] = append(body[status].([]string), v.URL())
}
return body, nil
@@ -621,18 +174,18 @@ func operationsGet(d *Daemon, r *http.Request) Response {
localOperations := func() (shared.Jmap, error) {
// Get all the operations
- operationsLock.Lock()
- ops := operations
- operationsLock.Unlock()
+ operations.Lock()
+ ops := operations.Operations()
+ operations.Unlock()
// Build a list of operations
body := shared.Jmap{}
for _, v := range ops {
- if v.project != "" && v.project != project {
+ if v.Project() != "" && v.Project() != project {
continue
}
- status := strings.ToLower(v.status.String())
+ status := strings.ToLower(v.Status().String())
_, ok := body[status]
if !ok {
body[status] = make([]*api.Operation, 0)
@@ -656,19 +209,19 @@ func operationsGet(d *Daemon, r *http.Request) Response {
// Recursive queries
body, err := localOperations()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
// Normal queries
body, err := localOperationURLs()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
// Start with local operations
@@ -678,24 +231,24 @@ func operationsGet(d *Daemon, r *http.Request) Response {
if recursion {
md, err = localOperations()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
} else {
md, err = localOperationURLs()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
// Check if clustered
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// Return now if not clustered
if !clustered {
- return SyncResponse(true, md)
+ return response.SyncResponse(true, md)
}
// Get all nodes with running operations in this project.
@@ -711,13 +264,13 @@ func operationsGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get local address
localAddress, err := node.HTTPSAddress(d.db)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
cert := d.endpoints.NetworkCert()
@@ -729,13 +282,13 @@ func operationsGet(d *Daemon, r *http.Request) Response {
// Connect to the remote server
client, err := cluster.Connect(node, cert, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get operation data
ops, err := client.GetOperations()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Merge with existing data
@@ -759,31 +312,31 @@ func operationsGet(d *Daemon, r *http.Request) Response {
}
}
- return SyncResponse(true, md)
+ return response.SyncResponse(true, md)
}
-func operationWaitGet(d *Daemon, r *http.Request) Response {
+func operationWaitGet(d *Daemon, r *http.Request) response.Response {
id := mux.Vars(r)["id"]
timeout, err := shared.AtoiEmptyDefault(r.FormValue("timeout"), -1)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// First check if the query is for a local operation from this node
- op, err := operationGetInternal(id)
+ op, err := operations.OperationGetInternal(id)
if err == nil {
_, err = op.WaitFinal(timeout)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
_, body, err := op.Render()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, body)
+ return response.SyncResponse(true, body)
}
// Then check if the query is from an operation on another node, and, if so, forward it
@@ -798,79 +351,36 @@ func operationWaitGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
_, body, err := client.GetOperationWait(id, timeout)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, body)
-}
-
-type operationWebSocket struct {
- req *http.Request
- op *operation
-}
-
-func (r *operationWebSocket) Render(w http.ResponseWriter) error {
- chanErr, err := r.op.Connect(r.req, w)
- if err != nil {
- return err
- }
-
- err = <-chanErr
- return err
-}
-
-func (r *operationWebSocket) String() string {
- _, md, err := r.op.Render()
- if err != nil {
- return fmt.Sprintf("error: %s", err)
- }
-
- return md.ID
-}
-
-type forwardedOperationWebSocket struct {
- req *http.Request
- id string
- source *websocket.Conn // Connection to the node were the operation is running
-}
-
-func (r *forwardedOperationWebSocket) Render(w http.ResponseWriter) error {
- target, err := shared.WebsocketUpgrader.Upgrade(w, r.req, nil)
- if err != nil {
- return err
- }
- <-shared.WebsocketProxy(r.source, target)
- return nil
-}
-
-func (r *forwardedOperationWebSocket) String() string {
- return r.id
+ return response.SyncResponse(true, body)
}
-func operationWebsocketGet(d *Daemon, r *http.Request) Response {
+func operationWebsocketGet(d *Daemon, r *http.Request) response.Response {
id := mux.Vars(r)["id"]
// First check if the query is for a local operation from this node
- op, err := operationGetInternal(id)
+ op, err := operations.OperationGetInternal(id)
if err == nil {
- return &operationWebSocket{r, op}
+ return operations.OperationWebSocket(r, op)
}
// Then check if the query is from an operation on another node, and, if so, forward it
secret := r.FormValue("secret")
if secret == "" {
- return BadRequest(fmt.Errorf("missing secret"))
+ return response.BadRequest(fmt.Errorf("missing secret"))
}
var address string
@@ -884,20 +394,20 @@ func operationWebsocketGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
logger.Debugf("Forward operation websocket from node %s", address)
source, err := client.GetOperationWebsocket(id, secret)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return &forwardedOperationWebSocket{req: r, id: id, source: source}
+ return operations.ForwardedOperationWebSocket(r, id, source)
}
diff --git a/lxd/operations/operations.go b/lxd/operations/operations.go
new file mode 100644
index 0000000000..b1432b1989
--- /dev/null
+++ b/lxd/operations/operations.go
@@ -0,0 +1,543 @@
+package operations
+
+import (
+ "fmt"
+ "net/http"
+ "sync"
+ "time"
+
+ "github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/events"
+ "github.com/lxc/lxd/lxd/response"
+ "github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/api"
+ "github.com/lxc/lxd/shared/cancel"
+ "github.com/lxc/lxd/shared/logger"
+ "github.com/lxc/lxd/shared/version"
+ "github.com/pborman/uuid"
+ "github.com/pkg/errors"
+)
+
+var debug bool
+
+var operationsLock sync.Mutex
+var operations map[string]*Operation = make(map[string]*Operation)
+
+type operationClass int
+
+const (
+ // OperationClassTask represents the Task OperationClass
+ OperationClassTask operationClass = 1
+ // OperationClassWebsocket represents the Websocket OperationClass
+ OperationClassWebsocket operationClass = 2
+ // OperationClassToken represents the Token OperationClass
+ OperationClassToken operationClass = 3
+)
+
+func (t operationClass) String() string {
+ return map[operationClass]string{
+ OperationClassTask: "task",
+ OperationClassWebsocket: "websocket",
+ OperationClassToken: "token",
+ }[t]
+}
+
+// Init sets the debug value for the operations package.
+func Init(d bool) {
+ debug = d
+}
+
+// Lock locks the operations mutex.
+func Lock() {
+ operationsLock.Lock()
+}
+
+// Unlock unlocks the operations mutex.
+func Unlock() {
+ operationsLock.Unlock()
+}
+
+// Operations returns a map of operations.
+func Operations() map[string]*Operation {
+ return operations
+}
+
+// OperationGetInternal returns the operation with the given id. It returns an
+// error if it doesn't exist.
+func OperationGetInternal(id string) (*Operation, error) {
+ operationsLock.Lock()
+ op, ok := operations[id]
+ operationsLock.Unlock()
+
+ if !ok {
+ return nil, fmt.Errorf("Operation '%s' doesn't exist", id)
+ }
+
+ return op, nil
+}
+
+// Operation represents an operation.
+type Operation struct {
+ project string
+ id string
+ class operationClass
+ createdAt time.Time
+ updatedAt time.Time
+ status api.StatusCode
+ url string
+ resources map[string][]string
+ metadata map[string]interface{}
+ err string
+ readonly bool
+ canceler *cancel.Canceler
+ description string
+ permission string
+
+ // Those functions are called at various points in the Operation lifecycle
+ onRun func(*Operation) error
+ onCancel func(*Operation) error
+ onConnect func(*Operation, *http.Request, http.ResponseWriter) error
+
+ // Channels used for error reporting and state tracking of background actions
+ chanDone chan error
+
+ // Locking for concurent access to the Operation
+ lock sync.Mutex
+
+ cluster *db.Cluster
+}
+
+func (op *Operation) done() {
+ if op.readonly {
+ return
+ }
+
+ op.lock.Lock()
+ op.readonly = true
+ op.onRun = nil
+ op.onCancel = nil
+ op.onConnect = nil
+ close(op.chanDone)
+ op.lock.Unlock()
+
+ time.AfterFunc(time.Second*5, func() {
+ operationsLock.Lock()
+ _, ok := operations[op.id]
+ if !ok {
+ operationsLock.Unlock()
+ return
+ }
+
+ delete(operations, op.id)
+ operationsLock.Unlock()
+
+ err := op.cluster.Transaction(func(tx *db.ClusterTx) error {
+ return tx.OperationRemove(op.id)
+ })
+ if err != nil {
+ logger.Warnf("Failed to delete Operation %s: %s", op.id, err)
+ }
+ })
+}
+
+// Run runs a pending operation. It returns an error if the operation cannot
+// be started.
+func (op *Operation) Run() (chan error, error) {
+ if op.status != api.Pending {
+ return nil, fmt.Errorf("Only pending operations can be started")
+ }
+
+ chanRun := make(chan error, 1)
+
+ op.lock.Lock()
+ op.status = api.Running
+
+ if op.onRun != nil {
+ go func(op *Operation, chanRun chan error) {
+ err := op.onRun(op)
+ if err != nil {
+ op.lock.Lock()
+ op.status = api.Failure
+ op.err = response.SmartError(err).String()
+ op.lock.Unlock()
+ op.done()
+ chanRun <- err
+
+ logger.Debugf("Failure for %s Operation: %s: %s", op.class.String(), op.id, err)
+
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+ return
+ }
+
+ op.lock.Lock()
+ op.status = api.Success
+ op.lock.Unlock()
+ op.done()
+ chanRun <- nil
+
+ op.lock.Lock()
+ logger.Debugf("Success for %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+ op.lock.Unlock()
+ }(op, chanRun)
+ }
+ op.lock.Unlock()
+
+ logger.Debugf("Started %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+
+ return chanRun, nil
+}
+
+// Cancel cancels a running operation. If the operation cannot be cancelled, it
+// returns an error.
+func (op *Operation) Cancel() (chan error, error) {
+ if op.status != api.Running {
+ return nil, fmt.Errorf("Only running operations can be cancelled")
+ }
+
+ if !op.mayCancel() {
+ return nil, fmt.Errorf("This Operation can't be cancelled")
+ }
+
+ chanCancel := make(chan error, 1)
+
+ op.lock.Lock()
+ oldStatus := op.status
+ op.status = api.Cancelling
+ op.lock.Unlock()
+
+ if op.onCancel != nil {
+ go func(op *Operation, oldStatus api.StatusCode, chanCancel chan error) {
+ err := op.onCancel(op)
+ if err != nil {
+ op.lock.Lock()
+ op.status = oldStatus
+ op.lock.Unlock()
+ chanCancel <- err
+
+ logger.Debugf("Failed to cancel %s Operation: %s: %s", op.class.String(), op.id, err)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+ return
+ }
+
+ op.lock.Lock()
+ op.status = api.Cancelled
+ op.lock.Unlock()
+ op.done()
+ chanCancel <- nil
+
+ logger.Debugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+ }(op, oldStatus, chanCancel)
+ }
+
+ logger.Debugf("Cancelling %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+
+ if op.canceler != nil {
+ err := op.canceler.Cancel()
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ if op.onCancel == nil {
+ op.lock.Lock()
+ op.status = api.Cancelled
+ op.lock.Unlock()
+ op.done()
+ chanCancel <- nil
+ }
+
+ logger.Debugf("Cancelled %s Operation: %s", op.class.String(), op.id)
+ _, md, _ = op.Render()
+ events.Send(op.project, "Operation", md)
+
+ return chanCancel, nil
+}
+
+// Connect connects a websocket operation. If the operation is not a websocket
+// operation or the operation is not running, it returns an error.
+func (op *Operation) Connect(r *http.Request, w http.ResponseWriter) (chan error, error) {
+ if op.class != OperationClassWebsocket {
+ return nil, fmt.Errorf("Only websocket operations can be connected")
+ }
+
+ if op.status != api.Running {
+ return nil, fmt.Errorf("Only running operations can be connected")
+ }
+
+ chanConnect := make(chan error, 1)
+
+ op.lock.Lock()
+
+ go func(op *Operation, chanConnect chan error) {
+ err := op.onConnect(op, r, w)
+ if err != nil {
+ chanConnect <- err
+
+ logger.Debugf("Failed to handle %s Operation: %s: %s", op.class.String(), op.id, err)
+ return
+ }
+
+ chanConnect <- nil
+
+ logger.Debugf("Handled %s Operation: %s", op.class.String(), op.id)
+ }(op, chanConnect)
+ op.lock.Unlock()
+
+ logger.Debugf("Connected %s Operation: %s", op.class.String(), op.id)
+
+ return chanConnect, nil
+}
+
+func (op *Operation) mayCancel() bool {
+ if op.class == OperationClassToken {
+ return true
+ }
+
+ if op.onCancel != nil {
+ return true
+ }
+
+ if op.canceler != nil && op.canceler.Cancelable() {
+ return true
+ }
+
+ return false
+}
+
+// Render renders the operation structure.
+func (op *Operation) Render() (string, *api.Operation, error) {
+ // Setup the resource URLs
+ resources := op.resources
+ if resources != nil {
+ tmpResources := make(map[string][]string)
+ for key, value := range resources {
+ var values []string
+ for _, c := range value {
+ values = append(values, fmt.Sprintf("/%s/%s/%s", version.APIVersion, key, c))
+ }
+ tmpResources[key] = values
+ }
+ resources = tmpResources
+ }
+
+ // Local server name
+ var err error
+ var serverName string
+ err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
+ serverName, err = tx.NodeName()
+ return err
+ })
+ if err != nil {
+ return "", nil, err
+ }
+
+ return op.url, &api.Operation{
+ ID: op.id,
+ Class: op.class.String(),
+ Description: op.description,
+ CreatedAt: op.createdAt,
+ UpdatedAt: op.updatedAt,
+ Status: op.status.String(),
+ StatusCode: op.status,
+ Resources: resources,
+ Metadata: op.metadata,
+ MayCancel: op.mayCancel(),
+ Err: op.err,
+ Location: serverName,
+ }, nil
+}
+
+// WaitFinal waits for the operation to be done. If timeout is -1, it will wait
+// indefinitely otherwise it will timeout after {timeout} seconds.
+func (op *Operation) WaitFinal(timeout int) (bool, error) {
+ // Check current state
+ if op.status.IsFinal() {
+ return true, nil
+ }
+
+ // Wait indefinitely
+ if timeout == -1 {
+ <-op.chanDone
+ return true, nil
+ }
+
+ // Wait until timeout
+ if timeout > 0 {
+ timer := time.NewTimer(time.Duration(timeout) * time.Second)
+ select {
+ case <-op.chanDone:
+ return true, nil
+
+ case <-timer.C:
+ return false, nil
+ }
+ }
+
+ return false, nil
+}
+
+// UpdateResources updates the resources of the operation. It returns an error
+// if the operation is not pending or running, or the operation is read-only.
+func (op *Operation) UpdateResources(opResources map[string][]string) error {
+ if op.status != api.Pending && op.status != api.Running {
+ return fmt.Errorf("Only pending or running operations can be updated")
+ }
+
+ if op.readonly {
+ return fmt.Errorf("Read-only operations can't be updated")
+ }
+
+ op.lock.Lock()
+ op.updatedAt = time.Now()
+ op.resources = opResources
+ op.lock.Unlock()
+
+ logger.Debugf("Updated resources for %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+
+ return nil
+}
+
+// UpdateMetadata updates the metadata of the operation. It returns an error
+// if the operation is not pending or running, or the operation is read-only.
+func (op *Operation) UpdateMetadata(opMetadata interface{}) error {
+ if op.status != api.Pending && op.status != api.Running {
+ return fmt.Errorf("Only pending or running operations can be updated")
+ }
+
+ if op.readonly {
+ return fmt.Errorf("Read-only operations can't be updated")
+ }
+
+ newMetadata, err := shared.ParseMetadata(opMetadata)
+ if err != nil {
+ return err
+ }
+
+ op.lock.Lock()
+ op.updatedAt = time.Now()
+ op.metadata = newMetadata
+ op.lock.Unlock()
+
+ logger.Debugf("Updated metadata for %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+
+ return nil
+}
+
+// ID returns the operation ID.
+func (op *Operation) ID() string {
+ return op.id
+}
+
+// Metadata returns the operation Metadata.
+func (op *Operation) Metadata() map[string]interface{} {
+ return op.metadata
+}
+
+// URL returns the operation URL.
+func (op *Operation) URL() string {
+ return op.url
+}
+
+// Resources returns the operation resources.
+func (op *Operation) Resources() map[string][]string {
+ return op.resources
+}
+
+// SetCanceler sets a canceler.
+func (op *Operation) SetCanceler(canceler *cancel.Canceler) {
+ op.canceler = canceler
+}
+
+// Permission returns the operation permission.
+func (op *Operation) Permission() string {
+ return op.permission
+}
+
+// Project returns the operation project.
+func (op *Operation) Project() string {
+ return op.project
+}
+
+// Status returns the operation status.
+func (op *Operation) Status() api.StatusCode {
+ return op.status
+}
+
+// OperationCreate creates a new operation and returns it. If it cannot be
+// created, it returns an error.
+func OperationCreate(cluster *db.Cluster, project string, opClass operationClass, opType db.OperationType, opResources map[string][]string, opMetadata interface{}, onRun func(*Operation) error, onCancel func(*Operation) error, onConnect func(*Operation, *http.Request, http.ResponseWriter) error) (*Operation, error) {
+ // Main attributes
+ op := Operation{}
+ op.project = project
+ op.id = uuid.NewRandom().String()
+ op.description = opType.Description()
+ op.permission = opType.Permission()
+ op.class = opClass
+ op.createdAt = time.Now()
+ op.updatedAt = op.createdAt
+ op.status = api.Pending
+ op.url = fmt.Sprintf("/%s/operations/%s", version.APIVersion, op.id)
+ op.resources = opResources
+ op.chanDone = make(chan error)
+ op.cluster = cluster
+
+ newMetadata, err := shared.ParseMetadata(opMetadata)
+ if err != nil {
+ return nil, err
+ }
+ op.metadata = newMetadata
+
+ // Callback functions
+ op.onRun = onRun
+ op.onCancel = onCancel
+ op.onConnect = onConnect
+
+ // Sanity check
+ if op.class != OperationClassWebsocket && op.onConnect != nil {
+ return nil, fmt.Errorf("Only websocket operations can have a Connect hook")
+ }
+
+ if op.class == OperationClassWebsocket && op.onConnect == nil {
+ return nil, fmt.Errorf("Websocket operations must have a Connect hook")
+ }
+
+ if op.class == OperationClassToken && op.onRun != nil {
+ return nil, fmt.Errorf("Token operations can't have a Run hook")
+ }
+
+ if op.class == OperationClassToken && op.onCancel != nil {
+ return nil, fmt.Errorf("Token operations can't have a Cancel hook")
+ }
+
+ operationsLock.Lock()
+ operations[op.id] = &op
+ operationsLock.Unlock()
+
+ err = op.cluster.Transaction(func(tx *db.ClusterTx) error {
+ _, err := tx.OperationAdd(project, op.id, opType)
+ return err
+ })
+ if err != nil {
+ return nil, errors.Wrapf(err, "failed to add Operation %s to database", op.id)
+ }
+
+ logger.Debugf("New %s Operation: %s", op.class.String(), op.id)
+ _, md, _ := op.Render()
+ events.Send(op.project, "Operation", md)
+
+ return &op, nil
+}
diff --git a/lxd/operations/response.go b/lxd/operations/response.go
new file mode 100644
index 0000000000..d22c5b9f6a
--- /dev/null
+++ b/lxd/operations/response.go
@@ -0,0 +1,96 @@
+package operations
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/lxc/lxd/lxd/response"
+ "github.com/lxc/lxd/lxd/util"
+ "github.com/lxc/lxd/shared/api"
+ "github.com/lxc/lxd/shared/version"
+)
+
+// Operation response
+type operationResponse struct {
+ op *Operation
+}
+
+// OperationResponse returns an operation response.
+func OperationResponse(op *Operation) response.Response {
+ return &operationResponse{op}
+}
+
+func (r *operationResponse) Render(w http.ResponseWriter) error {
+ _, err := r.op.Run()
+ if err != nil {
+ return err
+ }
+
+ url, md, err := r.op.Render()
+ if err != nil {
+ return err
+ }
+
+ body := api.ResponseRaw{
+ Type: api.AsyncResponse,
+ Status: api.OperationCreated.String(),
+ StatusCode: int(api.OperationCreated),
+ Operation: url,
+ Metadata: md,
+ }
+
+ w.Header().Set("Location", url)
+ w.WriteHeader(202)
+
+ return util.WriteJSON(w, body, debug)
+}
+
+func (r *operationResponse) String() string {
+ _, md, err := r.op.Render()
+ if err != nil {
+ return fmt.Sprintf("error: %s", err)
+ }
+
+ return md.ID
+}
+
+// Forwarded operation response.
+//
+// Returned when the operation has been created on another node
+type forwardedOperationResponse struct {
+ op *api.Operation
+ project string
+}
+
+// ForwardedOperationResponse creates a response that forwards the metadata of
+// an operation created on another node.
+func ForwardedOperationResponse(project string, op *api.Operation) response.Response {
+ return &forwardedOperationResponse{
+ op: op,
+ project: project,
+ }
+}
+
+func (r *forwardedOperationResponse) Render(w http.ResponseWriter) error {
+ url := fmt.Sprintf("/%s/operations/%s", version.APIVersion, r.op.ID)
+ if r.project != "" {
+ url += fmt.Sprintf("?project=%s", r.project)
+ }
+
+ body := api.ResponseRaw{
+ Type: api.AsyncResponse,
+ Status: api.OperationCreated.String(),
+ StatusCode: int(api.OperationCreated),
+ Operation: url,
+ Metadata: r.op,
+ }
+
+ w.Header().Set("Location", url)
+ w.WriteHeader(202)
+
+ return util.WriteJSON(w, body, debug)
+}
+
+func (r *forwardedOperationResponse) String() string {
+ return r.op.ID
+}
diff --git a/lxd/operations/websocket.go b/lxd/operations/websocket.go
new file mode 100644
index 0000000000..f16005da85
--- /dev/null
+++ b/lxd/operations/websocket.go
@@ -0,0 +1,63 @@
+package operations
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/gorilla/websocket"
+ "github.com/lxc/lxd/lxd/response"
+ "github.com/lxc/lxd/shared"
+)
+
+type operationWebSocket struct {
+ req *http.Request
+ op *Operation
+}
+
+// OperationWebSocket returns a new websocket operation.
+func OperationWebSocket(req *http.Request, op *Operation) response.Response {
+ return &operationWebSocket{req, op}
+}
+
+func (r *operationWebSocket) Render(w http.ResponseWriter) error {
+ chanErr, err := r.op.Connect(r.req, w)
+ if err != nil {
+ return err
+ }
+
+ err = <-chanErr
+ return err
+}
+
+func (r *operationWebSocket) String() string {
+ _, md, err := r.op.Render()
+ if err != nil {
+ return fmt.Sprintf("error: %s", err)
+ }
+
+ return md.ID
+}
+
+type forwardedOperationWebSocket struct {
+ req *http.Request
+ id string
+ source *websocket.Conn // Connection to the node were the operation is running
+}
+
+// ForwardedOperationWebSocket returns a new forwarted websocket operation.
+func ForwardedOperationWebSocket(req *http.Request, id string, source *websocket.Conn) response.Response {
+ return &forwardedOperationWebSocket{req, id, source}
+}
+
+func (r *forwardedOperationWebSocket) Render(w http.ResponseWriter) error {
+ target, err := shared.WebsocketUpgrader.Upgrade(w, r.req, nil)
+ if err != nil {
+ return err
+ }
+ <-shared.WebsocketProxy(r.source, target)
+ return nil
+}
+
+func (r *forwardedOperationWebSocket) String() string {
+ return r.id
+}
diff --git a/lxd/profiles.go b/lxd/profiles.go
index 6c6e7c6c17..424a9e0b47 100644
--- a/lxd/profiles.go
+++ b/lxd/profiles.go
@@ -15,6 +15,7 @@ import (
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
deviceConfig "github.com/lxc/lxd/lxd/device/config"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -39,7 +40,7 @@ var profileCmd = APIEndpoint{
}
/* This is used for both profiles post and profile put */
-func profilesGet(d *Daemon, r *http.Request) Response {
+func profilesGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
recursion := util.IsRecursionRequest(r)
@@ -75,41 +76,41 @@ func profilesGet(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, result)
+ return response.SyncResponse(true, result)
}
-func profilesPost(d *Daemon, r *http.Request) Response {
+func profilesPost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
req := api.ProfilesPost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Profile names may not contain slashes"))
}
if shared.StringInSlice(req.Name, []string{".", ".."}) {
- return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+ return response.BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
}
err := containerValidConfig(d.os, req.Config, true, false)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Validate container devices with an empty instanceName to indicate profile validation.
err = containerValidDevices(d.State(), d.cluster, "", deviceConfig.NewDevices(req.Devices), false)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Update DB entry
@@ -139,14 +140,14 @@ func profilesPost(d *Daemon, r *http.Request) Response {
return err
})
if err != nil {
- return SmartError(
+ return response.SmartError(
fmt.Errorf("Error inserting %s into database: %s", req.Name, err))
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}
-func profileGet(d *Daemon, r *http.Request) Response {
+func profileGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
@@ -172,7 +173,7 @@ func profileGet(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// For backward-compatibility, we strip the "?project" query parameter
@@ -185,10 +186,10 @@ func profileGet(d *Daemon, r *http.Request) Response {
}
etag := []interface{}{resp.Config, resp.Description, resp.Devices}
- return SyncResponseETag(true, resp, etag)
+ return response.SyncResponseETag(true, resp, etag)
}
-func profilePut(d *Daemon, r *http.Request) Response {
+func profilePut(d *Daemon, r *http.Request) response.Response {
// Get the project
project := projectParam(r)
@@ -202,11 +203,11 @@ func profilePut(d *Daemon, r *http.Request) Response {
old := api.ProfilePut{}
err := json.NewDecoder(r.Body).Decode(&old)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = doProfileUpdateCluster(d, project, name, old)
- return SmartError(err)
+ return response.SmartError(err)
}
var id int64
@@ -233,19 +234,19 @@ func profilePut(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate the ETag
etag := []interface{}{profile.Config, profile.Description, profile.Devices}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.ProfilePut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = doProfileUpdate(d, project, name, id, profile, req)
@@ -254,21 +255,21 @@ func profilePut(d *Daemon, r *http.Request) Response {
// Notify all other nodes. If a node is down, it will be ignored.
notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAlive)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
return client.UseProject(project).UpdateProfile(name, profile.ProfilePut, "")
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
- return SmartError(err)
+ return response.SmartError(err)
}
-func profilePatch(d *Daemon, r *http.Request) Response {
+func profilePatch(d *Daemon, r *http.Request) response.Response {
// Get the project
project := projectParam(r)
@@ -299,19 +300,19 @@ func profilePatch(d *Daemon, r *http.Request) Response {
return nil
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate the ETag
etag := []interface{}{profile.Config, profile.Description, profile.Devices}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
body, err := ioutil.ReadAll(r.Body)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
rdr1 := ioutil.NopCloser(bytes.NewBuffer(body))
@@ -319,12 +320,12 @@ func profilePatch(d *Daemon, r *http.Request) Response {
reqRaw := shared.Jmap{}
if err := json.NewDecoder(rdr1).Decode(&reqRaw); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
req := api.ProfilePut{}
if err := json.NewDecoder(rdr2).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Get Description
@@ -357,34 +358,34 @@ func profilePatch(d *Daemon, r *http.Request) Response {
}
}
- return SmartError(doProfileUpdate(d, project, name, id, profile, req))
+ return response.SmartError(doProfileUpdate(d, project, name, id, profile, req))
}
// The handler for the post operation.
-func profilePost(d *Daemon, r *http.Request) Response {
+func profilePost(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
if name == "default" {
- return Forbidden(errors.New("The 'default' profile cannot be renamed"))
+ return response.Forbidden(errors.New("The 'default' profile cannot be renamed"))
}
req := api.ProfilePost{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Profile names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Profile names may not contain slashes"))
}
if shared.StringInSlice(req.Name, []string{".", ".."}) {
- return BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
+ return response.BadRequest(fmt.Errorf("Invalid profile name '%s'", req.Name))
}
err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -406,19 +407,19 @@ func profilePost(d *Daemon, r *http.Request) Response {
return tx.ProfileRename(project, name, req.Name)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/profiles/%s", version.APIVersion, req.Name))
}
// The handler for the delete operation.
-func profileDelete(d *Daemon, r *http.Request) Response {
+func profileDelete(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
name := mux.Vars(r)["name"]
if name == "default" {
- return Forbidden(errors.New("The 'default' profile cannot be deleted"))
+ return response.Forbidden(errors.New("The 'default' profile cannot be deleted"))
}
err := d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -442,8 +443,8 @@ func profileDelete(d *Daemon, r *http.Request) Response {
return tx.ProfileDelete(project, name)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
diff --git a/lxd/resources.go b/lxd/resources.go
index fba15ac86f..32ddee2d50 100644
--- a/lxd/resources.go
+++ b/lxd/resources.go
@@ -6,6 +6,7 @@ import (
"github.com/gorilla/mux"
"github.com/lxc/lxd/lxd/resources"
+ "github.com/lxc/lxd/lxd/response"
)
var api10ResourcesCmd = APIEndpoint{
@@ -22,47 +23,47 @@ var storagePoolResourcesCmd = APIEndpoint{
// /1.0/resources
// Get system resources
-func api10ResourcesGet(d *Daemon, r *http.Request) Response {
+func api10ResourcesGet(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
// Get the local resource usage
res, err := resources.GetResources()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponse(true, res)
+ return response.SyncResponse(true, res)
}
// /1.0/storage-pools/{name}/resources
// Get resources for a specific storage pool
-func storagePoolResourcesGet(d *Daemon, r *http.Request) Response {
+func storagePoolResourcesGet(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
// Get the existing storage pool
poolName := mux.Vars(r)["name"]
s, err := storagePoolInit(d.State(), poolName)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
err = s.StoragePoolCheck()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
res, err := s.StoragePoolResources()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return SyncResponse(true, &res)
+ return response.SyncResponse(true, &res)
}
diff --git a/lxd/response.go b/lxd/response.go
index 7bca1b4e17..6e80cb7c01 100644
--- a/lxd/response.go
+++ b/lxd/response.go
@@ -1,169 +1,17 @@
package main
import (
- "bytes"
- "database/sql"
- "encoding/json"
- "fmt"
- "io"
- "mime/multipart"
"net/http"
- "os"
- "time"
- "github.com/canonical/go-dqlite/driver"
- sqlite3 "github.com/mattn/go-sqlite3"
- "github.com/pkg/errors"
-
- lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/util"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/version"
+ "github.com/lxc/lxd/lxd/response"
)
-type Response interface {
- Render(w http.ResponseWriter) error
- String() string
-}
-
-// Sync response
-type syncResponse struct {
- success bool
- etag interface{}
- metadata interface{}
- location string
- code int
- headers map[string]string
-}
-
-func (r *syncResponse) Render(w http.ResponseWriter) error {
- // Set an appropriate ETag header
- if r.etag != nil {
- etag, err := util.EtagHash(r.etag)
- if err == nil {
- w.Header().Set("ETag", etag)
- }
- }
-
- // Prepare the JSON response
- status := api.Success
- if !r.success {
- status = api.Failure
- }
-
- if r.headers != nil {
- for h, v := range r.headers {
- w.Header().Set(h, v)
- }
- }
-
- if r.location != "" {
- w.Header().Set("Location", r.location)
- code := r.code
- if code == 0 {
- code = 201
- }
- w.WriteHeader(code)
- }
-
- resp := api.ResponseRaw{
- Type: api.SyncResponse,
- Status: status.String(),
- StatusCode: int(status),
- Metadata: r.metadata,
- }
-
- return util.WriteJSON(w, resp, debug)
-}
-
-func (r *syncResponse) String() string {
- if r.success {
- return "success"
- }
-
- return "failure"
-}
-
-func SyncResponse(success bool, metadata interface{}) Response {
- return &syncResponse{success: success, metadata: metadata}
-}
-
-func SyncResponseETag(success bool, metadata interface{}, etag interface{}) Response {
- return &syncResponse{success: success, metadata: metadata, etag: etag}
-}
-
-func SyncResponseLocation(success bool, metadata interface{}, location string) Response {
- return &syncResponse{success: success, metadata: metadata, location: location}
-}
-
-func SyncResponseRedirect(address string) Response {
- return &syncResponse{success: true, location: address, code: http.StatusPermanentRedirect}
-}
-
-func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]string) Response {
- return &syncResponse{success: success, metadata: metadata, headers: headers}
-}
-
-var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
-
-type forwardedResponse struct {
- client lxd.InstanceServer
- request *http.Request
-}
-
-func (r *forwardedResponse) Render(w http.ResponseWriter) error {
- info, err := r.client.GetConnectionInfo()
- if err != nil {
- return err
- }
-
- url := fmt.Sprintf("%s%s", info.Addresses[0], r.request.URL.RequestURI())
- forwarded, err := http.NewRequest(r.request.Method, url, r.request.Body)
- if err != nil {
- return err
- }
- for key := range r.request.Header {
- forwarded.Header.Set(key, r.request.Header.Get(key))
- }
-
- httpClient, err := r.client.GetHTTPClient()
- if err != nil {
- return err
- }
- response, err := httpClient.Do(forwarded)
- if err != nil {
- return err
- }
-
- for key := range response.Header {
- w.Header().Set(key, response.Header.Get(key))
- }
-
- w.WriteHeader(response.StatusCode)
- _, err = io.Copy(w, response.Body)
- return err
-}
-
-func (r *forwardedResponse) String() string {
- return fmt.Sprintf("request to %s", r.request.URL)
-}
-
-// ForwardedResponse takes a request directed to a node and forwards it to
-// another node, writing back the response it gegs.
-func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
- return &forwardedResponse{
- client: client,
- request: request,
- }
-}
-
// ForwardedResponseIfTargetIsRemote redirects a request to the request has a
// targetNode parameter pointing to a node which is not the local one.
-func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Response {
+func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) response.Response {
targetNode := queryParam(request, "target")
if targetNode == "" {
return nil
@@ -173,7 +21,7 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
// this very same node).
address, err := cluster.ResolveTarget(d.cluster, targetNode)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if address != "" {
@@ -181,9 +29,9 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
cert := d.endpoints.NetworkCert()
client, err := cluster.Connect(address, cert, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return ForwardedResponse(client, request)
+ return response.ForwardedResponse(client, request)
}
return nil
@@ -192,7 +40,7 @@ func ForwardedResponseIfTargetIsRemote(d *Daemon, request *http.Request) Respons
// ForwardedResponseIfContainerIsRemote redirects a request to the node running
// the container with the given name. If the container is local, nothing gets
// done and nil is returned.
-func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string, instanceType instance.Type) (Response, error) {
+func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, name string, instanceType instance.Type) (response.Response, error) {
cert := d.endpoints.NetworkCert()
client, err := cluster.ConnectIfContainerIsRemote(d.cluster, project, name, cert, instanceType)
if err != nil {
@@ -201,7 +49,7 @@ func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, n
if client == nil {
return nil, nil
}
- return ForwardedResponse(client, r), nil
+ return response.ForwardedResponse(client, r), nil
}
// ForwardedResponseIfVolumeIsRemote redirects a request to the node hosting
@@ -211,7 +59,7 @@ func ForwardedResponseIfContainerIsRemote(d *Daemon, r *http.Request, project, n
//
// This is used when no targetNode is specified, and saves users some typing
// when the volume name/type is unique to a node.
-func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64, volumeName string, volumeType int) Response {
+func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64, volumeName string, volumeType int) response.Response {
if queryParam(r, "target") != "" {
return nil
}
@@ -219,332 +67,10 @@ func ForwardedResponseIfVolumeIsRemote(d *Daemon, r *http.Request, poolID int64,
cert := d.endpoints.NetworkCert()
client, err := cluster.ConnectIfVolumeIsRemote(d.cluster, poolID, volumeName, volumeType, cert)
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
if client == nil {
return nil
}
- return ForwardedResponse(client, r)
-}
-
-// File transfer response
-type fileResponseEntry struct {
- identifier string
- path string
- filename string
- buffer []byte /* either a path or a buffer must be provided */
-}
-
-type fileResponse struct {
- req *http.Request
- files []fileResponseEntry
- headers map[string]string
- removeAfterServe bool
-}
-
-func (r *fileResponse) Render(w http.ResponseWriter) error {
- if r.headers != nil {
- for k, v := range r.headers {
- w.Header().Set(k, v)
- }
- }
-
- // No file, well, it's easy then
- if len(r.files) == 0 {
- return nil
- }
-
- // For a single file, return it inline
- if len(r.files) == 1 {
- var rs io.ReadSeeker
- var mt time.Time
- var sz int64
-
- if r.files[0].path == "" {
- rs = bytes.NewReader(r.files[0].buffer)
- mt = time.Now()
- sz = int64(len(r.files[0].buffer))
- } else {
- f, err := os.Open(r.files[0].path)
- if err != nil {
- return err
- }
- defer f.Close()
-
- fi, err := f.Stat()
- if err != nil {
- return err
- }
-
- mt = fi.ModTime()
- sz = fi.Size()
- rs = f
- }
-
- w.Header().Set("Content-Type", "application/octet-stream")
- w.Header().Set("Content-Length", fmt.Sprintf("%d", sz))
- w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].filename))
-
- http.ServeContent(w, r.req, r.files[0].filename, mt, rs)
- if r.files[0].path != "" && r.removeAfterServe {
- err := os.Remove(r.files[0].path)
- if err != nil {
- return err
- }
- }
-
- return nil
- }
-
- // Now the complex multipart answer
- body := &bytes.Buffer{}
- mw := multipart.NewWriter(body)
-
- for _, entry := range r.files {
- var rd io.Reader
- if entry.path != "" {
- fd, err := os.Open(entry.path)
- if err != nil {
- return err
- }
- defer fd.Close()
- rd = fd
- } else {
- rd = bytes.NewReader(entry.buffer)
- }
-
- fw, err := mw.CreateFormFile(entry.identifier, entry.filename)
- if err != nil {
- return err
- }
-
- _, err = io.Copy(fw, rd)
- if err != nil {
- return err
- }
- }
- mw.Close()
-
- w.Header().Set("Content-Type", mw.FormDataContentType())
- w.Header().Set("Content-Length", fmt.Sprintf("%d", body.Len()))
-
- _, err := io.Copy(w, body)
- return err
-}
-
-func (r *fileResponse) String() string {
- return fmt.Sprintf("%d files", len(r.files))
-}
-
-func FileResponse(r *http.Request, files []fileResponseEntry, headers map[string]string, removeAfterServe bool) Response {
- return &fileResponse{r, files, headers, removeAfterServe}
-}
-
-// Operation response
-type operationResponse struct {
- op *operation
-}
-
-func (r *operationResponse) Render(w http.ResponseWriter) error {
- _, err := r.op.Run()
- if err != nil {
- return err
- }
-
- url, md, err := r.op.Render()
- if err != nil {
- return err
- }
-
- body := api.ResponseRaw{
- Type: api.AsyncResponse,
- Status: api.OperationCreated.String(),
- StatusCode: int(api.OperationCreated),
- Operation: url,
- Metadata: md,
- }
-
- w.Header().Set("Location", url)
- w.WriteHeader(202)
-
- return util.WriteJSON(w, body, debug)
-}
-
-func (r *operationResponse) String() string {
- _, md, err := r.op.Render()
- if err != nil {
- return fmt.Sprintf("error: %s", err)
- }
-
- return md.ID
-}
-
-func OperationResponse(op *operation) Response {
- return &operationResponse{op}
-}
-
-// Forwarded operation response.
-//
-// Returned when the operation has been created on another node
-type forwardedOperationResponse struct {
- op *api.Operation
- project string
-}
-
-func (r *forwardedOperationResponse) Render(w http.ResponseWriter) error {
- url := fmt.Sprintf("/%s/operations/%s", version.APIVersion, r.op.ID)
- if r.project != "" {
- url += fmt.Sprintf("?project=%s", r.project)
- }
-
- body := api.ResponseRaw{
- Type: api.AsyncResponse,
- Status: api.OperationCreated.String(),
- StatusCode: int(api.OperationCreated),
- Operation: url,
- Metadata: r.op,
- }
-
- w.Header().Set("Location", url)
- w.WriteHeader(202)
-
- return util.WriteJSON(w, body, debug)
-}
-
-func (r *forwardedOperationResponse) String() string {
- return r.op.ID
-}
-
-// ForwardedOperationResponse creates a response that forwards the metadata of
-// an operation created on another node.
-func ForwardedOperationResponse(project string, op *api.Operation) Response {
- return &forwardedOperationResponse{
- op: op,
- project: project,
- }
-}
-
-// Error response
-type errorResponse struct {
- code int
- msg string
-}
-
-func (r *errorResponse) String() string {
- return r.msg
-}
-
-func (r *errorResponse) Render(w http.ResponseWriter) error {
- var output io.Writer
-
- buf := &bytes.Buffer{}
- output = buf
- var captured *bytes.Buffer
- if debug {
- captured = &bytes.Buffer{}
- output = io.MultiWriter(buf, captured)
- }
-
- err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
-
- if err != nil {
- return err
- }
-
- if debug {
- shared.DebugJson(captured)
- }
-
- w.Header().Set("Content-Type", "application/json")
- w.Header().Set("X-Content-Type-Options", "nosniff")
- w.WriteHeader(r.code)
- fmt.Fprintln(w, buf.String())
-
- return nil
-}
-
-func NotImplemented(err error) Response {
- message := "not implemented"
- if err != nil {
- message = err.Error()
- }
- return &errorResponse{http.StatusNotImplemented, message}
-}
-
-func NotFound(err error) Response {
- message := "not found"
- if err != nil {
- message = err.Error()
- }
- return &errorResponse{http.StatusNotFound, message}
-}
-
-func Forbidden(err error) Response {
- message := "not authorized"
- if err != nil {
- message = err.Error()
- }
- return &errorResponse{http.StatusForbidden, message}
-}
-
-func Conflict(err error) Response {
- message := "already exists"
- if err != nil {
- message = err.Error()
- }
- return &errorResponse{http.StatusConflict, message}
-}
-
-func Unavailable(err error) Response {
- message := "unavailable"
- if err != nil {
- message = err.Error()
- }
- return &errorResponse{http.StatusServiceUnavailable, message}
-}
-
-func BadRequest(err error) Response {
- return &errorResponse{http.StatusBadRequest, err.Error()}
-}
-
-func InternalError(err error) Response {
- return &errorResponse{http.StatusInternalServerError, err.Error()}
-}
-
-func PreconditionFailed(err error) Response {
- return &errorResponse{http.StatusPreconditionFailed, err.Error()}
-}
-
-/*
- * SmartError returns the right error message based on err.
- */
-func SmartError(err error) Response {
- if err == nil {
- return EmptySyncResponse
- }
-
- switch errors.Cause(err) {
- case os.ErrNotExist, sql.ErrNoRows, db.ErrNoSuchObject:
- if errors.Cause(err) != err {
- return NotFound(err)
- }
-
- return NotFound(nil)
- case os.ErrPermission:
- if errors.Cause(err) != err {
- return Forbidden(err)
- }
-
- return Forbidden(nil)
- case db.ErrAlreadyDefined, sqlite3.ErrConstraintUnique:
- if errors.Cause(err) != err {
- return Conflict(err)
- }
-
- return Conflict(nil)
- case driver.ErrNoAvailableLeader:
- return Unavailable(err)
- default:
- return InternalError(err)
- }
+ return response.ForwardedResponse(client, r)
}
diff --git a/lxd/response/response.go b/lxd/response/response.go
new file mode 100644
index 0000000000..713bfa6f1c
--- /dev/null
+++ b/lxd/response/response.go
@@ -0,0 +1,425 @@
+package response
+
+import (
+ "bytes"
+ "database/sql"
+ "encoding/json"
+ "fmt"
+ "io"
+ "mime/multipart"
+ "net/http"
+ "os"
+ "time"
+
+ "github.com/canonical/go-dqlite/driver"
+ lxd "github.com/lxc/lxd/client"
+ "github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/util"
+ "github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/api"
+ "github.com/mattn/go-sqlite3"
+ "github.com/pkg/errors"
+)
+
+var debug bool
+
+// Init sets the debug variable to the provided value.
+func Init(d bool) {
+ debug = d
+}
+
+// Response represents an API response
+type Response interface {
+ Render(w http.ResponseWriter) error
+ String() string
+}
+
+// Sync response
+type syncResponse struct {
+ success bool
+ etag interface{}
+ metadata interface{}
+ location string
+ code int
+ headers map[string]string
+}
+
+// EmptySyncResponse represents an empty syncResponse.
+var EmptySyncResponse = &syncResponse{success: true, metadata: make(map[string]interface{})}
+
+// SyncResponse returns a new syncResponse with the success and metadata fields
+// set to the provided values.
+func SyncResponse(success bool, metadata interface{}) Response {
+ return &syncResponse{success: success, metadata: metadata}
+}
+
+// SyncResponseETag returns a new syncResponse with an etag.
+func SyncResponseETag(success bool, metadata interface{}, etag interface{}) Response {
+ return &syncResponse{success: success, metadata: metadata, etag: etag}
+}
+
+// SyncResponseLocation returns a new syncResponse with a location.
+func SyncResponseLocation(success bool, metadata interface{}, location string) Response {
+ return &syncResponse{success: success, metadata: metadata, location: location}
+}
+
+// SyncResponseRedirect returns a new syncResponse with a location, indicating
+// a permanent redirect.
+func SyncResponseRedirect(address string) Response {
+ return &syncResponse{success: true, location: address, code: http.StatusPermanentRedirect}
+}
+
+// SyncResponseHeaders returns a new syncResponse with headers.
+func SyncResponseHeaders(success bool, metadata interface{}, headers map[string]string) Response {
+ return &syncResponse{success: success, metadata: metadata, headers: headers}
+}
+
+func (r *syncResponse) Render(w http.ResponseWriter) error {
+ // Set an appropriate ETag header
+ if r.etag != nil {
+ etag, err := util.EtagHash(r.etag)
+ if err == nil {
+ w.Header().Set("ETag", etag)
+ }
+ }
+
+ // Prepare the JSON response
+ status := api.Success
+ if !r.success {
+ status = api.Failure
+ }
+
+ if r.headers != nil {
+ for h, v := range r.headers {
+ w.Header().Set(h, v)
+ }
+ }
+
+ if r.location != "" {
+ w.Header().Set("Location", r.location)
+ code := r.code
+ if code == 0 {
+ code = 201
+ }
+ w.WriteHeader(code)
+ }
+
+ resp := api.ResponseRaw{
+ Type: api.SyncResponse,
+ Status: status.String(),
+ StatusCode: int(status),
+ Metadata: r.metadata,
+ }
+
+ return util.WriteJSON(w, resp, debug)
+}
+
+func (r *syncResponse) String() string {
+ if r.success {
+ return "success"
+ }
+
+ return "failure"
+}
+
+// Error response
+type errorResponse struct {
+ code int
+ msg string
+}
+
+// ErrorResponse returns an error response with the given code and msg.
+func ErrorResponse(code int, msg string) Response {
+ return &errorResponse{code, msg}
+}
+
+// BadRequest returns a bad request response (400) with the given error.
+func BadRequest(err error) Response {
+ return &errorResponse{http.StatusBadRequest, err.Error()}
+}
+
+// Conflict returns a conflict response (409) with the given error.
+func Conflict(err error) Response {
+ message := "already exists"
+ if err != nil {
+ message = err.Error()
+ }
+ return &errorResponse{http.StatusConflict, message}
+}
+
+// Forbidden returns a forbidden response (403) with the given error.
+func Forbidden(err error) Response {
+ message := "not authorized"
+ if err != nil {
+ message = err.Error()
+ }
+ return &errorResponse{http.StatusForbidden, message}
+}
+
+// InternalError returns an internal error response (500) with the given error.
+func InternalError(err error) Response {
+ return &errorResponse{http.StatusInternalServerError, err.Error()}
+}
+
+// NotFound returns a not found response (404) with the given error.
+func NotFound(err error) Response {
+ message := "not found"
+ if err != nil {
+ message = err.Error()
+ }
+ return &errorResponse{http.StatusNotFound, message}
+}
+
+// NotImplemented returns a not implemented response (501) with the given error.
+func NotImplemented(err error) Response {
+ message := "not implemented"
+ if err != nil {
+ message = err.Error()
+ }
+ return &errorResponse{http.StatusNotImplemented, message}
+}
+
+// PreconditionFailed returns a precondition failed response (412) with the
+// given error.
+func PreconditionFailed(err error) Response {
+ return &errorResponse{http.StatusPreconditionFailed, err.Error()}
+}
+
+// Unavailable return an unavailable response (503) with the given error.
+func Unavailable(err error) Response {
+ message := "unavailable"
+ if err != nil {
+ message = err.Error()
+ }
+ return &errorResponse{http.StatusServiceUnavailable, message}
+}
+
+// SmartError returns the right error message based on err.
+func SmartError(err error) Response {
+ if err == nil {
+ return EmptySyncResponse
+ }
+
+ switch errors.Cause(err) {
+ case os.ErrNotExist, sql.ErrNoRows, db.ErrNoSuchObject:
+ if errors.Cause(err) != err {
+ return NotFound(err)
+ }
+
+ return NotFound(nil)
+ case os.ErrPermission:
+ if errors.Cause(err) != err {
+ return Forbidden(err)
+ }
+
+ return Forbidden(nil)
+ case db.ErrAlreadyDefined, sqlite3.ErrConstraintUnique:
+ if errors.Cause(err) != err {
+ return Conflict(err)
+ }
+
+ return Conflict(nil)
+ case driver.ErrNoAvailableLeader:
+ return Unavailable(err)
+ default:
+ return InternalError(err)
+ }
+}
+
+func (r *errorResponse) String() string {
+ return r.msg
+}
+
+func (r *errorResponse) Render(w http.ResponseWriter) error {
+ var output io.Writer
+
+ buf := &bytes.Buffer{}
+ output = buf
+ var captured *bytes.Buffer
+ if debug {
+ captured = &bytes.Buffer{}
+ output = io.MultiWriter(buf, captured)
+ }
+
+ err := json.NewEncoder(output).Encode(shared.Jmap{"type": api.ErrorResponse, "error": r.msg, "error_code": r.code})
+
+ if err != nil {
+ return err
+ }
+
+ if debug {
+ shared.DebugJson(captured)
+ }
+
+ w.Header().Set("Content-Type", "application/json")
+ w.Header().Set("X-Content-Type-Options", "nosniff")
+ w.WriteHeader(r.code)
+ fmt.Fprintln(w, buf.String())
+
+ return nil
+}
+
+// FileResponseEntry represents a file response entry.
+type FileResponseEntry struct {
+ Identifier string
+ Path string
+ Filename string
+ Buffer []byte /* either a path or a buffer must be provided */
+}
+
+type fileResponse struct {
+ req *http.Request
+ files []FileResponseEntry
+ headers map[string]string
+ removeAfterServe bool
+}
+
+// FileResponse returns a new file response.
+func FileResponse(r *http.Request, files []FileResponseEntry, headers map[string]string, removeAfterServe bool) Response {
+ return &fileResponse{r, files, headers, removeAfterServe}
+}
+
+func (r *fileResponse) Render(w http.ResponseWriter) error {
+ if r.headers != nil {
+ for k, v := range r.headers {
+ w.Header().Set(k, v)
+ }
+ }
+
+ // No file, well, it's easy then
+ if len(r.files) == 0 {
+ return nil
+ }
+
+ // For a single file, return it inline
+ if len(r.files) == 1 {
+ var rs io.ReadSeeker
+ var mt time.Time
+ var sz int64
+
+ if r.files[0].Path == "" {
+ rs = bytes.NewReader(r.files[0].Buffer)
+ mt = time.Now()
+ sz = int64(len(r.files[0].Buffer))
+ } else {
+ f, err := os.Open(r.files[0].Path)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+
+ fi, err := f.Stat()
+ if err != nil {
+ return err
+ }
+
+ mt = fi.ModTime()
+ sz = fi.Size()
+ rs = f
+ }
+
+ w.Header().Set("Content-Type", "application/octet-stream")
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", sz))
+ w.Header().Set("Content-Disposition", fmt.Sprintf("inline;filename=%s", r.files[0].Filename))
+
+ http.ServeContent(w, r.req, r.files[0].Filename, mt, rs)
+ if r.files[0].Path != "" && r.removeAfterServe {
+ err := os.Remove(r.files[0].Path)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+ }
+
+ // Now the complex multipart answer
+ body := &bytes.Buffer{}
+ mw := multipart.NewWriter(body)
+
+ for _, entry := range r.files {
+ var rd io.Reader
+ if entry.Path != "" {
+ fd, err := os.Open(entry.Path)
+ if err != nil {
+ return err
+ }
+ defer fd.Close()
+ rd = fd
+ } else {
+ rd = bytes.NewReader(entry.Buffer)
+ }
+
+ fw, err := mw.CreateFormFile(entry.Identifier, entry.Filename)
+ if err != nil {
+ return err
+ }
+
+ _, err = io.Copy(fw, rd)
+ if err != nil {
+ return err
+ }
+ }
+ mw.Close()
+
+ w.Header().Set("Content-Type", mw.FormDataContentType())
+ w.Header().Set("Content-Length", fmt.Sprintf("%d", body.Len()))
+
+ _, err := io.Copy(w, body)
+ return err
+}
+
+func (r *fileResponse) String() string {
+ return fmt.Sprintf("%d files", len(r.files))
+}
+
+type forwardedResponse struct {
+ client lxd.InstanceServer
+ request *http.Request
+}
+
+// ForwardedResponse takes a request directed to a node and forwards it to
+// another node, writing back the response it gegs.
+func ForwardedResponse(client lxd.InstanceServer, request *http.Request) Response {
+ return &forwardedResponse{
+ client: client,
+ request: request,
+ }
+}
+
+func (r *forwardedResponse) Render(w http.ResponseWriter) error {
+ info, err := r.client.GetConnectionInfo()
+ if err != nil {
+ return err
+ }
+
+ url := fmt.Sprintf("%s%s", info.Addresses[0], r.request.URL.RequestURI())
+ forwarded, err := http.NewRequest(r.request.Method, url, r.request.Body)
+ if err != nil {
+ return err
+ }
+ for key := range r.request.Header {
+ forwarded.Header.Set(key, r.request.Header.Get(key))
+ }
+
+ httpClient, err := r.client.GetHTTPClient()
+ if err != nil {
+ return err
+ }
+ response, err := httpClient.Do(forwarded)
+ if err != nil {
+ return err
+ }
+
+ for key := range response.Header {
+ w.Header().Set(key, response.Header.Get(key))
+ }
+
+ w.WriteHeader(response.StatusCode)
+ _, err = io.Copy(w, response.Body)
+ return err
+}
+
+func (r *forwardedResponse) String() string {
+ return fmt.Sprintf("request to %s", r.request.URL)
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index b0191ee4aa..dd5d5d188b 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -15,6 +15,7 @@ import (
"github.com/lxc/lxd/lxd/device"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
@@ -248,10 +249,10 @@ type storage interface {
// already present on the target instance as an exercise for the
// enterprising developer.
MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error)
- MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error
+ MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error
StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error)
- StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error
+ StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error
}
func storageCoreInit(driver string) (storage, error) {
@@ -739,8 +740,8 @@ func resetContainerDiskIdmap(container container, srcIdmap *idmap.IdmapSet) erro
return nil
}
-func progressWrapperRender(op *operation, key string, description string, progressInt int64, speedInt int64) {
- meta := op.metadata
+func progressWrapperRender(op *operations.Operation, key string, description string, progressInt int64, speedInt int64) {
+ meta := op.Metadata()
if meta == nil {
meta = make(map[string]interface{})
}
@@ -757,7 +758,7 @@ func progressWrapperRender(op *operation, key string, description string, progre
}
// StorageProgressReader reports the read progress.
-func StorageProgressReader(op *operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
+func StorageProgressReader(op *operations.Operation, key string, description string) func(io.ReadCloser) io.ReadCloser {
return func(reader io.ReadCloser) io.ReadCloser {
if op == nil {
return reader
@@ -779,7 +780,7 @@ func StorageProgressReader(op *operation, key string, description string) func(i
}
// StorageProgressWriter reports the write progress.
-func StorageProgressWriter(op *operation, key string, description string) func(io.WriteCloser) io.WriteCloser {
+func StorageProgressWriter(op *operations.Operation, key string, description string) func(io.WriteCloser) io.WriteCloser {
return func(writer io.WriteCloser) io.WriteCloser {
if op == nil {
return writer
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index f339b710a3..9233d27b60 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -17,6 +17,7 @@ import (
"golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
@@ -2481,7 +2482,7 @@ func (s *storageBtrfs) MigrationSource(args MigrationSourceArgs) (MigrationStora
return sourceDriver, nil
}
-func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
if s.s.OS.RunningInUserNS {
return rsyncMigrationSink(conn, op, args)
}
@@ -2961,7 +2962,7 @@ func (s *storageBtrfs) StorageMigrationSource(args MigrationSourceArgs) (Migrati
return rsyncStorageMigrationSource(args)
}
-func (s *storageBtrfs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageBtrfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 0abd661940..8c0282f519 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -16,6 +16,7 @@ import (
"github.com/lxc/lxd/lxd/db"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
@@ -2689,7 +2690,7 @@ func (s *storageCeph) StorageMigrationSource(args MigrationSourceArgs) (Migratio
return rsyncStorageMigrationSource(args)
}
-func (s *storageCeph) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCeph) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
@@ -2874,7 +2875,7 @@ func (s *storageCeph) MigrationSource(args MigrationSourceArgs) (MigrationStorag
return &driver, nil
}
-func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCeph) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
// Check that we received a valid root disk device with a pool property
// set.
parentStoragePool := ""
diff --git a/lxd/storage_cephfs.go b/lxd/storage_cephfs.go
index 0c3f8b9aa3..ed3f219ece 100644
--- a/lxd/storage_cephfs.go
+++ b/lxd/storage_cephfs.go
@@ -14,6 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
@@ -728,7 +729,7 @@ func (s *storageCephFs) MigrationSource(args MigrationSourceArgs) (MigrationStor
return rsyncMigrationSource(args)
}
-func (s *storageCephFs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCephFs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncMigrationSink(conn, op, args)
}
@@ -824,7 +825,7 @@ func (s *storageCephFs) StorageMigrationSource(args MigrationSourceArgs) (Migrat
return rsyncStorageMigrationSource(args)
}
-func (s *storageCephFs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageCephFs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index ff5973a8f7..c24eecaae4 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -13,6 +13,7 @@ import (
"golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/lxd/storage/quota"
@@ -1306,7 +1307,7 @@ func (s *storageDir) MigrationSource(args MigrationSourceArgs) (MigrationStorage
return rsyncMigrationSource(args)
}
-func (s *storageDir) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageDir) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncMigrationSink(conn, op, args)
}
@@ -1442,7 +1443,7 @@ func (s *storageDir) StorageMigrationSource(args MigrationSourceArgs) (Migration
return rsyncStorageMigrationSource(args)
}
-func (s *storageDir) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageDir) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index b3ef0b20eb..d41704a8bf 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -14,6 +14,7 @@ import (
"github.com/pkg/errors"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
@@ -2068,7 +2069,7 @@ func (s *storageLvm) MigrationSource(args MigrationSourceArgs) (MigrationStorage
return rsyncMigrationSource(args)
}
-func (s *storageLvm) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageLvm) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncMigrationSink(conn, op, args)
}
@@ -2265,7 +2266,7 @@ func (s *storageLvm) StorageMigrationSource(args MigrationSourceArgs) (Migration
return rsyncStorageMigrationSource(args)
}
-func (s *storageLvm) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageLvm) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 8d329cd5fa..a8f42d06bb 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -10,6 +10,7 @@ import (
deviceConfig "github.com/lxc/lxd/lxd/device/config"
"github.com/lxc/lxd/lxd/instance"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
@@ -23,7 +24,7 @@ type MigrationStorageSourceDriver interface {
/* send any bits of the container/snapshots that are possible while the
* container is still running.
*/
- SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error
+ SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error
/* send the final bits (e.g. a final delta snapshot for zfs, btrfs, or
* do a final rsync) of the fs after the container has been
@@ -37,7 +38,7 @@ type MigrationStorageSourceDriver interface {
*/
Cleanup()
- SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error
+ SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error
}
type rsyncStorageSourceDriver struct {
@@ -46,7 +47,7 @@ type rsyncStorageSourceDriver struct {
rsyncFeatures []string
}
-func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
ourMount, err := storage.StoragePoolVolumeMount()
if err != nil {
return err
@@ -90,7 +91,7 @@ func (s rsyncStorageSourceDriver) SendStorageVolume(conn *websocket.Conn, op *op
return nil
}
-func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s rsyncStorageSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
ctName, _, _ := shared.ContainerGetParentAndSnapshotName(s.container.Name())
if !containerOnly {
@@ -220,7 +221,7 @@ func snapshotProtobufToContainerArgs(project string, containerName string, snap
return args
}
-func rsyncStorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func rsyncStorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
err := args.Storage.StoragePoolVolumeCreate()
if err != nil {
return err
@@ -281,7 +282,7 @@ func rsyncStorageMigrationSink(conn *websocket.Conn, op *operation, args Migrati
return RsyncRecv(path, conn, wrapper, args.RsyncFeatures)
}
-func rsyncMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func rsyncMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
ourStart, err := args.Instance.StorageStart()
if err != nil {
return err
diff --git a/lxd/storage_migration_btrfs.go b/lxd/storage_migration_btrfs.go
index 166fd8599c..15a3c936c3 100644
--- a/lxd/storage_migration_btrfs.go
+++ b/lxd/storage_migration_btrfs.go
@@ -9,6 +9,7 @@ import (
"github.com/gorilla/websocket"
+ "github.com/lxc/lxd/lxd/operations"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/logger"
@@ -67,7 +68,7 @@ func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string
return err
}
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
_, containerPool, _ := s.container.Storage().GetContainerPoolInfo()
containerName := s.container.Name()
containersPath := driver.GetContainerMountPoint("default", containerPool, "")
@@ -178,7 +179,7 @@ func (s *btrfsMigrationSourceDriver) Cleanup() {
}
}
-func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
msg := fmt.Sprintf("Function not implemented")
logger.Errorf(msg)
return fmt.Errorf(msg)
diff --git a/lxd/storage_migration_ceph.go b/lxd/storage_migration_ceph.go
index 90fa2b80a5..aa13e326d4 100644
--- a/lxd/storage_migration_ceph.go
+++ b/lxd/storage_migration_ceph.go
@@ -9,6 +9,7 @@ import (
"github.com/gorilla/websocket"
"github.com/pborman/uuid"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/logger"
@@ -73,7 +74,7 @@ func (s *rbdMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwl
}
func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
- op *operation, bwlimit string, containerOnly bool) error {
+ op *operations.Operation, bwlimit string, containerOnly bool) error {
containerName := s.container.Name()
if s.container.IsSnapshot() {
// ContainerSnapshotStart() will create the clone that is
@@ -149,7 +150,7 @@ func (s *rbdMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn,
return nil
}
-func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *rbdMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
msg := fmt.Sprintf("Function not implemented")
logger.Errorf(msg)
return fmt.Errorf(msg)
diff --git a/lxd/storage_migration_zfs.go b/lxd/storage_migration_zfs.go
index fe94bf6eab..f3bc582efe 100644
--- a/lxd/storage_migration_zfs.go
+++ b/lxd/storage_migration_zfs.go
@@ -9,6 +9,7 @@ import (
"github.com/gorilla/websocket"
"github.com/pborman/uuid"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/logger"
@@ -78,7 +79,7 @@ func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zf
return err
}
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operation, bwlimit string, containerOnly bool) error {
+func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
if s.instance.IsSnapshot() {
_, snapOnlyName, _ := shared.ContainerGetParentAndSnapshotName(s.instance.Name())
snapshotName := fmt.Sprintf("snapshot-%s", snapOnlyName)
@@ -139,7 +140,7 @@ func (s *zfsMigrationSourceDriver) Cleanup() {
}
}
-func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operation, bwlimit string, storage storage, volumeOnly bool) error {
+func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
msg := fmt.Sprintf("Function not implemented")
logger.Errorf(msg)
return fmt.Errorf(msg)
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 3b9520928e..d3ab28b093 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -6,6 +6,7 @@ import (
"github.com/gorilla/websocket"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/state"
"github.com/lxc/lxd/shared/api"
"github.com/lxc/lxd/shared/ioprogress"
@@ -211,7 +212,7 @@ func (s *storageMock) MigrationSource(args MigrationSourceArgs) (MigrationStorag
return nil, nil
}
-func (s *storageMock) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageMock) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return nil
}
@@ -231,7 +232,7 @@ func (s *storageMock) StorageMigrationSource(args MigrationSourceArgs) (Migratio
return nil, nil
}
-func (s *storageMock) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageMock) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return nil
}
diff --git a/lxd/storage_pools.go b/lxd/storage_pools.go
index a9c5e60b10..6634c4c240 100644
--- a/lxd/storage_pools.go
+++ b/lxd/storage_pools.go
@@ -13,6 +13,7 @@ import (
lxd "github.com/lxc/lxd/client"
"github.com/lxc/lxd/lxd/cluster"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/response"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -41,12 +42,12 @@ var storagePoolCmd = APIEndpoint{
// /1.0/storage-pools
// List all storage pools.
-func storagePoolsGet(d *Daemon, r *http.Request) Response {
+func storagePoolsGet(d *Daemon, r *http.Request) response.Response {
recursion := util.IsRecursionRequest(r)
pools, err := d.cluster.StoragePools()
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
resultString := []string{}
@@ -63,7 +64,7 @@ func storagePoolsGet(d *Daemon, r *http.Request) Response {
// Get all users of the storage pool.
poolUsedBy, err := storagePoolUsedByGet(d.State(), plID, pool)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
pl.UsedBy = poolUsedBy
@@ -72,15 +73,15 @@ func storagePoolsGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
// /1.0/storage-pools
// Create a storage pool.
-func storagePoolsPost(d *Daemon, r *http.Request) Response {
+func storagePoolsPost(d *Daemon, r *http.Request) response.Response {
storagePoolCreateLock.Lock()
defer storagePoolCreateLock.Unlock()
@@ -89,24 +90,24 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks.
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Storage pool names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Storage pool names may not contain slashes"))
}
if req.Driver == "" {
- return BadRequest(fmt.Errorf("No driver provided"))
+ return response.BadRequest(fmt.Errorf("No driver provided"))
}
url := fmt.Sprintf("/%s/storage-pools/%s", version.APIVersion, req.Name)
- response := SyncResponseLocation(true, nil, url)
+ resp := response.SyncResponseLocation(true, nil, url)
if isClusterNotification(r) {
// This is an internal request which triggers the actual
@@ -114,21 +115,21 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
// previously defined.
err = storagePoolValidate(req.Name, req.Driver, req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = doStoragePoolCreateInternal(
d.State(), req.Name, req.Description, req.Driver, req.Config, true)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
targetNode := queryParam(r, "target")
if targetNode == "" {
count, err := cluster.Count(d.State())
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if count == 1 {
@@ -143,9 +144,9 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
err = storagePoolsPostCluster(d, req)
}
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return response
+ return resp
}
@@ -154,13 +155,13 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
// storage config are the ones in StoragePoolNodeConfigKeys.
for key := range req.Config {
if !shared.StringInSlice(key, db.StoragePoolNodeConfigKeys) {
- return SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
+ return response.SmartError(fmt.Errorf("Config key '%s' may not be used as node-specific key", key))
}
}
err = storagePoolValidate(req.Name, req.Driver, req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
@@ -168,12 +169,12 @@ func storagePoolsPost(d *Daemon, r *http.Request) Response {
})
if err != nil {
if err == db.ErrAlreadyDefined {
- return BadRequest(fmt.Errorf("The storage pool already defined on node %s", targetNode))
+ return response.BadRequest(fmt.Errorf("The storage pool already defined on node %s", targetNode))
}
- return SmartError(err)
+ return response.SmartError(err)
}
- return response
+ return resp
}
func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
@@ -269,11 +270,11 @@ func storagePoolsPostCluster(d *Daemon, req api.StoragePoolsPost) error {
// /1.0/storage-pools/{name}
// Get a single storage pool.
-func storagePoolGet(d *Daemon, r *http.Request) Response {
+func storagePoolGet(d *Daemon, r *http.Request) response.Response {
// If a target was specified, forward the request to the relevant node.
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolName := mux.Vars(r)["name"]
@@ -281,13 +282,13 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
// Get the existing storage pool.
poolID, pool, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get all users of the storage pool.
poolUsedBy, err := storagePoolUsedByGet(d.State(), poolID, poolName)
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
pool.UsedBy = poolUsedBy
@@ -295,7 +296,7 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// If no target node is specified and the daemon is clustered, we omit
@@ -308,35 +309,35 @@ func storagePoolGet(d *Daemon, r *http.Request) Response {
etag := []interface{}{pool.Name, pool.Driver, pool.Config}
- return SyncResponseETag(true, &pool, etag)
+ return response.SyncResponseETag(true, &pool, etag)
}
// /1.0/storage-pools/{name}
// Replace pool properties.
-func storagePoolPut(d *Daemon, r *http.Request) Response {
+func storagePoolPut(d *Daemon, r *http.Request) response.Response {
poolName := mux.Vars(r)["name"]
// Get the existing storage pool.
_, dbInfo, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
req := api.StoragePoolPut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
config := dbInfo.Config
if clustered {
err := storagePoolValidateClusterConfig(req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
config = storagePoolClusterConfigForEtag(config)
}
@@ -346,13 +347,13 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
// Validate the configuration
err = storagePoolValidateConfig(poolName, dbInfo.Driver, req.Config, dbInfo.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
config = req.Config
@@ -367,51 +368,51 @@ func storagePoolPut(d *Daemon, r *http.Request) Response {
cert := d.endpoints.NetworkCert()
notifier, err := cluster.NewNotifier(d.State(), cert, cluster.NotifyAll)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
withDB := !isClusterNotification(r)
err = storagePoolUpdate(d.State(), poolName, req.Description, config, withDB)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// /1.0/storage-pools/{name}
// Change pool properties.
-func storagePoolPatch(d *Daemon, r *http.Request) Response {
+func storagePoolPatch(d *Daemon, r *http.Request) response.Response {
poolName := mux.Vars(r)["name"]
// Get the existing network
_, dbInfo, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
req := api.StoragePoolPut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
config := dbInfo.Config
if clustered {
err := storagePoolValidateClusterConfig(req.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
config = storagePoolClusterConfigForEtag(config)
}
@@ -421,7 +422,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
// Config stacking
@@ -439,7 +440,7 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
// Validate the configuration
err = storagePoolValidateConfig(poolName, dbInfo.Driver, req.Config, dbInfo.Config)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
config = req.Config
@@ -454,23 +455,23 @@ func storagePoolPatch(d *Daemon, r *http.Request) Response {
cert := d.endpoints.NetworkCert()
notifier, err := cluster.NewNotifier(d.State(), cert, cluster.NotifyAll)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
return client.UpdateStoragePool(poolName, req, r.Header.Get("If-Match"))
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
withDB := !isClusterNotification(r)
err = storagePoolUpdate(d.State(), poolName, req.Description, config, withDB)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// This helper makes sure that, when clustered, we're not changing
@@ -510,12 +511,12 @@ func storagePoolClusterFillWithNodeConfig(dbConfig, reqConfig map[string]string)
// /1.0/storage-pools/{name}
// Delete storage pool.
-func storagePoolDelete(d *Daemon, r *http.Request) Response {
+func storagePoolDelete(d *Daemon, r *http.Request) response.Response {
poolName := mux.Vars(r)["name"]
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
// If this is not an internal cluster request, check if the storage
@@ -531,19 +532,19 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
// the database.
_, pool, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if pool.Status == "Pending" {
_, err := d.cluster.StoragePoolDelete(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
s, err := storagePoolInit(d.State(), poolName)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// If this is a notification for a ceph pool deletion, we don't want to
@@ -555,49 +556,49 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
if shared.PathExists(poolMntPoint) {
err := os.RemoveAll(poolMntPoint)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
volumeNames, err := d.cluster.StoragePoolVolumesGetNames(poolID)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, volume := range volumeNames {
_, imgInfo, err := d.cluster.ImageGet("default", volume, false, false)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
err = doDeleteImageFromPool(d.State(), imgInfo.Fingerprint, poolName)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
err = s.StoragePoolDelete()
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// If this is a cluster notification, we're done, any database work
// will be done by the node that is originally serving the request.
if isClusterNotification(r) {
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
// If we are clustered, also notify all other nodes, if any.
clustered, err := cluster.Enabled(d.db)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if clustered {
notifier, err := cluster.NewNotifier(d.State(), d.endpoints.NetworkCert(), cluster.NotifyAll)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
err = notifier(func(client lxd.InstanceServer) error {
_, _, err := client.GetServer()
@@ -607,33 +608,33 @@ func storagePoolDelete(d *Daemon, r *http.Request) Response {
return client.DeleteStoragePool(poolName)
})
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
err = dbStoragePoolDeleteAndUpdateCache(d.cluster, poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, poolID int64) Response {
+func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, poolID int64) response.Response {
volumeNames, err := cluster.StoragePoolVolumesGetNames(poolID)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if len(volumeNames) > 0 {
volumes, err := cluster.StoragePoolVolumesGet("default", poolID, supportedVolumeTypes)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
for _, volume := range volumes {
if volume.Type != "image" {
- return BadRequest(fmt.Errorf("storage pool \"%s\" has volumes attached to it", poolName))
+ return response.BadRequest(fmt.Errorf("storage pool \"%s\" has volumes attached to it", poolName))
}
}
}
@@ -641,11 +642,11 @@ func storagePoolDeleteCheckPreconditions(cluster *db.Cluster, poolName string, p
// Check if the storage pool is still referenced in any profiles.
profiles, err := profilesUsingPoolGetNames(cluster, poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if len(profiles) > 0 {
- return BadRequest(fmt.Errorf("Storage pool \"%s\" has profiles using it:\n%s", poolName, strings.Join(profiles, "\n")))
+ return response.BadRequest(fmt.Errorf("Storage pool \"%s\" has profiles using it:\n%s", poolName, strings.Join(profiles, "\n")))
}
return nil
diff --git a/lxd/storage_volumes.go b/lxd/storage_volumes.go
index e9baf04748..6b4378bf0d 100644
--- a/lxd/storage_volumes.go
+++ b/lxd/storage_volumes.go
@@ -12,6 +12,8 @@ import (
"github.com/gorilla/mux"
"github.com/gorilla/websocket"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
@@ -67,7 +69,7 @@ var storagePoolVolumeTypeImageCmd = APIEndpoint{
// /1.0/storage-pools/{name}/volumes
// List all storage volumes attached to a given storage pool.
-func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumesGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
poolName := mux.Vars(r)["name"]
@@ -77,7 +79,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
// exists).
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get all volumes currently attached to the storage pool by ID of the
@@ -91,17 +93,17 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
// by the project.
volumes, err := d.cluster.StoragePoolVolumesGet(project, poolID, supportedVolumeTypesExceptImages)
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
imageVolumes, err := d.cluster.StoragePoolVolumesGet("default", poolID, []int{storagePoolVolumeTypeImage})
if err != nil && err != db.ErrNoSuchObject {
- return SmartError(err)
+ return response.SmartError(err)
}
projectImages, err := d.cluster.ImagesGet(project, false)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, volume := range imageVolumes {
if shared.StringInSlice(volume.Name, projectImages) {
@@ -113,7 +115,7 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
for _, volume := range volumes {
apiEndpoint, err := storagePoolVolumeTypeNameToAPIEndpoint(volume.Type)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if apiEndpoint == storagePoolVolumeAPIEndpointContainers {
@@ -136,22 +138,22 @@ func storagePoolVolumesGet(d *Daemon, r *http.Request) Response {
} else {
volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volume.Name, volume.Type)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
volume.UsedBy = volumeUsedBy
}
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, volumes)
+ return response.SyncResponse(true, volumes)
}
// /1.0/storage-pools/{name}/volumes/{type}
// List all storage volumes of a given volume type for a given storage pool.
-func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) response.Response {
project := projectParam(r)
// Get the name of the pool the storage volume is supposed to be
@@ -166,25 +168,25 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
// Retrieve ID of the storage pool (and check if the storage pool
// exists).
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get the names of all storage volumes of a given volume type currently
// attached to the storage pool.
volumes, err := d.cluster.StoragePoolNodeVolumesGetType(volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
resultString := []string{}
@@ -193,7 +195,7 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
if !recursion {
apiEndpoint, err := storagePoolVolumeTypeToAPIEndpoint(volumeType)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if apiEndpoint == storagePoolVolumeAPIEndpointContainers {
@@ -211,7 +213,7 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, vol.Name, vol.Type)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
vol.UsedBy = volumeUsedBy
@@ -220,18 +222,18 @@ func storagePoolVolumesTypeGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
// /1.0/storage-pools/{name}/volumes/{type}
// Create a storage volume in a given storage pool.
-func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+func storagePoolVolumesTypePost(d *Daemon, r *http.Request) response.Response {
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
req := api.StorageVolumesPost{}
@@ -239,16 +241,16 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks.
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
}
req.Type = mux.Vars(r)["type"]
@@ -257,7 +259,7 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
// storagePoolVolumeTypeCustom. So check, that nothing else was
// requested.
if req.Type != storagePoolVolumeTypeNameCustom {
- return BadRequest(fmt.Errorf(`Currently not allowed to create `+
+ return response.BadRequest(fmt.Errorf(`Currently not allowed to create `+
`storage volumes of type %s`, req.Type))
}
@@ -271,11 +273,11 @@ func storagePoolVolumesTypePost(d *Daemon, r *http.Request) Response {
case "migration":
return doVolumeMigration(d, poolName, &req)
default:
- return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+ return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
}
}
-func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPost) Response {
+func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPost) response.Response {
doWork := func() error {
return storagePoolVolumeCreateInternal(d.State(), poolName, req)
}
@@ -283,31 +285,31 @@ func doVolumeCreateOrCopy(d *Daemon, poolName string, req *api.StorageVolumesPos
if req.Source.Name == "" {
err := doWork()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
return doWork()
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeCopy, nil, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeCopy, nil, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// /1.0/storage-pools/{name}/volumes/{type}
// Create a storage volume of a given volume type in a given storage pool.
-func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+func storagePoolVolumesPost(d *Daemon, r *http.Request) response.Response {
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
req := api.StorageVolumesPost{}
@@ -315,29 +317,29 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks.
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
}
// Check that the user gave use a storage volume type for the storage
// volume we are about to create.
if req.Type == "" {
- return BadRequest(fmt.Errorf("You must provide a storage volume type of the storage volume"))
+ return response.BadRequest(fmt.Errorf("You must provide a storage volume type of the storage volume"))
}
// We currently only allow to create storage volumes of type
// storagePoolVolumeTypeCustom. So check, that nothing else was
// requested.
if req.Type != storagePoolVolumeTypeNameCustom {
- return BadRequest(fmt.Errorf(`Currently not allowed to create `+
+ return response.BadRequest(fmt.Errorf(`Currently not allowed to create `+
`storage volumes of type %s`, req.Type))
}
@@ -351,19 +353,19 @@ func storagePoolVolumesPost(d *Daemon, r *http.Request) Response {
case "migration":
return doVolumeMigration(d, poolName, &req)
default:
- return BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+ return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
}
}
-func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost) Response {
+func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost) response.Response {
// Validate migration mode
if req.Source.Mode != "pull" && req.Source.Mode != "push" {
- return NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
+ return response.NotImplemented(fmt.Errorf("Mode '%s' not implemented", req.Source.Mode))
}
storage, err := storagePoolVolumeDBCreateInternal(d.State(), poolName, req)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// create new certificate
@@ -371,18 +373,18 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
if req.Source.Certificate != "" {
certBlock, _ := pem.Decode([]byte(req.Source.Certificate))
if certBlock == nil {
- return InternalError(fmt.Errorf("Invalid certificate"))
+ return response.InternalError(fmt.Errorf("Invalid certificate"))
}
cert, err = x509.ParseCertificate(certBlock.Bytes)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
config, err := shared.GetTLSConfig("", "", "", cert)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
push := false
@@ -403,13 +405,13 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
sink, err := NewStorageMigrationSink(&migrationArgs)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
resources := map[string][]string{}
resources["storage_volumes"] = []string{fmt.Sprintf("%s/volumes/custom/%s", poolName, req.Name)}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
// And finally run the migration.
err = sink.DoStorage(op)
if err != nil {
@@ -420,25 +422,25 @@ func doVolumeMigration(d *Daemon, poolName string, req *api.StorageVolumesPost)
return nil
}
- var op *operation
+ var op *operations.Operation
if push {
- op, err = operationCreate(d.cluster, "", operationClassWebsocket, db.OperationVolumeCreate, resources, sink.Metadata(), run, nil, sink.Connect)
+ op, err = operations.OperationCreate(d.cluster, "", operations.OperationClassWebsocket, db.OperationVolumeCreate, resources, sink.Metadata(), run, nil, sink.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
} else {
- op, err = operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeCopy, resources, nil, run, nil, nil)
+ op, err = operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeCopy, resources, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// /1.0/storage-pools/{name}/volumes/{type}/{name}
// Rename a storage volume of a given volume type in a given storage pool.
-func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
// Get the name of the storage volume.
var volumeName string
fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -452,7 +454,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"]))
+ return response.BadRequest(fmt.Errorf("Invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -464,23 +466,23 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks.
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
}
// We currently only allow to create storage volumes of type
// storagePoolVolumeTypeCustom. So check, that nothing else was
// requested.
if volumeTypeName != storagePoolVolumeTypeNameCustom {
- return BadRequest(fmt.Errorf("Renaming storage volumes of type %s is not allowed", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("Renaming storage volumes of type %s is not allowed", volumeTypeName))
}
// Retrieve ID of the storage pool (and check if the storage pool
@@ -492,7 +494,7 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
poolID, err = d.cluster.StoragePoolGetID(poolName)
}
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// We need to restore the body of the request since it has already been
@@ -500,36 +502,36 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
buf := bytes.Buffer{}
err = json.NewEncoder(&buf).Encode(req)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
r.Body = shared.BytesReadCloser{Buf: &buf}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
s, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, storagePoolVolumeTypeCustom)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
// This is a migration request so send back requested secrets
if req.Migration {
ws, err := NewStorageMigrationSource(s, req.VolumeOnly)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
resources := map[string][]string{}
@@ -539,34 +541,34 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
// Push mode
err := ws.ConnectStorageTarget(*req.Target)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeMigrate, resources, nil, ws.DoStorage, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeMigrate, resources, nil, ws.DoStorage, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Pull mode
- op, err := operationCreate(d.cluster, "", operationClassWebsocket, db.OperationVolumeMigrate, resources, ws.Metadata(), ws.DoStorage, nil, ws.Connect)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassWebsocket, db.OperationVolumeMigrate, resources, ws.Metadata(), ws.DoStorage, nil, ws.Connect)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
// Check that the name isn't already in use.
_, err = d.cluster.StoragePoolNodeVolumeGetTypeID(req.Name, storagePoolVolumeTypeCustom, poolID)
if err != db.ErrNoSuchObject {
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
+ return response.Conflict(fmt.Errorf("Name '%s' already in use", req.Name))
}
doWork := func() error {
@@ -625,39 +627,39 @@ func storagePoolVolumeTypePost(d *Daemon, r *http.Request, volumeTypeName string
if req.Pool == "" {
err = doWork()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return SyncResponseLocation(true, nil, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s", version.APIVersion, poolName, storagePoolVolumeAPIEndpointCustom))
+ return response.SyncResponseLocation(true, nil, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s", version.APIVersion, poolName, storagePoolVolumeAPIEndpointCustom))
}
- run := func(op *operation) error {
+ run := func(op *operations.Operation) error {
return doWork()
}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeMove, nil, nil, run, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeMove, nil, nil, run, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func storagePoolVolumeTypeContainerPost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPost(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePost(d, r, "container")
}
-func storagePoolVolumeTypeCustomPost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPost(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePost(d, r, "custom")
}
-func storagePoolVolumeTypeImagePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePost(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePost(d, r, "image")
}
// /1.0/storage-pools/{pool}/volumes/{type}/{name}
// Get storage volume of a given volume type on a given storage pool.
-func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
project := projectParam(r)
// Get the name of the storage volume.
@@ -673,7 +675,7 @@ func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string)
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+ return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -683,61 +685,61 @@ func storagePoolVolumeTypeGet(d *Daemon, r *http.Request, volumeTypeName string)
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
// Get the ID of the storage pool the storage volume is supposed to be
// attached to.
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
// Get the storage volume.
_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volume.Name, volume.Type)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
volume.UsedBy = volumeUsedBy
etag := []interface{}{volumeName, volume.Type, volume.Config}
- return SyncResponseETag(true, volume, etag)
+ return response.SyncResponseETag(true, volume, etag)
}
-func storagePoolVolumeTypeContainerGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerGet(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeGet(d, r, "container")
}
-func storagePoolVolumeTypeCustomGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomGet(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeGet(d, r, "custom")
}
-func storagePoolVolumeTypeImageGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImageGet(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeGet(d, r, "image")
}
// /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
// Get the name of the storage volume.
var volumeName string
fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -751,7 +753,7 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+ return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -761,32 +763,32 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
poolID, pool, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
// Get the existing storage volume.
_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate the ETag
@@ -794,58 +796,58 @@ func storagePoolVolumeTypePut(d *Daemon, r *http.Request, volumeTypeName string)
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.StorageVolumePut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Restore != "" {
ctsUsingVolume, err := storagePoolVolumeUsedByRunningContainersWithProfilesGet(d.State(), poolName, volume.Name, storagePoolVolumeTypeNameCustom, true)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if len(ctsUsingVolume) != 0 {
- return BadRequest(fmt.Errorf("Cannot restore custom volume used by running containers"))
+ return response.BadRequest(fmt.Errorf("Cannot restore custom volume used by running containers"))
}
err = storagePoolVolumeRestore(d.State(), poolName, volumeName, volumeType, req.Restore)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
} else {
// Validate the configuration
err = storageVolumeValidateConfig(volumeName, req.Config, pool)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, volumeType, req.Description, req.Config)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func storagePoolVolumeTypeContainerPut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPut(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePut(d, r, "container")
}
-func storagePoolVolumeTypeCustomPut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPut(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePut(d, r, "custom")
}
-func storagePoolVolumeTypeImagePut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePut(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePut(d, r, "image")
}
// /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
// Get the name of the storage volume.
var volumeName string
fields := strings.Split(mux.Vars(r)["name"], "/")
@@ -859,7 +861,7 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+ return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -869,34 +871,34 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
// Get the ID of the storage pool the storage volume is supposed to be
// attached to.
poolID, pool, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
// Get the existing storage volume.
_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(volumeName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate the ETag
@@ -904,12 +906,12 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.StorageVolumePut{}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
if req.Config == nil {
@@ -926,31 +928,31 @@ func storagePoolVolumeTypePatch(d *Daemon, r *http.Request, volumeTypeName strin
// Validate the configuration
err = storageVolumeValidateConfig(volumeName, req.Config, pool)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
err = storagePoolVolumeUpdate(d.State(), poolName, volumeName, volumeType, req.Description, req.Config)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func storagePoolVolumeTypeContainerPatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerPatch(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePatch(d, r, "container")
}
-func storagePoolVolumeTypeCustomPatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomPatch(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePatch(d, r, "custom")
}
-func storagePoolVolumeTypeImagePatch(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImagePatch(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypePatch(d, r, "image")
}
// /1.0/storage-pools/{pool}/volumes/{type}/{name}
-func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName string) Response {
+func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName string) response.Response {
project := projectParam(r)
// Get the name of the storage volume.
@@ -964,7 +966,7 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
// Handle volume
volumeName = fields[0]
} else {
- return BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
+ return response.BadRequest(fmt.Errorf("invalid storage volume %s", mux.Vars(r)["name"]))
}
// Get the name of the storage pool the volume is supposed to be
@@ -974,26 +976,26 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolID, _, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
switch volumeType {
@@ -1002,12 +1004,12 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
case storagePoolVolumeTypeImage:
// allowed
default:
- return BadRequest(fmt.Errorf("storage volumes of type \"%s\" cannot be deleted with the storage api", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("storage volumes of type \"%s\" cannot be deleted with the storage api", volumeTypeName))
}
volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), project, poolName, volumeName, volumeTypeName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if len(volumeUsedBy) > 0 {
@@ -1017,13 +1019,13 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
"/%s/images/%s",
version.APIVersion,
volumeName) {
- return BadRequest(fmt.Errorf("The storage volume is still in use"))
+ return response.BadRequest(fmt.Errorf("The storage volume is still in use"))
}
}
s, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, volumeType)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
switch volumeType {
@@ -1033,18 +1035,18 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
// Delete storage volume snapshots
snapshots, err = d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
for _, snapshot := range snapshots {
s, err := storagePoolVolumeInit(d.State(), project, poolName, snapshot, volumeType)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
err = s.StoragePoolVolumeSnapshotDelete()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
}
@@ -1052,25 +1054,25 @@ func storagePoolVolumeTypeDelete(d *Daemon, r *http.Request, volumeTypeName stri
case storagePoolVolumeTypeImage:
err = s.ImageDelete(volumeName)
default:
- return BadRequest(fmt.Errorf(`Storage volumes of type "%s" `+
+ return response.BadRequest(fmt.Errorf(`Storage volumes of type "%s" `+
`cannot be deleted with the storage api`,
volumeTypeName))
}
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return EmptySyncResponse
+ return response.EmptySyncResponse
}
-func storagePoolVolumeTypeContainerDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeContainerDelete(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeDelete(d, r, "container")
}
-func storagePoolVolumeTypeCustomDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeCustomDelete(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeDelete(d, r, "custom")
}
-func storagePoolVolumeTypeImageDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeTypeImageDelete(d *Daemon, r *http.Request) response.Response {
return storagePoolVolumeTypeDelete(d, r, "image")
}
diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go
index 7a5ff87ee7..db8f50fc81 100644
--- a/lxd/storage_volumes_snapshot.go
+++ b/lxd/storage_volumes_snapshot.go
@@ -9,6 +9,8 @@ import (
"github.com/gorilla/mux"
"github.com/lxc/lxd/lxd/db"
+ "github.com/lxc/lxd/lxd/operations"
+ "github.com/lxc/lxd/lxd/response"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/lxd/util"
"github.com/lxc/lxd/shared"
@@ -32,7 +34,7 @@ var storagePoolVolumeSnapshotTypeCmd = APIEndpoint{
Put: APIEndpointAction{Handler: storagePoolVolumeSnapshotTypePut},
}
-func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) response.Response {
// Get the name of the pool.
poolName := mux.Vars(r)["pool"]
@@ -46,18 +48,18 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
req := api.StorageVolumeSnapshotsPost{}
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("Invalid storage volume type \"%d\"", volumeType))
+ return response.BadRequest(fmt.Errorf("Invalid storage volume type \"%d\"", volumeType))
}
// Get a snapshot name.
@@ -69,56 +71,56 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
// Validate the name
err = driver.ValidName(req.Name)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that this isn't a restricted volume
used, err := daemonStorageUsed(d.State(), poolName, volumeName)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
if used {
- return BadRequest(fmt.Errorf("Volumes used by LXD itself cannot have snapshots"))
+ return response.BadRequest(fmt.Errorf("Volumes used by LXD itself cannot have snapshots"))
}
// Retrieve ID of the storage pool (and check if the storage pool
// exists).
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, volumeName, volumeType)
+ if resp != nil {
+ return resp
}
// Ensure that the storage volume exists.
storage, err := storagePoolVolumeInit(d.State(), "default", poolName, volumeName, volumeType)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Ensure that the snapshot doens't already exist
_, _, err = d.cluster.StoragePoolNodeVolumeGetType(fmt.Sprintf("%s/%s", volumeName, req.Name), volumeType, poolID)
if err != db.ErrNoSuchObject {
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
- return Conflict(fmt.Errorf("Snapshot '%s' already in use", req.Name))
+ return response.Conflict(fmt.Errorf("Snapshot '%s' already in use", req.Name))
}
// Start the storage.
ourMount, err := storage.StoragePoolVolumeMount()
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
if ourMount {
defer storage.StoragePoolVolumeUmount()
@@ -127,7 +129,7 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
volWritable := storage.GetStoragePoolVolumeWritable()
fullSnapName := fmt.Sprintf("%s%s%s", volumeName, shared.SnapshotDelimiter, req.Name)
req.Name = fullSnapName
- snapshot := func(op *operation) error {
+ snapshot := func(op *operations.Operation) error {
dbArgs := &db.StorageVolumeArgs{
Name: fullSnapName,
PoolName: poolName,
@@ -152,15 +154,15 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["storage_volumes"] = []string{volumeName}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotCreate, resources, nil, snapshot, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotCreate, resources, nil, snapshot, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) response.Response {
// Get the name of the pool the storage volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]
@@ -176,24 +178,24 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
// Retrieve ID of the storage pool (and check if the storage pool
// exists).
poolID, err := d.cluster.StoragePoolGetID(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Get the names of all storage volume snapshots of a given volume
volumes, err := d.cluster.StoragePoolVolumeSnapshotsGetType(volumeName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
resultString := []string{}
@@ -204,7 +206,7 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
if !recursion {
apiEndpoint, err := storagePoolVolumeTypeToAPIEndpoint(volumeType)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
resultString = append(resultString, fmt.Sprintf("/%s/storage-pools/%s/volumes/%s/%s/snapshots/%s", version.APIVersion, poolName, apiEndpoint, volumeName, snapshotName))
} else {
@@ -215,7 +217,7 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
volumeUsedBy, err := storagePoolVolumeUsedByGet(d.State(), "default", poolName, vol.Name, vol.Type)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
vol.UsedBy = volumeUsedBy
@@ -229,13 +231,13 @@ func storagePoolVolumeSnapshotsTypeGet(d *Daemon, r *http.Request) Response {
}
if !recursion {
- return SyncResponse(true, resultString)
+ return response.SyncResponse(true, resultString)
}
- return SyncResponse(true, resultMap)
+ return response.SyncResponse(true, resultMap)
}
-func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) response.Response {
// Get the name of the storage pool the volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]
@@ -254,51 +256,51 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
// Parse the request.
err := json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Sanity checks.
if req.Name == "" {
- return BadRequest(fmt.Errorf("No name provided"))
+ return response.BadRequest(fmt.Errorf("No name provided"))
}
if strings.Contains(req.Name, "/") {
- return BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
+ return response.BadRequest(fmt.Errorf("Storage volume names may not contain slashes"))
}
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if volumeType != storagePoolVolumeTypeCustom {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolID, _, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+ if resp != nil {
+ return resp
}
s, err := storagePoolVolumeInit(d.State(), "default", poolName, fullSnapshotName, volumeType)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
- snapshotRename := func(op *operation) error {
+ snapshotRename := func(op *operations.Operation) error {
err = s.StoragePoolVolumeSnapshotRename(req.Name)
if err != nil {
return err
@@ -310,15 +312,15 @@ func storagePoolVolumeSnapshotTypePost(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["storage_volume_snapshots"] = []string{volumeName}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotRename, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotRename, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) response.Response {
// Get the name of the storage pool the volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]
@@ -335,33 +337,33 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if volumeType != storagePoolVolumeTypeCustom {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolID, _, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+ if resp != nil {
+ return resp
}
_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(fullSnapshotName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
snapshot := api.StorageVolumeSnapshot{}
@@ -371,10 +373,10 @@ func storagePoolVolumeSnapshotTypeGet(d *Daemon, r *http.Request) Response {
etag := []interface{}{snapshot.Name, snapshot.Description, snapshot.Config}
- return SyncResponseETag(true, &snapshot, etag)
+ return response.SyncResponseETag(true, &snapshot, etag)
}
-func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) response.Response {
// Get the name of the storage pool the volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]
@@ -391,51 +393,51 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if volumeType != storagePoolVolumeTypeCustom {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolID, _, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+ if resp != nil {
+ return resp
}
_, volume, err := d.cluster.StoragePoolNodeVolumeGetType(fullSnapshotName, volumeType, poolID)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
// Validate the ETag
etag := []interface{}{snapshotName, volume.Description, volume.Config}
err = util.EtagCheck(r, etag)
if err != nil {
- return PreconditionFailed(err)
+ return response.PreconditionFailed(err)
}
req := api.StorageVolumeSnapshotPut{}
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
- var do func(*operation) error
+ var do func(*operations.Operation) error
var opDescription db.OperationType
- do = func(op *operation) error {
+ do = func(op *operations.Operation) error {
err = storagePoolVolumeSnapshotUpdate(d.State(), poolName, volume.Name, volumeType, req.Description)
if err != nil {
return err
@@ -448,15 +450,15 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["storage_volume_snapshots"] = []string{volumeName}
- op, err := operationCreate(d.cluster, "", operationClassTask, opDescription, resources, nil, do, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, opDescription, resources, nil, do, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
-func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
+func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) response.Response {
// Get the name of the storage pool the volume is supposed to be
// attached to.
poolName := mux.Vars(r)["pool"]
@@ -473,36 +475,36 @@ func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
// Convert the volume type name to our internal integer representation.
volumeType, err := storagePoolVolumeTypeNameToType(volumeTypeName)
if err != nil {
- return BadRequest(err)
+ return response.BadRequest(err)
}
// Check that the storage volume type is valid.
if volumeType != storagePoolVolumeTypeCustom {
- return BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
+ return response.BadRequest(fmt.Errorf("invalid storage volume type %s", volumeTypeName))
}
- response := ForwardedResponseIfTargetIsRemote(d, r)
- if response != nil {
- return response
+ resp := ForwardedResponseIfTargetIsRemote(d, r)
+ if resp != nil {
+ return resp
}
poolID, _, err := d.cluster.StoragePoolGet(poolName)
if err != nil {
- return SmartError(err)
+ return response.SmartError(err)
}
fullSnapshotName := fmt.Sprintf("%s/%s", volumeName, snapshotName)
- response = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
- if response != nil {
- return response
+ resp = ForwardedResponseIfVolumeIsRemote(d, r, poolID, fullSnapshotName, volumeType)
+ if resp != nil {
+ return resp
}
s, err := storagePoolVolumeInit(d.State(), "default", poolName, fullSnapshotName, volumeType)
if err != nil {
- return NotFound(err)
+ return response.NotFound(err)
}
- snapshotDelete := func(op *operation) error {
+ snapshotDelete := func(op *operations.Operation) error {
err = s.StoragePoolVolumeSnapshotDelete()
if err != nil {
return err
@@ -514,10 +516,10 @@ func storagePoolVolumeSnapshotTypeDelete(d *Daemon, r *http.Request) Response {
resources := map[string][]string{}
resources["storage_volume_snapshots"] = []string{volumeName}
- op, err := operationCreate(d.cluster, "", operationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotDelete, nil, nil)
+ op, err := operations.OperationCreate(d.cluster, "", operations.OperationClassTask, db.OperationVolumeSnapshotDelete, resources, nil, snapshotDelete, nil, nil)
if err != nil {
- return InternalError(err)
+ return response.InternalError(err)
}
- return OperationResponse(op)
+ return operations.OperationResponse(op)
}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index aff449d39a..8476d7a05e 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -15,6 +15,7 @@ import (
"golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/migration"
+ "github.com/lxc/lxd/lxd/operations"
"github.com/lxc/lxd/lxd/project"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/lxd/util"
@@ -2566,7 +2567,7 @@ func (s *storageZfs) MigrationSource(args MigrationSourceArgs) (MigrationStorage
return &driver, nil
}
-func (s *storageZfs) MigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageZfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
poolName := s.getOnDiskPoolName()
zfsName := fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name()))
zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
@@ -3204,7 +3205,7 @@ func (s *storageZfs) StorageMigrationSource(args MigrationSourceArgs) (Migration
return rsyncStorageMigrationSource(args)
}
-func (s *storageZfs) StorageMigrationSink(conn *websocket.Conn, op *operation, args MigrationSinkArgs) error {
+func (s *storageZfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
return rsyncStorageMigrationSink(conn, op, args)
}
From ed945f29bc2ef833950e6fc115bb71efcda1e960 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.hipp at canonical.com>
Date: Thu, 26 Sep 2019 14:38:36 +0200
Subject: [PATCH 2/2] test: Add response and operations to static analysis
Signed-off-by: Thomas Hipp <thomas.hipp at canonical.com>
---
test/suites/static_analysis.sh | 2 ++
1 file changed, 2 insertions(+)
diff --git a/test/suites/static_analysis.sh b/test/suites/static_analysis.sh
index 21291beeb6..1edbb3d6b5 100644
--- a/test/suites/static_analysis.sh
+++ b/test/suites/static_analysis.sh
@@ -82,6 +82,8 @@ test_static_analysis() {
golint -set_exit_status lxd/maas
#golint -set_exit_status lxd/migration
golint -set_exit_status lxd/node
+ golint -set_exit_status lxd/operations
+ golint -set_exit_status lxd/response
golint -set_exit_status lxd/state
golint -set_exit_status lxd/storage
golint -set_exit_status lxd/sys
More information about the lxc-devel
mailing list