[lxc-devel] [lxd/master] [DRAFT] Define default profile for image #4567
ralubis on Github
lxc-bot at linuxcontainers.org
Fri Nov 29 22:26:23 UTC 2019
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 779 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20191129/a877b269/attachment-0001.bin>
-------------- next part --------------
From e171c7844a3f8bd3e326a2fe18c990b8c2b88a87 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:08:54 -0600
Subject: [PATCH 1/4] api: Add image_profiles extension
Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
doc/api-extensions.md | 3 +++
shared/version/api.go | 1 +
2 files changed, 4 insertions(+)
diff --git a/doc/api-extensions.md b/doc/api-extensions.md
index 1b836a0623..632ce186ac 100644
--- a/doc/api-extensions.md
+++ b/doc/api-extensions.md
@@ -886,3 +886,6 @@ This allows for existing a CEPH RDB or FS to be directly connected to a LXD cont
## virtual\_machines
Add virtual machine support.
+
+## image\_profiles
+Allows a list of profiles to be applied to an image when launching a new container.
diff --git a/shared/version/api.go b/shared/version/api.go
index 1afdc1b2d0..7a2602b1b5 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -179,6 +179,7 @@ var APIExtensions = []string{
"container_syscall_intercept_mount_fuse",
"container_disk_ceph",
"virtual-machines",
+ "image_profiles"
}
// APIExtensionsCount returns the number of available API extensions.
From 57c49ee07753922e239c76f0d60ecc0d7e647819 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:15:17 -0600
Subject: [PATCH 2/4] shared/api: Add image profiles
Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
shared/api/image.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/shared/api/image.go b/shared/api/image.go
index 0eb4c392e6..dc48850c4b 100644
--- a/shared/api/image.go
+++ b/shared/api/image.go
@@ -44,6 +44,9 @@ type ImagePut struct {
// API extension: images_expiry
ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"`
+
+ // API extension: image_profiles
+ Profiles []string `json:"profiles" yaml:"profiles"`
}
// Image represents a LXD image
From d224caf8862dc0aab16e680e0a17c0b6edf457cf Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Sun, 24 Nov 2019 18:52:14 -0600
Subject: [PATCH 3/4] lxd/db: Add images_profiles table
Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
lxc/image.go | 12 ++++++
lxd/db/cluster/schema.go | 8 +++-
lxd/db/cluster/update.go | 15 +++++++
lxd/db/images.go | 38 +++++++++++++++-
lxd/db/instances.mapper.go | 88 +++++++++++++++++++-------------------
lxd/images.go | 20 +++++++--
shared/version/api.go | 2 +-
7 files changed, 131 insertions(+), 52 deletions(-)
diff --git a/lxc/image.go b/lxc/image.go
index 47db1ae9ed..a89e729933 100644
--- a/lxc/image.go
+++ b/lxc/image.go
@@ -406,6 +406,9 @@ func (c *cmdImageEdit) Run(cmd *cobra.Command, args []string) error {
newdata := api.ImagePut{}
err = yaml.Unmarshal(content, &newdata)
if err == nil {
+ if newdata.Profiles == nil {
+ newdata.Profiles = []string{"default"}
+ }
err = resource.server.UpdateImage(image, newdata, etag)
}
@@ -927,6 +930,15 @@ func (c *cmdImageInfo) Run(cmd *cobra.Command, args []string) error {
fmt.Printf(" Alias: %s\n", info.UpdateSource.Alias)
}
+ if len(info.Profiles) == 0 {
+ fmt.Printf(i18n.G("Profiles: ") + "[]\n")
+ } else {
+ fmt.Println(i18n.G("Profiles:"))
+ for _, name := range info.Profiles {
+ fmt.Printf(" - %s\n", name)
+ }
+ }
+
return nil
}
diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index 33fe82b2e0..db03bbec58 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -57,6 +57,12 @@ CREATE TABLE images_nodes (
FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
);
+CREATE TABLE images_profiles (
+ image_id INTEGER NOT NULL,
+ profile_id INTEGER NOT NULL,
+ FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+ FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
+);
CREATE INDEX images_project_id_idx ON images (project_id);
CREATE TABLE images_properties (
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@@ -487,5 +493,5 @@ CREATE TABLE storage_volumes_config (
FOREIGN KEY (storage_volume_id) REFERENCES storage_volumes (id) ON DELETE CASCADE
);
-INSERT INTO schema (version, updated_at) VALUES (18, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (19, strftime("%s"))
`
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 461672ad67..85d4c91efb 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -53,6 +53,21 @@ var updates = map[int]schema.Update{
16: updateFromV15,
17: updateFromV16,
18: updateFromV17,
+ 19: updateFromV18,
+}
+
+// Add images_profiles table
+func updateFromV18(tx *sql.Tx) error {
+ stmts := `
+CREATE TABLE images_profiles (
+ image_id INTEGER NOT NULL,
+ profile_id INTEGER NOT NULL,
+ FOREIGN KEY (image_id) REFERENCES images (id) ON DELETE CASCADE,
+ FOREIGN KEY (profile_id) REFERENCES profiles (id) ON DELETE CASCADE
+);
+`
+ _, err := tx.Exec(stmts)
+ return err
}
// Add nodes_roles table
diff --git a/lxd/db/images.go b/lxd/db/images.go
index 2db6855437..dc9e34878c 100644
--- a/lxd/db/images.go
+++ b/lxd/db/images.go
@@ -8,7 +8,6 @@ import (
"time"
"github.com/pkg/errors"
-
"github.com/lxc/lxd/lxd/db/query"
"github.com/lxc/lxd/lxd/instance/instancetype"
"github.com/lxc/lxd/shared/api"
@@ -476,6 +475,23 @@ func (c *Cluster) imageFill(id int, image *api.Image, create, expire, used, uplo
image.Aliases = aliases
+ // Get the profiles
+ q = "SELECT name FROM profiles WHERE id IN (SELECT profile_id FROM images_profiles WHERE image_id=?)"
+ inargs = []interface{}{id}
+ outfmt = []interface{}{name}
+ results, err = queryScan(c.db, q, inargs, outfmt)
+ if err != nil {
+ return err
+ }
+
+ profiles := make([]string, 0)
+ for _, r := range results {
+ name = r[0].(string)
+ profiles = append(profiles, name)
+ }
+
+ image.Profiles = profiles
+
_, source, err := c.ImageSourceGet(id)
if err == nil {
image.UpdateSource = &source
@@ -737,7 +753,7 @@ func (c *Cluster) ImageLastAccessInit(fingerprint string) error {
}
// ImageUpdate updates the image with the given ID.
-func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string) error {
+func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoUpdate bool, architecture string, createdAt time.Time, expiresAt time.Time, properties map[string]string, profileIds []int64) error {
arch, err := osarch.ArchitectureId(architecture)
if err != nil {
arch = 0
@@ -783,6 +799,24 @@ func (c *Cluster) ImageUpdate(id int, fname string, sz int64, public bool, autoU
}
}
+ _, err = tx.tx.Exec(`DELETE FROM images_profiles WHERE image_id=?`, id)
+ if err != nil {
+ return err
+ }
+
+ stmt3, err := tx.tx.Prepare(`INSERT INTO images_profiles (image_id, profile_id) VALUES (?, ?)`)
+ if err != nil {
+ return err
+ }
+ defer stmt3.Close()
+
+ for _, profileId := range profileIds {
+ _, err = stmt3.Exec(id, profileId)
+ if err != nil {
+ return err
+ }
+ }
+
return nil
})
return err
diff --git a/lxd/db/instances.mapper.go b/lxd/db/instances.mapper.go
index ce827a8960..171d0aad25 100644
--- a/lxd/db/instances.mapper.go
+++ b/lxd/db/instances.mapper.go
@@ -235,19 +235,19 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
filter.Node,
filter.Name,
}
- } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Node"] != nil {
- stmt = c.stmt(instanceObjectsByProjectAndTypeAndNode)
+ } else if criteria["Project"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
+ stmt = c.stmt(instanceObjectsByProjectAndNameAndNode)
args = []interface{}{
filter.Project,
- filter.Type,
+ filter.Name,
filter.Node,
}
- } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Name"] != nil {
- stmt = c.stmt(instanceObjectsByProjectAndTypeAndName)
+ } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Node"] != nil {
+ stmt = c.stmt(instanceObjectsByProjectAndTypeAndNode)
args = []interface{}{
filter.Project,
filter.Type,
- filter.Name,
+ filter.Node,
}
} else if criteria["Type"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
stmt = c.stmt(instanceObjectsByTypeAndNameAndNode)
@@ -256,12 +256,12 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
filter.Name,
filter.Node,
}
- } else if criteria["Project"] != nil && criteria["Name"] != nil && criteria["Node"] != nil {
- stmt = c.stmt(instanceObjectsByProjectAndNameAndNode)
+ } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Name"] != nil {
+ stmt = c.stmt(instanceObjectsByProjectAndTypeAndName)
args = []interface{}{
filter.Project,
+ filter.Type,
filter.Name,
- filter.Node,
}
} else if criteria["Project"] != nil && criteria["Type"] != nil {
stmt = c.stmt(instanceObjectsByProjectAndType)
@@ -275,16 +275,22 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
filter.Type,
filter.Node,
}
+ } else if criteria["Type"] != nil && criteria["Name"] != nil {
+ stmt = c.stmt(instanceObjectsByTypeAndName)
+ args = []interface{}{
+ filter.Type,
+ filter.Name,
+ }
} else if criteria["Project"] != nil && criteria["Node"] != nil {
stmt = c.stmt(instanceObjectsByProjectAndNode)
args = []interface{}{
filter.Project,
filter.Node,
}
- } else if criteria["Type"] != nil && criteria["Name"] != nil {
- stmt = c.stmt(instanceObjectsByTypeAndName)
+ } else if criteria["Node"] != nil && criteria["Name"] != nil {
+ stmt = c.stmt(instanceObjectsByNodeAndName)
args = []interface{}{
- filter.Type,
+ filter.Node,
filter.Name,
}
} else if criteria["Project"] != nil && criteria["Name"] != nil {
@@ -293,10 +299,14 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
filter.Project,
filter.Name,
}
- } else if criteria["Node"] != nil && criteria["Name"] != nil {
- stmt = c.stmt(instanceObjectsByNodeAndName)
+ } else if criteria["Project"] != nil {
+ stmt = c.stmt(instanceObjectsByProject)
+ args = []interface{}{
+ filter.Project,
+ }
+ } else if criteria["Name"] != nil {
+ stmt = c.stmt(instanceObjectsByName)
args = []interface{}{
- filter.Node,
filter.Name,
}
} else if criteria["Type"] != nil {
@@ -309,16 +319,6 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) {
args = []interface{}{
filter.Node,
}
- } else if criteria["Project"] != nil {
- stmt = c.stmt(instanceObjectsByProject)
- args = []interface{}{
- filter.Project,
- }
- } else if criteria["Name"] != nil {
- stmt = c.stmt(instanceObjectsByName)
- args = []interface{}{
- filter.Name,
- }
} else {
stmt = c.stmt(instanceObjects)
args = []interface{}{}
@@ -583,28 +583,28 @@ func (c *ClusterTx) InstanceProfilesRef(filter InstanceFilter) (map[string]map[s
var stmt *sql.Stmt
var args []interface{}
- if criteria["Project"] != nil && criteria["Name"] != nil {
- stmt = c.stmt(instanceProfilesRefByProjectAndName)
- args = []interface{}{
- filter.Project,
- filter.Name,
- }
- } else if criteria["Project"] != nil && criteria["Node"] != nil {
+ if criteria["Project"] != nil && criteria["Node"] != nil {
stmt = c.stmt(instanceProfilesRefByProjectAndNode)
args = []interface{}{
filter.Project,
filter.Node,
}
- } else if criteria["Project"] != nil {
- stmt = c.stmt(instanceProfilesRefByProject)
+ } else if criteria["Project"] != nil && criteria["Name"] != nil {
+ stmt = c.stmt(instanceProfilesRefByProjectAndName)
args = []interface{}{
filter.Project,
+ filter.Name,
}
} else if criteria["Node"] != nil {
stmt = c.stmt(instanceProfilesRefByNode)
args = []interface{}{
filter.Node,
}
+ } else if criteria["Project"] != nil {
+ stmt = c.stmt(instanceProfilesRefByProject)
+ args = []interface{}{
+ filter.Project,
+ }
} else {
stmt = c.stmt(instanceProfilesRef)
args = []interface{}{}
@@ -686,16 +686,16 @@ func (c *ClusterTx) InstanceConfigRef(filter InstanceFilter) (map[string]map[str
filter.Project,
filter.Name,
}
- } else if criteria["Project"] != nil {
- stmt = c.stmt(instanceConfigRefByProject)
- args = []interface{}{
- filter.Project,
- }
} else if criteria["Node"] != nil {
stmt = c.stmt(instanceConfigRefByNode)
args = []interface{}{
filter.Node,
}
+ } else if criteria["Project"] != nil {
+ stmt = c.stmt(instanceConfigRefByProject)
+ args = []interface{}{
+ filter.Project,
+ }
} else {
stmt = c.stmt(instanceConfigRef)
args = []interface{}{}
@@ -782,16 +782,16 @@ func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[st
filter.Project,
filter.Node,
}
- } else if criteria["Node"] != nil {
- stmt = c.stmt(instanceDevicesRefByNode)
- args = []interface{}{
- filter.Node,
- }
} else if criteria["Project"] != nil {
stmt = c.stmt(instanceDevicesRefByProject)
args = []interface{}{
filter.Project,
}
+ } else if criteria["Node"] != nil {
+ stmt = c.stmt(instanceDevicesRefByNode)
+ args = []interface{}{
+ filter.Node,
+ }
} else {
stmt = c.stmt(instanceDevicesRef)
args = []interface{}{}
diff --git a/lxd/images.go b/lxd/images.go
index cf989c4011..7eb732b8cd 100644
--- a/lxd/images.go
+++ b/lxd/images.go
@@ -347,7 +347,7 @@ func imgPostRemoteInfo(d *Daemon, req api.ImagesPost, op *operations.Operation,
// Update the DB record if needed
if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
- err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+ err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
if err != nil {
return nil, err
}
@@ -415,7 +415,7 @@ func imgPostURLInfo(d *Daemon, req api.ImagesPost, op *operations.Operation, pro
}
if req.Public || req.AutoUpdate || req.Filename != "" || len(req.Properties) > 0 {
- err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+ err = d.cluster.ImageUpdate(id, req.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
if err != nil {
return nil, err
}
@@ -1597,7 +1597,19 @@ func imagePut(d *Daemon, r *http.Request) response.Response {
info.ExpiresAt = req.ExpiresAt
}
- err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties)
+ // Get profile ids
+ profileIds := make([]int64, len(req.Profiles))
+ for i, profile := range req.Profiles {
+ profileId, _, err := d.cluster.ProfileGet(project, profile)
+ if err == db.ErrNoSuchObject {
+ return response.BadRequest(fmt.Errorf("Profile '%s' doesn't exist", profile))
+ } else if (err != nil) {
+ return response.SmartError(err)
+ }
+ profileIds[i] = profileId
+ }
+
+ err = d.cluster.ImageUpdate(id, info.Filename, info.Size, req.Public, req.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, req.Properties, profileIds)
if err != nil {
return response.SmartError(err)
}
@@ -1664,7 +1676,7 @@ func imagePatch(d *Daemon, r *http.Request) response.Response {
info.Properties = properties
}
- err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties)
+ err = d.cluster.ImageUpdate(id, info.Filename, info.Size, info.Public, info.AutoUpdate, info.Architecture, info.CreatedAt, info.ExpiresAt, info.Properties, nil)
if err != nil {
return response.SmartError(err)
}
diff --git a/shared/version/api.go b/shared/version/api.go
index 7a2602b1b5..931606c8f0 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -179,7 +179,7 @@ var APIExtensions = []string{
"container_syscall_intercept_mount_fuse",
"container_disk_ceph",
"virtual-machines",
- "image_profiles"
+ "image_profiles",
}
// APIExtensionsCount returns the number of available API extensions.
From 2e40fc32048b3ad1c6f0eebcbc30539de8201aa8 Mon Sep 17 00:00:00 2001
From: Jack Stenglein <jackstenglein at utexas.edu>
Date: Fri, 29 Nov 2019 15:18:28 -0600
Subject: [PATCH 4/4] lxd/images: Add support for image profiles
Signed-off-by: Rizwan Lubis <rizwan.lubis at gmail.com>
Signed-off-by: Jack Stenglein <jackstenglein at gmail.com>
---
lxc/init.go | 21 +++++++++++----------
lxd/container.go | 7 ++++++-
lxd/db/images.go | 25 +++++++++++++++++++++++++
3 files changed, 42 insertions(+), 11 deletions(-)
diff --git a/lxc/init.go b/lxc/init.go
index 3530ac214f..873cfe10b6 100644
--- a/lxc/init.go
+++ b/lxc/init.go
@@ -219,16 +219,6 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer
}
req.Config = configMap
req.Devices = devicesMap
-
- if !c.flagNoProfiles && len(profiles) == 0 {
- if len(stdinData.Profiles) > 0 {
- req.Profiles = stdinData.Profiles
- } else {
- req.Profiles = nil
- }
- } else {
- req.Profiles = profiles
- }
req.Ephemeral = c.flagEphemeral
var opInfo api.Operation
@@ -274,6 +264,17 @@ func (c *cmdInit) create(conf *config.Config, args []string) (lxd.InstanceServer
}
}
+ // Set up profiles
+ if c.flagNoProfiles {
+ req.Profiles = []string{}
+ } else if len(profiles) > 0 {
+ req.Profiles = profiles
+ } else if len(stdinData.Profiles) > 0 {
+ req.Profiles = stdinData.Profiles
+ } else {
+ req.Profiles = imgInfo.Profiles
+ }
+
// Create the instance
op, err := d.CreateInstanceFromImage(imgRemote, *imgInfo, req)
if err != nil {
diff --git a/lxd/container.go b/lxd/container.go
index 54ab1691d7..9e8b426c89 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -868,7 +868,12 @@ func instanceCreateInternal(s *state.State, args db.InstanceArgs) (instance.Inst
}
if args.Profiles == nil {
- args.Profiles = []string{"default"}
+ profiles, err := s.Cluster.ImageProfilesGet(args.BaseImage)
+ if err != nil {
+ return nil, err
+ }
+ args.Profiles = profiles
+ logger.Infof("Got profiles: %v", args.Profiles)
}
if args.Config == nil {
diff --git a/lxd/db/images.go b/lxd/db/images.go
index dc9e34878c..eb1bad5b50 100644
--- a/lxd/db/images.go
+++ b/lxd/db/images.go
@@ -737,6 +737,31 @@ func (c *Cluster) ImageAliasUpdate(id int, imageID int, desc string) error {
return err
}
+func (c *Cluster) ImageProfilesGet(fingerprint string) ([]string, error) {
+ // Get the profiles
+ q := `
+SELECT profiles.name FROM profiles
+ JOIN images_profiles ON images_profiles.profile_id = profiles.id
+ JOIN images ON images_profiles.image_id = images.id
+WHERE images.fingerprint = ?
+`
+ var name string
+ inargs := []interface{}{fingerprint}
+ outfmt := []interface{}{name}
+ results, err := queryScan(c.db, q, inargs, outfmt)
+ if err != nil {
+ return nil, err
+ }
+
+ profiles := make([]string, 0)
+ for _, r := range results {
+ name = r[0].(string)
+ profiles = append(profiles, name)
+ }
+
+ return profiles, nil
+}
+
// ImageLastAccessUpdate updates the last_use_date field of the image with the
// given fingerprint.
func (c *Cluster) ImageLastAccessUpdate(fingerprint string, date time.Time) error {
More information about the lxc-devel
mailing list