The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/3420

This e-mail was sent by the LXC bot, direct replies will not reach the author
unless they happen to be subscribed to this list.

=== Description (from pull-request) ===
Cancellation is not wired in the client yet, but it's possible to perform it via direct API call.

An operation is now considered cancellable when the .Cancellable(true) is called, so that the cancel channel is set.
From 79e94873b7ca7f1f0308980106c95de04ca65cf6 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Wed, 14 Jun 2017 10:50:46 +0200
Subject: [PATCH 1/2] Cleanup.

Signed-off-by: Alberto Donato <[email protected]>
---
 lxd/images.go | 24 +++++++-----------------
 1 file changed, 7 insertions(+), 17 deletions(-)

diff --git a/lxd/images.go b/lxd/images.go
index 8cdb17009..64da6ad2d 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -720,35 +720,25 @@ func imagesPost(d *Daemon, r *http.Request) Response {
                // Setup the cleanup function
                defer cleanup(builddir, post)
 
-               if !imageUpload {
+               if imageUpload {
+                       /* Processing image upload */
+                       info, err = getImgPostInfo(d, r, builddir, post)
+               } else {
                        if req.Source.Type == "image" {
                                /* Processing image copy from remote */
                                info, err = imgPostRemoteInfo(d, req, op)
-                               if err != nil {
-                                       return err
-                               }
                        } else if req.Source.Type == "url" {
                                /* Processing image copy from URL */
                                info, err = imgPostURLInfo(d, req, op)
-                               if err != nil {
-                                       return err
-                               }
                        } else {
                                /* Processing image creation from container */
                                imagePublishLock.Lock()
                                info, err = imgPostContInfo(d, r, req, builddir)
-                               if err != nil {
-                                       imagePublishLock.Unlock()
-                                       return err
-                               }
                                imagePublishLock.Unlock()
                        }
-               } else {
-                       /* Processing image upload */
-                       info, err = getImgPostInfo(d, r, builddir, post)
-                       if err != nil {
-                               return err
-                       }
+               }
+               if err != nil {
+                       return err
                }
 
                // Apply any provided alias

From be1bb85f2f9a50a2e3ff2fd3bcbab9789f435ce2 Mon Sep 17 00:00:00 2001
From: Alberto Donato <[email protected]>
Date: Thu, 15 Jun 2017 15:01:42 +0200
Subject: [PATCH 2/2] Add logic to cancel HTTP request.

Signed-off-by: Alberto Donato <[email protected]>
---
 client/interfaces.go           |  4 ++++
 client/lxd_images.go           |  4 +++-
 client/simplestreams_images.go |  8 ++++----
 client/util.go                 |  8 +++++---
 lxd/daemon_images.go           |  5 ++++-
 lxd/operations.go              | 25 ++++++++++++++++++++-----
 shared/operation/operation.go  | 31 +++++++++++++++++++++++++++++++
 test/suites/image.sh           | 24 ++++++++++++++++++++++++
 8 files changed, 95 insertions(+), 14 deletions(-)
 create mode 100644 shared/operation/operation.go

diff --git a/client/interfaces.go b/client/interfaces.go
index 1d76196b5..dddac03d7 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -6,6 +6,7 @@ import (
        "github.com/gorilla/websocket"
 
        "github.com/lxc/lxd/shared/api"
+       "github.com/lxc/lxd/shared/operation"
 )
 
 // The Server type represents a generic read-only server.
@@ -194,6 +195,9 @@ type ImageFileRequest struct {
 
        // Progress handler (called whenever some progress is made)
        ProgressHandler func(progress ProgressData)
+
+       // The cancellable operation that's handling the request
+       Operation operation.CancellableOperation
 }
 
 // The ImageFileResponse struct is used as the response for image downloads
diff --git a/client/lxd_images.go b/client/lxd_images.go
index edc7a4530..b7310169a 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -16,6 +16,7 @@ import (
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/ioprogress"
+       "github.com/lxc/lxd/shared/operation"
 )
 
 // Image handling functions
@@ -118,10 +119,11 @@ func (r *ProtocolLXD) GetPrivateImageFile(fingerprint 
string, secret string, req
        }
 
        // Start the request
-       response, err := r.http.Do(request)
+       response, err, doneCh := operation.CancellableDownload(req.Operation, 
r.http, request)
        if err != nil {
                return nil, err
        }
+       defer close(doneCh)
        defer response.Body.Close()
 
        if response.StatusCode != http.StatusOK {
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index d84f8ed09..ef803105f 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -63,11 +63,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint 
string, req ImageFileRe
                // Try over http
                url := fmt.Sprintf("http://%s/%s";, 
strings.TrimPrefix(r.httpHost, "https://";), meta.Path)
 
-               size, err := downloadFileSha256(r.http, r.httpUserAgent, 
req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+               size, err := downloadFileSha256(req.Operation, r.http, 
r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, 
req.MetaFile)
                if err != nil {
                        // Try over https
                        url = fmt.Sprintf("%s/%s", r.httpHost, meta.Path)
-                       size, err = downloadFileSha256(r.http, r.httpUserAgent, 
req.ProgressHandler, "metadata", url, meta.Sha256, req.MetaFile)
+                       size, err = downloadFileSha256(req.Operation, r.http, 
r.httpUserAgent, req.ProgressHandler, "metadata", url, meta.Sha256, 
req.MetaFile)
                        if err != nil {
                                return nil, err
                        }
@@ -84,11 +84,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint 
string, req ImageFileRe
                // Try over http
                url := fmt.Sprintf("http://%s/%s";, 
strings.TrimPrefix(r.httpHost, "https://";), rootfs.Path)
 
-               size, err := downloadFileSha256(r.http, r.httpUserAgent, 
req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+               size, err := downloadFileSha256(req.Operation, r.http, 
r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, 
req.RootfsFile)
                if err != nil {
                        // Try over https
                        url = fmt.Sprintf("%s/%s", r.httpHost, rootfs.Path)
-                       size, err = downloadFileSha256(r.http, r.httpUserAgent, 
req.ProgressHandler, "rootfs", url, rootfs.Sha256, req.RootfsFile)
+                       size, err = downloadFileSha256(req.Operation, r.http, 
r.httpUserAgent, req.ProgressHandler, "rootfs", url, rootfs.Sha256, 
req.RootfsFile)
                        if err != nil {
                                return nil, err
                        }
diff --git a/client/util.go b/client/util.go
index 973f1d562..2b35e606b 100644
--- a/client/util.go
+++ b/client/util.go
@@ -10,6 +10,7 @@ import (
 
        "github.com/lxc/lxd/shared"
        "github.com/lxc/lxd/shared/ioprogress"
+       "github.com/lxc/lxd/shared/operation"
 )
 
 func tlsHTTPClient(tlsClientCert string, tlsClientKey string, tlsCA string, 
tlsServerCert string, proxy func(req *http.Request) (*url.URL, error)) 
(*http.Client, error) {
@@ -81,7 +82,7 @@ func unixHTTPClient(path string) (*http.Client, error) {
        return &client, nil
 }
 
-func downloadFileSha256(httpClient *http.Client, useragent string, progress 
func(progress ProgressData), filename string, url string, hash string, target 
io.WriteSeeker) (int64, error) {
+func downloadFileSha256(op operation.CancellableOperation, httpClient 
*http.Client, useragent string, progress func(progress ProgressData), filename 
string, url string, hash string, target io.WriteSeeker) (int64, error) {
        // Always seek to the beginning
        target.Seek(0, 0)
 
@@ -95,12 +96,13 @@ func downloadFileSha256(httpClient *http.Client, useragent 
string, progress func
                req.Header.Set("User-Agent", useragent)
        }
 
-       // Start the request
-       r, err := httpClient.Do(req)
+       // Perform the request
+       r, err, doneCh := operation.CancellableDownload(op, httpClient, req)
        if err != nil {
                return -1, err
        }
        defer r.Body.Close()
+       defer close(doneCh)
 
        if r.StatusCode != http.StatusOK {
                return -1, fmt.Errorf("Unable to fetch %s: %s", url, r.Status)
diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index b2023d42f..dab78cc9a 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -19,6 +19,7 @@ import (
        "github.com/lxc/lxd/shared/api"
        "github.com/lxc/lxd/shared/ioprogress"
        "github.com/lxc/lxd/shared/logger"
+       cancellable_op "github.com/lxc/lxd/shared/operation"
        "github.com/lxc/lxd/shared/version"
 
        log "gopkg.in/inconshreveable/log15.v2"
@@ -385,6 +386,7 @@ func (d *Daemon) ImageDownload(op *operation, server 
string, protocol string, ce
                        MetaFile:        io.WriteSeeker(dest),
                        RootfsFile:      io.WriteSeeker(destRootfs),
                        ProgressHandler: progress,
+                       Operation:       op,
                }
 
                if secret != "" {
@@ -418,7 +420,8 @@ func (d *Daemon) ImageDownload(op *operation, server 
string, protocol string, ce
                req.Header.Set("User-Agent", version.UserAgent)
 
                // Make the request
-               raw, err := httpClient.Do(req)
+               raw, err, doneCh := cancellable_op.CancellableDownload(op, 
httpClient, req)
+               defer close(doneCh)
                if err != nil {
                        return nil, err
                }
diff --git a/lxd/operations.go b/lxd/operations.go
index 0794bc5d0..d5aa21684 100644
--- a/lxd/operations.go
+++ b/lxd/operations.go
@@ -54,7 +54,8 @@ type operation struct {
        onConnect func(*operation, *http.Request, http.ResponseWriter) error
 
        // Channels used for error reporting and state tracking of background 
actions
-       chanDone chan error
+       chanDone   chan error
+       chanCancel chan error
 
        // Locking for concurent access to the operation
        lock sync.Mutex
@@ -153,17 +154,18 @@ func (op *operation) Cancel() (chan error, error) {
                return nil, fmt.Errorf("Only running operations can be 
cancelled")
        }
 
+       op.lock.Lock()
        if !op.mayCancel() {
+               op.lock.Unlock()
                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
+       close(op.chanCancel)
        op.lock.Unlock()
 
+       chanCancel := make(chan error, 1)
        if op.onCancel != nil {
                go func(op *operation, oldStatus api.StatusCode, chanCancel 
chan error) {
                        err := op.onCancel(op)
@@ -244,7 +246,20 @@ func (op *operation) Connect(r *http.Request, w 
http.ResponseWriter) (chan error
 }
 
 func (op *operation) mayCancel() bool {
-       return op.onCancel != nil || op.class == operationClassToken
+       return op.chanCancel != nil || op.class == operationClassToken
+}
+
+// Toggle whether the operation is cancellable. If `true` is passed, the
+// channel to cancel the operation is returned, otherwise nil.
+func (op *operation) Cancellable(flag bool) chan error {
+       var ch chan error
+       if flag {
+               ch = make(chan error)
+       }
+       op.lock.Lock()
+       op.chanCancel = ch
+       op.lock.Unlock()
+       return ch
 }
 
 func (op *operation) Render() (string, *api.Operation, error) {
diff --git a/shared/operation/operation.go b/shared/operation/operation.go
new file mode 100644
index 000000000..b0ee2c0d9
--- /dev/null
+++ b/shared/operation/operation.go
@@ -0,0 +1,31 @@
+package operation
+
+import (
+       "net/http"
+)
+
+// An operation that can be canceled.
+type CancellableOperation interface {
+
+       // Toggle whether the operation is cancellable
+       Cancellable(flag bool) chan error
+}
+
+func CancellableDownload(op CancellableOperation, client *http.Client, req 
*http.Request) (*http.Response, error, chan bool) {
+       chDone := make(chan bool)
+
+       go func() {
+               chCancel := op.Cancellable(true)
+               select {
+               case <-chCancel:
+                       if transport, ok := client.Transport.(*http.Transport); 
ok {
+                               transport.CancelRequest(req)
+                       }
+               case <-chDone:
+               }
+               op.Cancellable(false)
+       }()
+
+       resp, err := client.Do(req)
+       return resp, err, chDone
+}
diff --git a/test/suites/image.sh b/test/suites/image.sh
index 7b5c19101..ecbc269a5 100644
--- a/test/suites/image.sh
+++ b/test/suites/image.sh
@@ -48,5 +48,29 @@ test_image_list_all_aliases() {
     # both aliases are listed if the "aliases" column is included in output
     lxc image list -c L | grep -q testimage
     lxc image list -c L | grep -q zzz
+}
 
+test_image_copy_interrupt() {
+    # shellcheck disable=2039,2153,2155
+    local operation_id=$(
+        curl -s --unix-socket "${LXD_DIR}"/unix.socket lxd/1.0/images -d '
+             {
+                "auto_update": false,
+                "public": false,
+                "source": {
+                        "certificate": "",
+                        "fingerprint": "x",
+                        "mode": "pull",
+                        "protocol": "simplestreams",
+                        "server": "https://cloud-images.ubuntu.com/releases";,
+                        "type": "image"
+                        }
+             }' | jq -r .metadata.id)
+    sleep 1
+    # cancel the operation and expect a success response
+    curl -s --unix-socket "${LXD_DIR}"/unix.socket -X DELETE \
+         "lxd/1.0/operations/$operation_id" | grep -q 200
+    [ "$(lxc image list --format=csv | wc -l)" = 0 ]
+    # Remove leftover files from image download
+    rm -rf "${LXD_DIR}/images"
 }
_______________________________________________
lxc-devel mailing list
[email protected]
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to