[lxc-devel] [lxd/master] Bugfixes and tweaks to new client library

stgraber on Github lxc-bot at linuxcontainers.org
Tue May 30 05:39:17 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/20170530/aba60fbb/attachment.bin>
-------------- next part --------------
From baf4204563d0095cb08b9cc64e913df4f7099b33 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Fri, 26 May 2017 23:11:42 -0400
Subject: [PATCH 1/6] client: Improve error on image copy
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/lxd_images.go | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index 1abf5f7a4..641f000c2 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -467,7 +467,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		}
 
 		if !success {
-			rop.err = fmt.Errorf("%s", strings.Join(errors, "\n"))
+			rop.err = fmt.Errorf("Failed remote image download:\n - %s", strings.Join(errors, "\n - "))
 		}
 
 		close(rop.chDone)

From 2e43489b4c149a9fef043e40d211e7492af2c4d4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 16:39:07 -0400
Subject: [PATCH 2/6] client: Add image_create_aliases backward compat
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/lxd_images.go           | 33 +++++++++++++++++++++++++++++++++
 client/operations.go           | 27 +++++++++++++++++++++------
 client/simplestreams_images.go | 36 +++++++++++++++++++++++++++++++++++-
 3 files changed, 89 insertions(+), 7 deletions(-)

diff --git a/client/lxd_images.go b/client/lxd_images.go
index 641f000c2..eb67ee268 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -437,6 +437,39 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		chDone: make(chan bool),
 	}
 
+	// For older servers, apply the aliases after copy
+	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+		rop.chPost = make(chan bool)
+
+		go func() {
+			defer close(rop.chPost)
+
+			// Wait for the main operation to finish
+			<-rop.chDone
+			if rop.err != nil {
+				return
+			}
+
+			// Get the operation data
+			op, err := rop.GetTarget()
+			if err != nil {
+				return
+			}
+
+			// Extract the fingerprint
+			fingerprint := op.Metadata["fingerprint"].(string)
+
+			// Add the aliases
+			for _, entry := range req.Aliases {
+				alias := api.ImageAliasesPost{}
+				alias.Name = entry.Name
+				alias.Target = fingerprint
+
+				target.CreateImageAlias(alias)
+			}
+		}()
+	}
+
 	// Forward targetOp to remote op
 	go func() {
 		success := false
diff --git a/client/operations.go b/client/operations.go
index 6520556cb..ec092644c 100644
--- a/client/operations.go
+++ b/client/operations.go
@@ -232,15 +232,10 @@ type RemoteOperation struct {
 	handlers []func(api.Operation)
 
 	chDone chan bool
+	chPost 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
@@ -265,3 +260,23 @@ func (op *RemoteOperation) AddHandler(function func(api.Operation)) (*EventTarge
 
 	return target, nil
 }
+
+// GetTarget returns the target operation
+func (op *RemoteOperation) GetTarget() (*api.Operation, error) {
+	if op.targetOp == nil {
+		return nil, fmt.Errorf("No associated target operation")
+	}
+
+	return &op.targetOp.Operation, nil
+}
+
+// Wait lets you wait until the operation reaches a final state
+func (op *RemoteOperation) Wait() error {
+	<-op.chDone
+
+	if op.chPost != nil {
+		<-op.chPost
+	}
+
+	return op.err
+}
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index 3d9b7ecef..970c308a0 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -2,8 +2,9 @@ package lxd
 
 import (
 	"fmt"
-	"github.com/lxc/lxd/shared/api"
 	"strings"
+
+	"github.com/lxc/lxd/shared/api"
 )
 
 // Image handling functions
@@ -183,6 +184,39 @@ func (r *ProtocolSimpleStreams) CopyImage(image api.Image, target ContainerServe
 		chDone:   make(chan bool),
 	}
 
+	// For older servers, apply the aliases after copy
+	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+		rop.chPost = make(chan bool)
+
+		go func() {
+			defer close(rop.chPost)
+
+			// Wait for the main operation to finish
+			<-rop.chDone
+			if rop.err != nil {
+				return
+			}
+
+			// Get the operation data
+			op, err := rop.GetTarget()
+			if err != nil {
+				return
+			}
+
+			// Extract the fingerprint
+			fingerprint := op.Metadata["fingerprint"].(string)
+
+			// Add the aliases
+			for _, entry := range req.Aliases {
+				alias := api.ImageAliasesPost{}
+				alias.Name = entry.Name
+				alias.Target = fingerprint
+
+				target.CreateImageAlias(alias)
+			}
+		}()
+	}
+
 	// Forward targetOp to remote op
 	go func() {
 		rop.err = rop.targetOp.Wait()

From 5b6010e06a34596e7a9f47bc78f415ac8f50279d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 18:56:26 -0400
Subject: [PATCH 3/6] client: Move CopyImage to the target server
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This avoids a bunch of duplication and better aligns with what we'll do
for CreateFromImage and CopyContainer.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 client/interfaces.go           | 21 ++++++++--
 client/lxd.go                  | 45 ++++++++++++----------
 client/lxd_images.go           | 36 ++++++++++-------
 client/simplestreams.go        | 10 +++++
 client/simplestreams_images.go | 87 +++---------------------------------------
 test/lxd-benchmark/main.go     |  2 +-
 6 files changed, 81 insertions(+), 120 deletions(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index f11adce0e..9ddc6cc93 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -8,14 +8,22 @@ import (
 	"github.com/lxc/lxd/shared/api"
 )
 
+// The Server type represents a generic read-only server.
+type Server interface {
+	GetConnectionInfo() (info *ConnectionInfo, err error)
+}
+
 // The ImageServer type represents a read-only image server.
 type ImageServer interface {
+	Server
+
 	// Image handling functions
 	GetImages() (images []api.Image, err error)
 	GetImageFingerprints() (fingerprints []string, err error)
 
 	GetImage(fingerprint string) (image *api.Image, ETag string, err error)
 	GetImageFile(fingerprint string, req ImageFileRequest) (resp *ImageFileResponse, err error)
+	GetImageSecret(fingerprint string) (secret string, err error)
 
 	GetPrivateImage(fingerprint string, secret string) (image *api.Image, ETag string, err error)
 	GetPrivateImageFile(fingerprint string, secret string, req ImageFileRequest) (resp *ImageFileResponse, err error)
@@ -24,12 +32,12 @@ type ImageServer interface {
 	GetImageAliasNames() (names []string, err error)
 
 	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
-
-	CopyImage(image api.Image, target ContainerServer, args *ImageCopyArgs) (op *RemoteOperation, err error)
 }
 
 // The ContainerServer type represents a full featured LXD server.
 type ContainerServer interface {
+	ImageServer
+
 	// Server functions
 	GetServer() (server *api.Server, ETag string, err error)
 	UpdateServer(server api.ServerPut, ETag string) (err error)
@@ -78,8 +86,8 @@ type ContainerServer interface {
 	GetEvents() (listener *EventListener, err error)
 
 	// Image functions
-	ImageServer
 	CreateImage(image api.ImagesPost, args *ImageCreateArgs) (op *Operation, err error)
+	CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (op *RemoteOperation, err error)
 	UpdateImage(fingerprint string, image api.ImagePut, ETag string) (err error)
 	DeleteImage(fingerprint string) (op *Operation, err error)
 	RefreshImage(fingerprint string) (op *Operation, err error)
@@ -133,6 +141,13 @@ type ContainerServer interface {
 	RawWebsocket(path string) (conn *websocket.Conn, err error)
 }
 
+// The ConnectionInfo struct represents general information for a connection
+type ConnectionInfo struct {
+	Addresses   []string
+	Certificate string
+	Protocol    string
+}
+
 // The ProgressData struct represents new progress information on an operation
 type ProgressData struct {
 	// Preferred string repreentation of progress (always set)
diff --git a/client/lxd.go b/client/lxd.go
index 69bd4e8e9..4c02eabe4 100644
--- a/client/lxd.go
+++ b/client/lxd.go
@@ -29,6 +29,30 @@ type ProtocolLXD struct {
 	httpUserAgent   string
 }
 
+// GetConnectionInfo returns the basic connection information used to interact with the server
+func (r *ProtocolLXD) GetConnectionInfo() (*ConnectionInfo, error) {
+	info := ConnectionInfo{}
+	info.Certificate = r.httpCertificate
+	info.Protocol = "lxd"
+
+	urls := []string{}
+	if len(r.server.Environment.Addresses) > 0 {
+		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)
+			}
+		}
+	}
+	info.Addresses = urls
+
+	return &info, nil
+}
+
 // RawQuery allows directly querying the LXD API
 //
 // This should only be used by internal LXD tools.
@@ -223,24 +247,3 @@ 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 eb67ee268..90ddfc6fe 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -62,6 +62,16 @@ func (r *ProtocolLXD) GetImageFile(fingerprint string, req ImageFileRequest) (*I
 	return r.GetPrivateImageFile(fingerprint, "", req)
 }
 
+// GetImageSecret is a helper around CreateImageSecret that returns a secret for the image
+func (r *ProtocolLXD) GetImageSecret(fingerprint string) (string, error) {
+	op, err := r.CreateImageSecret(fingerprint)
+	if err != nil {
+		return "", err
+	}
+
+	return op.Metadata["secret"].(string), nil
+}
+
 // GetPrivateImage is similar to GetImage but allows passing a secret download token
 func (r *ProtocolLXD) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
 	image := api.Image{}
@@ -432,13 +442,13 @@ func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (
 }
 
 // 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) {
+func (r *ProtocolLXD) tryCopyImage(req api.ImagesPost, urls []string) (*RemoteOperation, error) {
 	rop := RemoteOperation{
 		chDone: make(chan bool),
 	}
 
 	// For older servers, apply the aliases after copy
-	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
+	if !r.HasExtension("image_create_aliases") && req.Aliases != nil {
 		rop.chPost = make(chan bool)
 
 		go func() {
@@ -465,7 +475,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 				alias.Name = entry.Name
 				alias.Target = fingerprint
 
-				target.CreateImageAlias(alias)
+				r.CreateImageAlias(alias)
 			}
 		}()
 	}
@@ -477,7 +487,7 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 		for _, serverURL := range urls {
 			req.Source.Server = serverURL
 
-			op, err := target.CreateImage(req, nil)
+			op, err := r.CreateImage(req, nil)
 			if err != nil {
 				errors = append(errors, fmt.Sprintf("%s: %v", serverURL, err))
 				continue
@@ -509,15 +519,15 @@ func (r *ProtocolLXD) tryCopyImage(target ContainerServer, req api.ImagesPost, u
 	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) (*RemoteOperation, error) {
+// CopyImage copies an image from a remote server. Additional options can be passed using ImageCopyArgs
+func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *ImageCopyArgs) (*RemoteOperation, error) {
 	// Sanity checks
-	if r == target {
+	if r == source {
 		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()
+	info, err := source.GetConnectionInfo()
 	if err != nil {
 		return nil, err
 	}
@@ -526,8 +536,8 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 	req := api.ImagesPost{
 		Source: &api.ImagesPostSource{
 			ImageSource: api.ImageSource{
-				Certificate: r.httpCertificate,
-				Protocol:    "lxd",
+				Certificate: info.Certificate,
+				Protocol:    info.Protocol,
 			},
 			Fingerprint: image.Fingerprint,
 			Mode:        "pull",
@@ -537,12 +547,12 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 
 	// Generate secret token if needed
 	if !image.Public {
-		op, err := r.CreateImageSecret(image.Fingerprint)
+		secret, err := source.GetImageSecret(image.Fingerprint)
 		if err != nil {
 			return nil, err
 		}
 
-		req.Source.Secret = op.Metadata["secret"].(string)
+		req.Source.Secret = secret
 	}
 
 	// Process the arguments
@@ -559,7 +569,7 @@ func (r *ProtocolLXD) CopyImage(image api.Image, target ContainerServer, args *I
 		}
 	}
 
-	return r.tryCopyImage(target, req, urls)
+	return r.tryCopyImage(req, info.Addresses)
 }
 
 // UpdateImage updates the image definition
diff --git a/client/simplestreams.go b/client/simplestreams.go
index a69a2ea8d..79fef42ae 100644
--- a/client/simplestreams.go
+++ b/client/simplestreams.go
@@ -15,3 +15,13 @@ type ProtocolSimpleStreams struct {
 	httpUserAgent   string
 	httpCertificate string
 }
+
+// GetConnectionInfo returns the basic connection information used to interact with the server
+func (r *ProtocolSimpleStreams) GetConnectionInfo() (*ConnectionInfo, error) {
+	info := ConnectionInfo{}
+	info.Addresses = []string{r.httpHost}
+	info.Certificate = r.httpCertificate
+	info.Protocol = "simplestreams"
+
+	return &info, nil
+}
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index 970c308a0..d84f8ed09 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -102,6 +102,11 @@ func (r *ProtocolSimpleStreams) GetImageFile(fingerprint string, req ImageFileRe
 	return &resp, nil
 }
 
+// GetImageSecret isn't relevant for the simplestreams protocol
+func (r *ProtocolSimpleStreams) GetImageSecret(fingerprint string) (string, error) {
+	return "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
+}
+
 // GetPrivateImage isn't relevant for the simplestreams protocol
 func (r *ProtocolSimpleStreams) GetPrivateImage(fingerprint string, secret string) (*api.Image, string, error) {
 	return nil, "", fmt.Errorf("Private images aren't supported by the simplestreams protocol")
@@ -143,85 +148,3 @@ func (r *ProtocolSimpleStreams) GetImageAlias(name string) (*api.ImageAliasesEnt
 
 	return alias, "", err
 }
-
-// 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) (*RemoteOperation, error) {
-	// Prepare the copy request
-	req := api.ImagesPost{
-		Source: &api.ImagesPostSource{
-			ImageSource: api.ImageSource{
-				Certificate: r.httpCertificate,
-				Protocol:    "simplestreams",
-				Server:      r.httpHost,
-			},
-			Fingerprint: image.Fingerprint,
-			Mode:        "pull",
-			Type:        "image",
-		},
-	}
-
-	// Process the arguments
-	if args != nil {
-		req.Aliases = args.Aliases
-		req.AutoUpdate = args.AutoUpdate
-		req.Public = args.Public
-
-		if args.CopyAliases {
-			req.Aliases = image.Aliases
-			if args.Aliases != nil {
-				req.Aliases = append(req.Aliases, args.Aliases...)
-			}
-		}
-	}
-
-	op, err := target.CreateImage(req, nil)
-	if err != nil {
-		return nil, err
-	}
-
-	rop := RemoteOperation{
-		targetOp: op,
-		chDone:   make(chan bool),
-	}
-
-	// For older servers, apply the aliases after copy
-	if !target.HasExtension("image_create_aliases") && req.Aliases != nil {
-		rop.chPost = make(chan bool)
-
-		go func() {
-			defer close(rop.chPost)
-
-			// Wait for the main operation to finish
-			<-rop.chDone
-			if rop.err != nil {
-				return
-			}
-
-			// Get the operation data
-			op, err := rop.GetTarget()
-			if err != nil {
-				return
-			}
-
-			// Extract the fingerprint
-			fingerprint := op.Metadata["fingerprint"].(string)
-
-			// Add the aliases
-			for _, entry := range req.Aliases {
-				alias := api.ImageAliasesPost{}
-				alias.Name = entry.Name
-				alias.Target = fingerprint
-
-				target.CreateImageAlias(alias)
-			}
-		}()
-	}
-
-	// Forward targetOp to remote op
-	go func() {
-		rop.err = rop.targetOp.Wait()
-		close(rop.chDone)
-	}()
-
-	return &rop, nil
-}
diff --git a/test/lxd-benchmark/main.go b/test/lxd-benchmark/main.go
index 686146249..9c8eb3c11 100644
--- a/test/lxd-benchmark/main.go
+++ b/test/lxd-benchmark/main.go
@@ -168,7 +168,7 @@ func spawnContainers(c lxd.ContainerServer, count int, image string, privileged
 				return err
 			}
 
-			op, err := d.CopyImage(*image, c, nil)
+			op, err := c.CopyImage(d, *image, nil)
 			if err != nil {
 				logf(fmt.Sprintf("Failed to import image: %s", err))
 				return err

From a79b763480f0359076cda4362579185a877464fa Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Mon, 29 May 2017 23:58:57 -0400
Subject: [PATCH 4/6] client: Add CreateContainerFromImage function
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     |   1 +
 client/lxd_containers.go | 107 +++++++++++++++++++++++++++++++++++++++++++++++
 client/lxd_images.go     |   2 +-
 3 files changed, 109 insertions(+), 1 deletion(-)

diff --git a/client/interfaces.go b/client/interfaces.go
index 9ddc6cc93..e5541564d 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -56,6 +56,7 @@ type ContainerServer interface {
 	GetContainers() (containers []api.Container, err error)
 	GetContainer(name string) (container *api.Container, ETag string, err error)
 	CreateContainer(container api.ContainersPost) (op *Operation, err error)
+	CreateContainerFromImage(source ImageServer, image api.Image, imgcontainer api.ContainersPost) (op *RemoteOperation, err error)
 	UpdateContainer(name string, container api.ContainerPut, ETag string) (op *Operation, err error)
 	RenameContainer(name string, container api.ContainerPost) (op *Operation, err error)
 	MigrateContainer(name string, container api.ContainerPost) (op *Operation, err error)
diff --git a/client/lxd_containers.go b/client/lxd_containers.go
index 06b9add22..ead43a04a 100644
--- a/client/lxd_containers.go
+++ b/client/lxd_containers.go
@@ -78,6 +78,113 @@ func (r *ProtocolLXD) CreateContainer(container api.ContainersPost) (*Operation,
 	return op, nil
 }
 
+func (r *ProtocolLXD) tryCreateContainerFromImage(req api.ContainersPost, 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 := r.CreateContainer(req)
+			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("Failed container creation:\n - %s", strings.Join(errors, "\n - "))
+		}
+
+		close(rop.chDone)
+	}()
+
+	return &rop, nil
+}
+
+// CreateContainerFromImage is a convenience function to make it easier to create a container from an existing image
+func (r *ProtocolLXD) CreateContainerFromImage(source ImageServer, image api.Image, req api.ContainersPost) (*RemoteOperation, error) {
+	// Set the minimal source fields
+	req.Source.Type = "image"
+
+	// Optimization for the local image case
+	if r == source {
+		// Always use fingerprints for local case
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+
+		op, err := r.CreateContainer(req)
+		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
+	}
+
+	// Minimal source fields for remote image
+	req.Source.Mode = "pull"
+
+	// If we have an alias and the image is public, use that
+	if req.Source.Alias != "" && image.Public {
+		req.Source.Fingerprint = ""
+	} else {
+		req.Source.Fingerprint = image.Fingerprint
+		req.Source.Alias = ""
+	}
+
+	// Get source server connection information
+	info, err := source.GetConnectionInfo()
+	if err != nil {
+		return nil, err
+	}
+
+	req.Source.Protocol = info.Protocol
+	req.Source.Certificate = info.Certificate
+
+	// Generate secret token if needed
+	if !image.Public {
+		secret, err := source.GetImageSecret(image.Fingerprint)
+		if err != nil {
+			return nil, err
+		}
+
+		req.Source.Secret = secret
+	}
+
+	return r.tryCreateContainerFromImage(req, info.Addresses)
+}
+
 // UpdateContainer updates the container definition
 func (r *ProtocolLXD) UpdateContainer(name string, container api.ContainerPut, ETag string) (*Operation, error) {
 	// Send the request
diff --git a/client/lxd_images.go b/client/lxd_images.go
index 90ddfc6fe..f9065e3e5 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -526,7 +526,7 @@ func (r *ProtocolLXD) CopyImage(source ImageServer, image api.Image, args *Image
 		return nil, fmt.Errorf("The source and target servers must be different")
 	}
 
-	// Get a list of addresses for the source server
+	// Get source server connection information
 	info, err := source.GetConnectionInfo()
 	if err != nil {
 		return nil, err

From 76fadef65d8791894c640ecceb1275a496bda50b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 30 May 2017 00:27:40 -0400
Subject: [PATCH 5/6] tests: Test suites use space indent
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>
---
 test/suites/image_auto_update.sh |  6 +++---
 test/suites/init_preseed.sh      | 16 ++++++++--------
 2 files changed, 11 insertions(+), 11 deletions(-)

diff --git a/test/suites/image_auto_update.sh b/test/suites/image_auto_update.sh
index 6cfda3650..4e6007803 100644
--- a/test/suites/image_auto_update.sh
+++ b/test/suites/image_auto_update.sh
@@ -49,9 +49,9 @@ test_image_auto_update() {
   retries=10
   while [ "${retries}" != "0" ]; do
     if lxc image info "${fp1}" > /dev/null 2>&1; then
-	sleep 2
-	retries=$((retries-1))
-	continue
+        sleep 2
+        retries=$((retries-1))
+        continue
     fi
     break
   done
diff --git a/test/suites/init_preseed.sh b/test/suites/init_preseed.sh
index b218050f7..b1499cf64 100644
--- a/test/suites/init_preseed.sh
+++ b/test/suites/init_preseed.sh
@@ -13,14 +13,14 @@ test_init_preseed() {
     # In case we're running against the ZFS backend, let's test
     # creating a zfs storage pool, otherwise just use dir.
     if [ "$lxd_backend" = "zfs" ]; then
-	configure_loop_device loop_file_4 loop_device_4
+        configure_loop_device loop_file_4 loop_device_4
         # shellcheck disable=SC2154
-	zpool create "lxdtest-$(basename "${LXD_DIR}")-preseed-pool" "${loop_device_4}" -f -m none -O compression=on
-	driver="zfs"
-	source="lxdtest-$(basename "${LXD_DIR}")-preseed-pool"
+        zpool create "lxdtest-$(basename "${LXD_DIR}")-preseed-pool" "${loop_device_4}" -f -m none -O compression=on
+        driver="zfs"
+        source="lxdtest-$(basename "${LXD_DIR}")-preseed-pool"
     else
-	driver="dir"
-	source=""
+        driver="dir"
+        source=""
     fi
 
     cat <<EOF | lxd init --preseed
@@ -73,8 +73,8 @@ EOF
     lxc storage delete data
 
     if [ "$lxd_backend" = "zfs" ]; then
-	# shellcheck disable=SC2154
-	deconfigure_loop_device "${loop_file_4}" "${loop_device_4}"
+        # shellcheck disable=SC2154
+        deconfigure_loop_device "${loop_file_4}" "${loop_device_4}"
     fi
   )
   kill_lxd "${LXD_INIT_DIR}"

From 6aaa1c8b6e3ebe14f2a952436d9cafe120d4debb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 30 May 2017 01:07:44 -0400
Subject: [PATCH 6/6] images: Fix potential double unlock
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>
---
 lxd/daemon_images.go | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/lxd/daemon_images.go b/lxd/daemon_images.go
index e5b223251..b2023d42f 100644
--- a/lxd/daemon_images.go
+++ b/lxd/daemon_images.go
@@ -296,9 +296,12 @@ func (d *Daemon) ImageDownload(op *operation, server string, protocol string, ce
 			// Other download succeeded, we're done
 			return imgInfo, nil
 		}
+	} else {
+		imagesDownloadingLock.Unlock()
 	}
 
 	// Add the download to the queue
+	imagesDownloadingLock.Lock()
 	imagesDownloading[fp] = make(chan bool)
 	imagesDownloadingLock.Unlock()
 


More information about the lxc-devel mailing list