The following pull request was submitted through Github.
It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7386

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) ===

From 72b248177ce376d598037f99a485b0bfab919fd8 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Mon, 11 May 2020 11:19:21 +0100
Subject: [PATCH 1/8] lxd/db: Change UpdateCertificate to RenameCertificate
 (only renaming supported)

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/certificates.go           |  2 +-
 lxd/db/certificates.go        |  9 +++++----
 lxd/db/certificates.mapper.go | 22 ++++++++++++++++++++++
 3 files changed, 28 insertions(+), 5 deletions(-)

diff --git a/lxd/certificates.go b/lxd/certificates.go
index fa817c1ee2..917f5d1178 100644
--- a/lxd/certificates.go
+++ b/lxd/certificates.go
@@ -322,7 +322,7 @@ func doCertificateUpdate(d *Daemon, fingerprint string, req 
api.CertificatePut)
                return response.BadRequest(fmt.Errorf("Unknown request type 
%s", req.Type))
        }
 
-       err := d.cluster.UpdateCertificate(fingerprint, req.Name, 1)
+       err := d.cluster.RenameCertificate(fingerprint, req.Name)
        if err != nil {
                return response.SmartError(err)
        }
diff --git a/lxd/db/certificates.go b/lxd/db/certificates.go
index cfefaa773e..5176ceb29c 100644
--- a/lxd/db/certificates.go
+++ b/lxd/db/certificates.go
@@ -12,6 +12,7 @@ package db
 //go:generate mapper stmt -p db -e certificate id
 //go:generate mapper stmt -p db -e certificate create struct=Certificate
 //go:generate mapper stmt -p db -e certificate delete
+//go:generate mapper stmt -p db -e certificate rename
 //
 //go:generate mapper method -p db -e certificate List
 //go:generate mapper method -p db -e certificate Get
@@ -19,6 +20,7 @@ package db
 //go:generate mapper method -p db -e certificate Exists struct=Certificate
 //go:generate mapper method -p db -e certificate Create struct=Certificate
 //go:generate mapper method -p db -e certificate Delete
+//go:generate mapper method -p db -e certificate Rename
 
 // Certificate is here to pass the certificates content
 // from the database around
@@ -66,11 +68,10 @@ func (c *Cluster) DeleteCertificate(fingerprint string) 
error {
        return err
 }
 
-// UpdateCertificate updates the certificate with the given fingerprint.
-func (c *Cluster) UpdateCertificate(fingerprint string, certName string, 
certType int) error {
+// RenameCertificate updates a certificate's name.
+func (c *Cluster) RenameCertificate(fingerprint string, name string) error {
        err := c.Transaction(func(tx *ClusterTx) error {
-               _, err := tx.tx.Exec("UPDATE certificates SET name=?, type=? 
WHERE fingerprint=?", certName, certType, fingerprint)
-               return err
+               return c.RenameCertificate(fingerprint, name)
        })
        return err
 }
diff --git a/lxd/db/certificates.mapper.go b/lxd/db/certificates.mapper.go
index 4f6e718358..c682094754 100644
--- a/lxd/db/certificates.mapper.go
+++ b/lxd/db/certificates.mapper.go
@@ -41,6 +41,10 @@ var certificateDelete = cluster.RegisterStmt(`
 DELETE FROM certificates WHERE fingerprint = ?
 `)
 
+var certificateRename = cluster.RegisterStmt(`
+UPDATE certificates SET name = ? WHERE fingerprint = ?
+`)
+
 // GetCertificates returns all available certificates.
 func (c *ClusterTx) GetCertificates(filter CertificateFilter) ([]Certificate, 
error) {
        // Result slice.
@@ -203,3 +207,21 @@ func (c *ClusterTx) DeleteCertificate(fingerprint string) 
error {
 
        return nil
 }
+
+// RenameCertificate renames the certificate matching the given key parameters.
+func (c *ClusterTx) RenameCertificate(fingerprint string, to string) error {
+       stmt := c.stmt(certificateRename)
+       result, err := stmt.Exec(to, fingerprint)
+       if err != nil {
+               return errors.Wrap(err, "Rename certificate")
+       }
+
+       n, err := result.RowsAffected()
+       if err != nil {
+               return errors.Wrap(err, "Fetch affected rows")
+       }
+       if n != 1 {
+               return fmt.Errorf("Query affected %d rows instead of 1", n)
+       }
+       return nil
+}

From 1bb5dde03ee8174197bdea33dfe6446cb66e0f7e Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Mon, 11 May 2020 11:23:28 +0100
Subject: [PATCH 2/8] lxd/db: Rename containers.go to instances.go

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/db/{containers.go => instances.go}                         | 0
 lxd/db/{containers_export_test.go => instances_export_test.go} | 0
 lxd/db/{containers_test.go => instances_test.go}               | 0
 3 files changed, 0 insertions(+), 0 deletions(-)
 rename lxd/db/{containers.go => instances.go} (100%)
 rename lxd/db/{containers_export_test.go => instances_export_test.go} (100%)
 rename lxd/db/{containers_test.go => instances_test.go} (100%)

diff --git a/lxd/db/containers.go b/lxd/db/instances.go
similarity index 100%
rename from lxd/db/containers.go
rename to lxd/db/instances.go
diff --git a/lxd/db/containers_export_test.go b/lxd/db/instances_export_test.go
similarity index 100%
rename from lxd/db/containers_export_test.go
rename to lxd/db/instances_export_test.go
diff --git a/lxd/db/containers_test.go b/lxd/db/instances_test.go
similarity index 100%
rename from lxd/db/containers_test.go
rename to lxd/db/instances_test.go

From 3ebe673c97af596794cc0a3cfa206902576da160 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Mon, 11 May 2020 14:21:32 +0100
Subject: [PATCH 3/8] shared/generate/db: Statement for deleting references
 (config and devices)

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 shared/generate/db/stmt.go | 102 ++++++++++++++++++++++++++-----------
 1 file changed, 72 insertions(+), 30 deletions(-)

diff --git a/shared/generate/db/stmt.go b/shared/generate/db/stmt.go
index 08f2d39adb..227733a655 100644
--- a/shared/generate/db/stmt.go
+++ b/shared/generate/db/stmt.go
@@ -50,6 +50,10 @@ func (s *Stmt) Generate(buf *file.Buffer) error {
                return s.createRef(buf)
        }
 
+       if strings.HasPrefix(s.kind, "delete") && strings.HasSuffix(s.kind, 
"-ref") {
+               return s.deleteRef(buf)
+       }
+
        if strings.HasSuffix(s.kind, "-ref") || strings.Contains(s.kind, 
"-ref-by-") {
                return s.ref(buf)
        }
@@ -417,7 +421,6 @@ func (s *Stmt) createRef(buf *file.Buffer) error {
 
                sql := fmt.Sprintf(stmts["create"], table, columns, params)
                s.register(buf, sql)
-
        } else if field.Type.Name == "map[string]map[string]string" {
                // Assume this is a devices table
                columns := fmt.Sprintf("%s_id, name, type", s.entity)
@@ -443,35 +446,7 @@ func (s *Stmt) id(buf *file.Buffer) error {
        if err != nil {
                return errors.Wrap(err, "Parse entity struct")
        }
-       nk := mapping.NaturalKey()
-       criteria := ""
-       for i, field := range nk {
-               if i > 0 {
-                       criteria += " AND "
-               }
-
-               var column string
-               if field.IsScalar() {
-                       column = field.Config.Get("join")
-               } else {
-                       column = mapping.FieldColumnName(field.Name)
-               }
-
-               criteria += fmt.Sprintf("%s = ?", column)
-       }
-
-       table := entityTable(s.entity)
-       for _, field := range mapping.ScalarFields() {
-               join := field.Config.Get("join")
-               right := strings.Split(join, ".")[0]
-               via := entityTable(s.entity)
-               if field.Config.Get("via") != "" {
-                       via = entityTable(field.Config.Get("via"))
-               }
-               table += fmt.Sprintf(" JOIN %s ON %s.%s_id = %s.id", right, 
via, lex.Singular(right), right)
-       }
-
-       sql := fmt.Sprintf(stmts[s.kind], entityTable(s.entity), table, 
criteria)
+       sql := naturalKeySelect(s.entity, mapping)
        s.register(buf, sql)
 
        return nil
@@ -558,6 +533,38 @@ func (s *Stmt) delete(buf *file.Buffer) error {
        return nil
 }
 
+func (s *Stmt) deleteRef(buf *file.Buffer) error {
+       // Base snake-case name of the references (e.g. "used-by-ref" -> 
"used_by")
+       name := strings.Replace(s.kind[len("create-"):strings.Index(s.kind, 
"-ref")], "-", "_", -1)
+
+       // Field name of the reference
+       fieldName := lex.Camel(name)
+
+       // Table name where reference objects can be fetched.
+       table := fmt.Sprintf("%s_%s", entityTable(s.entity), name)
+
+       mapping, err := Parse(s.packages[s.pkg], lex.Camel(s.entity))
+       if err != nil {
+               return err
+       }
+
+       field := mapping.FieldByName(fieldName)
+       if field == nil {
+               return fmt.Errorf("Entity %s has no field named %s", s.entity, 
fieldName)
+       }
+
+       where := fmt.Sprintf("%s_id = ?", s.entity)
+
+       if field.Type.Name == "map[string]string" || field.Type.Name == 
"map[string]map[string]string" {
+               // Assume this is a config or devices table
+               sql := fmt.Sprintf(stmts["delete"], table, where)
+               s.register(buf, sql)
+
+       }
+
+       return nil
+}
+
 // Return a where clause that filters an entity by natural key.
 func naturalKeyWhere(mapping *Mapping) string {
        nk := mapping.NaturalKey()
@@ -605,6 +612,41 @@ func naturalKeyWhere(mapping *Mapping) string {
        return strings.Join(where, " AND ")
 }
 
+// Return a select statement that returns the ID of an entity given its 
natural key.
+func naturalKeySelect(entity string, mapping *Mapping) string {
+       nk := mapping.NaturalKey()
+       criteria := ""
+       for i, field := range nk {
+               if i > 0 {
+                       criteria += " AND "
+               }
+
+               var column string
+               if field.IsScalar() {
+                       column = field.Config.Get("join")
+               } else {
+                       column = mapping.FieldColumnName(field.Name)
+               }
+
+               criteria += fmt.Sprintf("%s = ?", column)
+       }
+
+       table := entityTable(entity)
+       for _, field := range mapping.ScalarFields() {
+               join := field.Config.Get("join")
+               right := strings.Split(join, ".")[0]
+               via := entityTable(entity)
+               if field.Config.Get("via") != "" {
+                       via = entityTable(field.Config.Get("via"))
+               }
+               table += fmt.Sprintf(" JOIN %s ON %s.%s_id = %s.id", right, 
via, lex.Singular(right), right)
+       }
+
+       sql := fmt.Sprintf(stmts["id"], entityTable(entity), table, criteria)
+
+       return sql
+}
+
 // Output a line of code that registers the given statement and declares the
 // associated statement code global variable.
 func (s *Stmt) register(buf *file.Buffer, sql string, filters ...string) {

From 6451a292bd71d0739a565e95638b488e90c42d4e Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Tue, 19 May 2020 12:04:28 +0100
Subject: [PATCH 4/8] lxd/db: Generate delete stements for profile config and
 devices

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/db/profiles.go        | 2 ++
 lxd/db/profiles.mapper.go | 8 ++++++++
 2 files changed, 10 insertions(+)

diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go
index 19e97787c2..8bd37c96b5 100644
--- a/lxd/db/profiles.go
+++ b/lxd/db/profiles.go
@@ -37,6 +37,8 @@ import (
 //go:generate mapper stmt -p db -e profile create-devices-ref
 //go:generate mapper stmt -p db -e profile rename
 //go:generate mapper stmt -p db -e profile delete
+//go:generate mapper stmt -p db -e profile delete-config-ref
+//go:generate mapper stmt -p db -e profile delete-devices-ref
 //
 //go:generate mapper method -p db -e profile URIs
 //go:generate mapper method -p db -e profile List
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index f71d742c42..df5c752a63 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -119,6 +119,14 @@ var profileDelete = cluster.RegisterStmt(`
 DELETE FROM profiles WHERE project_id = (SELECT projects.id FROM projects 
WHERE projects.name = ?) AND name = ?
 `)
 
+var profileDeleteConfigRef = cluster.RegisterStmt(`
+DELETE FROM profiles_config WHERE profile_id = ?
+`)
+
+var profileDeleteDevicesRef = cluster.RegisterStmt(`
+DELETE FROM profiles_devices WHERE profile_id = ?
+`)
+
 // GetProfileURIs returns all available profile URIs.
 func (c *ClusterTx) GetProfileURIs(filter ProfileFilter) ([]string, error) {
        // Check which filter criteria are active.

From b5ee104c37eb5c692f30d3ab8a28cc321bdc7859 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Tue, 19 May 2020 12:24:33 +0100
Subject: [PATCH 5/8] shared/generate/db: update statement: take ID instead of
 natural key

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/db/projects.go         | 12 ++++++------
 lxd/db/projects.mapper.go  |  2 +-
 shared/generate/db/stmt.go | 21 +--------------------
 3 files changed, 8 insertions(+), 27 deletions(-)

diff --git a/lxd/db/projects.go b/lxd/db/projects.go
index 49457c1533..295ec8fde1 100644
--- a/lxd/db/projects.go
+++ b/lxd/db/projects.go
@@ -132,8 +132,13 @@ func (c *ClusterTx) ProjectHasImages(name string) (bool, 
error) {
 
 // UpdateProject updates the project matching the given key parameters.
 func (c *ClusterTx) UpdateProject(name string, object api.ProjectPut) error {
+       id, err := c.GetProjectID(name)
+       if err != nil {
+               return errors.Wrap(err, "Fetch project ID")
+       }
+
        stmt := c.stmt(projectUpdate)
-       result, err := stmt.Exec(object.Description, name)
+       result, err := stmt.Exec(object.Description, id)
        if err != nil {
                return errors.Wrap(err, "Update project")
        }
@@ -146,11 +151,6 @@ func (c *ClusterTx) UpdateProject(name string, object 
api.ProjectPut) error {
                return fmt.Errorf("Query updated %d rows instead of 1", n)
        }
 
-       id, err := c.GetProjectID(name)
-       if err != nil {
-               return errors.Wrap(err, "Fetch project ID")
-       }
-
        // Clear config.
        _, err = c.tx.Exec(`
 DELETE FROM projects_config WHERE projects_config.project_id = ?
diff --git a/lxd/db/projects.mapper.go b/lxd/db/projects.mapper.go
index 2e8fd787e6..3b763721f3 100644
--- a/lxd/db/projects.mapper.go
+++ b/lxd/db/projects.mapper.go
@@ -77,7 +77,7 @@ UPDATE projects SET name = ? WHERE name = ?
 var projectUpdate = cluster.RegisterStmt(`
 UPDATE projects
   SET description = ?
- WHERE name = ?
+ WHERE id = ?
 `)
 
 var projectDelete = cluster.RegisterStmt(`
diff --git a/shared/generate/db/stmt.go b/shared/generate/db/stmt.go
index 227733a655..db28faad47 100644
--- a/shared/generate/db/stmt.go
+++ b/shared/generate/db/stmt.go
@@ -492,28 +492,9 @@ func (s *Stmt) update(buf *file.Buffer) error {
                }
        }
 
-       mapping, err = Parse(s.packages[s.pkg], lex.Capital(s.entity))
-       if err != nil {
-               return errors.Wrap(err, "Parse entity struct")
-       }
-
-       nk := mapping.NaturalKey()
-       where := make([]string, len(nk))
-
-       for i, field := range nk {
-               if field.IsScalar() {
-                       ref := lex.Snake(field.Name)
-                       where[i] = fmt.Sprintf("%s_id = (SELECT id FROM %s 
WHERE name = ?)", ref, lex.Plural(ref))
-               } else {
-
-                       where[i] = fmt.Sprintf("%s = ?", field.Column())
-               }
-
-       }
-
        sql := fmt.Sprintf(
                stmts[s.kind], entityTable(s.entity),
-               strings.Join(updates, ", "), strings.Join(where, ", "))
+               strings.Join(updates, ", "), "id = ?")
        s.register(buf, sql)
 
        return nil

From 4f4e9c8773706d045a85496fa2425b07ee112f67 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Tue, 19 May 2020 12:49:12 +0100
Subject: [PATCH 6/8] shared/generate/db: Handle config and devices in Update
 method

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 shared/generate/db/method.go | 69 +++++++++++++++++++++++++++++++++++-
 1 file changed, 68 insertions(+), 1 deletion(-)

diff --git a/shared/generate/db/method.go b/shared/generate/db/method.go
index 0ddc903cd9..c0f6859aa0 100644
--- a/shared/generate/db/method.go
+++ b/shared/generate/db/method.go
@@ -811,8 +811,14 @@ func (m *Method) update(buf *file.Buffer) error {
                params[i] = fmt.Sprintf("object.%s", field.Name)
        }
 
+       //buf.L("id, err := c.Get%s(%s)", lex.Camel(m.entity), FieldArgs(nk))
+       buf.L("id, err := c.Get%sID(%s)", lex.Camel(m.entity), FieldParams(nk))
+       buf.L("if err != nil {")
+       buf.L("        return errors.Wrap(err, \"Get %s\")", m.entity)
+       buf.L("}")
+       buf.N()
        buf.L("stmt := c.stmt(%s)", stmtCodeVar(m.entity, "update"))
-       buf.L("result, err := stmt.Exec(%s)", strings.Join(params, ", ")+", 
"+FieldParams(nk))
+       buf.L("result, err := stmt.Exec(%s)", strings.Join(params, ", ")+", id")
        buf.L("if err != nil {")
        buf.L("        return errors.Wrap(err, \"Update %s\")", m.entity)
        buf.L("}")
@@ -825,6 +831,67 @@ func (m *Method) update(buf *file.Buffer) error {
        buf.L("        return fmt.Errorf(\"Query updated %%d rows instead of 
1\", n)")
        buf.L("}")
        buf.N()
+
+       fields = mapping.RefFields()
+       for _, field := range fields {
+               switch field.Type.Name {
+               case "map[string]string":
+                       buf.L("// Delete current config. ")
+                       buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, 
"deleteConfigRef"))
+                       buf.L("_, err = stmt.Exec(id)")
+                       buf.L("if err != nil {")
+                       buf.L("        return errors.Wrap(err, \"Delete current 
config\")")
+                       buf.L("}")
+                       buf.N()
+                       buf.L("// Insert config reference. ")
+                       buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, 
"createConfigRef"))
+                       buf.L("for key, value := range object.%s {", field.Name)
+                       buf.L("        _, err := stmt.Exec(id, key, value)")
+                       buf.L("        if err != nil {")
+                       buf.L("                return errors.Wrap(err, \"Insert 
config for %s\")", m.entity)
+                       buf.L("        }")
+                       buf.L("}")
+                       buf.N()
+               case "map[string]map[string]string":
+                       buf.L("// Delete current devices. ")
+                       buf.L("stmt = c.stmt(%s)", stmtCodeVar(m.entity, 
"deleteDevicesRef"))
+                       buf.L("_, err = stmt.Exec(id)")
+                       buf.L("if err != nil {")
+                       buf.L("        return errors.Wrap(err, \"Delete current 
devices\")")
+                       buf.L("}")
+                       buf.N()
+                       buf.N()
+                       buf.L("// Insert devices reference. ")
+                       buf.L("for name, config := range object.%s {", 
field.Name)
+                       buf.L("        typ, ok := config[\"type\"]")
+                       buf.L("        if !ok {")
+                       buf.L("                return fmt.Errorf(\"No type for 
device %%s\", name)")
+                       buf.L("        }")
+                       buf.L("        typCode, err := dbDeviceTypeToInt(typ)")
+                       buf.L("        if err != nil {")
+                       buf.L("                return errors.Wrapf(err, 
\"Device type code for %%s\", typ)")
+                       buf.L("        }")
+                       buf.L("        stmt = c.stmt(%s)", 
stmtCodeVar(m.entity, "createDevicesRef"))
+                       buf.L("        result, err := stmt.Exec(id, name, 
typCode)")
+                       buf.L("        if err != nil {")
+                       buf.L("                return errors.Wrapf(err, 
\"Insert device %%s\", name)")
+                       buf.L("        }")
+                       buf.L("        deviceID, err := result.LastInsertId()")
+                       buf.L("        if err != nil {")
+                       buf.L("                return errors.Wrap(err, \"Failed 
to fetch device ID\")")
+                       buf.L("        }")
+                       buf.L("        stmt = c.stmt(%s)", 
stmtCodeVar(m.entity, "createDevicesConfigRef"))
+                       buf.L("        for key, value := range config {")
+                       buf.L("                _, err := stmt.Exec(deviceID, 
key, value)")
+                       buf.L("                if err != nil {")
+                       buf.L("                        return errors.Wrap(err, 
\"Insert config for %s\")", m.entity)
+                       buf.L("                }")
+                       buf.L("        }")
+                       buf.L("}")
+                       buf.N()
+               }
+       }
+
        buf.L("return nil")
 
        return nil

From 3215afdffe7d8ada49cf5057d8ef2ca6615a466f Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Tue, 19 May 2020 12:49:16 +0100
Subject: [PATCH 7/8] lxd/db: Generate Update method for profiles

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/db/profiles.go        |  2 +
 lxd/db/profiles.mapper.go | 81 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 83 insertions(+)

diff --git a/lxd/db/profiles.go b/lxd/db/profiles.go
index 8bd37c96b5..b83ad849f8 100644
--- a/lxd/db/profiles.go
+++ b/lxd/db/profiles.go
@@ -39,6 +39,7 @@ import (
 //go:generate mapper stmt -p db -e profile delete
 //go:generate mapper stmt -p db -e profile delete-config-ref
 //go:generate mapper stmt -p db -e profile delete-devices-ref
+//go:generate mapper stmt -p db -e profile update struct=Profile
 //
 //go:generate mapper method -p db -e profile URIs
 //go:generate mapper method -p db -e profile List
@@ -51,6 +52,7 @@ import (
 //go:generate mapper method -p db -e profile Create struct=Profile
 //go:generate mapper method -p db -e profile Rename
 //go:generate mapper method -p db -e profile Delete
+//go:generate mapper method -p db -e profile Update struct=Profile
 
 // Profile is a value object holding db-related details about a profile.
 type Profile struct {
diff --git a/lxd/db/profiles.mapper.go b/lxd/db/profiles.mapper.go
index df5c752a63..c9970eb9b4 100644
--- a/lxd/db/profiles.mapper.go
+++ b/lxd/db/profiles.mapper.go
@@ -127,6 +127,12 @@ var profileDeleteDevicesRef = cluster.RegisterStmt(`
 DELETE FROM profiles_devices WHERE profile_id = ?
 `)
 
+var profileUpdate = cluster.RegisterStmt(`
+UPDATE profiles
+  SET project_id = (SELECT id FROM projects WHERE name = ?), name = ?, 
description = ?
+ WHERE id = ?
+`)
+
 // GetProfileURIs returns all available profile URIs.
 func (c *ClusterTx) GetProfileURIs(filter ProfileFilter) ([]string, error) {
        // Check which filter criteria are active.
@@ -715,3 +721,78 @@ func (c *ClusterTx) DeleteProfile(project string, name 
string) error {
 
        return nil
 }
+
+// UpdateProfile updates the profile matching the given key parameters.
+func (c *ClusterTx) UpdateProfile(project string, name string, object Profile) 
error {
+       id, err := c.GetProfileID(project, name)
+       if err != nil {
+               return errors.Wrap(err, "Get profile")
+       }
+
+       stmt := c.stmt(profileUpdate)
+       result, err := stmt.Exec(object.Project, object.Name, 
object.Description, id)
+       if err != nil {
+               return errors.Wrap(err, "Update profile")
+       }
+
+       n, err := result.RowsAffected()
+       if err != nil {
+               return errors.Wrap(err, "Fetch affected rows")
+       }
+       if n != 1 {
+               return fmt.Errorf("Query updated %d rows instead of 1", n)
+       }
+
+       // Delete current config.
+       stmt = c.stmt(profileDeleteConfigRef)
+       _, err = stmt.Exec(id)
+       if err != nil {
+               return errors.Wrap(err, "Delete current config")
+       }
+
+       // Insert config reference.
+       stmt = c.stmt(profileCreateConfigRef)
+       for key, value := range object.Config {
+               _, err := stmt.Exec(id, key, value)
+               if err != nil {
+                       return errors.Wrap(err, "Insert config for profile")
+               }
+       }
+
+       // Delete current devices.
+       stmt = c.stmt(profileDeleteDevicesRef)
+       _, err = stmt.Exec(id)
+       if err != nil {
+               return errors.Wrap(err, "Delete current devices")
+       }
+
+       // Insert devices reference.
+       for name, config := range object.Devices {
+               typ, ok := config["type"]
+               if !ok {
+                       return fmt.Errorf("No type for device %s", name)
+               }
+               typCode, err := dbDeviceTypeToInt(typ)
+               if err != nil {
+                       return errors.Wrapf(err, "Device type code for %s", typ)
+               }
+               stmt = c.stmt(profileCreateDevicesRef)
+               result, err := stmt.Exec(id, name, typCode)
+               if err != nil {
+                       return errors.Wrapf(err, "Insert device %s", name)
+               }
+               deviceID, err := result.LastInsertId()
+               if err != nil {
+                       return errors.Wrap(err, "Failed to fetch device ID")
+               }
+               stmt = c.stmt(profileCreateDevicesConfigRef)
+               for key, value := range config {
+                       _, err := stmt.Exec(deviceID, key, value)
+                       if err != nil {
+                               return errors.Wrap(err, "Insert config for 
profile")
+                       }
+               }
+       }
+
+       return nil
+}

From 94b74aa66d2010f5657621b5611566d071f159a4 Mon Sep 17 00:00:00 2001
From: Free Ekanayaka <free.ekanay...@canonical.com>
Date: Tue, 19 May 2020 13:04:08 +0100
Subject: [PATCH 8/8] lxd: Plug new UpdateProfile() db method into
 doProfileUpdate

Signed-off-by: Free Ekanayaka <free.ekanay...@canonical.com>
---
 lxd/profiles_utils.go | 57 ++++++-------------------------------------
 1 file changed, 8 insertions(+), 49 deletions(-)

diff --git a/lxd/profiles_utils.go b/lxd/profiles_utils.go
index 4728c55601..672d584d10 100644
--- a/lxd/profiles_utils.go
+++ b/lxd/profiles_utils.go
@@ -2,10 +2,8 @@ package main
 
 import (
        "fmt"
-       "reflect"
 
        "github.com/lxc/lxd/lxd/db"
-       "github.com/lxc/lxd/lxd/db/query"
        deviceConfig "github.com/lxc/lxd/lxd/device/config"
        "github.com/lxc/lxd/lxd/instance"
        "github.com/lxc/lxd/lxd/instance/instancetype"
@@ -78,53 +76,14 @@ func doProfileUpdate(d *Daemon, project, name string, id 
int64, profile *api.Pro
        }
 
        // Update the database
-       err = query.Retry(func() error {
-               tx, err := d.cluster.Begin()
-               if err != nil {
-                       return err
-               }
-
-               if profile.Description != req.Description {
-                       err = db.UpdateProfileDescription(tx, id, 
req.Description)
-                       if err != nil {
-                               tx.Rollback()
-                               return err
-                       }
-               }
-
-               // Optimize for description-only changes
-               if reflect.DeepEqual(profile.Config, req.Config) && 
reflect.DeepEqual(profile.Devices, req.Devices) {
-                       err = db.TxCommit(tx)
-                       if err != nil {
-                               return err
-                       }
-
-                       return nil
-               }
-
-               err = db.ClearProfileConfig(tx, id)
-               if err != nil {
-                       tx.Rollback()
-                       return err
-               }
-
-               err = db.CreateProfileConfig(tx, id, req.Config)
-               if err != nil {
-                       tx.Rollback()
-                       return err
-               }
-
-               err = db.AddDevicesToEntity(tx, "profile", id, 
deviceConfig.NewDevices(req.Devices))
-               if err != nil {
-                       tx.Rollback()
-                       return err
-               }
-
-               err = db.TxCommit(tx)
-               if err != nil {
-                       return err
-               }
-               return nil
+       err = d.cluster.Transaction(func(tx *db.ClusterTx) error {
+               return tx.UpdateProfile(project, name, db.Profile{
+                       Project:     project,
+                       Name:        name,
+                       Description: req.Description,
+                       Config:      req.Config,
+                       Devices:     req.Devices,
+               })
        })
        if err != nil {
                return err
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to