The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/6532
This e-mail was sent by the LXC bot, direct replies will not reach the author unless they happen to be subscribed to this list. === Description (from pull-request) === Hi @stgraber, we wanted to get some input for our progress. We have created the new images_profiles table, and we can successfully read and modify it within the three lxc commands referenced in the issue. We are also able to apply the user-specified default profiles with `lxc launch <image>`, but we are still working on adding the "default" profile to the images_profile table when an image is first created. Please let us know if you have any thoughts about our progress.
From e171c7844a3f8bd3e326a2fe18c990b8c2b88a87 Mon Sep 17 00:00:00 2001 From: Jack Stenglein <jackstengl...@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.lu...@gmail.com> Signed-off-by: Jack Stenglein <jackstengl...@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 <jackstengl...@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.lu...@gmail.com> Signed-off-by: Jack Stenglein <jackstengl...@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 <jackstengl...@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.lu...@gmail.com> Signed-off-by: Jack Stenglein <jackstengl...@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 <jackstengl...@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.lu...@gmail.com> Signed-off-by: Jack Stenglein <jackstengl...@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 {
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel