[lxc-devel] [lxd/master] Better handling for remote operations in new client

stgraber on Github lxc-bot at linuxcontainers.org
Fri May 26 22:41:06 UTC 2017


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/20170526/6fff1a09/attachment.bin>
-------------- next part --------------
From 7247204ecd39869bc05ec727ba8cc6f88bca8a39 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 17:59:39 -0400
Subject: [PATCH 1/3] client: Keep track of protocol
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/connection.go | 7 +++++--
 client/lxd.go        | 3 ++-
 2 files changed, 7 insertions(+), 3 deletions(-)

diff --git a/client/connection.go b/client/connection.go
index fab941fd5..02e0e6aa6 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -48,9 +48,10 @@ func ConnectLXD(url string, args *ConnectionArgs) (ContainerServer, error) {
 
 	// Initialize the client struct
 	server := ProtocolLXD{
+		httpCertificate: args.TLSServerCert,
 		httpHost:        url,
+		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
@@ -84,6 +85,7 @@ func ConnectLXDUnix(path string, args *ConnectionArgs) (ContainerServer, error)
 	// Initialize the client struct
 	server := ProtocolLXD{
 		httpHost:      "http://unix.socket",
+		httpProtocol:  "unix",
 		httpUserAgent: args.UserAgent,
 	}
 
@@ -129,9 +131,10 @@ func ConnectPublicLXD(url string, args *ConnectionArgs) (ImageServer, error) {
 
 	// Initialize the client struct
 	server := ProtocolLXD{
+		httpCertificate: args.TLSServerCert,
 		httpHost:        url,
+		httpProtocol:    "https",
 		httpUserAgent:   args.UserAgent,
-		httpCertificate: args.TLSServerCert,
 	}
 
 	// Setup the HTTP client
diff --git a/client/lxd.go b/client/lxd.go
index 3f8663b38..90b11e6dc 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -22,9 +22,10 @@ type ProtocolLXD struct {
 	eventListenersLock sync.Mutex
 
 	http            *http.Client
+	httpCertificate string
 	httpHost        string
+	httpProtocol    string
 	httpUserAgent   string
-	httpCertificate string
 }
 
 // RawQuery allows directly querying the LXD API

From 9bfebc0d736464657aa1d3f47635b47ed93c3b3f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 18:01:23 -0400
Subject: [PATCH 2/3] client: Implement remote operations
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/operations.go | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)

diff --git a/client/operations.go b/client/operations.go
index 7f381a011..6520556cb 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -224,3 +224,44 @@ func (op *Operation) extractOperation(data interface{}) *api.Operation {
 
 	return &newOp
 }
+
+// The RemoteOperation type represents an ongoing LXD operation between two servers
+type RemoteOperation struct {
+	targetOp *Operation
+
+	handlers []func(api.Operation)
+
+	chDone chan bool
+	err    error
+}
+
+// Wait lets you wait until the operation reaches a final state
+func (op *RemoteOperation) Wait() error {
+	<-op.chDone
+	return op.err
+}
+
+// AddHandler adds a function to be called whenever an event is received
+func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarget, error) {
+	var err error
+	var target *EventTarget
+
+	// Attach to the existing target operation
+	if op.targetOp != nil {
+		target, err = op.targetOp.AddHandler(function)
+		if err != nil {
+			return nil, err
+		}
+	} else {
+		// Generate a mock EventTarget
+		target = &EventTarget{
+			function: func(interface{}) { function(api.Operation{}) },
+			types:    []string{"operation"},
+		}
+	}
+
+	// Add the handler to our list
+	op.handlers = append(op.handlers, function)
+
+	return target, nil
+}

From d783a2c6e681b187476d07ab30f6564d1a55e4b3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 18:30:45 -0400
Subject: [PATCH 3/3] client: Use RemoteOperation for CopyImage
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           |  2 +-
 client/lxd.go                  | 21 +++++++++++++++
 client/lxd_images.go           | 61 +++++++++++++++++++++++++++++++++++++++---
 client/simplestreams_images.go | 20 ++++++++++++--
 4 files changed, 98 insertions(+), 6 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 2cc6d2297..f11adce0e 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -25,7 +25,7 @@ type ImageServer interface {
 
 	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
 
-	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *Operation, err error)
+	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *RemoteOperation, err error)
 }
 
 // The ContainerServer type represents a full featured LXD server.
diff --git a/client/lxd.go b/client/lxd.go
index 90b11e6dc..6a5892353 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -222,3 +222,24 @@ func (r *ProtocolLXD) websocket(path string) (*websocket.Conn, error) {
 
 	return r.rawWebsocket(url)
 }
+
+// getServerUrls returns a prioritized list of potential URLs for the server
+func (r *ProtocolLXD) getServerUrls() ([]string, error) {
+	if len(r.server.Environment.Addresses) == 0 {
+		return nil, fmt.Errorf("Source server isn't reachable over the network")
+	}
+
+	urls := []string{}
+	if r.httpProtocol == "https" {
+		urls = append(urls, r.httpHost)
+	}
+
+	for _, addr := range r.server.Environment.Addresses {
+		url := fmt.Sprintf("https://%s", addr)
+		if !shared.StringInSlice(url, urls) {
+			urls = append(urls, url)
+		}
+	}
+
+	return urls, nil
+}
diff --git a/client/lxd_images.go b/client/lxd_images.go
index bea83bd09..1abf5f7a4 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -431,15 +431,70 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 	return &op, nil
 }
 
+// tryCopyImage iterates through the source server URLs until one lets it download the image
+func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, urls []string) (*RemoteOperation, error) {
+	rop := RemoteOperation{
+		chDone: make(chan bool),
+	}
+
+	// Forward targetOp to remote op
+	go func() {
+		success := false
+		errors := []string{}
+		for _, serverURL := range urls {
+			req.Source.Server = serverURL
+
+			op, err := target.CreateImage(req, nil)
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			rop.targetOp = op
+
+			for _, handler := range rop.handlers {
+				rop.targetOp.AddHandler(handler)
+			}
+
+			err = rop.targetOp.Wait()
+			if err != nil {
+				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
+				continue
+			}
+
+			success = true
+			break
+		}
+
+		if !success {
+			rop.err = fmt.Errorf("%s", strings.Join(errors, "\n"))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
 // CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
+	// Sanity checks
+	if r == target {
+		return nil, fmt.Errorf("The source and target servers must be different")
+	}
+
+	// Get a list of addresses for the source server
+	urls, err := r.getServerUrls()
+	if err != nil {
+		return nil, err
+	}
+
 	// Prepare the copy request
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
 			ImageSource: api.ImageSource{
 				Certificate: r.httpCertificate,
 				Protocol:    "lxd",
-				Server:      r.httpHost,
 			},
 			Fingerprint: image.Fingerprint,
 			Mode:        "pull",
@@ -471,7 +526,7 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		}
 	}
 
-	return target.CreateImage(req, nil)
+	return r.tryCopyImage(target, req, urls)
 }
 
 // UpdateImage updates the image definition
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index f04a0801a..3d9b7ecef 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -144,7 +144,7 @@ func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEnt
 }
 
 // CopyImage copies an existing image to a remote server. Additional options can be passed using ImageCopyArgs
-func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*Operation, error) {
+func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (*RemoteOperation, error) {
 	// Prepare the copy request
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
@@ -173,5 +173,21 @@ func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServe
 		}
 	}
 
-	return target.CreateImage(req, nil)
+	op, err := target.CreateImage(req, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	rop := RemoteOperation{
+		targetOp: op,
+		chDone:   make(chan bool),
+	}
+
+	// Forward targetOp to remote op
+	go func() {
+		rop.err = rop.targetOp.Wait()
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
 }


More information about the lxc-devel mailing list