[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