[lxc-devel] [lxd/master] Implement SuitableArchitectures

stgraber on Github lxc-bot at linuxcontainers.org
Tue Dec 10 20:37:08 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/20191210/3eeac40c/attachment-0001.bin>
-------------- next part --------------
From e5414ef534ef380f9b10733c3a4f4df6b7430bf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Dec 2019 12:23:13 -0500
Subject: [PATCH 1/5] lxd/instance: Split instance image resolving
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/containers_post.go         | 65 +++-------------------------------
 lxd/instance/instance_utils.go | 65 ++++++++++++++++++++++++++++++++++
 2 files changed, 69 insertions(+), 61 deletions(-)

diff --git a/lxd/containers_post.go b/lxd/containers_post.go
index ab9daf5544..5da87077af 100644
--- a/lxd/containers_post.go
+++ b/lxd/containers_post.go
@@ -36,66 +36,9 @@ import (
 )
 
 func createFromImage(d *Daemon, project string, req *api.InstancesPost) response.Response {
-	var hash string
-	var err error
-
-	if req.Source.Fingerprint != "" {
-		hash = req.Source.Fingerprint
-	} else if req.Source.Alias != "" {
-		if req.Source.Server != "" {
-			hash = req.Source.Alias
-		} else {
-			_, alias, err := d.cluster.ImageAliasGet(project, req.Source.Alias, true)
-			if err != nil {
-				return response.SmartError(err)
-			}
-
-			hash = alias.Target
-		}
-	} else if req.Source.Properties != nil {
-		if req.Source.Server != "" {
-			return response.BadRequest(fmt.Errorf("Property match is only supported for local images"))
-		}
-
-		hashes, err := d.cluster.ImagesGet(project, false)
-		if err != nil {
-			return response.SmartError(err)
-		}
-
-		var image *api.Image
-
-		for _, imageHash := range hashes {
-			_, img, err := d.cluster.ImageGet(project, imageHash, false, true)
-			if err != nil {
-				continue
-			}
-
-			if image != nil && img.CreatedAt.Before(image.CreatedAt) {
-				continue
-			}
-
-			match := true
-			for key, value := range req.Source.Properties {
-				if img.Properties[key] != value {
-					match = false
-					break
-				}
-			}
-
-			if !match {
-				continue
-			}
-
-			image = img
-		}
-
-		if image == nil {
-			return response.BadRequest(fmt.Errorf("No matching image could be found"))
-		}
-
-		hash = image.Fingerprint
-	} else {
-		return response.BadRequest(fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image"))
+	hash, err := instance.ResolveImage(d.State(), project, req.Source)
+	if err != nil {
+		return response.BadRequest(err)
 	}
 
 	dbType, err := instancetype.New(string(req.Type))
@@ -933,7 +876,7 @@ func containersPost(d *Daemon, r *http.Request) response.Response {
 	case "copy":
 		return createFromCopy(d, project, &req)
 	default:
-		return response.BadRequest(fmt.Errorf("unknown source type %s", req.Source.Type))
+		return response.BadRequest(fmt.Errorf("Unknown source type %s", req.Source.Type))
 	}
 }
 
diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index 475b496313..3750c3d39e 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -596,3 +596,68 @@ func BackupLoadByName(s *state.State, project, name string) (*backup.Backup, err
 
 	return backup.New(s, instance, args.ID, name, args.CreationDate, args.ExpiryDate, args.InstanceOnly, args.OptimizedStorage), nil
 }
+
+// ResolveImage takes an instance source and returns a hash suitable for instance creation or download.
+func ResolveImage(s *state.State, project string, source api.InstanceSource) (string, error) {
+	if source.Fingerprint != "" {
+		return source.Fingerprint, nil
+	}
+
+	if source.Alias != "" {
+		if source.Server != "" {
+			return source.Alias, nil
+		}
+
+		_, alias, err := s.Cluster.ImageAliasGet(project, source.Alias, true)
+		if err != nil {
+			return "", err
+		}
+
+		return alias.Target, nil
+	}
+
+	if source.Properties != nil {
+		if source.Server != "" {
+			return "", fmt.Errorf("Property match is only supported for local images")
+		}
+
+		hashes, err := s.Cluster.ImagesGet(project, false)
+		if err != nil {
+			return "", err
+		}
+
+		var image *api.Image
+		for _, imageHash := range hashes {
+			_, img, err := s.Cluster.ImageGet(project, imageHash, false, true)
+			if err != nil {
+				continue
+			}
+
+			if image != nil && img.CreatedAt.Before(image.CreatedAt) {
+				continue
+			}
+
+			match := true
+			for key, value := range source.Properties {
+				if img.Properties[key] != value {
+					match = false
+					break
+				}
+			}
+
+			if !match {
+				continue
+			}
+
+			image = img
+		}
+
+		if image != nil {
+			return image.Fingerprint, nil
+		}
+
+		return "", fmt.Errorf("No matching image could be found")
+	}
+
+	return "", fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image")
+}

From cc9a7a9d2f039381d0b5fa1c1c042cd1491bd830 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Dec 2019 14:52:03 -0500
Subject: [PATCH 2/5] lxd/state: Expose proxy 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>
---
 lxd/daemon.go        | 2 +-
 lxd/state/state.go   | 9 +++++++--
 lxd/state/testing.go | 2 +-
 3 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/lxd/daemon.go b/lxd/daemon.go
index 5c1b92cf61..9562828691 100644
--- a/lxd/daemon.go
+++ b/lxd/daemon.go
@@ -333,7 +333,7 @@ func writeMacaroonsRequiredResponse(b *identchecker.Bakery, r *http.Request, w h
 
 // State creates a new State instance linked to our internal db and os.
 func (d *Daemon) State() *state.State {
-	return state.NewState(d.db, d.cluster, d.maas, d.os, d.endpoints, d.events, d.devlxdEvents)
+	return state.NewState(d.db, d.cluster, d.maas, d.os, d.endpoints, d.events, d.devlxdEvents, d.proxy)
 }
 
 // UnixSocket returns the full path to the unix.socket file that this daemon is
diff --git a/lxd/state/state.go b/lxd/state/state.go
index 6b60d3e96b..d8b7647e8b 100644
--- a/lxd/state/state.go
+++ b/lxd/state/state.go
@@ -3,6 +3,9 @@
 package state
 
 import (
+	"net/http"
+	"net/url"
+
 	"github.com/lxc/lxd/lxd/db"
 	"github.com/lxc/lxd/lxd/endpoints"
 	"github.com/lxc/lxd/lxd/events"
@@ -22,7 +25,8 @@ type State struct {
 	MAAS *maas.Controller
 
 	// OS access
-	OS *sys.OS
+	OS    *sys.OS
+	Proxy func(req *http.Request) (*url.URL, error)
 
 	// LXD server
 	Endpoints *endpoints.Endpoints
@@ -34,7 +38,7 @@ type State struct {
 
 // NewState returns a new State object with the given database and operating
 // system components.
-func NewState(node *db.Node, cluster *db.Cluster, maas *maas.Controller, os *sys.OS, endpoints *endpoints.Endpoints, events *events.Server, devlxdEvents *events.Server) *State {
+func NewState(node *db.Node, cluster *db.Cluster, maas *maas.Controller, os *sys.OS, endpoints *endpoints.Endpoints, events *events.Server, devlxdEvents *events.Server, proxy func(req *http.Request) (*url.URL, error)) *State {
 	return &State{
 		Node:         node,
 		Cluster:      cluster,
@@ -43,5 +47,6 @@ func NewState(node *db.Node, cluster *db.Cluster, maas *maas.Controller, os *sys
 		Endpoints:    endpoints,
 		DevlxdEvents: devlxdEvents,
 		Events:       events,
+		Proxy:        proxy,
 	}
 }
diff --git a/lxd/state/testing.go b/lxd/state/testing.go
index 93fc785ee0..f14e1203b6 100644
--- a/lxd/state/testing.go
+++ b/lxd/state/testing.go
@@ -25,7 +25,7 @@ func NewTestState(t *testing.T) (*State, func()) {
 		osCleanup()
 	}
 
-	state := NewState(node, cluster, nil, os, nil, nil, nil)
+	state := NewState(node, cluster, nil, os, nil, nil, nil, nil)
 
 	return state, cleanup
 }

From afee5bcd586df5ae595e5cf2864e44ebe2ebbfae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Dec 2019 15:20:35 -0500
Subject: [PATCH 3/5] client: Add caching options
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This will allow moving the current caching logic from the lxd code into
the shared client package.

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

diff --git a/client/connection.go b/client/connection.go
index 86cdcbaafb..db638872f5 100644
--- a/client/connection.go
+++ b/client/connection.go
@@ -6,6 +6,7 @@ import (
 	"os"
 	"path/filepath"
 	"strings"
+	"time"
 
 	"gopkg.in/macaroon-bakery.v2/httpbakery"
 
@@ -50,6 +51,10 @@ type ConnectionArgs struct {
 
 	// Skip automatic GetServer request upon connection
 	SkipGetServer bool
+
+	// Caching support for image servers
+	CachePath   string
+	CacheExpiry time.Duration
 }
 
 // ConnectLXD lets you connect to a remote LXD daemon over HTTPs.

From 344c943d851ef75eac3eb99ce514abba7ec4a878 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Dec 2019 15:21:07 -0500
Subject: [PATCH 4/5] client: Add arch-dependent aliases
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_images.go           | 15 +++++++++++++++
 client/simplestreams_images.go | 15 +++++++++++++++
 3 files changed, 31 insertions(+)

diff --git a/client/interfaces.go b/client/interfaces.go
index b034c1b871..8f0ce20bee 100644
--- a/client/interfaces.go
+++ b/client/interfaces.go
@@ -57,6 +57,7 @@ type ImageServer interface {
 
 	GetImageAlias(name string) (alias *api.ImageAliasesEntry, ETag string, err error)
 	GetImageAliasType(imageType string, name string) (alias *api.ImageAliasesEntry, ETag string, err error)
+	GetImageAliasArchitectures(imageType string, name string) (entries map[string]*api.ImageAliasesEntry, err error)
 }
 
 // The InstanceServer type represents a full featured LXD server.
diff --git a/client/lxd_images.go b/client/lxd_images.go
index b46213f0cc..50d90e6616 100644
--- a/client/lxd_images.go
+++ b/client/lxd_images.go
@@ -333,6 +333,21 @@ func (r *ProtocolLXD) GetImageAliasType(imageType string, name string) (*api.Ima
 	return alias, etag, nil
 }
 
+// GetImageAliasArchitectures returns a map of architectures / targets
+func (r *ProtocolLXD) GetImageAliasArchitectures(imageType string, name string) (map[string]*api.ImageAliasesEntry, error) {
+	alias, _, err := r.GetImageAliasType(imageType, name)
+	if err != nil {
+		return nil, err
+	}
+
+	img, _, err := r.GetImage(alias.Target)
+	if err != nil {
+		return nil, err
+	}
+
+	return map[string]*api.ImageAliasesEntry{img.Architecture: alias}, nil
+}
+
 // CreateImage requests that LXD creates, copies or import a new image
 func (r *ProtocolLXD) CreateImage(image api.ImagesPost, args *ImageCreateArgs) (Operation, error) {
 	if image.CompressionAlgorithm != "" {
diff --git a/client/simplestreams_images.go b/client/simplestreams_images.go
index cb05607d75..2e66778093 100644
--- a/client/simplestreams_images.go
+++ b/client/simplestreams_images.go
@@ -253,3 +253,18 @@ func (r *ProtocolSimpleStreams) GetImageAliasType(imageType string, name string)
 
 	return alias, "", err
 }
+
+// GetImageAliasArchitectures returns a map of architectures / targets
+func (r *ProtocolSimpleStreams) GetImageAliasArchitectures(imageType string, name string) (map[string]*api.ImageAliasesEntry, error) {
+	alias, _, err := r.GetImageAliasType(imageType, name)
+	if err != nil {
+		return nil, err
+	}
+
+	img, _, err := r.GetImage(alias.Target)
+	if err != nil {
+		return nil, err
+	}
+
+	return map[string]*api.ImageAliasesEntry{img.Architecture: alias}, nil
+}

From 12e580acbe814bdd344d4a7b6aa0b8f33b16955a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Tue, 10 Dec 2019 15:21:43 -0500
Subject: [PATCH 5/5] lxd/instance: Implement SuitableArchitectures
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This function takes an instance creation request and returns the list of
suitable architectures for the request.

Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
 lxd/instance/instance_utils.go | 142 +++++++++++++++++++++++++++++++++
 1 file changed, 142 insertions(+)

diff --git a/lxd/instance/instance_utils.go b/lxd/instance/instance_utils.go
index 3750c3d39e..9d25e4a853 100644
--- a/lxd/instance/instance_utils.go
+++ b/lxd/instance/instance_utils.go
@@ -14,6 +14,7 @@ import (
 	"github.com/pkg/errors"
 	yaml "gopkg.in/yaml.v2"
 
+	"github.com/lxc/lxd/client"
 	"github.com/lxc/lxd/lxd/backup"
 	"github.com/lxc/lxd/lxd/db"
 	deviceConfig "github.com/lxc/lxd/lxd/device/config"
@@ -28,6 +29,7 @@ import (
 	log "github.com/lxc/lxd/shared/log15"
 	"github.com/lxc/lxd/shared/logger"
 	"github.com/lxc/lxd/shared/osarch"
+	"github.com/lxc/lxd/shared/version"
 )
 
 // ValidDevices is linked from main.instanceValidDevices to validate device config. Currently
@@ -661,3 +663,143 @@ func ResolveImage(s *state.State, project string, source api.InstanceSource) (st
 
 	return "", fmt.Errorf("Must specify one of alias, fingerprint or properties for init from image")
 }
+
+// SuitableArchitectures returns a slice of architecture ids based on an instance create request.
+//
+// An empty list indicates that the request may be handled by any architecture.
+// A nil list indicates that we can't tell at this stage, typically for private images.
+func SuitableArchitectures(s *state.State, project string, req api.InstancesPost) ([]int, error) {
+	// Handle cases where the architecture is already provided.
+	if shared.StringInSlice(req.Source.Type, []string{"migration", "none"}) && req.Architecture != "" {
+		id, err := osarch.ArchitectureId(req.Architecture)
+		if err != nil {
+			return nil, err
+		}
+
+		return []int{id}, nil
+	}
+
+	// For migration, an architecture must be specified in the req.
+	if req.Source.Type == "migration" && req.Architecture == "" {
+		return nil, fmt.Errorf("An architecture must be specified in migration requests")
+	}
+
+	// For none, allow any architecture.
+	if req.Source.Type == "none" {
+		return []int{}, nil
+	}
+
+	// For copy, always use the source architecture.
+	if req.Source.Type == "copy" {
+		srcProject := req.Source.Project
+		if srcProject == "" {
+			srcProject = project
+		}
+
+		var inst *db.Instance
+		err := s.Cluster.Transaction(func(tx *db.ClusterTx) error {
+			var err error
+
+			inst, err = tx.InstanceGet(srcProject, req.Source.Source)
+			if err != nil {
+				return errors.Wrapf(err, "Failed to fetch instance %s in project %s", req.Source.Source, srcProject)
+			}
+
+			return nil
+		})
+		if err != nil {
+			return nil, err
+		}
+
+		return []int{inst.Architecture}, nil
+	}
+
+	// For image, things get a bit more complicated.
+	if req.Source.Type == "image" {
+		// Resolve the image.
+		hash, err := ResolveImage(s, project, req.Source)
+		if err != nil {
+			return nil, err
+		}
+
+		// Handle local images.
+		if req.Source.Server == "" {
+			_, img, err := s.Cluster.ImageGet(project, hash, false, false)
+			if err != nil {
+				return nil, err
+			}
+
+			id, err := osarch.ArchitectureId(img.Architecture)
+			if err != nil {
+				return nil, err
+			}
+
+			return []int{id}, nil
+		}
+
+		// Handle remote images.
+		if req.Source.Server != "" {
+			// Detect image type based on instance type requested.
+			imgType := "container"
+			if req.Type == "virtual-machine" {
+				imgType = "virtual-machine"
+			}
+
+			if req.Source.Secret != "" {
+				// We can't retrieve a private image, defer to later processing.
+				return nil, nil
+			}
+
+			var remote lxd.ImageServer
+			if shared.StringInSlice(req.Source.Protocol, []string{"", "lxd"}) {
+				// Remote LXD image server.
+				remote, err = lxd.ConnectPublicLXD(req.Source.Server, &lxd.ConnectionArgs{
+					TLSServerCert: req.Source.Certificate,
+					UserAgent:     version.UserAgent,
+					Proxy:         s.Proxy,
+					CachePath:     s.OS.CacheDir,
+					CacheExpiry:   time.Hour,
+				})
+				if err != nil {
+					return nil, err
+				}
+			} else if req.Source.Protocol == "simplestreams" {
+				// Remote simplestreams image server.
+			} else {
+				return nil, fmt.Errorf("Unsupported remote image server protocol: %s", req.Source.Protocol)
+			}
+
+			// Look for a matching alias.
+			entries, err := remote.GetImageAliasArchitectures(imgType, hash)
+			if err != nil {
+				// Look for a matching image by fingerprint.
+				img, _, err := remote.GetImage(hash)
+				if err != nil {
+					return nil, err
+				}
+
+				id, err := osarch.ArchitectureId(img.Architecture)
+				if err != nil {
+					return nil, err
+				}
+
+				return []int{id}, nil
+			}
+
+			architectures := []int{}
+			for arch := range entries {
+				id, err := osarch.ArchitectureId(arch)
+				if err != nil {
+					return nil, err
+				}
+
+				architectures = append(architectures, id)
+			}
+
+			return architectures, nil
+		}
+	}
+
+	// No other known types
+	return nil, fmt.Errorf("Unknown instance source type: %s", req.Source.Type)
+}


More information about the lxc-devel mailing list