The following pull request was submitted through Github. It can be accessed and reviewed at: https://github.com/lxc/lxd/pull/7013
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 af10f7c4658046164c060c5a0ada7f7022e6007a Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 5 Mar 2020 15:02:15 +0100 Subject: [PATCH 1/6] shared/version/api: Add custom_volume_snapshot_expiry extension Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- shared/version/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/shared/version/api.go b/shared/version/api.go index 3cc3c02ea8..529669d180 100644 --- a/shared/version/api.go +++ b/shared/version/api.go @@ -193,6 +193,7 @@ var APIExtensions = []string{ "container_syscall_intercept_hugetlbfs", "limits_hugepages", "container_nic_routed_gateway", + "custom_volume_snapshot_expiry", } // APIExtensionsCount returns the number of available API extensions. From 5f9d384f1c2b1258daf9d68a6d1bb8b472e1bdca Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 12 Mar 2020 10:23:48 +0100 Subject: [PATCH 2/6] doc: Add custom_volume_snapshot_expiry Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- doc/api-extensions.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/doc/api-extensions.md b/doc/api-extensions.md index 4ae2accbe9..0d85b8b57a 100644 --- a/doc/api-extensions.md +++ b/doc/api-extensions.md @@ -949,3 +949,7 @@ This introduces the `ipv4.gateway` and `ipv6.gateway` NIC config keys that can t gateway being added inside the container and the same gateway address being added to the host-side interface. If the value is set to "none" then no default gateway nor will the address be added to the host-side interface. This allows multiple routed NIC devices to be added to a container. + +## custom\_volume\_snapshot\_expiry +This allows custom volume snapshots to expiry. +Expiry dates can be set individually, or by setting the `snapshots.expiry` config key on the parent custom volume which then automatically applies to all created snapshots. From f3326d2ef88fa543d7609197d6369fa0affd670b Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 12 Mar 2020 10:35:55 +0100 Subject: [PATCH 3/6] lxd/db: Add expiry_date to storage_volumes_snapshots This adds the expiry_date column to the storage_volumes_snapshots table. Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- lxd/db/cluster/schema.go | 3 +- lxd/db/cluster/update.go | 7 +++ lxd/db/instances.mapper.go | 102 ++++++++++++++++++------------------- 3 files changed, 60 insertions(+), 52 deletions(-) diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go index 5553b3709f..06f9a9a106 100644 --- a/lxd/db/cluster/schema.go +++ b/lxd/db/cluster/schema.go @@ -532,6 +532,7 @@ CREATE TABLE storage_volumes_snapshots ( storage_volume_id INTEGER NOT NULL, name TEXT NOT NULL, description TEXT, + expiry_date DATETIME, UNIQUE (id), UNIQUE (storage_volume_id, name), FOREIGN KEY (storage_volume_id) REFERENCES storage_volumes (id) ON DELETE CASCADE @@ -552,5 +553,5 @@ CREATE TABLE storage_volumes_snapshots_config ( UNIQUE (storage_volume_snapshot_id, key) ); -INSERT INTO schema (version, updated_at) VALUES (26, strftime("%s")) +INSERT INTO schema (version, updated_at) VALUES (27, strftime("%s")) ` diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go index 87d929c20e..2e3fb9bf91 100644 --- a/lxd/db/cluster/update.go +++ b/lxd/db/cluster/update.go @@ -63,6 +63,13 @@ var updates = map[int]schema.Update{ 24: updateFromV23, 25: updateFromV24, 26: updateFromV25, + 27: updateFromV26, +} + +// Add expiry date to storage volume snapshots +func updateFromV26(tx *sql.Tx) error { + _, err := tx.Exec("ALTER TABLE storage_volumes_snapshots ADD COLUMN expiry_date DATETIME;") + return err } // Create new storage snapshot tables and migrate data to them. diff --git a/lxd/db/instances.mapper.go b/lxd/db/instances.mapper.go index bac855fa3d..19e5e9bbb1 100644 --- a/lxd/db/instances.mapper.go +++ b/lxd/db/instances.mapper.go @@ -235,6 +235,13 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { filter.Node, filter.Name, } + } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Name"] != nil { + stmt = c.stmt(instanceObjectsByProjectAndTypeAndName) + args = []interface{}{ + filter.Project, + filter.Type, + filter.Name, + } } else if criteria["Project"] != nil && criteria["Name"] != nil && criteria["Node"] != nil { stmt = c.stmt(instanceObjectsByProjectAndNameAndNode) args = []interface{}{ @@ -242,12 +249,12 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { 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,17 +263,16 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { filter.Name, filter.Node, } - } else if criteria["Project"] != nil && criteria["Type"] != nil && criteria["Node"] != nil { - stmt = c.stmt(instanceObjectsByProjectAndTypeAndNode) + } else if criteria["Project"] != nil && criteria["Type"] != nil { + stmt = c.stmt(instanceObjectsByProjectAndType) args = []interface{}{ filter.Project, filter.Type, - filter.Node, } - } else if criteria["Project"] != nil && criteria["Name"] != nil { - stmt = c.stmt(instanceObjectsByProjectAndName) + } else if criteria["Type"] != nil && criteria["Name"] != nil { + stmt = c.stmt(instanceObjectsByTypeAndName) args = []interface{}{ - filter.Project, + filter.Type, filter.Name, } } else if criteria["Project"] != nil && criteria["Node"] != nil { @@ -275,17 +281,17 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { filter.Project, filter.Node, } - } else if criteria["Type"] != nil && criteria["Name"] != nil { - stmt = c.stmt(instanceObjectsByTypeAndName) + } else if criteria["Project"] != nil && criteria["Name"] != nil { + stmt = c.stmt(instanceObjectsByProjectAndName) args = []interface{}{ - filter.Type, + filter.Project, filter.Name, } - } else if criteria["Project"] != nil && criteria["Type"] != nil { - stmt = c.stmt(instanceObjectsByProjectAndType) + } else if criteria["Type"] != nil && criteria["Node"] != nil { + stmt = c.stmt(instanceObjectsByTypeAndNode) args = []interface{}{ - filter.Project, filter.Type, + filter.Node, } } else if criteria["Node"] != nil && criteria["Name"] != nil { stmt = c.stmt(instanceObjectsByNodeAndName) @@ -293,11 +299,15 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { filter.Node, filter.Name, } - } else if criteria["Type"] != nil && criteria["Node"] != nil { - stmt = c.stmt(instanceObjectsByTypeAndNode) + } else if criteria["Type"] != nil { + stmt = c.stmt(instanceObjectsByType) args = []interface{}{ filter.Type, - filter.Node, + } + } else if criteria["Name"] != nil { + stmt = c.stmt(instanceObjectsByName) + args = []interface{}{ + filter.Name, } } else if criteria["Project"] != nil { stmt = c.stmt(instanceObjectsByProject) @@ -309,16 +319,6 @@ func (c *ClusterTx) InstanceList(filter InstanceFilter) ([]Instance, error) { args = []interface{}{ filter.Node, } - } else if criteria["Type"] != nil { - stmt = c.stmt(instanceObjectsByType) - args = []interface{}{ - filter.Type, - } - } else if criteria["Name"] != nil { - stmt = c.stmt(instanceObjectsByName) - args = []interface{}{ - filter.Name, - } } else { stmt = c.stmt(instanceObjects) args = []interface{}{} @@ -595,16 +595,16 @@ func (c *ClusterTx) InstanceProfilesRef(filter InstanceFilter) (map[string]map[s 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 if criteria["Node"] != nil { + stmt = c.stmt(instanceProfilesRefByNode) + args = []interface{}{ + filter.Node, + } } else { stmt = c.stmt(instanceProfilesRef) args = []interface{}{} @@ -674,21 +674,16 @@ func (c *ClusterTx) InstanceConfigRef(filter InstanceFilter) (map[string]map[str var stmt *sql.Stmt var args []interface{} - if criteria["Project"] != nil && criteria["Node"] != nil { - stmt = c.stmt(instanceConfigRefByProjectAndNode) - args = []interface{}{ - filter.Project, - filter.Node, - } - } else if criteria["Project"] != nil && criteria["Name"] != nil { + if criteria["Project"] != nil && criteria["Name"] != nil { stmt = c.stmt(instanceConfigRefByProjectAndName) args = []interface{}{ filter.Project, filter.Name, } - } else if criteria["Node"] != nil { - stmt = c.stmt(instanceConfigRefByNode) + } else if criteria["Project"] != nil && criteria["Node"] != nil { + stmt = c.stmt(instanceConfigRefByProjectAndNode) args = []interface{}{ + filter.Project, filter.Node, } } else if criteria["Project"] != nil { @@ -696,6 +691,11 @@ func (c *ClusterTx) InstanceConfigRef(filter InstanceFilter) (map[string]map[str args = []interface{}{ filter.Project, } + } else if criteria["Node"] != nil { + stmt = c.stmt(instanceConfigRefByNode) + args = []interface{}{ + filter.Node, + } } else { stmt = c.stmt(instanceConfigRef) args = []interface{}{} @@ -770,21 +770,16 @@ func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[st var stmt *sql.Stmt var args []interface{} - if criteria["Project"] != nil && criteria["Node"] != nil { - stmt = c.stmt(instanceDevicesRefByProjectAndNode) - args = []interface{}{ - filter.Project, - filter.Node, - } - } else if criteria["Project"] != nil && criteria["Name"] != nil { + if criteria["Project"] != nil && criteria["Name"] != nil { stmt = c.stmt(instanceDevicesRefByProjectAndName) args = []interface{}{ filter.Project, filter.Name, } - } else if criteria["Node"] != nil { - stmt = c.stmt(instanceDevicesRefByNode) + } else if criteria["Project"] != nil && criteria["Node"] != nil { + stmt = c.stmt(instanceDevicesRefByProjectAndNode) args = []interface{}{ + filter.Project, filter.Node, } } else if criteria["Project"] != nil { @@ -792,6 +787,11 @@ func (c *ClusterTx) InstanceDevicesRef(filter InstanceFilter) (map[string]map[st args = []interface{}{ filter.Project, } + } else if criteria["Node"] != nil { + stmt = c.stmt(instanceDevicesRefByNode) + args = []interface{}{ + filter.Node, + } } else { stmt = c.stmt(instanceDevicesRef) args = []interface{}{} From b7870c9ca743a441d8f77f77728dcdffb9f40630 Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 12 Mar 2020 12:55:09 +0100 Subject: [PATCH 4/6] shared/api: Add expiry fields to StorageVolumeSnapshot* This adds an ExpiresAt field to both StorageVolumeSnapshotsPost and StorageVolumeSnapshotPut, the same way it's done for Instances. Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- shared/api/storage_pool_volume_snapshot.go | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/shared/api/storage_pool_volume_snapshot.go b/shared/api/storage_pool_volume_snapshot.go index 4ba21da05d..11d3b5bb7a 100644 --- a/shared/api/storage_pool_volume_snapshot.go +++ b/shared/api/storage_pool_volume_snapshot.go @@ -1,10 +1,15 @@ package api +import "time" + // StorageVolumeSnapshotsPost represents the fields available for a new LXD storage volume snapshot // // API extension: storage_api_volume_snapshots type StorageVolumeSnapshotsPost struct { Name string `json:"name" yaml:"name"` + + // API extension: custom_volume_snapshot_expiry + ExpiresAt *time.Time `json:"expires_at" yaml:"expires_at"` } // StorageVolumeSnapshotPost represents the fields required to rename/move a LXD storage volume snapshot @@ -18,9 +23,10 @@ type StorageVolumeSnapshotPost struct { // // API extension: storage_api_volume_snapshots type StorageVolumeSnapshot struct { - Name string `json:"name" yaml:"name"` - Config map[string]string `json:"config" yaml:"config"` - Description string `json:"description" yaml:"description"` + StorageVolumeSnapshotPut `json:",inline", yaml:",inline"` + + Name string `json:"name" yaml:"name"` + Config map[string]string `json:"config" yaml:"config"` } // StorageVolumeSnapshotPut represents the modifiable fields of a LXD storage volume @@ -28,4 +34,7 @@ type StorageVolumeSnapshot struct { // API extension: storage_api_volume_snapshots type StorageVolumeSnapshotPut struct { Description string `json:"description" yaml:"description"` + + // API extension: custom_volume_snapshot_expiry + ExpiresAt time.Time `json:"expires_at" yaml:"expires_at"` } From 303c6f897a55d2a79db751a3277ac8ccba354a7c Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 12 Mar 2020 12:59:43 +0100 Subject: [PATCH 5/6] lxd/storage: Add expiry to volume snapshot pool functions This adds an expiry field to both CreateCustomVolumeSnapshot and UpdateCustomVolumeSnapshot. Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- lxd/storage/backend_lxd.go | 5 +++-- lxd/storage/backend_mock.go | 5 +++-- lxd/storage/pool_interface.go | 5 +++-- lxd/storage_volumes_snapshot.go | 5 +++-- 4 files changed, 12 insertions(+), 8 deletions(-) diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go index 8e22694520..8ca12dfb28 100644 --- a/lxd/storage/backend_lxd.go +++ b/lxd/storage/backend_lxd.go @@ -6,6 +6,7 @@ import ( "os" "path/filepath" "strings" + "time" "github.com/pkg/errors" yaml "gopkg.in/yaml.v2" @@ -2547,7 +2548,7 @@ func (b *lxdBackend) UpdateCustomVolume(projectName string, volName string, newD // UpdateCustomVolumeSnapshot updates the description of a custom volume snapshot. // Volume config is not allowd to be updated and will return an error. -func (b *lxdBackend) UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, op *operations.Operation) error { +func (b *lxdBackend) UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, newExpiryDate time.Time, op *operations.Operation) error { logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "volName": volName, "newDesc": newDesc, "newConfig": newConfig}) logger.Debug("UpdateCustomVolumeSnapshot started") defer logger.Debug("UpdateCustomVolumeSnapshot finished") @@ -2653,7 +2654,7 @@ func (b *lxdBackend) UnmountCustomVolume(projectName, volName string, op *operat } // CreateCustomVolumeSnapshot creates a snapshot of a custom volume. -func (b *lxdBackend) CreateCustomVolumeSnapshot(projectName, volName string, newSnapshotName string, op *operations.Operation) error { +func (b *lxdBackend) CreateCustomVolumeSnapshot(projectName, volName string, newSnapshotName string, newExpiryDate time.Time, op *operations.Operation) error { logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "volName": volName, "newSnapshotName": newSnapshotName}) logger.Debug("CreateCustomVolumeSnapshot started") defer logger.Debug("CreateCustomVolumeSnapshot finished") diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go index 5823c333b2..5378221656 100644 --- a/lxd/storage/backend_mock.go +++ b/lxd/storage/backend_mock.go @@ -2,6 +2,7 @@ package storage import ( "io" + "time" "github.com/lxc/lxd/lxd/backup" "github.com/lxc/lxd/lxd/instance" @@ -217,7 +218,7 @@ func (b *mockBackend) UnmountCustomVolume(projectName string, volName string, op return true, nil } -func (b *mockBackend) CreateCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, op *operations.Operation) error { +func (b *mockBackend) CreateCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, expiryDate time.Time, op *operations.Operation) error { return nil } @@ -229,7 +230,7 @@ func (b *mockBackend) DeleteCustomVolumeSnapshot(projectName string, volName str return nil } -func (b *mockBackend) UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, op *operations.Operation) error { +func (b *mockBackend) UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, expiryDate time.Time, op *operations.Operation) error { return nil } diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go index ffacb2a455..cc2d14d034 100644 --- a/lxd/storage/pool_interface.go +++ b/lxd/storage/pool_interface.go @@ -2,6 +2,7 @@ package storage import ( "io" + "time" "github.com/lxc/lxd/lxd/backup" "github.com/lxc/lxd/lxd/instance" @@ -75,10 +76,10 @@ type Pool interface { UnmountCustomVolume(projectName string, volName string, op *operations.Operation) (bool, error) // Custom volume snapshots. - CreateCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, op *operations.Operation) error + CreateCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, newExpiryDate time.Time, op *operations.Operation) error RenameCustomVolumeSnapshot(projectName string, volName string, newSnapshotName string, op *operations.Operation) error DeleteCustomVolumeSnapshot(projectName string, volName string, op *operations.Operation) error - UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, op *operations.Operation) error + UpdateCustomVolumeSnapshot(projectName string, volName string, newDesc string, newConfig map[string]string, newExpiryDate time.Time, op *operations.Operation) error RestoreCustomVolume(projectName string, volName string, snapshotName string, op *operations.Operation) error // Custom volume migration. diff --git a/lxd/storage_volumes_snapshot.go b/lxd/storage_volumes_snapshot.go index 1c4139ff9d..e9780933bc 100644 --- a/lxd/storage_volumes_snapshot.go +++ b/lxd/storage_volumes_snapshot.go @@ -5,6 +5,7 @@ import ( "fmt" "net/http" "strings" + "time" "github.com/gorilla/mux" @@ -122,7 +123,7 @@ func storagePoolVolumeSnapshotsTypePost(d *Daemon, r *http.Request) response.Res return err } - return pool.CreateCustomVolumeSnapshot(projectName, volumeName, req.Name, op) + return pool.CreateCustomVolumeSnapshot(projectName, volumeName, req.Name, time.Unix(0, 0), op) } resources := map[string][]string{} @@ -430,7 +431,7 @@ func storagePoolVolumeSnapshotTypePut(d *Daemon, r *http.Request) response.Respo } // Handle custom volume update requests. - return pool.UpdateCustomVolumeSnapshot(projectName, vol.Name, req.Description, nil, op) + return pool.UpdateCustomVolumeSnapshot(projectName, vol.Name, req.Description, nil, time.Unix(0, 0), op) } resources := map[string][]string{} From 3c6cf6b1a670f291deb234b5e5a8dd88ce04d0bc Mon Sep 17 00:00:00 2001 From: Thomas Hipp <thomas.h...@canonical.com> Date: Thu, 12 Mar 2020 13:26:53 +0100 Subject: [PATCH 6/6] lxd: Add snapshots.expiry config key for storage pools This adds the snapshots.expiry config key to storage pools. The key needs to be set per pool. Signed-off-by: Thomas Hipp <thomas.h...@canonical.com> --- lxd/storage_pools_config.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lxd/storage_pools_config.go b/lxd/storage_pools_config.go index 71195b7e5a..0e63dfc6d2 100644 --- a/lxd/storage_pools_config.go +++ b/lxd/storage_pools_config.go @@ -4,6 +4,7 @@ import ( "fmt" "strconv" "strings" + "time" "golang.org/x/sys/unix" @@ -52,6 +53,13 @@ var storagePoolConfigKeys = map[string]func(value string) error{ // valid drivers: btrfs, lvm, zfs "size": shared.IsSize, + // valid drivers: all + "snapshots.expiry": func(value string) error { + // Validate expression + _, err := shared.GetSnapshotExpiry(time.Time{}, value) + return err + }, + // valid drivers: btrfs, dir, lvm, zfs "source": shared.IsAny,
_______________________________________________ lxc-devel mailing list lxc-devel@lists.linuxcontainers.org http://lists.linuxcontainers.org/listinfo/lxc-devel