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

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) ===
This feature allows importing and exporting containers (with snapshots).

## Status
What has been done so far:

- [x] Database
- [ ] API endpoints
  - [x] /1.0/containers/NAME/backups (GET)
  - [x] /1.0/containers/NAME/backups (POST)
  - [x] /1.0/containers/NAME/backups/NAME (DELETE)
  - [x] /1.0/containers/NAME/backups/NAME (GET)
  - [x] /1.0/containers/NAME/backups/NAME (POST)
  - [ ] /1.0/containers/NAME/backups/NAME/export (GET)
  - [ ] /1.0/containers (extend POST)
- [ ] Storage
  - [x] dir
  - [x] LVM
  - [ ] Btrfs
  - [ ] ZFS
  - [ ] Ceph
- [ ] Commands
  - [ ] `lxc export [<remote>:]<container> [target] [--container-only] [--optimized-storage]`
  - [ ] `lxc import [<remote>:] <backup file>`
- [ ] Documentation
- [ ] Tests
From 81e84e9b3165d340b36ea03b72c8cd7437c43446 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Wed, 4 Apr 2018 17:22:59 +0200
Subject: [PATCH 1/4] db: Create containers_backups table

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/db/cluster/schema.go | 13 ++++++++++++-
 lxd/db/cluster/update.go | 19 +++++++++++++++++++
 2 files changed, 31 insertions(+), 1 deletion(-)

diff --git a/lxd/db/cluster/schema.go b/lxd/db/cluster/schema.go
index e1371b1b9..89ea9a921 100644
--- a/lxd/db/cluster/schema.go
+++ b/lxd/db/cluster/schema.go
@@ -34,6 +34,17 @@ CREATE TABLE containers (
     UNIQUE (name),
     FOREIGN KEY (node_id) REFERENCES nodes (id) ON DELETE CASCADE
 );
+CREATE TABLE containers_backups (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    creation_date DATETIME,
+    expiry_date DATETIME,
+    container_only INTEGER NOT NULL default 0,
+    optimized_storage INTEGER NOT NULL default 0,
+    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
+    UNIQUE (container_id, name)
+);
 CREATE TABLE containers_config (
     id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
     container_id INTEGER NOT NULL,
@@ -235,5 +246,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 (7, strftime("%s"))
+INSERT INTO schema (version, updated_at) VALUES (8, strftime("%s"))
 `
diff --git a/lxd/db/cluster/update.go b/lxd/db/cluster/update.go
index 8b40b628e..e782ee1ad 100644
--- a/lxd/db/cluster/update.go
+++ b/lxd/db/cluster/update.go
@@ -32,6 +32,25 @@ var updates = map[int]schema.Update{
        5: updateFromV4,
        6: updateFromV5,
        7: updateFromV6,
+       8: updateFromV7,
+}
+
+func updateFromV7(tx *sql.Tx) error {
+       stmts := `
+CREATE TABLE containers_backups (
+    id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
+    container_id INTEGER NOT NULL,
+    name VARCHAR(255) NOT NULL,
+    creation_date DATETIME,
+    expiry_date DATETIME,
+    container_only INTEGER NOT NULL default 0,
+    optimized_storage INTEGER NOT NULL default 0,
+    FOREIGN KEY (container_id) REFERENCES containers (id) ON DELETE CASCADE,
+    UNIQUE (container_id, name)
+);
+`
+       _, err := tx.Exec(stmts)
+       return err
 }
 
 // The zfs.pool_name config key is node-specific, and needs to be linked to

From 946be28114f54e547d28eed4ccbb2eac0ee1ea2a Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 9 Apr 2018 16:13:06 +0200
Subject: [PATCH 2/4] lxd: Add container backups

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/api_1.0.go                 |   2 +
 lxd/backup.go                  | 106 ++++++++++++++++++
 lxd/container.go               |  52 ++++++++-
 lxd/container_backup.go        | 243 +++++++++++++++++++++++++++++++++++++++++
 lxd/container_lxc.go           |  21 ++++
 lxd/containers.go              |  20 ++++
 lxd/db/containers.go           | 158 +++++++++++++++++++++++++++
 lxd/storage.go                 |  56 ++++++++++
 lxd/storage_btrfs.go           |  12 ++
 lxd/storage_ceph.go            |  12 ++
 lxd/storage_dir.go             |  12 ++
 lxd/storage_lvm.go             |  12 ++
 lxd/storage_mock.go            |  12 ++
 lxd/storage_zfs.go             |  12 ++
 lxd/sys/fs.go                  |   1 +
 shared/api/container_backup.go |  26 +++++
 shared/version/api.go          |   1 +
 17 files changed, 757 insertions(+), 1 deletion(-)
 create mode 100644 lxd/backup.go
 create mode 100644 lxd/container_backup.go
 create mode 100644 shared/api/container_backup.go

diff --git a/lxd/api_1.0.go b/lxd/api_1.0.go
index 9bc06cad3..171d0b7b6 100644
--- a/lxd/api_1.0.go
+++ b/lxd/api_1.0.go
@@ -33,6 +33,8 @@ var api10 = []Command{
        containerExecCmd,
        containerMetadataCmd,
        containerMetadataTemplatesCmd,
+       containerBackupsCmd,
+       containerBackupCmd,
        aliasCmd,
        aliasesCmd,
        eventsCmd,
diff --git a/lxd/backup.go b/lxd/backup.go
new file mode 100644
index 000000000..e6da46be2
--- /dev/null
+++ b/lxd/backup.go
@@ -0,0 +1,106 @@
+package main
+
+import (
+       "time"
+
+       "github.com/lxc/lxd/lxd/state"
+       "github.com/lxc/lxd/shared/api"
+)
+
+// backup represents a container backup.
+type backup struct {
+       state     *state.State
+       container container
+
+       // Properties
+       id               int
+       name             string
+       creationDate     time.Time
+       expiryDate       time.Time
+       containerOnly    bool
+       optimizedStorage bool
+}
+
+// Rename renames a container backup.
+func (b *backup) Rename(newName string) error {
+       ourStart, err := b.container.StorageStart()
+       if err != nil {
+               return err
+       }
+       if ourStart {
+               defer b.container.StorageStop()
+       }
+
+       // Rename the database entry
+       err = b.state.Cluster.ContainerBackupRename(b.Name(), newName)
+       if err != nil {
+               return err
+       }
+
+       // Rename the directories and files
+       err = b.container.Storage().ContainerBackupRename(*b, newName)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// Delete removes a container backup.
+func (b *backup) Delete() error {
+       ourStart, err := b.container.StorageStart()
+       if err != nil {
+               return err
+       }
+       if ourStart {
+               defer b.container.StorageStop()
+       }
+
+       // Remove the database record
+       err = b.state.Cluster.ContainerBackupRemove(b.Name())
+       if err != nil {
+               return err
+       }
+
+       // Delete backup from storage
+       err = b.container.Storage().ContainerBackupDelete(b.Name())
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+func (b *backup) Render() interface{} {
+       return &api.ContainerBackup{
+               Name:             b.name,
+               CreationDate:     b.creationDate,
+               ExpiryDate:       b.expiryDate,
+               ContainerOnly:    b.containerOnly,
+               OptimizedStorage: b.optimizedStorage,
+       }
+}
+
+func (b *backup) Id() int {
+       return b.id
+}
+
+func (b *backup) Name() string {
+       return b.name
+}
+
+func (b *backup) CreationDate() time.Time {
+       return b.creationDate
+}
+
+func (b *backup) ExpiryDate() time.Time {
+       return b.expiryDate
+}
+
+func (b *backup) ContainerOnly() bool {
+       return b.containerOnly
+}
+
+func (b *backup) OptimizedStorage() bool {
+       return b.optimizedStorage
+}
diff --git a/lxd/container.go b/lxd/container.go
index 6134ffd60..7f66e8e34 100644
--- a/lxd/container.go
+++ b/lxd/container.go
@@ -435,13 +435,14 @@ type container interface {
        Stop(stateful bool) error
        Unfreeze() error
 
-       // Snapshots & migration
+       // Snapshots & migration & backups
        Restore(sourceContainer container, stateful bool) error
        /* actionScript here is a script called action.sh in the stateDir, to
         * be passed to CRIU as --action-script
         */
        Migrate(args *CriuMigrationArgs) error
        Snapshots() ([]container, error)
+       Backups() ([]backup, error)
 
        // Config handling
        Rename(newName string) error
@@ -977,3 +978,52 @@ func containerLoadByName(s *state.State, name string) 
(container, error) {
 
        return containerLXCLoad(s, args)
 }
+
+func containerBackupLoadByName(s *state.State, name string) (*backup, error) {
+       // Get the DB record
+       args, err := s.Cluster.ContainerGetBackup(name)
+       if err != nil {
+               return nil, err
+       }
+
+       c, err := containerLoadById(s, args.ContainerID)
+       if err != nil {
+               return nil, err
+       }
+
+       return &backup{
+               state:            s,
+               container:        c,
+               id:               args.ID,
+               name:             name,
+               creationDate:     args.CreationDate,
+               expiryDate:       args.ExpiryDate,
+               containerOnly:    args.ContainerOnly,
+               optimizedStorage: args.OptimizedStorage,
+       }, nil
+}
+
+func containerBackupCreate(s *state.State, args db.ContainerBackupArgs,
+       sourceContainer container) error {
+       err := s.Cluster.ContainerBackupCreate(args)
+       if err != nil {
+               if err == db.ErrAlreadyDefined {
+                       return fmt.Errorf("backup '%s' already exists", 
args.Name)
+               }
+               return err
+       }
+
+       b, err := containerBackupLoadByName(s, args.Name)
+       if err != nil {
+               return err
+       }
+
+       // Now create the empty snapshot
+       err = sourceContainer.Storage().ContainerBackupCreate(*b, 
sourceContainer)
+       if err != nil {
+               s.Cluster.ContainerBackupRemove(args.Name)
+               return err
+       }
+
+       return nil
+}
diff --git a/lxd/container_backup.go b/lxd/container_backup.go
new file mode 100644
index 000000000..e4df43828
--- /dev/null
+++ b/lxd/container_backup.go
@@ -0,0 +1,243 @@
+package main
+
+import (
+       "encoding/json"
+       "fmt"
+       "net/http"
+       "strings"
+       "time"
+
+       "github.com/gorilla/mux"
+       "github.com/lxc/lxd/lxd/db"
+       "github.com/lxc/lxd/lxd/util"
+       "github.com/lxc/lxd/shared"
+       "github.com/lxc/lxd/shared/api"
+       "github.com/lxc/lxd/shared/version"
+)
+
+func containerBackupsGet(d *Daemon, r *http.Request) Response {
+       cname := mux.Vars(r)["name"]
+
+       // Handle requests targeted to a container on a different node
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, cname)
+       if err != nil {
+               return SmartError(err)
+       }
+       if response != nil {
+               return response
+       }
+
+       recursion := util.IsRecursionRequest(r)
+
+       c, err := containerLoadByName(d.State(), cname)
+       if err != nil {
+               return SmartError(err)
+       }
+
+       backups, err := c.Backups()
+       if err != nil {
+               return SmartError(err)
+       }
+
+       resultString := []string{}
+       resultMap := []*api.ContainerBackup{}
+
+       for _, backup := range backups {
+               if !recursion {
+                       url := fmt.Sprintf("/%s/containers/%s/backups/%s",
+                               version.APIVersion, cname, backup.Name())
+                       resultString = append(resultString, url)
+               } else {
+                       render := backup.Render()
+                       resultMap = append(resultMap, 
render.(*api.ContainerBackup))
+               }
+       }
+
+       if !recursion {
+               return SyncResponse(true, resultString)
+       }
+
+       return SyncResponse(true, resultMap)
+}
+
+func containerBackupsPost(d *Daemon, r *http.Request) Response {
+       name := mux.Vars(r)["name"]
+
+       // Handle requests targeted to a container on a different node
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, name)
+       if err != nil {
+               return SmartError(err)
+       }
+       if response != nil {
+               return response
+       }
+
+       c, err := containerLoadByName(d.State(), name)
+       if err != nil {
+               return SmartError(err)
+       }
+
+       ourStart, err := c.StorageStart()
+       if err != nil {
+               return InternalError(err)
+       }
+       if ourStart {
+               defer c.StorageStop()
+       }
+
+       req := api.ContainerBackupsPost{}
+       err = json.NewDecoder(r.Body).Decode(&req)
+       if err != nil {
+               return BadRequest(err)
+       }
+
+       // Validate the name
+       if strings.Contains(req.Name, "/") {
+               return BadRequest(fmt.Errorf("Backup names may not contain 
slashes"))
+       }
+
+       fullName := name + shared.SnapshotDelimiter + req.Name
+
+       backup := func(op *operation) error {
+               args := db.ContainerBackupArgs{
+                       Name:             fullName,
+                       ContainerID:      c.Id(),
+                       CreationDate:     time.Now(),
+                       ExpiryDate:       
time.Now().Add(time.Duration(req.ExpiryDate) * time.Second),
+                       ContainerOnly:    req.ContainerOnly,
+                       OptimizedStorage: req.OptimizedStorage,
+               }
+
+               err := containerBackupCreate(d.State(), args, c)
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       resources := map[string][]string{}
+       resources["containers"] = []string{name}
+
+       op, err := operationCreate(d.cluster, operationClassTask,
+               "Backing up container", resources, nil, backup, nil, nil)
+       if err != nil {
+               return InternalError(err)
+       }
+
+       return OperationResponse(op)
+}
+
+func containerBackupGet(d *Daemon, r *http.Request) Response {
+       name := mux.Vars(r)["name"]
+       backupName := mux.Vars(r)["backupName"]
+
+       // Handle requests targeted to a container on a different node
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, name)
+       if err != nil {
+               return SmartError(err)
+       }
+       if response != nil {
+               return response
+       }
+
+       backup, err := containerBackupLoadByName(d.State(), backupName)
+       if err != nil {
+               return SmartError(err)
+       }
+
+       return SyncResponse(true, backup.Render())
+}
+
+func containerBackupPost(d *Daemon, r *http.Request) Response {
+       name := mux.Vars(r)["name"]
+       backupName := mux.Vars(r)["backupName"]
+
+       // Handle requests targeted to a container on a different node
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, name)
+       if err != nil {
+               return SmartError(err)
+       }
+       if response != nil {
+               return response
+       }
+
+       req := api.ContainerBackupPost{}
+       err = json.NewDecoder(r.Body).Decode(&req)
+       if err != nil {
+               return BadRequest(err)
+       }
+
+       // Validate the name
+       if strings.Contains(req.Name, "/") {
+               return BadRequest(fmt.Errorf("Backup names may not contain 
slashes"))
+       }
+
+       oldName := name + shared.SnapshotDelimiter + backupName
+       backup, err := containerBackupLoadByName(d.State(), oldName)
+       if err != nil {
+               SmartError(err)
+       }
+
+       newName := name + shared.SnapshotDelimiter + req.Name
+
+       rename := func(op *operation) error {
+               err := backup.Rename(newName)
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       resources := map[string][]string{}
+       resources["containers"] = []string{name}
+
+       op, err := operationCreate(d.cluster, operationClassTask,
+               "Renaming container backup", resources, nil, rename, nil, nil)
+       if err != nil {
+               return InternalError(err)
+       }
+
+       return OperationResponse(op)
+}
+
+func containerBackupDelete(d *Daemon, r *http.Request) Response {
+       name := mux.Vars(r)["name"]
+       backupName := mux.Vars(r)["backupName"]
+
+       // Handle requests targeted to a container on a different node
+       response, err := ForwardedResponseIfContainerIsRemote(d, r, name)
+       if err != nil {
+               return SmartError(err)
+       }
+       if response != nil {
+               return response
+       }
+
+       fullName := name + shared.SnapshotDelimiter + backupName
+       backup, err := containerBackupLoadByName(d.State(), fullName)
+       if err != nil {
+               return SmartError(err)
+       }
+
+       remove := func(op *operation) error {
+               err := backup.Delete()
+               if err != nil {
+                       return err
+               }
+
+               return nil
+       }
+
+       resources := map[string][]string{}
+       resources["container"] = []string{name}
+
+       op, err := operationCreate(d.cluster, operationClassTask,
+               "Removing container backup", resources, nil, remove, nil, nil)
+       if err != nil {
+               return InternalError(err)
+       }
+
+       return OperationResponse(op)
+}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index b40eb99f3..35e642a77 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -3000,6 +3000,27 @@ func (c *containerLXC) Snapshots() ([]container, error) {
        return containers, nil
 }
 
+func (c *containerLXC) Backups() ([]backup, error) {
+       // Get all the backups
+       backupNames, err := c.state.Cluster.ContainerGetBackups(c.name)
+       if err != nil {
+               return nil, err
+       }
+
+       // Build the backup list
+       backups := []backup{}
+       for _, backupName := range backupNames {
+               backup, err := containerBackupLoadByName(c.state, backupName)
+               if err != nil {
+                       return nil, err
+               }
+
+               backups = append(backups, *backup)
+       }
+
+       return backups, nil
+}
+
 func (c *containerLXC) Restore(sourceContainer container, stateful bool) error 
{
        var ctxMap log.Ctx
 
diff --git a/lxd/containers.go b/lxd/containers.go
index e28357adc..1764cf99e 100644
--- a/lxd/containers.go
+++ b/lxd/containers.go
@@ -81,6 +81,26 @@ var containerMetadataTemplatesCmd = Command{
        delete: containerMetadataTemplatesDelete,
 }
 
+var containerBackupsCmd = Command{
+       name: "containers/{name}/backups",
+       get:  containerBackupsGet,
+       post: containerBackupsPost,
+}
+
+var containerBackupCmd = Command{
+       name:   "containers/{name}/backups/{backupName}",
+       get:    containerBackupGet,
+       post:   containerBackupPost,
+       delete: containerBackupDelete,
+}
+
+/*
+var containerBackupExportCmd = Command{
+       name: "containers/{name}/backups/{backupName}/export",
+       get:  containerBackupExportGet,
+}
+*/
+
 type containerAutostartList []container
 
 func (slice containerAutostartList) Len() int {
diff --git a/lxd/db/containers.go b/lxd/db/containers.go
index 3b93a0d51..5b30c7013 100644
--- a/lxd/db/containers.go
+++ b/lxd/db/containers.go
@@ -36,6 +36,18 @@ type ContainerArgs struct {
        Stateful     bool
 }
 
+type ContainerBackupArgs struct {
+       // Don't set manually
+       ID int
+
+       ContainerID      int
+       Name             string
+       CreationDate     time.Time
+       ExpiryDate       time.Time
+       ContainerOnly    bool
+       OptimizedStorage bool
+}
+
 // ContainerType encodes the type of container (either regular or snapshot).
 type ContainerType int
 
@@ -872,3 +884,149 @@ WHERE storage_volumes.node_id=? AND 
storage_volumes.name=? AND storage_volumes.t
 
        return poolName, nil
 }
+
+// ContainerBackupID returns the ID of the container backup with the given 
name.
+func (c *Cluster) ContainerBackupID(name string) (int, error) {
+       q := "SELECT id FROM containers_backups WHERE name=?"
+       id := -1
+       arg1 := []interface{}{name}
+       arg2 := []interface{}{&id}
+       err := dbQueryRowScan(c.db, q, arg1, arg2)
+       return id, err
+}
+
+// ContainerGetBackup returns the backup with the given name.
+func (c *Cluster) ContainerGetBackup(name string) (ContainerBackupArgs, error) 
{
+       args := ContainerBackupArgs{}
+       args.Name = name
+
+       containerOnlyInt := -1
+       optimizedStorageInt := -1
+       q := `
+SELECT id, container_id, creation_date, expiry_date, container_only, 
optimized_storage
+    FROM containers_backups
+    WHERE name=?
+`
+       arg1 := []interface{}{name}
+       arg2 := []interface{}{&args.ID, &args.ContainerID, &args.CreationDate,
+               &args.ExpiryDate, &containerOnlyInt, &optimizedStorageInt}
+       err := dbQueryRowScan(c.db, q, arg1, arg2)
+       if err != nil {
+               return args, err
+       }
+
+       if containerOnlyInt == 1 {
+               args.ContainerOnly = true
+       }
+
+       if optimizedStorageInt == 1 {
+               args.OptimizedStorage = true
+       }
+
+       return args, nil
+}
+
+// ContainerGetBackups returns the names of all backups of the container
+// with the given name.
+func (c *Cluster) ContainerGetBackups(name string) ([]string, error) {
+       var result []string
+
+       q := `SELECT containers_backups.name FROM containers_backups
+JOIN containers ON containers_backups.container_id=containers.id
+WHERE containers.name=?`
+       inargs := []interface{}{name}
+       outfmt := []interface{}{name}
+       dbResults, err := queryScan(c.db, q, inargs, outfmt)
+       if err != nil {
+               return nil, err
+       }
+
+       for _, r := range dbResults {
+               result = append(result, r[0].(string))
+       }
+
+       return result, nil
+}
+
+func (c *Cluster) ContainerBackupCreate(args ContainerBackupArgs) error {
+       _, err := c.ContainerBackupID(args.Name)
+       if err == nil {
+               return ErrAlreadyDefined
+       }
+
+       err = c.Transaction(func(tx *ClusterTx) error {
+               containerOnlyInt := 0
+               if args.ContainerOnly {
+                       containerOnlyInt = 1
+               }
+
+               optimizedStorageInt := 0
+               if args.OptimizedStorage {
+                       optimizedStorageInt = 1
+               }
+
+               str := fmt.Sprintf("INSERT INTO containers_backups 
(container_id, name, creation_date, expiry_date, container_only, 
optimized_storage) VALUES (?, ?, ?, ?, ?, ?)")
+               stmt, err := tx.tx.Prepare(str)
+               if err != nil {
+                       return err
+               }
+               defer stmt.Close()
+               result, err := stmt.Exec(args.ContainerID, args.Name,
+                       args.CreationDate.Unix(), args.ExpiryDate.Unix(), 
containerOnlyInt,
+                       optimizedStorageInt)
+               if err != nil {
+                       return err
+               }
+
+               _, err = result.LastInsertId()
+               if err != nil {
+                       return fmt.Errorf("Error inserting %s into database", 
args.Name)
+               }
+
+               return nil
+       })
+
+       return err
+}
+
+// ContainerBackupRemove removes the container backup with the given name from
+// the database.
+func (c *Cluster) ContainerBackupRemove(name string) error {
+       id, err := c.ContainerBackupID(name)
+       if err != nil {
+               return err
+       }
+
+       err = exec(c.db, "DELETE FROM containers_backups WHERE id=?", id)
+       if err != nil {
+               return err
+       }
+
+       return nil
+}
+
+// ContainerBackupRename renames a container backup from the given current name
+// to the new one.
+func (c *Cluster) ContainerBackupRename(oldName, newName string) error {
+       err := c.Transaction(func(tx *ClusterTx) error {
+               str := fmt.Sprintf("UPDATE containers_backups SET name = ? 
WHERE name = ?")
+               stmt, err := tx.tx.Prepare(str)
+               if err != nil {
+                       return err
+               }
+               defer stmt.Close()
+
+               logger.Debug(
+                       "Calling SQL Query",
+                       log.Ctx{
+                               "query":   "UPDATE containers_backups SET name 
= ? WHERE name = ?",
+                               "oldName": oldName,
+                               "newName": newName})
+               if _, err := stmt.Exec(newName, oldName); err != nil {
+                       return err
+               }
+
+               return nil
+       })
+       return err
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index f718027e0..a060f52b4 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -187,6 +187,10 @@ type storage interface {
        ContainerSnapshotStart(c container) (bool, error)
        ContainerSnapshotStop(c container) (bool, error)
 
+       ContainerBackupCreate(backup backup, sourceContainer container) error
+       ContainerBackupDelete(name string) error
+       ContainerBackupRename(backup backup, newName string) error
+
        // For use in migrating snapshots.
        ContainerSnapshotCreateEmpty(c container) error
 
@@ -580,6 +584,11 @@ func getStoragePoolVolumeMountPoint(poolName string, 
volumeName string) string {
        return shared.VarPath("storage-pools", poolName, "custom", volumeName)
 }
 
+// ${LXD_DIR}/storage-pools/<pool>/backups/<backup_name>
+func getBackupMountPoint(poolName string, backupName string) string {
+       return shared.VarPath("storage-pools", poolName, "backups", backupName)
+}
+
 func createContainerMountpoint(mountPoint string, mountPointSymlink string, 
privileged bool) error {
        var mode os.FileMode
        if privileged {
@@ -718,6 +727,53 @@ func deleteSnapshotMountpoint(snapshotMountpoint string, 
snapshotsSymlinkTarget
        return nil
 }
 
+func createBackupMountpoint(backupMountpoint string, backupsSymlinkTarget 
string, backupsSymlink string) error {
+       backupMntPointExists := shared.PathExists(backupMountpoint)
+       mntPointSymlinkExist := shared.PathExists(backupsSymlink)
+
+       if !backupMntPointExists {
+               err := os.MkdirAll(backupMountpoint, 0711)
+               if err != nil {
+                       return err
+               }
+       }
+
+       if !mntPointSymlinkExist {
+               err := os.Symlink(backupsSymlinkTarget, backupsSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
+func deleteBackupMountpoint(backupMountpoint string, backupsSymlinkTarget 
string, backupsSymlink string) error {
+       if shared.PathExists(backupMountpoint) {
+               err := os.Remove(backupMountpoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       couldRemove := false
+       if shared.PathExists(backupsSymlinkTarget) {
+               err := os.Remove(backupsSymlinkTarget)
+               if err == nil {
+                       couldRemove = true
+               }
+       }
+
+       if couldRemove && shared.PathExists(backupsSymlink) {
+               err := os.Remove(backupsSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
+       return nil
+}
+
 // ShiftIfNecessary sets the volatile.last_state.idmap key to the idmap last
 // used by the container.
 func ShiftIfNecessary(container container, srcIdmap *idmap.IdmapSet) error {
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
index 6c2e02f4f..4e09b3298 100644
--- a/lxd/storage_btrfs.go
+++ b/lxd/storage_btrfs.go
@@ -1383,6 +1383,18 @@ func (s *storageBtrfs) 
ContainerSnapshotCreateEmpty(snapshotContainer container)
        return nil
 }
 
+func (s *storageBtrfs) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageBtrfs) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageBtrfs) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 func (s *storageBtrfs) ImageCreate(fingerprint string) error {
        logger.Debugf("Creating BTRFS storage volume for image \"%s\" on 
storage pool \"%s\".", fingerprint, s.pool.Name)
 
diff --git a/lxd/storage_ceph.go b/lxd/storage_ceph.go
index 1839c0867..c9f89d570 100644
--- a/lxd/storage_ceph.go
+++ b/lxd/storage_ceph.go
@@ -2226,6 +2226,18 @@ func (s *storageCeph) ContainerSnapshotCreateEmpty(c 
container) error {
        return nil
 }
 
+func (s *storageCeph) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageCeph) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageCeph) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 func (s *storageCeph) ImageCreate(fingerprint string) error {
        logger.Debugf(`Creating RBD storage volume for image "%s" on storage `+
                `pool "%s"`, fingerprint, s.pool.Name)
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index d555596d7..cfc4be2d6 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -1011,6 +1011,18 @@ func (s *storageDir) ContainerSnapshotStop(container 
container) (bool, error) {
        return true, nil
 }
 
+func (s *storageDir) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageDir) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageDir) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 func (s *storageDir) ImageCreate(fingerprint string) error {
        return nil
 }
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index df5c7cb30..214ee3bc8 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -1561,6 +1561,18 @@ func (s *storageLvm) 
ContainerSnapshotCreateEmpty(snapshotContainer container) e
        return nil
 }
 
+func (s *storageLvm) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageLvm) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageLvm) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 func (s *storageLvm) ImageCreate(fingerprint string) error {
        logger.Debugf("Creating LVM storage volume for image \"%s\" on storage 
pool \"%s\".", fingerprint, s.pool.Name)
 
diff --git a/lxd/storage_mock.go b/lxd/storage_mock.go
index 673fea4a6..7bae39168 100644
--- a/lxd/storage_mock.go
+++ b/lxd/storage_mock.go
@@ -190,6 +190,18 @@ func (s *storageMock) 
ContainerSnapshotCreateEmpty(snapshotContainer container)
        return nil
 }
 
+func (s *storageMock) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageMock) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageMock) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 func (s *storageMock) ImageCreate(fingerprint string) error {
        return nil
 }
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
index e3a0594d6..66f7b2d1b 100644
--- a/lxd/storage_zfs.go
+++ b/lxd/storage_zfs.go
@@ -1853,6 +1853,18 @@ func (s *storageZfs) 
ContainerSnapshotCreateEmpty(snapshotContainer container) e
        return nil
 }
 
+func (s *storageZfs) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       return nil
+}
+
+func (s *storageZfs) ContainerBackupDelete(name string) error {
+       return nil
+}
+
+func (s *storageZfs) ContainerBackupRename(backup backup, newName string) 
error {
+       return nil
+}
+
 // - create temporary directory ${LXD_DIR}/images/lxd_images_
 // - create new zfs volume images/<fingerprint>
 // - mount the zfs volume on ${LXD_DIR}/images/lxd_images_
diff --git a/lxd/sys/fs.go b/lxd/sys/fs.go
index 8370838eb..61dc47298 100644
--- a/lxd/sys/fs.go
+++ b/lxd/sys/fs.go
@@ -25,6 +25,7 @@ func (s *OS) initDirs() error {
                {filepath.Join(s.VarDir, "networks"), 0711},
                {filepath.Join(s.VarDir, "disks"), 0700},
                {filepath.Join(s.VarDir, "storage-pools"), 0711},
+               {filepath.Join(s.VarDir, "backups"), 0711},
        }
 
        for _, dir := range dirs {
diff --git a/shared/api/container_backup.go b/shared/api/container_backup.go
new file mode 100644
index 000000000..b6c8c0545
--- /dev/null
+++ b/shared/api/container_backup.go
@@ -0,0 +1,26 @@
+package api
+
+import "time"
+
+// ContainerBackupsPost represents the fields available for a new LXD 
container backup
+type ContainerBackupsPost struct {
+       Name             string `json:"name" yaml:"name"`
+       ExpiryDate       int64  `json:"expiry" yaml:"expiry"`
+       ContainerOnly    bool   `json:"container_only" yaml:"container_only"`
+       OptimizedStorage bool   `json:"optimized_storage" 
yaml:"optimized_storage"`
+}
+
+// ContainerBackup represents a LXD conainer backup
+type ContainerBackup struct {
+       Name             string    `json:"name" yaml:"name"`
+       CreationDate     time.Time `json:"creation_date" yaml:"creation_date"`
+       ExpiryDate       time.Time `json:"expiry_date" yaml:"expiry_date"`
+       ContainerOnly    bool      `json:"container_only" yaml:"container_only"`
+       OptimizedStorage bool      `json:"optimized_storage" 
yaml:"optimized_storage"`
+}
+
+// ContainerBackupPost represents the fields available for the renaming of a
+// container backup
+type ContainerBackupPost struct {
+       Name string `json:"name" yaml:"name"`
+}
diff --git a/shared/version/api.go b/shared/version/api.go
index bec41352f..dcaac1d61 100644
--- a/shared/version/api.go
+++ b/shared/version/api.go
@@ -102,6 +102,7 @@ var APIExtensions = []string{
        "event_lifecycle",
        "storage_api_remote_volume_handling",
        "nvidia_runtime",
+       "backup",
 }
 
 // APIExtensionsCount returns the number of available API extensions.

From 9d9e83f11a4f10a8f2c5cc5f525bd306634a1699 Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Thu, 12 Apr 2018 15:36:42 +0200
Subject: [PATCH 3/4] lxd: Implement backups for storage_dir

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/storage_dir.go | 228 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 228 insertions(+)

diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
index cfc4be2d6..d1df8b5ef 100644
--- a/lxd/storage_dir.go
+++ b/lxd/storage_dir.go
@@ -590,6 +590,25 @@ func (s *storageDir) ContainerDelete(container container) 
error {
                }
        }
 
+       // Delete potential leftover backup mountpoints.
+       backupMntPoint := getBackupMountPoint(s.pool.Name, container.Name())
+       if shared.PathExists(backupMntPoint) {
+               err := os.RemoveAll(backupMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Delete potential leftover backup symlinks:
+       // ${LXD_DIR}/backups/<container_name> -> 
${POOL}/backups/<container_name>
+       backupSymlink := shared.VarPath("backups", container.Name())
+       if shared.PathExists(backupSymlink) {
+               err := os.Remove(backupSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
        logger.Debugf("Deleted DIR storage volume for container \"%s\" on 
storage pool \"%s\".", s.volume.Name, s.pool.Name)
        return nil
 }
@@ -773,6 +792,35 @@ func (s *storageDir) ContainerRename(container container, 
newName string) error
                }
        }
 
+       // Rename the backup mountpoint for the container if existing:
+       // ${POOL}/backups/<old_container_name> to 
${POOL}/backups/<new_container_name>
+       oldBackupsMntPoint := getBackupMountPoint(s.pool.Name, container.Name())
+       newBackupsMntPoint := getBackupMountPoint(s.pool.Name, newName)
+       if shared.PathExists(oldBackupsMntPoint) {
+               err = os.Rename(oldBackupsMntPoint, newBackupsMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       // Remove the old backup symlink:
+       // ${LXD_DIR}/backups/<old_container_name>
+       oldBackupSymlink := shared.VarPath("backups", container.Name())
+       newBackupSymlink := shared.VarPath("backups", newName)
+       if shared.PathExists(oldBackupSymlink) {
+               err := os.Remove(oldBackupSymlink)
+               if err != nil {
+                       return err
+               }
+
+               // Create the new backup symlink:
+               // ${LXD_DIR}/backups/<new_container_name> -> 
${POOL}/backups/<new_container_name>
+               err = os.Symlink(newBackupsMntPoint, newBackupSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
        logger.Debugf("Renamed DIR storage volume for container \"%s\" from %s 
-> %s.", s.volume.Name, s.volume.Name, newName)
        return nil
 }
@@ -1012,14 +1060,194 @@ func (s *storageDir) ContainerSnapshotStop(container 
container) (bool, error) {
 }
 
 func (s *storageDir) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       logger.Debugf("Creating DIR storage volume for backup \"%s\" on storage 
pool \"%s\".",
+               backup.Name(), s.pool.Name)
+
+       _, err := s.StoragePoolMount()
+       if err != nil {
+               return err
+       }
+
+       // Create the path for the backup.
+       baseMntPoint := getBackupMountPoint(s.pool.Name, backup.Name())
+       targetBackupContainerMntPoint := fmt.Sprintf("%s/container",
+               baseMntPoint)
+       targetBackupSnapshotsMntPoint := fmt.Sprintf("%s/snapshots",
+               baseMntPoint)
+
+       err = os.MkdirAll(targetBackupContainerMntPoint, 0711)
+       if err != nil {
+               return err
+       }
+
+       if !backup.ContainerOnly() {
+               // Create path for snapshots as well.
+               err = os.MkdirAll(targetBackupSnapshotsMntPoint, 0711)
+               if err != nil {
+                       return err
+               }
+       }
+
+       rsync := func(oldPath string, newPath string, bwlimit string) error {
+               output, err := rsyncLocalCopy(oldPath, newPath, bwlimit)
+               if err != nil {
+                       s.ContainerBackupDelete(backup.Name())
+                       return fmt.Errorf("failed to rsync: %s: %s", 
string(output), err)
+               }
+               return nil
+       }
+
+       ourStart, err := sourceContainer.StorageStart()
+       if err != nil {
+               return err
+       }
+       if ourStart {
+               defer sourceContainer.StorageStop()
+       }
+
+       _, sourcePool, _ := sourceContainer.Storage().GetContainerPoolInfo()
+       sourceContainerMntPoint := getContainerMountPoint(sourcePool,
+               sourceContainer.Name())
+       bwlimit := s.pool.Config["rsync.bwlimit"]
+       err = rsync(sourceContainerMntPoint, targetBackupContainerMntPoint, 
bwlimit)
+       if err != nil {
+               return err
+       }
+
+       if sourceContainer.IsRunning() {
+               // This is done to ensure consistency when snapshotting. But we
+               // probably shouldn't fail just because of that.
+               logger.Debugf("Trying to freeze and rsync again to ensure 
consistency.")
+
+               err := sourceContainer.Freeze()
+               if err != nil {
+                       logger.Errorf("Trying to freeze and rsync again 
failed.")
+                       goto onSuccess
+               }
+               defer sourceContainer.Unfreeze()
+
+               err = rsync(sourceContainerMntPoint, 
targetBackupContainerMntPoint, bwlimit)
+               if err != nil {
+                       return err
+               }
+       }
+
+       if !backup.ContainerOnly() {
+               // Backup snapshots as well.
+               snaps, err := sourceContainer.Snapshots()
+               if err != nil {
+                       return nil
+               }
+
+               for _, ct := range snaps {
+                       snapshotMntPoint := getSnapshotMountPoint(sourcePool,
+                               ct.Name())
+                       err = rsync(snapshotMntPoint, 
targetBackupSnapshotsMntPoint, bwlimit)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+onSuccess:
+       // Check if the symlink
+       // ${LXD_DIR}/backups/<backup_name> -> 
${POOL_PATH}/backups/<backup_name>
+       // exists and if not create it.
+       backupSymlink := shared.VarPath("backups", sourceContainer.Name())
+       backupSymlinkTarget := getBackupMountPoint(sourcePool, 
sourceContainer.Name())
+       if !shared.PathExists(backupSymlink) {
+               err = os.Symlink(backupSymlinkTarget, backupSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
+       logger.Debugf("Created DIR storage volume for backup \"%s\" on storage 
pool \"%s\".",
+               backup.Name(), s.pool.Name)
        return nil
 }
 
 func (s *storageDir) ContainerBackupDelete(name string) error {
+       logger.Debugf("Deleting DIR storage volume for backup \"%s\" on storage 
pool \"%s\".",
+               name, s.pool.Name)
+
+       _, err := s.StoragePoolMount()
+       if err != nil {
+               return err
+       }
+
+       source := s.pool.Config["source"]
+       if source == "" {
+               return fmt.Errorf("no \"source\" property found for the storage 
pool")
+       }
+
+       err = dirBackupDeleteInternal(s.pool.Name, name)
+       if err != nil {
+               return err
+       }
+
+       logger.Debugf("Deleted DIR storage volume for backup \"%s\" on storage 
pool \"%s\".",
+               name, s.pool.Name)
+       return nil
+}
+
+func dirBackupDeleteInternal(poolName string, backupName string) error {
+       backupContainerMntPoint := getBackupMountPoint(poolName, backupName)
+       if shared.PathExists(backupContainerMntPoint) {
+               err := os.RemoveAll(backupContainerMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       sourceContainerName, _, _ := 
containerGetParentAndSnapshotName(backupName)
+       backupContainerPath := getBackupMountPoint(poolName, 
sourceContainerName)
+       empty, _ := shared.PathIsEmpty(backupContainerPath)
+       if empty == true {
+               err := os.Remove(backupContainerPath)
+               if err != nil {
+                       return err
+               }
+
+               backupSymlink := shared.VarPath("backups", sourceContainerName)
+               if shared.PathExists(backupSymlink) {
+                       err := os.Remove(backupSymlink)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
        return nil
 }
 
 func (s *storageDir) ContainerBackupRename(backup backup, newName string) 
error {
+       logger.Debugf("Renaming DIR storage volume for backup \"%s\" from %s -> 
%s.",
+               backup.Name(), backup.Name(), newName)
+
+       _, err := s.StoragePoolMount()
+       if err != nil {
+               return err
+       }
+
+       source := s.pool.Config["source"]
+       if source == "" {
+               return fmt.Errorf("no \"source\" property found for the storage 
pool")
+       }
+
+       oldBackupMntPoint := getBackupMountPoint(s.pool.Name, backup.Name())
+       newBackupMntPoint := getBackupMountPoint(s.pool.Name, newName)
+
+       // Rename directory
+       if shared.PathExists(oldBackupMntPoint) {
+               err := os.Rename(oldBackupMntPoint, newBackupMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       logger.Debugf("Renamed DIR storage volume for backup \"%s\" from %s -> 
%s.",
+               backup.Name(), backup.Name(), newName)
        return nil
 }
 

From d210ea2ed33acf909cbb398e4107d5063454746b Mon Sep 17 00:00:00 2001
From: Thomas Hipp <thomas.h...@canonical.com>
Date: Mon, 16 Apr 2018 18:11:44 +0200
Subject: [PATCH 4/4] lxd: Implement backups for LVM storage

Signed-off-by: Thomas Hipp <thomas.h...@canonical.com>
---
 lxd/storage_lvm.go           | 268 +++++++++++++++++++++++++++++++++++++++++++
 lxd/storage_lvm_utils.go     |  32 ++++++
 lxd/storage_volumes_utils.go |   1 +
 3 files changed, 301 insertions(+)

diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
index 214ee3bc8..4748cf1a0 100644
--- a/lxd/storage_lvm.go
+++ b/lxd/storage_lvm.go
@@ -1068,6 +1068,67 @@ func (s *storageLvm) ContainerDelete(container 
container) error {
                return err
        }
 
+       if container.IsSnapshot() {
+               // Snapshots will return a empty list when calling Backups(). 
We need to
+               // find the correct backup by iterating over the container's 
backups.
+               ctName, snapshotName, _ := 
containerGetParentAndSnapshotName(container.Name())
+               ct, err := containerLoadByName(s.s, ctName)
+               if err != nil {
+                       return err
+               }
+
+               backups, err := ct.Backups()
+               if err != nil {
+                       return err
+               }
+
+               for _, backup := range backups {
+                       if backup.ContainerOnly() {
+                               // Skip container-only backups since they don't 
include
+                               // snapshots
+                               continue
+                       }
+
+                       parts := strings.Split(backup.Name(), "/")
+                       err := s.ContainerBackupDelete(fmt.Sprintf("%s/%s/%s", 
ctName,
+                               snapshotName, parts[1]))
+                       if err != nil {
+                               return err
+                       }
+               }
+       } else {
+               backups, err := container.Backups()
+               if err != nil {
+                       return err
+               }
+
+               for _, backup := range backups {
+                       err := s.ContainerBackupDelete(backup.Name())
+                       if err != nil {
+                               return err
+                       }
+                       continue
+
+                       if !backup.ContainerOnly() {
+                               // Remove the snapshots
+                               snapshots, err := container.Snapshots()
+                               if err != nil {
+                                       return err
+                               }
+
+                               for _, snap := range snapshots {
+                                       ctName, snapshotName, _ := 
containerGetParentAndSnapshotName(snap.Name())
+                                       parts := strings.Split(backup.Name(), 
"/")
+                                       err := 
s.ContainerBackupDelete(fmt.Sprintf("%s/%s/%s", ctName,
+                                               snapshotName, parts[1]))
+                                       if err != nil {
+                                               return err
+                                       }
+                               }
+                       }
+               }
+       }
+
        logger.Debugf("Deleted LVM storage volume for container \"%s\" on 
storage pool \"%s\".", s.volume.Name, s.pool.Name)
        return nil
 }
@@ -1308,6 +1369,43 @@ func (s *storageLvm) ContainerRename(container 
container, newContainerName strin
                }
        }
 
+       // Rename backups
+       if !container.IsSnapshot() {
+               oldBackupPath := getBackupMountPoint(s.pool.Name, oldName)
+               newBackupPath := getBackupMountPoint(s.pool.Name, 
newContainerName)
+               if shared.PathExists(oldBackupPath) {
+                       err = os.Rename(oldBackupPath, newBackupPath)
+                       if err != nil {
+                               return err
+                       }
+               }
+
+               oldBackupSymlink := shared.VarPath("backups", oldName)
+               newBackupSymlink := shared.VarPath("backups", newContainerName)
+               if shared.PathExists(oldBackupSymlink) {
+                       err := os.Remove(oldBackupSymlink)
+                       if err != nil {
+                               return err
+                       }
+
+                       err = os.Symlink(newBackupPath, newBackupSymlink)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       backups, err := container.Backups()
+       if err != nil {
+               return err
+       }
+
+       for _, backup := range backups {
+               backupName := strings.Split(backup.Name(), "/")[1]
+               newName := fmt.Sprintf("%s/%s", newContainerName, backupName)
+               s.ContainerBackupRename(backup, newName)
+       }
+
        tryUndo = false
 
        logger.Debugf("Renamed LVM storage volume for container \"%s\" from %s 
-> %s.", s.volume.Name, s.volume.Name, newContainerName)
@@ -1562,14 +1660,184 @@ func (s *storageLvm) 
ContainerSnapshotCreateEmpty(snapshotContainer container) e
 }
 
 func (s *storageLvm) ContainerBackupCreate(backup backup, sourceContainer 
container) error {
+       logger.Debugf("Creating LVM storage volume for backup \"%s\" on storage 
pool \"%s\".", s.volume.Name, s.pool.Name)
+
+       // Create the path for the backup.
+       baseMntPoint := getBackupMountPoint(s.pool.Name, backup.Name())
+       targetBackupContainerMntPoint := fmt.Sprintf("%s/container",
+               baseMntPoint)
+       targetBackupSnapshotsMntPoint := fmt.Sprintf("%s/snapshots",
+               baseMntPoint)
+
+       err := os.MkdirAll(targetBackupContainerMntPoint, 0711)
+       if err != nil {
+               return err
+       }
+
+       if !backup.ContainerOnly() {
+               // Create path for snapshots as well.
+               err = os.MkdirAll(targetBackupSnapshotsMntPoint, 0711)
+               if err != nil {
+                       return err
+               }
+       }
+
+       err = s.createContainerBackup(backup, sourceContainer, true)
+       if err != nil {
+               return err
+       }
+
+       if !backup.ContainerOnly() {
+               // Backup snapshots
+               snaps, err := sourceContainer.Snapshots()
+               if err != nil {
+                       return nil
+               }
+
+               for _, ct := range snaps {
+                       err = s.createContainerBackup(backup, ct, true)
+                       if err != nil {
+                               return err
+                       }
+
+                       // Create path to snapshot
+                       _, snapName, _ := 
containerGetParentAndSnapshotName(ct.Name())
+                       err = os.MkdirAll(fmt.Sprintf("%s/%s", 
targetBackupSnapshotsMntPoint,
+                               snapName), 0711)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       backupSymlink := shared.VarPath("backups", sourceContainer.Name())
+       backupSymlinkTarget := getBackupMountPoint(s.pool.Name, 
sourceContainer.Name())
+       if !shared.PathExists(backupSymlink) {
+               err = os.Symlink(backupSymlinkTarget, backupSymlink)
+               if err != nil {
+                       return err
+               }
+       }
+
+       logger.Debugf("Created LVM storage volume for backup \"%s\" on storage 
pool \"%s\".", s.volume.Name, s.pool.Name)
        return nil
 }
 
 func (s *storageLvm) ContainerBackupDelete(name string) error {
+       lvName := containerNameToLVName(name)
+       poolName := s.getOnDiskPoolName()
+
+       containerLvmDevPath := getLvmDevPath(poolName,
+               storagePoolVolumeAPIEndpointBackups, lvName)
+
+       lvExists, _ := storageLVExists(containerLvmDevPath)
+       if lvExists {
+               err := removeLV(poolName, storagePoolVolumeAPIEndpointBackups, 
lvName)
+               if err != nil {
+                       return err
+               }
+       }
+
+       lvmBackupDeleteInternal(poolName, name)
+
        return nil
 }
 
+func lvmBackupDeleteInternal(poolName string, backupName string) error {
+       backupContainerMntPoint := getBackupMountPoint(poolName, backupName)
+       if shared.PathExists(backupContainerMntPoint) {
+               err := os.RemoveAll(backupContainerMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       sourceContainerName, _, _ := 
containerGetParentAndSnapshotName(backupName)
+       backupContainerPath := getBackupMountPoint(poolName, 
sourceContainerName)
+       empty, _ := shared.PathIsEmpty(backupContainerPath)
+       if empty == true {
+               err := os.Remove(backupContainerPath)
+               if err != nil {
+                       return err
+               }
+
+               backupSymlink := shared.VarPath("backups", sourceContainerName)
+               if shared.PathExists(backupSymlink) {
+                       err := os.Remove(backupSymlink)
+                       if err != nil {
+                               return err
+                       }
+               }
+       }
+
+       return nil
+}
 func (s *storageLvm) ContainerBackupRename(backup backup, newName string) 
error {
+       logger.Debugf("Renaming LVM storage volume for backup \"%s\" from %s -> 
%s.",
+               s.volume.Name, s.volume.Name, newName)
+
+       tryUndo := true
+
+       oldName := backup.Name()
+       oldLvmName := containerNameToLVName(oldName)
+       newLvmName := containerNameToLVName(newName)
+
+       err := s.renameLVByPath(oldLvmName, newLvmName, 
storagePoolVolumeAPIEndpointBackups)
+       if err != nil {
+               return fmt.Errorf("Failed to rename a backup LV, oldName='%s', 
newName='%s', err='%s'",
+                       oldLvmName, newLvmName, err)
+       }
+       defer func() {
+               if tryUndo {
+                       s.renameLVByPath(newLvmName, oldLvmName, 
storagePoolVolumeAPIEndpointBackups)
+               }
+       }()
+
+       if !backup.ContainerOnly() {
+               // Rename snapshot backups if they exist
+               snaps, err := backup.container.Snapshots()
+               if err != nil {
+                       return err
+               }
+
+               for _, snap := range snaps {
+                       ctName, snapshotName, _ := 
containerGetParentAndSnapshotName(snap.Name())
+                       oldName := strings.Split(backup.Name(), "/")[1]
+                       newName := strings.Split(newName, "/")[1]
+                       oldLvmName := 
containerNameToLVName(fmt.Sprintf("%s/%s/%s", ctName,
+                               snapshotName, oldName))
+                       newLvmName := 
containerNameToLVName(fmt.Sprintf("%s/%s/%s", ctName,
+                               snapshotName, newName))
+
+                       exists, err := storageLVExists(oldLvmName)
+                       if err != nil {
+                               return err
+                       }
+
+                       if exists {
+                               err := s.renameLVByPath(oldLvmName, newLvmName,
+                                       storagePoolVolumeAPIEndpointBackups)
+                               if err != nil {
+                                       return err
+                               }
+                       }
+               }
+       }
+
+       oldBackupMntPoint := getBackupMountPoint(s.pool.Name, oldName)
+       newBackupMntPoint := getBackupMountPoint(s.pool.Name, newName)
+
+       if shared.PathExists(oldBackupMntPoint) {
+               err = os.Rename(oldBackupMntPoint, newBackupMntPoint)
+               if err != nil {
+                       return err
+               }
+       }
+
+       tryUndo = false
+
+       logger.Debugf("Renamed LVM storage volume for backup \"%s\" from %s -> 
%s.",
+               s.volume.Name, s.volume.Name, newName)
        return nil
 }
 
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
index 1bd244042..97e5929e3 100644
--- a/lxd/storage_lvm_utils.go
+++ b/lxd/storage_lvm_utils.go
@@ -280,6 +280,38 @@ func (s *storageLvm) 
createSnapshotContainer(snapshotContainer container, source
        return nil
 }
 
+func (s *storageLvm) createContainerBackup(backup backup, sourceContainer 
container,
+       readonly bool) error {
+       tryUndo := true
+
+       sourceContainerName := sourceContainer.Name()
+       sourceContainerLvmName := containerNameToLVName(sourceContainerName)
+
+       // Always add the backup name as suffix, e.g. container-backup0 or
+       // container-snap1-backup1.
+       names := strings.Split(backup.Name(), "/")
+       targetContainerLvmName := fmt.Sprintf("%s-%s", sourceContainerLvmName,
+               names[len(names)-1])
+
+       poolName := s.getOnDiskPoolName()
+       _, err := s.createSnapshotLV(poolName, sourceContainerLvmName,
+               storagePoolVolumeAPIEndpointContainers, targetContainerLvmName,
+               storagePoolVolumeAPIEndpointBackups, readonly, s.useThinpool)
+       if err != nil {
+               return fmt.Errorf("Error creating snapshot LV: %s", err)
+       }
+
+       defer func() {
+               if tryUndo {
+                       s.ContainerBackupDelete(backup.Name())
+               }
+       }()
+
+       tryUndo = false
+
+       return nil
+}
+
 // Copy a container on a storage pool that does use a thinpool.
 func (s *storageLvm) copyContainerThinpool(target container, source container, 
readonly bool) error {
        err := s.createSnapshotContainer(target, source, readonly)
diff --git a/lxd/storage_volumes_utils.go b/lxd/storage_volumes_utils.go
index d7e4a9ef6..861dc5442 100644
--- a/lxd/storage_volumes_utils.go
+++ b/lxd/storage_volumes_utils.go
@@ -33,6 +33,7 @@ const (
        storagePoolVolumeAPIEndpointContainers string = "containers"
        storagePoolVolumeAPIEndpointImages     string = "images"
        storagePoolVolumeAPIEndpointCustom     string = "custom"
+       storagePoolVolumeAPIEndpointBackups    string = "backups"
 )
 
 var supportedVolumeTypes = []int{storagePoolVolumeTypeContainer, 
storagePoolVolumeTypeImage, storagePoolVolumeTypeCustom}
_______________________________________________
lxc-devel mailing list
lxc-devel@lists.linuxcontainers.org
http://lists.linuxcontainers.org/listinfo/lxc-devel

Reply via email to