[lxc-devel] [lxd/master] Storage: Updates internalImport to use new storage framework
tomponline on Github
lxc-bot at linuxcontainers.org
Fri Feb 21 13:59:39 UTC 2020
A non-text attachment was scrubbed...
Name: not available
Type: text/x-mailbox
Size: 434 bytes
Desc: not available
URL: <http://lists.linuxcontainers.org/pipermail/lxc-devel/attachments/20200221/724e13e8/attachment-0001.bin>
-------------- next part --------------
From 768499bc8a0cdcc3bc022c7e7b488c2aa9df5ec7 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 11:05:14 +0000
Subject: [PATCH 01/17] lxd/api/internal: Removes duplicate storage package
import
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 6310fbf307..8e6cab8f35 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -712,7 +712,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
switch backup.Pool.Driver {
case "btrfs":
snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backup.Pool.Name, snap.Name)
- if !shared.PathExists(snpMntPt) || !isBtrfsSubVolume(snpMntPt) {
+ if !shared.PathExists(snpMntPt) || !btrfsIsSubVolume(snpMntPt) {
if req.Force {
continue
}
From 6781e79abb35beebb9050db5a91de2c2907de0e9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 5 Dec 2019 15:56:33 -0500
Subject: [PATCH 02/17] lxd/storage: Remove legacy dir implementation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/patches.go | 29 +-
lxd/patches_utils.go | 39 +
lxd/storage.go | 27 +-
lxd/storage_dir.go | 1589 --------------------------------------
lxd/storage_migration.go | 155 ++--
5 files changed, 95 insertions(+), 1744 deletions(-)
create mode 100644 lxd/patches_utils.go
delete mode 100644 lxd/storage_dir.go
diff --git a/lxd/patches.go b/lxd/patches.go
index dbb5050316..d2400b1763 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -329,25 +329,20 @@ func patchStorageApi(name string, d *Daemon) error {
lvmVgName := daemonConfig["storage.lvm_vg_name"]
zfsPoolName := daemonConfig["storage.zfs_pool_name"]
defaultPoolName := "default"
- preStorageApiStorageType := storageTypeDir
+ preStorageApiStorageType := "dir"
if lvmVgName != "" {
- preStorageApiStorageType = storageTypeLvm
+ preStorageApiStorageType = "lvm"
defaultPoolName = lvmVgName
} else if zfsPoolName != "" {
- preStorageApiStorageType = storageTypeZfs
+ preStorageApiStorageType = "zfs"
defaultPoolName = zfsPoolName
} else if d.os.BackingFS == "btrfs" {
- preStorageApiStorageType = storageTypeBtrfs
+ preStorageApiStorageType = "btrfs"
} else {
// Dir storage pool.
}
- defaultStorageTypeName, err := storageTypeToString(preStorageApiStorageType)
- if err != nil {
- return err
- }
-
// In case we detect that an lvm name or a zfs name exists it makes
// sense to create a storage pool in the database, independent of
// whether anything currently exists on that pool. We can still probably
@@ -392,13 +387,13 @@ func patchStorageApi(name string, d *Daemon) error {
// If any of these are actually called, there's no way back.
poolName := defaultPoolName
switch preStorageApiStorageType {
- case storageTypeBtrfs:
- err = upgradeFromStorageTypeBtrfs(name, d, defaultPoolName, defaultStorageTypeName, cRegular, cSnapshots, imgPublic, imgPrivate)
- case storageTypeDir:
- err = upgradeFromStorageTypeDir(name, d, defaultPoolName, defaultStorageTypeName, cRegular, cSnapshots, imgPublic, imgPrivate)
- case storageTypeLvm:
- err = upgradeFromStorageTypeLvm(name, d, defaultPoolName, defaultStorageTypeName, cRegular, cSnapshots, imgPublic, imgPrivate)
- case storageTypeZfs:
+ case "btrfs":
+ err = upgradeFromStorageTypeBtrfs(name, d, defaultPoolName, preStorageApiStorageType, cRegular, cSnapshots, imgPublic, imgPrivate)
+ case "dir":
+ err = upgradeFromStorageTypeDir(name, d, defaultPoolName, preStorageApiStorageType, cRegular, cSnapshots, imgPublic, imgPrivate)
+ case "lvm":
+ err = upgradeFromStorageTypeLvm(name, d, defaultPoolName, preStorageApiStorageType, cRegular, cSnapshots, imgPublic, imgPrivate)
+ case "zfs":
// The user is using a zfs dataset. This case needs to be
// handled with care:
@@ -410,7 +405,7 @@ func patchStorageApi(name string, d *Daemon) error {
if strings.Contains(defaultPoolName, "/") {
poolName = "default"
}
- err = upgradeFromStorageTypeZfs(name, d, defaultPoolName, defaultStorageTypeName, cRegular, []string{}, imgPublic, imgPrivate)
+ err = upgradeFromStorageTypeZfs(name, d, defaultPoolName, preStorageApiStorageType, cRegular, []string{}, imgPublic, imgPrivate)
default: // Shouldn't happen.
return fmt.Errorf("Invalid storage type. Upgrading not possible")
}
diff --git a/lxd/patches_utils.go b/lxd/patches_utils.go
new file mode 100644
index 0000000000..01429d1a59
--- /dev/null
+++ b/lxd/patches_utils.go
@@ -0,0 +1,39 @@
+package main
+
+import (
+ "os"
+
+ "github.com/lxc/lxd/lxd/project"
+ driver "github.com/lxc/lxd/lxd/storage"
+ "github.com/lxc/lxd/shared"
+)
+
+func dirSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
+ snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
+ if shared.PathExists(snapshotContainerMntPoint) {
+ err := os.RemoveAll(snapshotContainerMntPoint)
+ if err != nil {
+ return err
+ }
+ }
+
+ sourceContainerName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
+ snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
+ empty, _ := shared.PathIsEmpty(snapshotContainerPath)
+ if empty == true {
+ err := os.Remove(snapshotContainerPath)
+ if err != nil {
+ return err
+ }
+
+ snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
+ if shared.PathExists(snapshotSymlink) {
+ err := os.Remove(snapshotSymlink)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ return nil
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index 71d5b02a26..d78220e4cd 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -102,7 +102,6 @@ type storageType int
const (
storageTypeBtrfs storageType = iota
storageTypeCeph
- storageTypeDir
storageTypeLvm
storageTypeMock
storageTypeZfs
@@ -116,8 +115,6 @@ func storageTypeToString(sType storageType) (string, error) {
return "btrfs", nil
case storageTypeCeph:
return "ceph", nil
- case storageTypeDir:
- return "dir", nil
case storageTypeLvm:
return "lvm", nil
case storageTypeMock:
@@ -135,8 +132,6 @@ func storageStringToType(sName string) (storageType, error) {
return storageTypeBtrfs, nil
case "ceph":
return storageTypeCeph, nil
- case "dir":
- return storageTypeDir, nil
case "lvm":
return storageTypeLvm, nil
case "mock":
@@ -268,13 +263,6 @@ func storageCoreInit(driver string) (storage, error) {
return nil, err
}
return &btrfs, nil
- case storageTypeDir:
- dir := storageDir{}
- err = dir.StorageCoreInit()
- if err != nil {
- return nil, err
- }
- return &dir, nil
case storageTypeCeph:
ceph := storageCeph{}
err = ceph.StorageCoreInit()
@@ -324,9 +312,8 @@ func storageInit(s *state.State, project, poolName, volumeName string, volumeTyp
// Load the storage volume.
volume := &api.StorageVolume{}
- volumeID := int64(-1)
if volumeName != "" {
- volumeID, volume, err = s.Cluster.StoragePoolNodeVolumeGetTypeByProject(project, volumeName, volumeType, poolID)
+ _, volume, err = s.Cluster.StoragePoolNodeVolumeGetTypeByProject(project, volumeName, volumeType, poolID)
if err != nil {
return nil, err
}
@@ -349,18 +336,6 @@ func storageInit(s *state.State, project, poolName, volumeName string, volumeTyp
return nil, err
}
return &btrfs, nil
- case storageTypeDir:
- dir := storageDir{}
- dir.poolID = poolID
- dir.pool = pool
- dir.volume = volume
- dir.volumeID = volumeID
- dir.s = s
- err = dir.StoragePoolInit()
- if err != nil {
- return nil, err
- }
- return &dir, nil
case storageTypeCeph:
ceph := storageCeph{}
ceph.poolID = poolID
diff --git a/lxd/storage_dir.go b/lxd/storage_dir.go
deleted file mode 100644
index 52e01e1814..0000000000
--- a/lxd/storage_dir.go
+++ /dev/null
@@ -1,1589 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strings"
-
- "github.com/gorilla/websocket"
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/lxc/lxd/lxd/backup"
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/lxd/rsync"
- driver "github.com/lxc/lxd/lxd/storage"
- "github.com/lxc/lxd/lxd/storage/quota"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/ioprogress"
- "github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/units"
-)
-
-type storageDir struct {
- storageShared
-
- volumeID int64
-}
-
-// Only initialize the minimal information we need about a given storage type.
-func (s *storageDir) StorageCoreInit() error {
- s.sType = storageTypeDir
- typeName, err := storageTypeToString(s.sType)
- if err != nil {
- return err
- }
- s.sTypeName = typeName
- s.sTypeVersion = "1"
-
- return nil
-}
-
-// Initialize a full storage interface.
-func (s *storageDir) StoragePoolInit() error {
- err := s.StorageCoreInit()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-// Initialize a full storage interface.
-func (s *storageDir) StoragePoolCheck() error {
- logger.Debugf("Checking DIR storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolCreate() error {
- logger.Infof("Creating DIR storage pool \"%s\"", s.pool.Name)
-
- s.pool.Config["volatile.initial_source"] = s.pool.Config["source"]
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- source := shared.HostPath(s.pool.Config["source"])
- if source == "" {
- source = filepath.Join(shared.VarPath("storage-pools"), s.pool.Name)
- s.pool.Config["source"] = source
- } else {
- cleanSource := filepath.Clean(source)
- lxdDir := shared.VarPath()
- if strings.HasPrefix(cleanSource, lxdDir) &&
- cleanSource != poolMntPoint {
- return fmt.Errorf(`DIR storage pool requests in LXD `+
- `directory "%s" are only valid under `+
- `"%s"\n(e.g. source=%s)`, shared.VarPath(),
- shared.VarPath("storage-pools"), poolMntPoint)
- }
- source = filepath.Clean(source)
- }
-
- revert := true
- if !shared.PathExists(source) {
- err := os.MkdirAll(source, 0711)
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- os.Remove(source)
- }()
- } else {
- empty, err := shared.PathIsEmpty(source)
- if err != nil {
- return err
- }
-
- if !empty {
- return fmt.Errorf("The provided directory is not empty")
- }
- }
-
- if !shared.PathExists(poolMntPoint) {
- err := os.MkdirAll(poolMntPoint, 0711)
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- os.Remove(poolMntPoint)
- }()
- }
-
- err := s.StoragePoolCheck()
- if err != nil {
- return err
- }
-
- _, err = s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- revert = false
-
- logger.Infof("Created DIR storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolDelete() error {
- logger.Infof("Deleting DIR storage pool \"%s\"", s.pool.Name)
-
- source := shared.HostPath(s.pool.Config["source"])
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- _, err := s.StoragePoolUmount()
- if err != nil {
- return err
- }
-
- if shared.PathExists(source) {
- err := os.RemoveAll(source)
- if err != nil {
- return err
- }
- }
-
- prefix := shared.VarPath("storage-pools")
- if !strings.HasPrefix(source, prefix) {
- storagePoolSymlink := driver.GetStoragePoolMountPoint(s.pool.Name)
- if !shared.PathExists(storagePoolSymlink) {
- return nil
- }
-
- err := os.Remove(storagePoolSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Infof("Deleted DIR storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolMount() (bool, error) {
- source := shared.HostPath(s.pool.Config["source"])
- if source == "" {
- return false, fmt.Errorf("no \"source\" property found for the storage pool")
- }
- cleanSource := filepath.Clean(source)
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- if cleanSource == poolMntPoint {
- return true, nil
- }
-
- logger.Debugf("Mounting DIR storage pool \"%s\"", s.pool.Name)
-
- poolMountLockID := getPoolMountLockID(s.pool.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage pool.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[poolMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, poolMountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
- defer removeLockFromMap()
-
- mountSource := cleanSource
- mountFlags := unix.MS_BIND
-
- if shared.IsMountPoint(poolMntPoint) {
- return false, nil
- }
-
- err := unix.Mount(mountSource, poolMntPoint, "", uintptr(mountFlags), "")
- if err != nil {
- logger.Errorf(`Failed to mount DIR storage pool "%s" onto "%s": %s`, mountSource, poolMntPoint, err)
- return false, err
- }
-
- logger.Debugf("Mounted DIR storage pool \"%s\"", s.pool.Name)
-
- return true, nil
-}
-
-func (s *storageDir) StoragePoolUmount() (bool, error) {
- source := s.pool.Config["source"]
- if source == "" {
- return false, fmt.Errorf("no \"source\" property found for the storage pool")
- }
- cleanSource := filepath.Clean(source)
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- if cleanSource == poolMntPoint {
- return true, nil
- }
-
- logger.Debugf("Unmounting DIR storage pool \"%s\"", s.pool.Name)
-
- poolUmountLockID := getPoolUmountLockID(s.pool.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage pool.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[poolUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, poolUmountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
-
- defer removeLockFromMap()
-
- if !shared.IsMountPoint(poolMntPoint) {
- return false, nil
- }
-
- err := unix.Unmount(poolMntPoint, unix.MNT_DETACH)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Unmounted DIR pool \"%s\"", s.pool.Name)
- return true, nil
-}
-
-func (s *storageDir) GetContainerPoolInfo() (int64, string, string) {
- return s.poolID, s.pool.Name, s.pool.Name
-}
-
-func (s *storageDir) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
- logger.Infof(`Updating DIR storage pool "%s"`, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- changeable := changeableStoragePoolProperties["dir"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolError(unchangeable, "dir")
- }
-
- // "rsync.bwlimit" requires no on-disk modifications.
-
- logger.Infof(`Updated DIR storage pool "%s"`, s.pool.Name)
- return nil
-}
-
-// Functions dealing with storage pools.
-func (s *storageDir) StoragePoolVolumeCreate() error {
- logger.Infof("Creating DIR storage volume \"%s\" on storage pool \"%s\"", s.volume.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")
- }
-
- isSnapshot := shared.IsSnapshot(s.volume.Name)
-
- var storageVolumePath string
-
- if isSnapshot {
- storageVolumePath = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- } else {
- storageVolumePath = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- err = os.MkdirAll(storageVolumePath, 0711)
- if err != nil {
- return err
- }
-
- err = s.initQuota(storageVolumePath, s.volumeID)
- if err != nil {
- return err
- }
-
- logger.Infof("Created DIR storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolVolumeDelete() error {
- logger.Infof("Deleting DIR storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- storageVolumePath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- if !shared.PathExists(storageVolumePath) {
- return nil
- }
-
- err := s.deleteQuota(storageVolumePath, s.volumeID)
- if err != nil {
- return err
- }
-
- err = os.RemoveAll(storageVolumePath)
- if err != nil {
- return err
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
- s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted DIR storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolVolumeMount() (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) StoragePoolVolumeUmount() (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, changedConfig []string) error {
- if writable.Restore == "" {
- logger.Infof(`Updating DIR storage volume "%s"`, s.volume.Name)
- }
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- if writable.Restore != "" {
- logger.Infof(`Restoring DIR storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
-
- sourcePath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name,
- fmt.Sprintf("%s/%s", s.volume.Name, writable.Restore))
- targetPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- // Restore using rsync
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourcePath, targetPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
-
- logger.Infof(`Restored DIR storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
- return nil
- }
-
- changeable := changeableStoragePoolVolumeProperties["dir"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolVolumeError(unchangeable, "dir")
- }
-
- if shared.StringInSlice("size", changedConfig) {
- if s.volume.Type != storagePoolVolumeTypeNameCustom {
- return updateStoragePoolVolumeError([]string{"size"}, "dir")
- }
-
- if s.volume.Config["size"] != writable.Config["size"] {
- size, err := units.ParseByteSizeString(writable.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
- }
-
- logger.Infof(`Updated DIR storage volume "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolVolumeRename(newName string) error {
- logger.Infof(`Renaming DIR storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- usedBy, err := storagePoolVolumeUsedByInstancesGet(s.s, "default", s.pool.Name, s.volume.Name)
- if err != nil {
- return err
- }
- if len(usedBy) > 0 {
- return fmt.Errorf(`DIR storage volume "%s" on storage pool "%s" is attached to containers`,
- s.volume.Name, s.pool.Name)
- }
-
- oldPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, newName)
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof(`Renamed DIR storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, newName,
- storagePoolVolumeTypeCustom, s.poolID)
-}
-
-func (s *storageDir) ContainerStorageReady(container instance.Instance) bool {
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- ok, _ := shared.PathIsEmpty(containerMntPoint)
- return !ok
-}
-
-func (s *storageDir) ContainerCreate(container instance.Instance) error {
- logger.Debugf("Creating empty DIR storage volume for container \"%s\" on storage pool \"%s\"", s.volume.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")
- }
-
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- err = driver.CreateContainerMountpoint(containerMntPoint, container.Path(), container.IsPrivileged())
- if err != nil {
- return err
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- deleteContainerMountpoint(containerMntPoint, container.Path(), s.GetStorageTypeName())
- }()
-
- err = s.initQuota(containerMntPoint, s.volumeID)
- if err != nil {
- return err
- }
-
- err = container.DeferTemplateApply("create")
- if err != nil {
- return errors.Wrap(err, "Apply template")
- }
-
- revert = false
-
- logger.Debugf("Created empty DIR storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) ContainerCreateFromImage(container instance.Instance, imageFingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating DIR storage volume for container \"%s\" on storage pool \"%s\"", s.volume.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")
- }
-
- privileged := container.IsPrivileged()
- containerName := container.Name()
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
- err = driver.CreateContainerMountpoint(containerMntPoint, container.Path(), privileged)
- if err != nil {
- return errors.Wrap(err, "Create container mount point")
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.ContainerDelete(container)
- }()
-
- err = s.initQuota(containerMntPoint, s.volumeID)
- if err != nil {
- return err
- }
-
- imagePath := shared.VarPath("images", imageFingerprint)
- err = driver.ImageUnpack(imagePath, containerMntPoint, "", false, s.s.OS.RunningInUserNS, nil)
- if err != nil {
- return errors.Wrap(err, "Unpack image")
- }
-
- err = container.DeferTemplateApply("create")
- if err != nil {
- return errors.Wrap(err, "Apply template")
- }
-
- revert = false
-
- logger.Debugf("Created DIR storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) ContainerDelete(container instance.Instance) error {
- logger.Debugf("Deleting DIR storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Delete the container on its storage pool:
- // ${POOL}/containers/<container_name>
- containerName := container.Name()
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
-
- err = s.deleteQuota(containerMntPoint, s.volumeID)
- if err != nil {
- return err
- }
-
- if shared.PathExists(containerMntPoint) {
- err := os.RemoveAll(containerMntPoint)
- if err != nil {
- // RemovaAll fails on very long paths, so attempt an rm -Rf
- _, err := shared.RunCommand("rm", "-Rf", containerMntPoint)
- if err != nil {
- return fmt.Errorf("error removing %s: %s", containerMntPoint, err)
- }
- }
- }
-
- err = deleteContainerMountpoint(containerMntPoint, container.Path(), s.GetStorageTypeName())
- if err != nil {
- return err
- }
-
- // Delete potential leftover snapshot mountpoints.
- snapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- if shared.PathExists(snapshotMntPoint) {
- err := os.RemoveAll(snapshotMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Delete potential leftover snapshot symlinks:
- // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), container.Name()))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- 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
-}
-
-func (s *storageDir) copyContainer(target instance.Instance, source instance.Instance) error {
- if source.Type() != instancetype.Container {
- return fmt.Errorf("Source Instance type must be container")
- }
-
- if target.Type() != instancetype.Container {
- return fmt.Errorf("Target Instance type must be container")
- }
-
- srcCt := source.(*containerLXC)
- targetCt := target.(*containerLXC)
-
- _, sourcePool, _ := srcCt.Storage().GetContainerPoolInfo()
- _, targetPool, _ := targetCt.Storage().GetContainerPoolInfo()
- sourceContainerMntPoint := driver.GetContainerMountPoint(source.Project(), sourcePool, source.Name())
- if source.IsSnapshot() {
- sourceContainerMntPoint = driver.GetSnapshotMountPoint(source.Project(), sourcePool, source.Name())
- }
- targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
-
- err := driver.CreateContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
- if err != nil {
- return err
- }
-
- err = s.initQuota(targetContainerMntPoint, s.volumeID)
- if err != nil {
- return err
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
-
- err = target.DeferTemplateApply("copy")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) copySnapshot(target instance.Instance, targetPool string, source instance.Instance, sourcePool string) error {
- sourceName := source.Name()
- targetName := target.Name()
- sourceContainerMntPoint := driver.GetSnapshotMountPoint(source.Project(), sourcePool, sourceName)
- targetContainerMntPoint := driver.GetSnapshotMountPoint(target.Project(), targetPool, targetName)
-
- targetParentName, _, _ := shared.InstanceGetParentAndSnapshotName(target.Name())
- containersPath := driver.GetSnapshotMountPoint(target.Project(), targetPool, targetParentName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", targetPool, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
- err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
-
- return nil
-}
-
-func (s *storageDir) ContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool) error {
- logger.Debugf("Copying DIR container storage %s to %s", source.Name(), target.Name())
-
- err := s.doContainerCopy(target, source, containerOnly, false, nil)
- if err != nil {
- return err
- }
-
- logger.Debugf("Copied DIR container storage %s to %s", source.Name(), target.Name())
- return nil
-}
-
-func (s *storageDir) doContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool, refresh bool, refreshSnapshots []instance.Instance) error {
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
- targetPool, err := target.StoragePool()
- if err != nil {
- return err
- }
-
- srcState := s.s
- if sourcePool != targetPool {
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, source.Name(), storagePoolVolumeTypeContainer)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
- srcState = srcStorage.GetState()
- }
-
- err = s.copyContainer(target, source)
- if err != nil {
- return err
- }
-
- if containerOnly {
- return nil
- }
-
- var snapshots []instance.Instance
-
- if refresh {
- snapshots = refreshSnapshots
- } else {
- snapshots, err = source.Snapshots()
- if err != nil {
- return err
- }
- }
-
- if len(snapshots) == 0 {
- return nil
- }
-
- for _, snap := range snapshots {
- sourceSnapshot, err := instance.LoadByProjectAndName(srcState, source.Project(), snap.Name())
- if err != nil {
- return err
- }
-
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- newSnapName := fmt.Sprintf("%s/%s", target.Name(), snapOnlyName)
- targetSnapshot, err := instance.LoadByProjectAndName(s.s, source.Project(), newSnapName)
- if err != nil {
- return err
- }
-
- err = s.copySnapshot(targetSnapshot, targetPool, sourceSnapshot, sourcePool)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageDir) ContainerRefresh(target instance.Instance, source instance.Instance, snapshots []instance.Instance) error {
- logger.Debugf("Refreshing DIR container storage for %s from %s", target.Name(), source.Name())
-
- err := s.doContainerCopy(target, source, len(snapshots) == 0, true, snapshots)
- if err != nil {
- return err
- }
-
- logger.Debugf("Refreshed DIR container storage for %s from %s", target.Name(), source.Name())
- return nil
-}
-
-func (s *storageDir) ContainerMount(c instance.Instance) (bool, error) {
- return s.StoragePoolMount()
-}
-
-func (s *storageDir) ContainerUmount(c instance.Instance, path string) (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) ContainerRename(container instance.Instance, newName string) error {
- logger.Debugf("Renaming DIR storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.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")
- }
-
- oldContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- oldContainerSymlink := shared.VarPath("containers", project.Prefix(container.Project(), container.Name()))
- newContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, newName)
- newContainerSymlink := shared.VarPath("containers", project.Prefix(container.Project(), newName))
- err = renameContainerMountpoint(oldContainerMntPoint, oldContainerSymlink, newContainerMntPoint, newContainerSymlink)
- if err != nil {
- return err
- }
-
- // Rename the snapshot mountpoint for the container if existing:
- // ${POOL}/snapshots/<old_container_name> to ${POOL}/snapshots/<new_container_name>
- oldSnapshotsMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- newSnapshotsMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, newName)
- if shared.PathExists(oldSnapshotsMntPoint) {
- err = os.Rename(oldSnapshotsMntPoint, newSnapshotsMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Remove the old snapshot symlink:
- // ${LXD_DIR}/snapshots/<old_container_name>
- oldSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), container.Name()))
- newSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), newName))
- if shared.PathExists(oldSnapshotSymlink) {
- err := os.Remove(oldSnapshotSymlink)
- if err != nil {
- return err
- }
-
- // Create the new snapshot symlink:
- // ${LXD_DIR}/snapshots/<new_container_name> to ${POOL}/snapshots/<new_container_name>
- err = os.Symlink(newSnapshotsMntPoint, newSnapshotSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Renamed DIR storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-func (s *storageDir) ContainerRestore(container instance.Instance, sourceContainer instance.Instance) error {
- logger.Debugf("Restoring DIR storage volume for container \"%s\" from %s to %s", s.volume.Name, sourceContainer.Name(), container.Name())
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- targetPath := container.Path()
- sourcePath := sourceContainer.Path()
-
- // Restore using rsync
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourcePath, targetPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
-
- logger.Debugf("Restored DIR storage volume for container \"%s\" from %s to %s", s.volume.Name, sourceContainer.Name(), container.Name())
- return nil
-}
-
-func (s *storageDir) ContainerGetUsage(c instance.Instance) (int64, error) {
- path := driver.GetContainerMountPoint(c.Project(), s.pool.Name, c.Name())
-
- ok, err := quota.Supported(path)
- if err != nil || !ok {
- return -1, fmt.Errorf("The backing filesystem doesn't support quotas")
- }
-
- projectID := uint32(s.volumeID + 10000)
- size, err := quota.GetProjectUsage(path, projectID)
- if err != nil {
- return -1, err
- }
-
- return size, nil
-}
-
-func (s *storageDir) ContainerSnapshotCreate(snapshotContainer instance.Instance, sourceContainer instance.Instance) error {
- logger.Debugf("Creating DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Create the path for the snapshot.
- targetContainerName := snapshotContainer.Name()
- targetContainerMntPoint := driver.GetSnapshotMountPoint(sourceContainer.Project(), s.pool.Name, targetContainerName)
- err = os.MkdirAll(targetContainerMntPoint, 0711)
- if err != nil {
- return err
- }
-
- rsync := func(snapshotContainer instance.Instance, oldPath string, newPath string, bwlimit string) error {
- output, err := rsync.LocalCopy(oldPath, newPath, bwlimit, true)
- if err != nil {
- s.ContainerDelete(snapshotContainer)
- 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()
- }
-
- if sourceContainer.Type() != instancetype.Container {
- return fmt.Errorf("Source Instance type must be container")
- }
-
- srcCt := sourceContainer.(*containerLXC)
-
- _, sourcePool, _ := srcCt.Storage().GetContainerPoolInfo()
- sourceContainerName := sourceContainer.Name()
- sourceContainerMntPoint := driver.GetContainerMountPoint(sourceContainer.Project(), sourcePool, sourceContainerName)
- bwlimit := s.pool.Config["rsync.bwlimit"]
- err = rsync(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint, 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(snapshotContainer, sourceContainerMntPoint, targetContainerMntPoint, bwlimit)
- if err != nil {
- return err
- }
- }
-
-onSuccess:
- // Check if the symlink
- // ${LXD_DIR}/snapshots/<source_container_name> to ${POOL_PATH}/snapshots/<source_container_name>
- // exists and if not create it.
- sourceContainerSymlink := shared.VarPath("snapshots", project.Prefix(sourceContainer.Project(), sourceContainerName))
- sourceContainerSymlinkTarget := driver.GetSnapshotMountPoint(sourceContainer.Project(), sourcePool, sourceContainerName)
- if !shared.PathExists(sourceContainerSymlink) {
- err = os.Symlink(sourceContainerSymlinkTarget, sourceContainerSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Created DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) ContainerSnapshotCreateEmpty(snapshotContainer instance.Instance) error {
- logger.Debugf("Creating empty DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Create the path for the snapshot.
- targetContainerName := snapshotContainer.Name()
- targetContainerMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, targetContainerName)
- err = os.MkdirAll(targetContainerMntPoint, 0711)
- if err != nil {
- return err
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.ContainerSnapshotDelete(snapshotContainer)
- }()
-
- // Check if the symlink
- // ${LXD_DIR}/snapshots/<source_container_name> to ${POOL_PATH}/snapshots/<source_container_name>
- // exists and if not create it.
- targetContainerMntPoint = driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name,
- targetContainerName)
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(targetContainerName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools",
- s.pool.Name, "containers-snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
- err = driver.CreateSnapshotMountpoint(targetContainerMntPoint,
- snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- revert = false
-
- logger.Debugf("Created empty DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func dirSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
- snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
- if shared.PathExists(snapshotContainerMntPoint) {
- err := os.RemoveAll(snapshotContainerMntPoint)
- if err != nil {
- return err
- }
- }
-
- sourceContainerName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
- snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
- empty, _ := shared.PathIsEmpty(snapshotContainerPath)
- if empty == true {
- err := os.Remove(snapshotContainerPath)
- if err != nil {
- return err
- }
-
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
-func (s *storageDir) ContainerSnapshotDelete(snapshotContainer instance.Instance) error {
- logger.Debugf("Deleting DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.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")
- }
-
- snapshotContainerName := snapshotContainer.Name()
- err = dirSnapshotDeleteInternal(snapshotContainer.Project(), s.pool.Name, snapshotContainerName)
- if err != nil {
- return err
- }
-
- logger.Debugf("Deleted DIR storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) ContainerSnapshotRename(snapshotContainer instance.Instance, newName string) error {
- logger.Debugf("Renaming DIR storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Rename the mountpoint for the snapshot:
- // ${POOL}/snapshots/<old_snapshot_name> to ${POOL}/snapshots/<new_snapshot_name>
- oldSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, snapshotContainer.Name())
- newSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, newName)
- err = os.Rename(oldSnapshotMntPoint, newSnapshotMntPoint)
- if err != nil {
- return err
- }
-
- logger.Debugf("Renamed DIR storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-func (s *storageDir) ContainerSnapshotStart(container instance.Instance) (bool, error) {
- return s.StoragePoolMount()
-}
-
-func (s *storageDir) ContainerSnapshotStop(container instance.Instance) (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) ContainerBackupCreate(path string, backup backup.Backup, source instance.Instance) error {
- // Prepare for rsync
- rsync := func(oldPath string, newPath string, bwlimit string) error {
- output, err := rsync.LocalCopy(oldPath, newPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync: %s: %s", string(output), err)
- }
-
- return nil
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- // Handle snapshots
- if !backup.InstanceOnly() {
- snapshotsPath := fmt.Sprintf("%s/snapshots", path)
-
- // Retrieve the snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- // Create the snapshot path
- if len(snapshots) > 0 {
- err = os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return err
- }
- }
-
- for _, snap := range snapshots {
- _, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- snapshotMntPoint := driver.GetSnapshotMountPoint(snap.Project(), s.pool.Name, snap.Name())
- target := fmt.Sprintf("%s/%s", snapshotsPath, snapName)
-
- // Copy the snapshot
- err = rsync(snapshotMntPoint, target, bwlimit)
- if err != nil {
- return err
- }
- }
- }
-
- if source.IsRunning() {
- // This is done to ensure consistency when snapshotting. But we
- // probably shouldn't fail just because of that.
- logger.Debugf("Freezing container '%s' for backup", source.Name())
-
- err := source.Freeze()
- if err != nil {
- logger.Errorf("Failed to freeze container '%s' for backup: %v", source.Name(), err)
- }
- defer source.Unfreeze()
- }
-
- // Copy the container
- containerPath := fmt.Sprintf("%s/container", path)
-
- return rsync(source.Path(), containerPath, bwlimit)
-}
-
-func (s *storageDir) ContainerBackupLoad(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- _, 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")
- }
-
- // Create mountpoints
- containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, info.Name)
- err = driver.CreateContainerMountpoint(containerMntPoint, driver.InstancePath(instancetype.Container, info.Project, info.Name, false), info.Privileged)
- if err != nil {
- return errors.Wrap(err, "Create container mount point")
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=2",
- "--xattrs-include=*",
- "-C", containerMntPoint, "backup/container",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- return err
- }
-
- if len(info.Snapshots) > 0 {
- // Create mountpoints
- snapshotMntPoint := driver.GetSnapshotMountPoint(info.Project, s.pool.Name, info.Name)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name,
- "containers-snapshots", project.Prefix(info.Project, info.Name))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, info.Name))
- err := driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget,
- snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=2",
- "--xattrs-include=*",
- "-C", snapshotMntPoint, "backup/snapshots",
- }...)
-
- // Extract snapshots
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageDir) ImageCreate(fingerprint string, tracker *ioprogress.ProgressTracker) error {
- return nil
-}
-
-func (s *storageDir) ImageDelete(fingerprint string) error {
- err := s.deleteImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) ImageMount(fingerprint string) (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) ImageUmount(fingerprint string) (bool, error) {
- return true, nil
-}
-
-func (s *storageDir) MigrationType() migration.MigrationFSType {
- return migration.MigrationFSType_RSYNC
-}
-
-func (s *storageDir) PreservesInodes() bool {
- return false
-}
-
-func (s *storageDir) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncMigrationSource(args)
-}
-
-func (s *storageDir) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncMigrationSink(conn, op, args)
-}
-
-func (s *storageDir) StorageEntitySetQuota(volumeType int, size int64, data interface{}) error {
- var path string
- switch volumeType {
- case storagePoolVolumeTypeContainer:
- c := data.(instance.Instance)
- path = driver.GetContainerMountPoint(c.Project(), s.pool.Name, c.Name())
- case storagePoolVolumeTypeCustom:
- path = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- ok, err := quota.Supported(path)
- if err != nil || !ok {
- logger.Warnf("Skipping setting disk quota for '%s' as the underlying filesystem doesn't support them", s.volume.Name)
- return nil
- }
-
- projectID := uint32(s.volumeID + 10000)
- err = quota.SetProjectQuota(path, projectID, size)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) initQuota(path string, id int64) error {
- if s.volumeID == 0 {
- return fmt.Errorf("Missing volume ID")
- }
-
- ok, err := quota.Supported(path)
- if err != nil || !ok {
- return nil
- }
-
- projectID := uint32(s.volumeID + 10000)
- err = quota.SetProject(path, projectID)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) deleteQuota(path string, id int64) error {
- if s.volumeID == 0 {
- return fmt.Errorf("Missing volume ID")
- }
-
- ok, err := quota.Supported(path)
- if err != nil || !ok {
- return nil
- }
-
- err = quota.SetProject(path, 0)
- if err != nil {
- return err
- }
-
- projectID := uint32(s.volumeID + 10000)
- err = quota.SetProjectQuota(path, projectID, 0)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) StoragePoolResources() (*api.ResourcesStoragePool, error) {
- _, err := s.StoragePoolMount()
- if err != nil {
- return nil, err
- }
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- return driver.GetStorageResource(poolMntPoint)
-}
-
-func (s *storageDir) StoragePoolVolumeCopy(source *api.StorageVolumeSource) error {
- logger.Infof("Copying DIR storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
- successMsg := fmt.Sprintf("Copied DIR storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
-
- if s.pool.Name != source.Pool {
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- logger.Errorf("Failed to initialize DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- logger.Errorf("Failed to mount DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
- }
-
- err := s.copyVolume(source.Pool, source.Name, s.volume.Name)
- if err != nil {
- return err
- }
-
- if source.VolumeOnly {
- logger.Infof(successMsg)
- return nil
- }
-
- snapshots, err := driver.VolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- for _, snap := range snapshots {
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
- err = s.copyVolumeSnapshot(source.Pool, snap.Name, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName))
- if err != nil {
- return err
- }
- }
-
- logger.Infof(successMsg)
- return nil
-}
-
-func (s *storageDir) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncStorageMigrationSource(args)
-}
-
-func (s *storageDir) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncStorageMigrationSink(conn, op, args)
-}
-
-func (s *storageDir) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
- logger.Infof("Creating DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.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")
- }
-
- sourceName, _, ok := shared.InstanceGetParentAndSnapshotName(target.Name)
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- targetPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
- err = os.MkdirAll(targetPath, 0711)
- if err != nil {
- return err
- }
-
- sourcePath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, sourceName)
- bwlimit := s.pool.Config["rsync.bwlimit"]
- msg, err := rsync.LocalCopy(sourcePath, targetPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync: %s: %s", string(msg), err)
- }
-
- logger.Infof("Created DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolVolumeSnapshotDelete() error {
- logger.Infof("Deleting DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- storageVolumePath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- err := os.RemoveAll(storageVolumePath)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- storageVolumeSnapshotPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
- empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
- if err == nil && empty {
- os.RemoveAll(storageVolumeSnapshotPath)
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
- s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted DIR storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageDir) StoragePoolVolumeSnapshotRename(newName string) error {
- sourceName, _, ok := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
-
- logger.Infof("Renaming DIR storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- oldPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
-
- err := os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof("Renamed DIR storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
-}
-
-func (s *storageDir) copyVolume(sourcePool string, source string, target string) error {
- var srcMountPoint string
-
- if shared.IsSnapshot(source) {
- srcMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
- } else {
- srcMountPoint = driver.GetStoragePoolVolumeMountPoint(sourcePool, source)
- }
-
- dstMountPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, target)
-
- err := os.MkdirAll(dstMountPoint, 0711)
- if err != nil {
- return err
- }
-
- err = s.initQuota(dstMountPoint, s.volumeID)
- if err != nil {
- return err
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- _, err = rsync.LocalCopy(srcMountPoint, dstMountPoint, bwlimit, true)
- if err != nil {
- os.RemoveAll(dstMountPoint)
- logger.Errorf("Failed to rsync into DIR storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageDir) copyVolumeSnapshot(sourcePool string, source string, target string) error {
- srcMountPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
- dstMountPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
-
- err := os.MkdirAll(dstMountPoint, 0711)
- if err != nil {
- return err
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- _, err = rsync.LocalCopy(srcMountPoint, dstMountPoint, bwlimit, true)
- if err != nil {
- os.RemoveAll(dstMountPoint)
- logger.Errorf("Failed to rsync into DIR storage volume \"%s\" on storage pool \"%s\": %s", target, s.pool.Name, err)
- return err
- }
-
- return nil
-}
diff --git a/lxd/storage_migration.go b/lxd/storage_migration.go
index 8d46d47e1f..aa9fc2c190 100644
--- a/lxd/storage_migration.go
+++ b/lxd/storage_migration.go
@@ -316,136 +316,67 @@ func rsyncMigrationSink(conn *websocket.Conn, op *operations.Operation, args Mig
return fmt.Errorf("Instance type must be container")
}
- ct := args.Instance.(*containerLXC)
-
- isDirBackend := ct.Storage().GetStorageType() == storageTypeDir
- if isDirBackend {
- if !args.InstanceOnly {
- for _, snap := range args.Snapshots {
- isSnapshotOutdated := true
-
- for _, localSnap := range localSnapshots {
- if localSnap.Name() == snap.GetName() {
- if localSnap.CreationDate().Unix() > snap.GetCreationDate() {
- isSnapshotOutdated = false
- break
- }
- }
- }
-
- // Only copy snapshot if it's outdated
- if !isSnapshotOutdated {
- continue
- }
-
- snapArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), args.Instance.Name(), snap)
-
- // Ensure that snapshot and parent container have the
- // same storage pool in their local root disk device.
- // If the root disk device for the snapshot comes from a
- // profile on the new instance as well we don't need to
- // do anything.
- if snapArgs.Devices != nil {
- snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(snapArgs.Devices.CloneNative())
- if snapLocalRootDiskDeviceKey != "" {
- snapArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
- }
- }
-
- // Try and a load instance
- s, err := instance.LoadByProjectAndName(args.Instance.DaemonState(),
- args.Instance.Project(), snapArgs.Name)
- if err != nil {
- // Create the snapshot since it doesn't seem to exist
- s, err = containerCreateEmptySnapshot(args.Instance.DaemonState(), snapArgs)
- if err != nil {
- return err
- }
- }
-
- wrapper := migration.ProgressTracker(op, "fs_progress", s.Name())
- if err := rsync.Recv(shared.AddSlash(s.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures); err != nil {
- return err
- }
+ if !args.InstanceOnly {
+ for _, snap := range args.Snapshots {
+ isSnapshotOutdated := true
- if args.Instance.Type() == instancetype.Container {
- c := args.Instance.(*containerLXC)
- err = resetContainerDiskIdmap(c, args.Idmap)
- if err != nil {
- return err
+ for _, localSnap := range localSnapshots {
+ if localSnap.Name() == snap.GetName() {
+ if localSnap.CreationDate().Unix() > snap.GetCreationDate() {
+ isSnapshotOutdated = false
+ break
}
}
}
- }
- wrapper := migration.ProgressTracker(op, "fs_progress", args.Instance.Name())
- err = rsync.Recv(shared.AddSlash(args.Instance.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures)
- if err != nil {
- return err
- }
- } else {
- if !args.InstanceOnly {
- for _, snap := range args.Snapshots {
- isSnapshotOutdated := true
-
- for _, localSnap := range localSnapshots {
- if localSnap.Name() == snap.GetName() {
- if localSnap.CreationDate().Unix() > snap.GetCreationDate() {
- isSnapshotOutdated = false
- break
- }
- }
- }
+ // Only copy snapshot if it's outdated
+ if !isSnapshotOutdated {
+ continue
+ }
- // Only copy snapshot if it's outdated
- if !isSnapshotOutdated {
- continue
+ snapArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), args.Instance.Name(), snap)
+
+ // Ensure that snapshot and parent container have the
+ // same storage pool in their local root disk device.
+ // If the root disk device for the snapshot comes from a
+ // profile on the new instance as well we don't need to
+ // do anything.
+ if snapArgs.Devices != nil {
+ snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(snapArgs.Devices.CloneNative())
+ if snapLocalRootDiskDeviceKey != "" {
+ snapArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
}
+ }
- snapArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), args.Instance.Name(), snap)
-
- // Ensure that snapshot and parent container have the
- // same storage pool in their local root disk device.
- // If the root disk device for the snapshot comes from a
- // profile on the new instance as well we don't need to
- // do anything.
- if snapArgs.Devices != nil {
- snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(snapArgs.Devices.CloneNative())
- if snapLocalRootDiskDeviceKey != "" {
- snapArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
- }
- }
+ wrapper := migration.ProgressTracker(op, "fs_progress", snap.GetName())
+ err := rsync.Recv(shared.AddSlash(args.Instance.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures)
+ if err != nil {
+ return err
+ }
- wrapper := migration.ProgressTracker(op, "fs_progress", snap.GetName())
- err := rsync.Recv(shared.AddSlash(args.Instance.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures)
+ if args.Instance.Type() == instancetype.Container {
+ c := args.Instance.(*containerLXC)
+ err = resetContainerDiskIdmap(c, args.Idmap)
if err != nil {
return err
}
+ }
- if args.Instance.Type() == instancetype.Container {
- c := args.Instance.(*containerLXC)
- err = resetContainerDiskIdmap(c, args.Idmap)
- if err != nil {
- return err
- }
- }
-
- _, err = instance.LoadByProjectAndName(args.Instance.DaemonState(),
- args.Instance.Project(), snapArgs.Name)
+ _, err = instance.LoadByProjectAndName(args.Instance.DaemonState(),
+ args.Instance.Project(), snapArgs.Name)
+ if err != nil {
+ _, err = instanceCreateAsSnapshot(args.Instance.DaemonState(), snapArgs, args.Instance, op)
if err != nil {
- _, err = instanceCreateAsSnapshot(args.Instance.DaemonState(), snapArgs, args.Instance, op)
- if err != nil {
- return err
- }
+ return err
}
}
}
+ }
- wrapper := migration.ProgressTracker(op, "fs_progress", args.Instance.Name())
- err = rsync.Recv(shared.AddSlash(args.Instance.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures)
- if err != nil {
- return err
- }
+ wrapper := migration.ProgressTracker(op, "fs_progress", args.Instance.Name())
+ err = rsync.Recv(shared.AddSlash(args.Instance.Path()), &shared.WebsocketIO{Conn: conn}, wrapper, args.RsyncFeatures)
+ if err != nil {
+ return err
}
if args.Live {
From e6f908ebc0e8dd39f27647778d076296637870f4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Thu, 12 Dec 2019 09:36:15 -0500
Subject: [PATCH 03/17] lxd/storage: Remove legacy btrfs implementation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/patches_utils.go | 245 +++
lxd/storage.go | 29 +-
lxd/storage_btrfs.go | 3087 --------------------------------
lxd/storage_migration_btrfs.go | 195 --
4 files changed, 246 insertions(+), 3310 deletions(-)
delete mode 100644 lxd/storage_btrfs.go
delete mode 100644 lxd/storage_migration_btrfs.go
diff --git a/lxd/patches_utils.go b/lxd/patches_utils.go
index 01429d1a59..217d684973 100644
--- a/lxd/patches_utils.go
+++ b/lxd/patches_utils.go
@@ -1,13 +1,23 @@
package main
import (
+ "fmt"
"os"
+ "path"
+ "path/filepath"
+ "sort"
+ "strings"
+
+ "golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/project"
+ "github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/logger"
)
+// For 'dir' storage backend.
func dirSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
if shared.PathExists(snapshotContainerMntPoint) {
@@ -37,3 +47,238 @@ func dirSnapshotDeleteInternal(projectName, poolName string, snapshotName string
return nil
}
+
+// For 'btrfs' storage backend.
+func btrfsSubVolumeCreate(subvol string) error {
+ parentDestPath := filepath.Dir(subvol)
+ if !shared.PathExists(parentDestPath) {
+ err := os.MkdirAll(parentDestPath, 0711)
+ if err != nil {
+ return err
+ }
+ }
+
+ _, err := shared.RunCommand(
+ "btrfs",
+ "subvolume",
+ "create",
+ subvol)
+ if err != nil {
+ logger.Errorf("Failed to create BTRFS subvolume \"%s\": %v", subvol, err)
+ return err
+ }
+
+ return nil
+}
+
+func btrfsSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
+ snapshotSubvolumeName := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
+ // Also delete any leftover .ro snapshot.
+ roSnapshotSubvolumeName := fmt.Sprintf("%s.ro", snapshotSubvolumeName)
+ names := []string{snapshotSubvolumeName, roSnapshotSubvolumeName}
+ for _, name := range names {
+ if shared.PathExists(name) && btrfsIsSubVolume(name) {
+ err := btrfsSubVolumesDelete(name)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ sourceSnapshotMntPoint := shared.VarPath("snapshots", project.Prefix(projectName, snapshotName))
+ os.Remove(sourceSnapshotMntPoint)
+ os.Remove(snapshotSubvolumeName)
+
+ sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
+ snapshotSubvolumePath := driver.GetSnapshotMountPoint(projectName, poolName, sourceName)
+ os.Remove(snapshotSubvolumePath)
+ if !shared.PathExists(snapshotSubvolumePath) {
+ snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
+ os.Remove(snapshotMntPointSymlink)
+ }
+
+ return nil
+}
+
+func btrfsSubVolumeQGroup(subvol string) (string, error) {
+ output, err := shared.RunCommand(
+ "btrfs",
+ "qgroup",
+ "show",
+ "-e",
+ "-f",
+ subvol)
+
+ if err != nil {
+ return "", fmt.Errorf("Quotas disabled on filesystem")
+ }
+
+ var qgroup string
+ for _, line := range strings.Split(output, "\n") {
+ if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
+ continue
+ }
+
+ fields := strings.Fields(line)
+ if len(fields) != 4 {
+ continue
+ }
+
+ qgroup = fields[0]
+ }
+
+ if qgroup == "" {
+ return "", fmt.Errorf("Unable to find quota group")
+ }
+
+ return qgroup, nil
+}
+
+func btrfsSubVolumeDelete(subvol string) error {
+ // Attempt (but don't fail on) to delete any qgroup on the subvolume
+ qgroup, err := btrfsSubVolumeQGroup(subvol)
+ if err == nil {
+ shared.RunCommand(
+ "btrfs",
+ "qgroup",
+ "destroy",
+ qgroup,
+ subvol)
+ }
+
+ // Attempt to make the subvolume writable
+ shared.RunCommand("btrfs", "property", "set", subvol, "ro", "false")
+
+ // Delete the subvolume itself
+ _, err = shared.RunCommand(
+ "btrfs",
+ "subvolume",
+ "delete",
+ subvol)
+
+ return err
+}
+
+func btrfsSubVolumesDelete(subvol string) error {
+ // Delete subsubvols.
+ subsubvols, err := btrfsSubVolumesGet(subvol)
+ if err != nil {
+ return err
+ }
+ sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
+
+ for _, subsubvol := range subsubvols {
+ err := btrfsSubVolumeDelete(path.Join(subvol, subsubvol))
+ if err != nil {
+ return err
+ }
+ }
+
+ // Delete the subvol itself
+ err = btrfsSubVolumeDelete(subvol)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func btrfsSnapshot(s *state.State, source string, dest string, readonly bool) error {
+ var output string
+ var err error
+ if readonly && !s.OS.RunningInUserNS {
+ output, err = shared.RunCommand(
+ "btrfs",
+ "subvolume",
+ "snapshot",
+ "-r",
+ source,
+ dest)
+ } else {
+ output, err = shared.RunCommand(
+ "btrfs",
+ "subvolume",
+ "snapshot",
+ source,
+ dest)
+ }
+ if err != nil {
+ return fmt.Errorf(
+ "subvolume snapshot failed, source=%s, dest=%s, output=%s",
+ source,
+ dest,
+ output,
+ )
+ }
+
+ return err
+}
+
+func btrfsIsSubVolume(subvolPath string) bool {
+ fs := unix.Stat_t{}
+ err := unix.Lstat(subvolPath, &fs)
+ if err != nil {
+ return false
+ }
+
+ // Check if BTRFS_FIRST_FREE_OBJECTID
+ if fs.Ino != 256 {
+ return false
+ }
+
+ return true
+}
+
+func btrfsSubVolumeIsRo(path string) bool {
+ output, err := shared.RunCommand("btrfs", "property", "get", "-ts", path)
+ if err != nil {
+ return false
+ }
+
+ return strings.HasPrefix(string(output), "ro=true")
+}
+
+func btrfsSubVolumeMakeRo(path string) error {
+ _, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", "true")
+ return err
+}
+
+func btrfsSubVolumeMakeRw(path string) error {
+ _, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", "false")
+ return err
+}
+
+func btrfsSubVolumesGet(path string) ([]string, error) {
+ result := []string{}
+
+ if !strings.HasSuffix(path, "/") {
+ path = path + "/"
+ }
+
+ // Unprivileged users can't get to fs internals
+ filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
+ // Skip walk errors
+ if err != nil {
+ return nil
+ }
+
+ // Ignore the base path
+ if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
+ return nil
+ }
+
+ // Subvolumes can only be directories
+ if !fi.IsDir() {
+ return nil
+ }
+
+ // Check if a btrfs subvolume
+ if btrfsIsSubVolume(fpath) {
+ result = append(result, strings.TrimPrefix(fpath, path))
+ }
+
+ return nil
+ })
+
+ return result, nil
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index d78220e4cd..b58034a345 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -58,10 +58,6 @@ func getPoolMountLockID(poolName string) string {
return fmt.Sprintf("mount/pool/%s", poolName)
}
-func getPoolUmountLockID(poolName string) string {
- return fmt.Sprintf("umount/pool/%s", poolName)
-}
-
func getImageCreateLockID(poolName string, fingerprint string) string {
return fmt.Sprintf("create/image/%s/%s", poolName, fingerprint)
}
@@ -100,8 +96,7 @@ func readStoragePoolDriversCache() map[string]string {
type storageType int
const (
- storageTypeBtrfs storageType = iota
- storageTypeCeph
+ storageTypeCeph storageType = iota
storageTypeLvm
storageTypeMock
storageTypeZfs
@@ -111,8 +106,6 @@ var supportedStoragePoolDrivers = []string{"btrfs", "ceph", "cephfs", "dir", "lv
func storageTypeToString(sType storageType) (string, error) {
switch sType {
- case storageTypeBtrfs:
- return "btrfs", nil
case storageTypeCeph:
return "ceph", nil
case storageTypeLvm:
@@ -128,8 +121,6 @@ func storageTypeToString(sType storageType) (string, error) {
func storageStringToType(sName string) (storageType, error) {
switch sName {
- case "btrfs":
- return storageTypeBtrfs, nil
case "ceph":
return storageTypeCeph, nil
case "lvm":
@@ -256,13 +247,6 @@ func storageCoreInit(driver string) (storage, error) {
}
switch sType {
- case storageTypeBtrfs:
- btrfs := storageBtrfs{}
- err = btrfs.StorageCoreInit()
- if err != nil {
- return nil, err
- }
- return &btrfs, nil
case storageTypeCeph:
ceph := storageCeph{}
err = ceph.StorageCoreInit()
@@ -325,17 +309,6 @@ func storageInit(s *state.State, project, poolName, volumeName string, volumeTyp
}
switch sType {
- case storageTypeBtrfs:
- btrfs := storageBtrfs{}
- btrfs.poolID = poolID
- btrfs.pool = pool
- btrfs.volume = volume
- btrfs.s = s
- err = btrfs.StoragePoolInit()
- if err != nil {
- return nil, err
- }
- return &btrfs, nil
case storageTypeCeph:
ceph := storageCeph{}
ceph.poolID = poolID
diff --git a/lxd/storage_btrfs.go b/lxd/storage_btrfs.go
deleted file mode 100644
index 86f9c09b3d..0000000000
--- a/lxd/storage_btrfs.go
+++ /dev/null
@@ -1,3087 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path"
- "path/filepath"
- "sort"
- "strconv"
- "strings"
-
- "github.com/gorilla/websocket"
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/lxc/lxd/lxd/backup"
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/lxd/rsync"
- "github.com/lxc/lxd/lxd/state"
- driver "github.com/lxc/lxd/lxd/storage"
- "github.com/lxc/lxd/lxd/storage/drivers"
- "github.com/lxc/lxd/lxd/util"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/ioprogress"
- "github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/units"
-)
-
-type storageBtrfs struct {
- remount uintptr
- storageShared
-}
-
-var btrfsVersion = ""
-
-func (s *storageBtrfs) getBtrfsMountOptions() string {
- if s.pool.Config["btrfs.mount_options"] != "" {
- return s.pool.Config["btrfs.mount_options"]
- }
-
- return "user_subvol_rm_allowed"
-}
-
-func (s *storageBtrfs) setBtrfsMountOptions(mountOptions string) {
- s.pool.Config["btrfs.mount_options"] = mountOptions
-}
-
-// ${LXD_DIR}/storage-pools/<pool>/containers
-func (s *storageBtrfs) getContainerSubvolumePath(poolName string) string {
- return shared.VarPath("storage-pools", poolName, "containers")
-}
-
-// ${LXD_DIR}/storage-pools/<pool>/containers-snapshots
-func getSnapshotSubvolumePath(projectName, poolName string, containerName string) string {
- return shared.VarPath("storage-pools", poolName, "containers-snapshots", project.Prefix(projectName, containerName))
-}
-
-// ${LXD_DIR}/storage-pools/<pool>/images
-func (s *storageBtrfs) getImageSubvolumePath(poolName string) string {
- return shared.VarPath("storage-pools", poolName, "images")
-}
-
-// ${LXD_DIR}/storage-pools/<pool>/custom
-func (s *storageBtrfs) getCustomSubvolumePath(poolName string) string {
- return shared.VarPath("storage-pools", poolName, "custom")
-}
-
-// ${LXD_DIR}/storage-pools/<pool>/custom-snapshots
-func (s *storageBtrfs) getCustomSnapshotSubvolumePath(poolName string) string {
- return shared.VarPath("storage-pools", poolName, "custom-snapshots")
-}
-
-func (s *storageBtrfs) StorageCoreInit() error {
- s.sType = storageTypeBtrfs
- typeName, err := storageTypeToString(s.sType)
- if err != nil {
- return err
- }
- s.sTypeName = typeName
-
- if btrfsVersion != "" {
- s.sTypeVersion = btrfsVersion
- return nil
- }
-
- out, err := exec.LookPath("btrfs")
- if err != nil || len(out) == 0 {
- return fmt.Errorf("The 'btrfs' tool isn't available")
- }
-
- output, err := shared.RunCommand("btrfs", "version")
- if err != nil {
- return fmt.Errorf("The 'btrfs' tool isn't working properly")
- }
-
- count, err := fmt.Sscanf(strings.SplitN(output, " ", 2)[1], "v%s\n", &s.sTypeVersion)
- if err != nil || count != 1 {
- return fmt.Errorf("The 'btrfs' tool isn't working properly")
- }
-
- btrfsVersion = s.sTypeVersion
-
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolInit() error {
- err := s.StorageCoreInit()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolCheck() error {
- // FIXEM(brauner): Think of something smart or useful (And then think
- // again if it is worth implementing it. :)).
- logger.Debugf("Checking BTRFS storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolCreate() error {
- logger.Infof("Creating BTRFS storage pool \"%s\"", s.pool.Name)
- s.pool.Config["volatile.initial_source"] = s.pool.Config["source"]
-
- isBlockDev := false
-
- source := s.pool.Config["source"]
- if strings.HasPrefix(source, "/") {
- source = shared.HostPath(s.pool.Config["source"])
- }
-
- defaultSource := filepath.Join(shared.VarPath("disks"), fmt.Sprintf("%s.img", s.pool.Name))
- if source == "" || source == defaultSource {
- source = defaultSource
- s.pool.Config["source"] = source
-
- f, err := os.Create(source)
- if err != nil {
- return fmt.Errorf("Failed to open %s: %s", source, err)
- }
- defer f.Close()
-
- err = f.Chmod(0600)
- if err != nil {
- return fmt.Errorf("Failed to chmod %s: %s", source, err)
- }
-
- size, err := units.ParseByteSizeString(s.pool.Config["size"])
- if err != nil {
- return err
- }
- err = f.Truncate(size)
- if err != nil {
- return fmt.Errorf("Failed to create sparse file %s: %s", source, err)
- }
-
- output, err := makeFSType(source, "btrfs", &mkfsOptions{Label: s.pool.Name})
- if err != nil {
- return fmt.Errorf("Failed to create the BTRFS pool: %v (%s)", err, output)
- }
- } else {
- // Unset size property since it doesn't make sense.
- s.pool.Config["size"] = ""
-
- if filepath.IsAbs(source) {
- isBlockDev = shared.IsBlockdevPath(source)
- if isBlockDev {
- output, err := makeFSType(source, "btrfs", &mkfsOptions{Label: s.pool.Name})
- if err != nil {
- return fmt.Errorf("Failed to create the BTRFS pool: %v (%s)", err, output)
- }
- } else {
- if isBtrfsSubVolume(source) {
- subvols, err := btrfsSubVolumesGet(source)
- if err != nil {
- return fmt.Errorf("Could not determine if existing BTRFS subvolume ist empty: %s", err)
- }
- if len(subvols) > 0 {
- return fmt.Errorf("Requested BTRFS subvolume exists but is not empty")
- }
- } else {
- cleanSource := filepath.Clean(source)
- lxdDir := shared.VarPath()
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- if shared.PathExists(source) && !isOnBtrfs(source) {
- return fmt.Errorf("Existing path is neither a BTRFS subvolume nor does it reside on a BTRFS filesystem")
- } else if strings.HasPrefix(cleanSource, lxdDir) {
- if cleanSource != poolMntPoint {
- return fmt.Errorf("BTRFS subvolumes requests in LXD directory \"%s\" are only valid under \"%s\"\n(e.g. source=%s)", shared.VarPath(), shared.VarPath("storage-pools"), poolMntPoint)
- } else if s.s.OS.BackingFS != "btrfs" {
- return fmt.Errorf("Creation of BTRFS subvolume requested but \"%s\" does not reside on BTRFS filesystem", source)
- }
- }
-
- err := btrfsSubVolumeCreate(source)
- if err != nil {
- return err
- }
- }
- }
- } else {
- return fmt.Errorf("Invalid \"source\" property")
- }
- }
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- if !shared.PathExists(poolMntPoint) {
- err := os.MkdirAll(poolMntPoint, driver.StoragePoolsDirMode)
- if err != nil {
- return err
- }
- }
-
- var err1 error
- var devUUID string
- mountFlags, mountOptions := resolveMountOptions(s.getBtrfsMountOptions())
- mountFlags |= s.remount
- if isBlockDev && filepath.IsAbs(source) {
- devUUID, _ = shared.LookupUUIDByBlockDevPath(source)
- // The symlink might not have been created even with the delay
- // we granted it above. So try to call btrfs filesystem show and
- // parse it out. (I __hate__ this!)
- if devUUID == "" {
- logger.Warnf("Failed to detect UUID by looking at /dev/disk/by-uuid")
- devUUID, err1 = s.btrfsLookupFsUUID(source)
- if err1 != nil {
- logger.Errorf("Failed to detect UUID by parsing filesystem info")
- return err1
- }
- }
- s.pool.Config["source"] = devUUID
-
- // If the symlink in /dev/disk/by-uuid hasn't been created yet
- // aka we only detected it by parsing btrfs filesystem show, we
- // cannot call StoragePoolMount() since it will try to do the
- // reverse operation. So instead we shamelessly mount using the
- // block device path at the time of pool creation.
- err1 = unix.Mount(source, poolMntPoint, "btrfs", mountFlags, mountOptions)
- } else {
- _, err1 = s.StoragePoolMount()
- }
- if err1 != nil {
- return err1
- }
-
- // Create default subvolumes.
- dummyDir := driver.GetContainerMountPoint("default", s.pool.Name, "")
- err := btrfsSubVolumeCreate(dummyDir)
- if err != nil {
- return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
- }
-
- dummyDir = driver.GetSnapshotMountPoint("default", s.pool.Name, "")
- err = btrfsSubVolumeCreate(dummyDir)
- if err != nil {
- return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
- }
-
- dummyDir = driver.GetImageMountPoint(s.pool.Name, "")
- err = btrfsSubVolumeCreate(dummyDir)
- if err != nil {
- return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
- }
-
- dummyDir = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, "")
- err = btrfsSubVolumeCreate(dummyDir)
- if err != nil {
- return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
- }
-
- dummyDir = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, "")
- err = btrfsSubVolumeCreate(dummyDir)
- if err != nil {
- return fmt.Errorf("Could not create btrfs subvolume: %s", dummyDir)
- }
-
- err = s.StoragePoolCheck()
- if err != nil {
- return err
- }
-
- logger.Infof("Created BTRFS storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolDelete() error {
- logger.Infof("Deleting BTRFS storage pool \"%s\"", s.pool.Name)
-
- source := s.pool.Config["source"]
- if strings.HasPrefix(source, "/") {
- source = shared.HostPath(s.pool.Config["source"])
- }
-
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- // Delete default subvolumes.
- dummyDir := driver.GetContainerMountPoint("default", s.pool.Name, "")
- btrfsSubVolumesDelete(dummyDir)
-
- dummyDir = driver.GetSnapshotMountPoint("default", s.pool.Name, "")
- btrfsSubVolumesDelete(dummyDir)
-
- dummyDir = driver.GetImageMountPoint(s.pool.Name, "")
- btrfsSubVolumesDelete(dummyDir)
-
- dummyDir = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, "")
- btrfsSubVolumesDelete(dummyDir)
-
- _, err := s.StoragePoolUmount()
- if err != nil {
- return err
- }
-
- // This is a UUID. Check whether we can find the block device.
- if !filepath.IsAbs(source) {
- // Try to lookup the disk device by UUID but don't fail. If we
- // don't find one this might just mean we have been given the
- // UUID of a subvolume.
- byUUID := fmt.Sprintf("/dev/disk/by-uuid/%s", source)
- diskPath, err := os.Readlink(byUUID)
- msg := ""
- if err == nil {
- msg = fmt.Sprintf("Removing disk device %s with UUID: %s.", diskPath, source)
- } else {
- msg = fmt.Sprintf("Failed to lookup disk device with UUID: %s: %s.", source, err)
- }
- logger.Debugf(msg)
- } else {
- var err error
- cleanSource := filepath.Clean(source)
- sourcePath := shared.VarPath("disks", s.pool.Name)
- loopFilePath := sourcePath + ".img"
- if cleanSource == loopFilePath {
- // This is a loop file so simply remove it.
- err = os.Remove(source)
- } else {
- if !isBtrfsFilesystem(source) && isBtrfsSubVolume(source) {
- err = btrfsSubVolumesDelete(source)
- }
- }
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- }
-
- // Remove the mountpoint for the storage pool.
- err = os.RemoveAll(driver.GetStoragePoolMountPoint(s.pool.Name))
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- logger.Infof("Deleted BTRFS storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolMount() (bool, error) {
- logger.Debugf("Mounting BTRFS storage pool \"%s\"", s.pool.Name)
-
- source := s.pool.Config["source"]
- if strings.HasPrefix(source, "/") {
- source = shared.HostPath(s.pool.Config["source"])
- }
-
- if source == "" {
- return false, fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- poolMountLockID := getPoolMountLockID(s.pool.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage pool.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[poolMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, poolMountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
- defer removeLockFromMap()
-
- // Check whether the mount poolMntPoint exits.
- if !shared.PathExists(poolMntPoint) {
- err := os.MkdirAll(poolMntPoint, driver.StoragePoolsDirMode)
- if err != nil {
- return false, err
- }
- }
-
- if shared.IsMountPoint(poolMntPoint) && (s.remount&unix.MS_REMOUNT) == 0 {
- return false, nil
- }
-
- mountFlags, mountOptions := resolveMountOptions(s.getBtrfsMountOptions())
- mountSource := source
- isBlockDev := shared.IsBlockdevPath(source)
- if filepath.IsAbs(source) {
- cleanSource := filepath.Clean(source)
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- loopFilePath := shared.VarPath("disks", s.pool.Name+".img")
- if !isBlockDev && cleanSource == loopFilePath {
- // If source == "${LXD_DIR}"/disks/{pool_name} it is a
- // loop file we're dealing with.
- //
- // Since we mount the loop device LO_FLAGS_AUTOCLEAR is
- // fine since the loop device will be kept around for as
- // long as the mount exists.
- loopF, loopErr := drivers.PrepareLoopDev(source, drivers.LoFlagsAutoclear)
- if loopErr != nil {
- return false, loopErr
- }
- mountSource = loopF.Name()
- defer loopF.Close()
- } else if !isBlockDev && cleanSource != poolMntPoint {
- mountSource = source
- mountFlags |= unix.MS_BIND
- } else if !isBlockDev && cleanSource == poolMntPoint && s.s.OS.BackingFS == "btrfs" {
- return false, nil
- }
- // User is using block device path.
- } else {
- // Try to lookup the disk device by UUID but don't fail. If we
- // don't find one this might just mean we have been given the
- // UUID of a subvolume.
- byUUID := fmt.Sprintf("/dev/disk/by-uuid/%s", source)
- diskPath, err := os.Readlink(byUUID)
- if err == nil {
- mountSource = fmt.Sprintf("/dev/%s", strings.Trim(diskPath, "../../"))
- } else {
- // We have very likely been given a subvolume UUID. In
- // this case we should simply assume that the user has
- // mounted the parent of the subvolume or the subvolume
- // itself. Otherwise this becomes a really messy
- // detection task.
- return false, nil
- }
- }
-
- mountFlags |= s.remount
- err := unix.Mount(mountSource, poolMntPoint, "btrfs", mountFlags, mountOptions)
- if err != nil {
- logger.Errorf("Failed to mount BTRFS storage pool \"%s\" onto \"%s\" with mountoptions \"%s\": %s", mountSource, poolMntPoint, mountOptions, err)
- return false, err
- }
-
- logger.Debugf("Mounted BTRFS storage pool \"%s\"", s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) StoragePoolUmount() (bool, error) {
- logger.Debugf("Unmounting BTRFS storage pool \"%s\"", s.pool.Name)
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- poolUmountLockID := getPoolUmountLockID(s.pool.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage pool.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[poolUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, poolUmountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
-
- defer removeLockFromMap()
-
- if shared.IsMountPoint(poolMntPoint) {
- err := unix.Unmount(poolMntPoint, 0)
- if err != nil {
- return false, err
- }
- }
-
- logger.Debugf("Unmounted BTRFS storage pool \"%s\"", s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) StoragePoolUpdate(writable *api.StoragePoolPut,
- changedConfig []string) error {
- logger.Infof(`Updating BTRFS storage pool "%s"`, s.pool.Name)
-
- changeable := changeableStoragePoolProperties["btrfs"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolError(unchangeable, "btrfs")
- }
-
- // "rsync.bwlimit" requires no on-disk modifications.
-
- if shared.StringInSlice("btrfs.mount_options", changedConfig) {
- s.setBtrfsMountOptions(writable.Config["btrfs.mount_options"])
- s.remount |= unix.MS_REMOUNT
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
- }
-
- logger.Infof(`Updated BTRFS storage pool "%s"`, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) GetContainerPoolInfo() (int64, string, string) {
- return s.poolID, s.pool.Name, s.pool.Name
-}
-
-// Functions dealing with storage volumes.
-func (s *storageBtrfs) StoragePoolVolumeCreate() error {
- logger.Infof("Creating BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- isSnapshot := shared.IsSnapshot(s.volume.Name)
-
- // Create subvolume path on the storage pool.
- var customSubvolumePath string
-
- if isSnapshot {
- customSubvolumePath = s.getCustomSnapshotSubvolumePath(s.pool.Name)
- } else {
- customSubvolumePath = s.getCustomSubvolumePath(s.pool.Name)
- }
-
- if !shared.PathExists(customSubvolumePath) {
- err := os.MkdirAll(customSubvolumePath, 0700)
- if err != nil {
- return err
- }
- }
-
- // Create subvolume.
- var customSubvolumeName string
-
- if isSnapshot {
- customSubvolumeName = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- } else {
- customSubvolumeName = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- err = btrfsSubVolumeCreate(customSubvolumeName)
- if err != nil {
- return err
- }
-
- // apply quota
- if s.volume.Config["size"] != "" {
- size, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
-
- logger.Infof("Created BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeDelete() error {
- logger.Infof("Deleting BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Delete subvolume.
- customSubvolumeName := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- if shared.PathExists(customSubvolumeName) && isBtrfsSubVolume(customSubvolumeName) {
- err = btrfsSubVolumesDelete(customSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- // Delete the mountpoint.
- if shared.PathExists(customSubvolumeName) {
- err = os.Remove(customSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for BTRFS storage volume "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeMount() (bool, error) {
- logger.Debugf("Mounting BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Mounted BTRFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeUmount() (bool, error) {
- return true, nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, changedConfig []string) error {
- if writable.Restore != "" {
- logger.Debugf(`Restoring BTRFS storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Create a backup so we can revert.
- targetVolumeSubvolumeName := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- backupTargetVolumeSubvolumeName := fmt.Sprintf("%s.tmp", targetVolumeSubvolumeName)
- err = os.Rename(targetVolumeSubvolumeName, backupTargetVolumeSubvolumeName)
- if err != nil {
- return err
- }
- undo := true
- defer func() {
- if undo {
- os.Rename(backupTargetVolumeSubvolumeName, targetVolumeSubvolumeName)
- }
- }()
-
- sourceVolumeSubvolumeName := driver.GetStoragePoolVolumeSnapshotMountPoint(
- s.pool.Name, fmt.Sprintf("%s/%s", s.volume.Name, writable.Restore))
- err = s.btrfsPoolVolumesSnapshot(sourceVolumeSubvolumeName,
- targetVolumeSubvolumeName, false, true)
- if err != nil {
- return err
- }
-
- undo = false
- err = btrfsSubVolumesDelete(backupTargetVolumeSubvolumeName)
- if err != nil {
- return err
- }
-
- logger.Debugf(`Restored BTRFS storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
- return nil
- }
-
- logger.Infof(`Updating BTRFS storage volume "%s"`, s.volume.Name)
-
- changeable := changeableStoragePoolVolumeProperties["btrfs"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolVolumeError(unchangeable, "btrfs")
- }
-
- if shared.StringInSlice("size", changedConfig) {
- if s.volume.Type != storagePoolVolumeTypeNameCustom {
- return updateStoragePoolVolumeError([]string{"size"}, "btrfs")
- }
-
- if s.volume.Config["size"] != writable.Config["size"] {
- size, err := units.ParseByteSizeString(writable.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
- }
-
- logger.Infof(`Updated BTRFS storage volume "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeRename(newName string) error {
- logger.Infof(`Renaming BTRFS storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- usedBy, err := storagePoolVolumeUsedByInstancesGet(s.s, "default", s.pool.Name, s.volume.Name)
- if err != nil {
- return err
- }
- if len(usedBy) > 0 {
- return fmt.Errorf(`BTRFS storage volume "%s" on storage pool "%s" is attached to containers`,
- s.volume.Name, s.pool.Name)
- }
-
- oldPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, newName)
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof(`Renamed BTRFS storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- err = s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, newName,
- storagePoolVolumeTypeCustom, s.poolID)
- if err != nil {
- return err
- }
-
- // Get volumes attached to source storage volume
- volumes, err := s.s.Cluster.StoragePoolVolumeSnapshotsGetType(s.volume.Name,
- storagePoolVolumeTypeCustom, s.poolID)
- if err != nil {
- return err
- }
-
- for _, vol := range volumes {
- _, snapshotName, _ := shared.InstanceGetParentAndSnapshotName(vol.Name)
- oldVolumeName := fmt.Sprintf("%s%s%s", s.volume.Name, shared.SnapshotDelimiter, snapshotName)
- newVolumeName := fmt.Sprintf("%s%s%s", newName, shared.SnapshotDelimiter, snapshotName)
-
- // Rename volume snapshots
- oldPath = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, oldVolumeName)
- newPath = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, newVolumeName)
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- err = s.s.Cluster.StoragePoolVolumeRename("default", oldVolumeName, newVolumeName,
- storagePoolVolumeTypeCustom, s.poolID)
- if err != nil {
- return nil
- }
- }
-
- return nil
-}
-
-// Functions dealing with container storage.
-func (s *storageBtrfs) ContainerStorageReady(container instance.Instance) bool {
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- return isBtrfsSubVolume(containerMntPoint)
-}
-
-func (s *storageBtrfs) doContainerCreate(projectName, name string, privileged bool) error {
- logger.Debugf("Creating empty BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // We can only create the btrfs subvolume under the mounted storage
- // pool. The on-disk layout for containers on a btrfs storage pool will
- // thus be
- // ${LXD_DIR}/storage-pools/<pool>/containers/. The btrfs tool will
- // complain if the intermediate path does not exist, so create it if it
- // doesn't already.
- containerSubvolumePath := s.getContainerSubvolumePath(s.pool.Name)
- if !shared.PathExists(containerSubvolumePath) {
- err := os.MkdirAll(containerSubvolumePath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
- }
-
- // Create empty subvolume for container.
- containerSubvolumeName := driver.GetContainerMountPoint(projectName, s.pool.Name, name)
- err = btrfsSubVolumeCreate(containerSubvolumeName)
- if err != nil {
- return err
- }
-
- // Create the mountpoint for the container at:
- // ${LXD_DIR}/containers/<name>
- err = driver.CreateContainerMountpoint(containerSubvolumeName, shared.VarPath("containers", project.Prefix(projectName, name)), privileged)
- if err != nil {
- return err
- }
-
- logger.Debugf("Created empty BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) ContainerCreate(container instance.Instance) error {
- err := s.doContainerCreate(container.Project(), container.Name(), container.IsPrivileged())
- if err != nil {
- return err
- }
-
- return container.DeferTemplateApply("create")
-}
-
-// And this function is why I started hating on btrfs...
-func (s *storageBtrfs) ContainerCreateFromImage(container instance.Instance, fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return errors.Wrap(err, "Failed to mount storage pool")
- }
-
- // We can only create the btrfs subvolume under the mounted storage
- // pool. The on-disk layout for containers on a btrfs storage pool will
- // thus be
- // ${LXD_DIR}/storage-pools/<pool>/containers/. The btrfs tool will
- // complain if the intermediate path does not exist, so create it if it
- // doesn't already.
- containerSubvolumePath := s.getContainerSubvolumePath(s.pool.Name)
- if !shared.PathExists(containerSubvolumePath) {
- err := os.MkdirAll(containerSubvolumePath, driver.ContainersDirMode)
- if err != nil {
- return errors.Wrap(err, "Failed to create volume directory")
- }
- }
-
- // Mountpoint of the image:
- // ${LXD_DIR}/images/<fingerprint>
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- imageStoragePoolLockID := getImageCreateLockID(s.pool.Name, fingerprint)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- } else {
- lxdStorageOngoingOperationMap[imageStoragePoolLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var imgerr error
- if !shared.PathExists(imageMntPoint) || !isBtrfsSubVolume(imageMntPoint) {
- imgerr = s.ImageCreate(fingerprint, tracker)
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, imageStoragePoolLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if imgerr != nil {
- return errors.Wrap(imgerr, "Failed to create image volume")
- }
- }
-
- // Create a rw snapshot at
- // ${LXD_DIR}/storage-pools/<pool>/containers/<name>
- // from the mounted ro image snapshot mounted at
- // ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>
- containerSubvolumeName := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- err = s.btrfsPoolVolumesSnapshot(imageMntPoint, containerSubvolumeName, false, false)
- if err != nil {
- return errors.Wrap(err, "Failed to storage pool volume snapshot")
- }
-
- // Create the mountpoint for the container at:
- // ${LXD_DIR}/containers/<name>
- err = driver.CreateContainerMountpoint(containerSubvolumeName, container.Path(), container.IsPrivileged())
- if err != nil {
- return errors.Wrap(err, "Failed to create container mountpoint")
- }
-
- logger.Debugf("Created BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- err = container.DeferTemplateApply("create")
- if err != nil {
- return errors.Wrap(err, "Failed to apply container template")
- }
- return nil
-}
-
-func (s *storageBtrfs) ContainerDelete(container instance.Instance) error {
- logger.Debugf("Deleting BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- // The storage pool needs to be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Delete the subvolume.
- containerSubvolumeName := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- if shared.PathExists(containerSubvolumeName) && isBtrfsSubVolume(containerSubvolumeName) {
- err = btrfsSubVolumesDelete(containerSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- // Delete the container's symlink to the subvolume.
- err = deleteContainerMountpoint(containerSubvolumeName, container.Path(), s.GetStorageTypeName())
- if err != nil {
- return err
- }
-
- // Delete potential snapshot mountpoints.
- snapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- if shared.PathExists(snapshotMntPoint) {
- err := os.RemoveAll(snapshotMntPoint)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- }
-
- // Delete potential symlink
- // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), container.Name()))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Deleted BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) copyContainer(target instance.Instance, source instance.Instance) error {
- sourceContainerSubvolumeName := driver.GetContainerMountPoint(source.Project(), s.pool.Name, source.Name())
- if source.IsSnapshot() {
- sourceContainerSubvolumeName = driver.GetSnapshotMountPoint(source.Project(), s.pool.Name, source.Name())
- }
- targetContainerSubvolumeName := driver.GetContainerMountPoint(target.Project(), s.pool.Name, target.Name())
-
- containersPath := driver.GetContainerMountPoint("default", s.pool.Name, "")
- // Ensure that the directories immediately preceding the subvolume directory exist.
- if !shared.PathExists(containersPath) {
- err := os.MkdirAll(containersPath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
- }
-
- err := s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, false, true)
- if err != nil {
- return err
- }
-
- err = driver.CreateContainerMountpoint(targetContainerSubvolumeName, target.Path(), target.IsPrivileged())
- if err != nil {
- return err
- }
-
- err = target.DeferTemplateApply("copy")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) copySnapshot(target instance.Instance, source instance.Instance) error {
- sourceName := source.Name()
- targetName := target.Name()
- sourceContainerSubvolumeName := driver.GetSnapshotMountPoint(source.Project(), s.pool.Name, sourceName)
- targetContainerSubvolumeName := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetName)
-
- targetParentName, _, _ := shared.InstanceGetParentAndSnapshotName(target.Name())
- containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
- err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- // Ensure that the directories immediately preceding the subvolume directory exist.
- if !shared.PathExists(containersPath) {
- err := os.MkdirAll(containersPath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
- }
-
- err = s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, true, true)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) doCrossPoolContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool, refresh bool, refreshSnapshots []instance.Instance) error {
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
-
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, source.Name(), storagePoolVolumeTypeContainer)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
-
- targetPool, err := target.StoragePool()
- if err != nil {
- return err
- }
-
- var snapshots []instance.Instance
-
- if refresh {
- snapshots = refreshSnapshots
- } else {
- snapshots, err = source.Snapshots()
- if err != nil {
- return err
- }
-
- // create the main container
- err = s.doContainerCreate(target.Project(), target.Name(), target.IsPrivileged())
- if err != nil {
- return err
- }
- }
-
- destContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
- bwlimit := s.pool.Config["rsync.bwlimit"]
- if !containerOnly {
- for _, snap := range snapshots {
- srcSnapshotMntPoint := driver.GetSnapshotMountPoint(target.Project(), sourcePool, snap.Name())
- _, err = rsync.LocalCopy(srcSnapshotMntPoint, destContainerMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- // create snapshot
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- err = s.doContainerSnapshotCreate(target.Project(), fmt.Sprintf("%s/%s", target.Name(), snapOnlyName), target.Name())
- if err != nil {
- return err
- }
- }
- }
-
- srcContainerMntPoint := driver.GetContainerMountPoint(source.Project(), sourcePool, source.Name())
- _, err = rsync.LocalCopy(srcContainerMntPoint, destContainerMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) ContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool) error {
- logger.Debugf("Copying BTRFS container storage %s to %s", source.Name(), target.Name())
-
- // The storage pool needs to be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- if target.Type() != instancetype.Container {
- return fmt.Errorf("Target Instance type must be container")
- }
-
- if source.Type() != instancetype.Container {
- return fmt.Errorf("Source Instance type must be container")
- }
-
- targetCt := target.(*containerLXC)
- srcCt := source.(*containerLXC)
-
- _, sourcePool, _ := srcCt.Storage().GetContainerPoolInfo()
- _, targetPool, _ := targetCt.Storage().GetContainerPoolInfo()
- if sourcePool != targetPool {
- err = s.doCrossPoolContainerCopy(target, source, containerOnly, false, nil)
- if err != nil {
- return err
- }
-
- return target.DeferTemplateApply("copy")
- }
-
- err = s.copyContainer(target, source)
- if err != nil {
- return err
- }
-
- if containerOnly {
- logger.Debugf("Copied BTRFS container storage %s to %s", source.Name(), target.Name())
- return nil
- }
-
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- if len(snapshots) == 0 {
- logger.Debugf("Copied BTRFS container storage %s to %s", source.Name(), target.Name())
- return nil
- }
-
- for _, snap := range snapshots {
- sourceSnapshot, err := instance.LoadByProjectAndName(s.s, source.Project(), snap.Name())
- if err != nil {
- return err
- }
-
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- newSnapName := fmt.Sprintf("%s/%s", target.Name(), snapOnlyName)
- targetSnapshot, err := instance.LoadByProjectAndName(s.s, target.Project(), newSnapName)
- if err != nil {
- return err
- }
-
- err = s.copySnapshot(targetSnapshot, sourceSnapshot)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Copied BTRFS container storage %s to %s", source.Name(), target.Name())
- return nil
-}
-
-func (s *storageBtrfs) ContainerRefresh(target instance.Instance, source instance.Instance, snapshots []instance.Instance) error {
- logger.Debugf("Refreshing BTRFS container storage for %s from %s", target.Name(), source.Name())
-
- // The storage pool needs to be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- return s.doCrossPoolContainerCopy(target, source, len(snapshots) == 0, true, snapshots)
-}
-
-func (s *storageBtrfs) ContainerMount(c instance.Instance) (bool, error) {
- logger.Debugf("Mounting BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Mounted BTRFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) ContainerUmount(c instance.Instance, path string) (bool, error) {
- return true, nil
-}
-
-func (s *storageBtrfs) ContainerRename(container instance.Instance, newName string) error {
- logger.Debugf("Renaming BTRFS storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- oldContainerSubvolumeName := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- newContainerSubvolumeName := driver.GetContainerMountPoint(container.Project(), s.pool.Name, newName)
- err = os.Rename(oldContainerSubvolumeName, newContainerSubvolumeName)
- if err != nil {
- return err
- }
-
- newSymlink := shared.VarPath("containers", project.Prefix(container.Project(), newName))
- err = renameContainerMountpoint(oldContainerSubvolumeName, container.Path(), newContainerSubvolumeName, newSymlink)
- if err != nil {
- return err
- }
-
- oldSnapshotSubvolumeName := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- newSnapshotSubvolumeName := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, newName)
- if shared.PathExists(oldSnapshotSubvolumeName) {
- err = os.Rename(oldSnapshotSubvolumeName, newSnapshotSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- oldSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), container.Name()))
- newSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), newName))
- if shared.PathExists(oldSnapshotSymlink) {
- err := os.Remove(oldSnapshotSymlink)
- if err != nil {
- return err
- }
-
- err = os.Symlink(newSnapshotSubvolumeName, newSnapshotSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Renamed BTRFS storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-func (s *storageBtrfs) ContainerRestore(container instance.Instance, sourceContainer instance.Instance) error {
- logger.Debugf("Restoring BTRFS storage volume for container \"%s\" from %s to %s", s.volume.Name, sourceContainer.Name(), container.Name())
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Create a backup so we can revert.
- targetContainerSubvolumeName := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- backupTargetContainerSubvolumeName := fmt.Sprintf("%s.tmp", targetContainerSubvolumeName)
- err = os.Rename(targetContainerSubvolumeName, backupTargetContainerSubvolumeName)
- if err != nil {
- return err
- }
- undo := true
- defer func() {
- if undo {
- os.Rename(backupTargetContainerSubvolumeName, targetContainerSubvolumeName)
- }
- }()
-
- ourStart, err := sourceContainer.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer sourceContainer.StorageStop()
- }
-
- // Mount the source container.
- if sourceContainer.Type() != instancetype.Container {
- return fmt.Errorf("Instance type must be container")
- }
-
- ct := sourceContainer.(*containerLXC)
-
- srcContainerStorage := ct.Storage()
- _, sourcePool, _ := srcContainerStorage.GetContainerPoolInfo()
- sourceContainerSubvolumeName := ""
- if sourceContainer.IsSnapshot() {
- sourceContainerSubvolumeName = driver.GetSnapshotMountPoint(sourceContainer.Project(), sourcePool, sourceContainer.Name())
- } else {
- sourceContainerSubvolumeName = driver.GetContainerMountPoint(container.Project(), sourcePool, sourceContainer.Name())
- }
-
- var failure error
- _, targetPool, _ := s.GetContainerPoolInfo()
- if targetPool == sourcePool {
- // They are on the same storage pool, so we can simply snapshot.
- err := s.btrfsPoolVolumesSnapshot(sourceContainerSubvolumeName, targetContainerSubvolumeName, false, true)
- if err != nil {
- failure = err
- }
- } else {
- err := btrfsSubVolumeCreate(targetContainerSubvolumeName)
- if err == nil {
- // Use rsync to fill the empty volume. Sync by using
- // the subvolume name.
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerSubvolumeName, targetContainerSubvolumeName, bwlimit, true)
- if err != nil {
- s.ContainerDelete(container)
- logger.Errorf("ContainerRestore: rsync failed: %s", string(output))
- failure = err
- }
- } else {
- failure = err
- }
- }
-
- if failure == nil {
- undo = false
- _, sourcePool, _ := srcContainerStorage.GetContainerPoolInfo()
- _, targetPool, _ := s.GetContainerPoolInfo()
- if targetPool == sourcePool {
- // Remove the backup, we made
- return btrfsSubVolumesDelete(backupTargetContainerSubvolumeName)
- }
-
- err = os.RemoveAll(backupTargetContainerSubvolumeName)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- }
-
- logger.Debugf("Restored BTRFS storage volume for container \"%s\" from %s to %s", s.volume.Name, sourceContainer.Name(), container.Name())
- return failure
-}
-
-func (s *storageBtrfs) ContainerGetUsage(container instance.Instance) (int64, error) {
- return s.btrfsPoolVolumeQGroupUsage(container.Path())
-}
-
-func (s *storageBtrfs) doContainerSnapshotCreate(projectName string, targetName string, sourceName string) error {
- logger.Debugf("Creating BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // We can only create the btrfs subvolume under the mounted storage
- // pool. The on-disk layout for snapshots on a btrfs storage pool will
- // thus be
- // ${LXD_DIR}/storage-pools/<pool>/snapshots/. The btrfs tool will
- // complain if the intermediate path does not exist, so create it if it
- // doesn't already.
- snapshotSubvolumePath := getSnapshotSubvolumePath(projectName, s.pool.Name, sourceName)
- if !shared.PathExists(snapshotSubvolumePath) {
- err := os.MkdirAll(snapshotSubvolumePath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(projectName, s.volume.Name))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
- if !shared.PathExists(snapshotMntPointSymlink) {
- if !shared.PathExists(snapshotMntPointSymlinkTarget) {
- err = os.MkdirAll(snapshotMntPointSymlinkTarget, driver.SnapshotsDirMode)
- if err != nil {
- return err
- }
- }
-
- err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- }
-
- srcContainerSubvolumeName := driver.GetContainerMountPoint(projectName, s.pool.Name, sourceName)
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(projectName, s.pool.Name, targetName)
- err = s.btrfsPoolVolumesSnapshot(srcContainerSubvolumeName, snapshotSubvolumeName, true, true)
- if err != nil {
- return err
- }
-
- logger.Debugf("Created BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) ContainerSnapshotCreate(snapshotContainer instance.Instance, sourceContainer instance.Instance) error {
- err := s.doContainerSnapshotCreate(sourceContainer.Project(), snapshotContainer.Name(), sourceContainer.Name())
- if err != nil {
- s.ContainerSnapshotDelete(snapshotContainer)
- return err
- }
-
- return nil
-}
-
-func btrfsSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
- // Also delete any leftover .ro snapshot.
- roSnapshotSubvolumeName := fmt.Sprintf("%s.ro", snapshotSubvolumeName)
- names := []string{snapshotSubvolumeName, roSnapshotSubvolumeName}
- for _, name := range names {
- if shared.PathExists(name) && isBtrfsSubVolume(name) {
- err := btrfsSubVolumesDelete(name)
- if err != nil {
- return err
- }
- }
- }
-
- sourceSnapshotMntPoint := shared.VarPath("snapshots", project.Prefix(projectName, snapshotName))
- os.Remove(sourceSnapshotMntPoint)
- os.Remove(snapshotSubvolumeName)
-
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
- snapshotSubvolumePath := getSnapshotSubvolumePath(projectName, poolName, sourceName)
- os.Remove(snapshotSubvolumePath)
- if !shared.PathExists(snapshotSubvolumePath) {
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
- os.Remove(snapshotMntPointSymlink)
- }
-
- return nil
-}
-
-func (s *storageBtrfs) ContainerSnapshotDelete(snapshotContainer instance.Instance) error {
- logger.Debugf("Deleting BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- err = btrfsSnapshotDeleteInternal(snapshotContainer.Project(), s.pool.Name, snapshotContainer.Name())
- if err != nil {
- return err
- }
-
- logger.Debugf("Deleted BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) ContainerSnapshotStart(container instance.Instance) (bool, error) {
- logger.Debugf("Initializing BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return false, err
- }
-
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- roSnapshotSubvolumeName := fmt.Sprintf("%s.ro", snapshotSubvolumeName)
- if shared.PathExists(roSnapshotSubvolumeName) {
- logger.Debugf("The BTRFS snapshot is already mounted read-write")
- return false, nil
- }
-
- err = os.Rename(snapshotSubvolumeName, roSnapshotSubvolumeName)
- if err != nil {
- return false, err
- }
-
- err = s.btrfsPoolVolumesSnapshot(roSnapshotSubvolumeName, snapshotSubvolumeName, false, true)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Initialized BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) ContainerSnapshotStop(container instance.Instance) (bool, error) {
- logger.Debugf("Stopping BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return false, err
- }
-
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- roSnapshotSubvolumeName := fmt.Sprintf("%s.ro", snapshotSubvolumeName)
- if !shared.PathExists(roSnapshotSubvolumeName) {
- logger.Debugf("The BTRFS snapshot is currently not mounted read-write")
- return false, nil
- }
-
- if shared.PathExists(snapshotSubvolumeName) && isBtrfsSubVolume(snapshotSubvolumeName) {
- err = btrfsSubVolumesDelete(snapshotSubvolumeName)
- if err != nil {
- return false, err
- }
- }
-
- err = os.Rename(roSnapshotSubvolumeName, snapshotSubvolumeName)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Stopped BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-// ContainerSnapshotRename renames a snapshot of a container.
-func (s *storageBtrfs) ContainerSnapshotRename(snapshotContainer instance.Instance, newName string) error {
- logger.Debugf("Renaming BTRFS storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Unmount the snapshot if it is mounted otherwise we'll get EBUSY.
- // Rename the subvolume on the storage pool.
- oldSnapshotSubvolumeName := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, snapshotContainer.Name())
- newSnapshotSubvolumeName := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, newName)
- err = os.Rename(oldSnapshotSubvolumeName, newSnapshotSubvolumeName)
- if err != nil {
- return err
- }
-
- logger.Debugf("Renamed BTRFS storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-// Needed for live migration where an empty snapshot needs to be created before
-// rsyncing into it.
-func (s *storageBtrfs) ContainerSnapshotCreateEmpty(snapshotContainer instance.Instance) error {
- logger.Debugf("Creating empty BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- // Mount the storage pool.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Create the snapshot subvole path on the storage pool.
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotContainer.Name())
- snapshotSubvolumePath := getSnapshotSubvolumePath(snapshotContainer.Project(), s.pool.Name, sourceName)
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, snapshotContainer.Name())
- if !shared.PathExists(snapshotSubvolumePath) {
- err := os.MkdirAll(snapshotSubvolumePath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
- }
-
- err = btrfsSubVolumeCreate(snapshotSubvolumeName)
- if err != nil {
- return err
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(snapshotContainer.Project(), sourceName))
- if !shared.PathExists(snapshotMntPointSymlink) {
- err := driver.CreateContainerMountpoint(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink, snapshotContainer.IsPrivileged())
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Created empty BTRFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) doBtrfsBackup(cur string, prev string, target string) error {
- args := []string{"send"}
- if prev != "" {
- args = append(args, "-p", prev)
- }
- args = append(args, cur)
-
- eater, err := os.OpenFile(target, os.O_RDWR|os.O_CREATE, 0644)
- if err != nil {
- return err
- }
- defer eater.Close()
-
- btrfsSendCmd := exec.Command("btrfs", args...)
- btrfsSendCmd.Stdout = eater
-
- err = btrfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- return err
-}
-
-func (s *storageBtrfs) doContainerBackupCreateOptimized(tmpPath string, backup backup.Backup, source instance.Instance) error {
- // Handle snapshots
- finalParent := ""
- if !backup.InstanceOnly() {
- snapshotsPath := fmt.Sprintf("%s/snapshots", tmpPath)
-
- // Retrieve the snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- // Create the snapshot path
- if len(snapshots) > 0 {
- err = os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return err
- }
- }
-
- for i, snap := range snapshots {
- _, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
-
- // Figure out previous and current subvolumes
- prev := ""
- if i > 0 {
- // /var/lib/lxd/storage-pools/<pool>/containers-snapshots/<container>/<snapshot>
- prev = driver.GetSnapshotMountPoint(source.Project(), s.pool.Name, snapshots[i-1].Name())
- }
- cur := driver.GetSnapshotMountPoint(source.Project(), s.pool.Name, snap.Name())
-
- // Make a binary btrfs backup
- target := fmt.Sprintf("%s/%s.bin", snapshotsPath, snapName)
- err := s.doBtrfsBackup(cur, prev, target)
- if err != nil {
- return err
- }
-
- finalParent = cur
- }
- }
-
- // Make a temporary copy of the container
- sourceVolume := driver.GetContainerMountPoint(source.Project(), s.pool.Name, source.Name())
- containersPath := driver.GetContainerMountPoint("default", s.pool.Name, "")
- tmpContainerMntPoint, err := ioutil.TempDir(containersPath, source.Name())
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
-
- targetVolume := fmt.Sprintf("%s/.backup", tmpContainerMntPoint)
- err = s.btrfsPoolVolumesSnapshot(sourceVolume, targetVolume, true, true)
- if err != nil {
- return err
- }
- defer btrfsSubVolumesDelete(targetVolume)
-
- // Dump the container to a file
- fsDump := fmt.Sprintf("%s/container.bin", tmpPath)
- err = s.doBtrfsBackup(targetVolume, finalParent, fsDump)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) doContainerBackupCreateVanilla(tmpPath string, backup backup.Backup, source instance.Instance) error {
- // Prepare for rsync
- rsync := func(oldPath string, newPath string, bwlimit string) error {
- output, err := rsync.LocalCopy(oldPath, newPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync: %s: %s", string(output), err)
- }
-
- return nil
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- // Handle snapshots
- if !backup.InstanceOnly() {
- snapshotsPath := fmt.Sprintf("%s/snapshots", tmpPath)
-
- // Retrieve the snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- // Create the snapshot path
- if len(snapshots) > 0 {
- err = os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return err
- }
- }
-
- for _, snap := range snapshots {
- _, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
-
- // Mount the snapshot to a usable path
- _, err := s.ContainerSnapshotStart(snap)
- if err != nil {
- return err
- }
-
- snapshotMntPoint := driver.GetSnapshotMountPoint(snap.Project(), s.pool.Name, snap.Name())
- target := fmt.Sprintf("%s/%s", snapshotsPath, snapName)
-
- // Copy the snapshot
- err = rsync(snapshotMntPoint, target, bwlimit)
- s.ContainerSnapshotStop(snap)
- if err != nil {
- return err
- }
- }
- }
-
- // Make a temporary copy of the container
- sourceVolume := driver.GetContainerMountPoint(source.Project(), s.pool.Name, source.Name())
- containersPath := driver.GetContainerMountPoint("default", s.pool.Name, "")
- tmpContainerMntPoint, err := ioutil.TempDir(containersPath, source.Name())
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
-
- targetVolume := fmt.Sprintf("%s/.backup", tmpContainerMntPoint)
- err = s.btrfsPoolVolumesSnapshot(sourceVolume, targetVolume, true, true)
- if err != nil {
- return err
- }
- defer btrfsSubVolumesDelete(targetVolume)
-
- // Copy the container
- containerPath := fmt.Sprintf("%s/container", tmpPath)
- err = rsync(targetVolume, containerPath, bwlimit)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) ContainerBackupCreate(path string, backup backup.Backup, source instance.Instance) error {
- var err error
-
- // Generate the actual backup
- if backup.OptimizedStorage() {
- err = s.doContainerBackupCreateOptimized(path, backup, source)
- if err != nil {
- return err
- }
- } else {
- err := s.doContainerBackupCreateVanilla(path, backup, source)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageBtrfs) doContainerBackupLoadOptimized(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- containerName, _, _ := shared.InstanceGetParentAndSnapshotName(info.Name)
-
- containerMntPoint := driver.GetContainerMountPoint("default", s.pool.Name, "")
- unpackDir, err := ioutil.TempDir(containerMntPoint, containerName)
- if err != nil {
- return err
- }
- defer os.RemoveAll(unpackDir)
-
- err = os.Chmod(unpackDir, 0100)
- if err != nil {
- return err
- }
-
- unpackPath := fmt.Sprintf("%s/.backup_unpack", unpackDir)
- err = os.MkdirAll(unpackPath, 0711)
- if err != nil {
- return err
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=1",
- "-C", unpackPath, "backup",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- logger.Errorf("Failed to untar \"%s\" into \"%s\": %s", "backup", unpackPath, err)
- return err
- }
-
- for _, snapshotOnlyName := range info.Snapshots {
- snapshotBackup := fmt.Sprintf("%s/snapshots/%s.bin", unpackPath, snapshotOnlyName)
- feeder, err := os.Open(snapshotBackup)
- if err != nil {
- return err
- }
-
- // create mountpoint
- snapshotMntPoint := driver.GetSnapshotMountPoint(info.Project, s.pool.Name, containerName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(info.Project, containerName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, containerName))
- err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- feeder.Close()
- return err
- }
-
- // /var/lib/lxd/storage-pools/<pool>/snapshots/<container>/
- btrfsRecvCmd := exec.Command("btrfs", "receive", "-e", snapshotMntPoint)
- btrfsRecvCmd.Stdin = feeder
- msg, err := btrfsRecvCmd.CombinedOutput()
- feeder.Close()
- if err != nil {
- logger.Errorf("Failed to receive contents of btrfs backup \"%s\": %s", snapshotBackup, string(msg))
- return err
- }
- }
-
- containerBackupFile := fmt.Sprintf("%s/container.bin", unpackPath)
- feeder, err := os.Open(containerBackupFile)
- if err != nil {
- return err
- }
- defer feeder.Close()
-
- // /var/lib/lxd/storage-pools/<pool>/containers/
- btrfsRecvCmd := exec.Command("btrfs", "receive", "-vv", "-e", unpackDir)
- btrfsRecvCmd.Stdin = feeder
- msg, err := btrfsRecvCmd.CombinedOutput()
- if err != nil {
- logger.Errorf("Failed to receive contents of btrfs backup \"%s\": %s", containerBackupFile, string(msg))
- return err
- }
- tmpContainerMntPoint := fmt.Sprintf("%s/.backup", unpackDir)
- defer btrfsSubVolumesDelete(tmpContainerMntPoint)
-
- containerMntPoint = driver.GetContainerMountPoint(info.Project, s.pool.Name, info.Name)
- err = s.btrfsPoolVolumesSnapshot(tmpContainerMntPoint, containerMntPoint, false, true)
- if err != nil {
- logger.Errorf("Failed to create btrfs snapshot \"%s\" of \"%s\": %s", tmpContainerMntPoint, containerMntPoint, err)
- return err
- }
-
- // Create mountpoints
- err = driver.CreateContainerMountpoint(containerMntPoint, shared.VarPath("containers", project.Prefix(info.Project, info.Name)), info.Privileged)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) doContainerBackupLoadVanilla(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- // create the main container
- err := s.doContainerCreate(info.Project, info.Name, info.Privileged)
- if err != nil {
- return err
- }
-
- containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, info.Name)
- // Extract container
- for _, snap := range info.Snapshots {
- cur := fmt.Sprintf("backup/snapshots/%s", snap)
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--recursive-unlink",
- "--xattrs-include=*",
- "--strip-components=3",
- "-C", containerMntPoint, cur,
- }...)
-
- // Extract snapshots
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- logger.Errorf("Failed to untar \"%s\" into \"%s\": %s", cur, containerMntPoint, err)
- return err
- }
-
- // create snapshot
- err = s.doContainerSnapshotCreate(info.Project, fmt.Sprintf("%s/%s", info.Name, snap), info.Name)
- if err != nil {
- return err
- }
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=2",
- "--xattrs-include=*",
- "-C", containerMntPoint, "backup/container",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- logger.Errorf("Failed to untar \"backup/container\" into \"%s\": %s", containerMntPoint, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) ContainerBackupLoad(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- logger.Debugf("Loading BTRFS storage volume for backup \"%s\" on storage pool \"%s\"", info.Name, s.pool.Name)
-
- if info.OptimizedStorage {
- return s.doContainerBackupLoadOptimized(info, data, tarArgs)
- }
-
- return s.doContainerBackupLoadVanilla(info, data, tarArgs)
-}
-
-func (s *storageBtrfs) ImageCreate(fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- // Create the subvolume.
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- err = s.createImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- // We can only create the btrfs subvolume under the mounted storage
- // pool. The on-disk layout for images on a btrfs storage pool will thus
- // be
- // ${LXD_DIR}/storage-pools/<pool>/images/. The btrfs tool will
- // complain if the intermediate path does not exist, so create it if it
- // doesn't already.
- imageSubvolumePath := s.getImageSubvolumePath(s.pool.Name)
- if !shared.PathExists(imageSubvolumePath) {
- err := os.MkdirAll(imageSubvolumePath, driver.ImagesDirMode)
- if err != nil {
- return err
- }
- }
-
- // Create a temporary rw btrfs subvolume. From this rw subvolume we'll
- // create a ro snapshot below. The path with which we do this is
- // ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>@<pool>_tmp.
- imageSubvolumeName := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- tmpImageSubvolumeName := fmt.Sprintf("%s_tmp", imageSubvolumeName)
- err = btrfsSubVolumeCreate(tmpImageSubvolumeName)
- if err != nil {
- return err
- }
- // Delete volume on error.
- undo := true
- defer func() {
- if undo {
- btrfsSubVolumesDelete(tmpImageSubvolumeName)
- }
- }()
-
- // Unpack the image in imageMntPoint.
- imagePath := shared.VarPath("images", fingerprint)
- err = driver.ImageUnpack(imagePath, tmpImageSubvolumeName, "", false, s.s.OS.RunningInUserNS, tracker)
- if err != nil {
- return err
- }
-
- // Now create a read-only snapshot of the subvolume.
- // The path with which we do this is
- // ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>.
- err = s.btrfsPoolVolumesSnapshot(tmpImageSubvolumeName, imageSubvolumeName, true, true)
- if err != nil {
- return err
- }
-
- defer func() {
- if undo {
- btrfsSubVolumesDelete(imageSubvolumeName)
- }
- }()
-
- err = btrfsSubVolumesDelete(tmpImageSubvolumeName)
- if err != nil {
- return err
- }
-
- undo = false
-
- logger.Debugf("Created BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) ImageDelete(fingerprint string) error {
- logger.Debugf("Deleting BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- // Delete the btrfs subvolume. The path with which we
- // do this is ${LXD_DIR}/storage-pools/<pool>/images/<fingerprint>.
- imageSubvolumeName := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if shared.PathExists(imageSubvolumeName) && isBtrfsSubVolume(imageSubvolumeName) {
- err = btrfsSubVolumesDelete(imageSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- err = s.deleteImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- // Now delete the mountpoint for the image:
- // ${LXD_DIR}/images/<fingerprint>.
- if shared.PathExists(imageSubvolumeName) {
- err := os.RemoveAll(imageSubvolumeName)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- }
-
- logger.Debugf("Deleted BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) ImageMount(fingerprint string) (bool, error) {
- logger.Debugf("Mounting BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- // The storage pool must be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Mounted BTRFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return true, nil
-}
-
-func (s *storageBtrfs) ImageUmount(fingerprint string) (bool, error) {
- return true, nil
-}
-
-func btrfsSubVolumeCreate(subvol string) error {
- parentDestPath := filepath.Dir(subvol)
- if !shared.PathExists(parentDestPath) {
- err := os.MkdirAll(parentDestPath, 0711)
- if err != nil {
- return err
- }
- }
-
- _, err := shared.RunCommand(
- "btrfs",
- "subvolume",
- "create",
- subvol)
- if err != nil {
- logger.Errorf("Failed to create BTRFS subvolume \"%s\": %v", subvol, err)
- return err
- }
-
- return nil
-}
-
-var btrfsErrNoQuota = fmt.Errorf("Quotas disabled on filesystem")
-var btrfsErrNoQGroup = fmt.Errorf("Unable to find quota group")
-
-func btrfsSubVolumeQGroup(subvol string) (string, error) {
- output, err := shared.RunCommand(
- "btrfs",
- "qgroup",
- "show",
- "-e",
- "-f",
- subvol)
-
- if err != nil {
- return "", btrfsErrNoQuota
- }
-
- var qgroup string
- for _, line := range strings.Split(output, "\n") {
- if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
- continue
- }
-
- fields := strings.Fields(line)
- if len(fields) != 4 {
- continue
- }
-
- qgroup = fields[0]
- }
-
- if qgroup == "" {
- return "", btrfsErrNoQGroup
- }
-
- return qgroup, nil
-}
-
-func (s *storageBtrfs) btrfsPoolVolumeQGroupUsage(subvol string) (int64, error) {
- output, err := shared.RunCommand(
- "btrfs",
- "qgroup",
- "show",
- "-e",
- "-f",
- subvol)
-
- if err != nil {
- return -1, fmt.Errorf("BTRFS quotas not supported. Try enabling them with \"btrfs quota enable\"")
- }
-
- for _, line := range strings.Split(output, "\n") {
- if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
- continue
- }
-
- fields := strings.Fields(line)
- if len(fields) != 4 {
- continue
- }
-
- usage, err := strconv.ParseInt(fields[2], 10, 64)
- if err != nil {
- continue
- }
-
- return usage, nil
- }
-
- return -1, fmt.Errorf("Unable to find current qgroup usage")
-}
-
-func btrfsSubVolumeDelete(subvol string) error {
- // Attempt (but don't fail on) to delete any qgroup on the subvolume
- qgroup, err := btrfsSubVolumeQGroup(subvol)
- if err == nil {
- shared.RunCommand(
- "btrfs",
- "qgroup",
- "destroy",
- qgroup,
- subvol)
- }
-
- // Attempt to make the subvolume writable
- shared.RunCommand("btrfs", "property", "set", subvol, "ro", "false")
-
- // Delete the subvolume itself
- _, err = shared.RunCommand(
- "btrfs",
- "subvolume",
- "delete",
- subvol)
-
- return err
-}
-
-// btrfsPoolVolumesDelete is the recursive variant on btrfsPoolVolumeDelete,
-// it first deletes subvolumes of the subvolume and then the
-// subvolume itself.
-func btrfsSubVolumesDelete(subvol string) error {
- // Delete subsubvols.
- subsubvols, err := btrfsSubVolumesGet(subvol)
- if err != nil {
- return err
- }
- sort.Sort(sort.Reverse(sort.StringSlice(subsubvols)))
-
- for _, subsubvol := range subsubvols {
- err := btrfsSubVolumeDelete(path.Join(subvol, subsubvol))
- if err != nil {
- return err
- }
- }
-
- // Delete the subvol itself
- err = btrfsSubVolumeDelete(subvol)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-/*
- * btrfsSnapshot creates a snapshot of "source" to "dest"
- * the result will be readonly if "readonly" is True.
- */
-func btrfsSnapshot(s *state.State, source string, dest string, readonly bool) error {
- var output string
- var err error
- if readonly && !s.OS.RunningInUserNS {
- output, err = shared.RunCommand(
- "btrfs",
- "subvolume",
- "snapshot",
- "-r",
- source,
- dest)
- } else {
- output, err = shared.RunCommand(
- "btrfs",
- "subvolume",
- "snapshot",
- source,
- dest)
- }
- if err != nil {
- return fmt.Errorf(
- "subvolume snapshot failed, source=%s, dest=%s, output=%s",
- source,
- dest,
- output,
- )
- }
-
- return err
-}
-
-func (s *storageBtrfs) btrfsPoolVolumeSnapshot(source string, dest string, readonly bool) error {
- return btrfsSnapshot(s.s, source, dest, readonly)
-}
-
-func (s *storageBtrfs) btrfsPoolVolumesSnapshot(source string, dest string, readonly bool, recursive bool) error {
- // Now snapshot all subvolumes of the root.
- if recursive {
- // Get a list of subvolumes of the root
- subsubvols, err := btrfsSubVolumesGet(source)
- if err != nil {
- return err
- }
- sort.Sort(sort.StringSlice(subsubvols))
-
- if len(subsubvols) > 0 && readonly {
- // A root with subvolumes can never be readonly,
- // also don't make subvolumes readonly.
- readonly = false
-
- logger.Warnf("Subvolumes detected, ignoring ro flag")
- }
-
- // First snapshot the root
- err = s.btrfsPoolVolumeSnapshot(source, dest, readonly)
- if err != nil {
- return err
- }
-
- for _, subsubvol := range subsubvols {
- // Clear the target for the subvol to use
- os.Remove(path.Join(dest, subsubvol))
-
- err := s.btrfsPoolVolumeSnapshot(path.Join(source, subsubvol), path.Join(dest, subsubvol), readonly)
- if err != nil {
- return err
- }
- }
- } else {
- err := s.btrfsPoolVolumeSnapshot(source, dest, readonly)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-// isBtrfsSubVolume returns true if the given Path is a btrfs subvolume else
-// false.
-func isBtrfsSubVolume(subvolPath string) bool {
- fs := unix.Stat_t{}
- err := unix.Lstat(subvolPath, &fs)
- if err != nil {
- return false
- }
-
- // Check if BTRFS_FIRST_FREE_OBJECTID
- if fs.Ino != 256 {
- return false
- }
-
- return true
-}
-
-func isBtrfsFilesystem(path string) bool {
- _, err := shared.RunCommand("btrfs", "filesystem", "show", path)
- if err != nil {
- return false
- }
-
- return true
-}
-
-func isOnBtrfs(path string) bool {
- fs := unix.Statfs_t{}
-
- err := unix.Statfs(path, &fs)
- if err != nil {
- return false
- }
-
- if fs.Type != util.FilesystemSuperMagicBtrfs {
- return false
- }
-
- return true
-}
-
-func btrfsSubVolumeIsRo(path string) bool {
- output, err := shared.RunCommand("btrfs", "property", "get", "-ts", path)
- if err != nil {
- return false
- }
-
- return strings.HasPrefix(string(output), "ro=true")
-}
-
-func btrfsSubVolumeMakeRo(path string) error {
- _, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", "true")
- return err
-}
-
-func btrfsSubVolumeMakeRw(path string) error {
- _, err := shared.RunCommand("btrfs", "property", "set", "-ts", path, "ro", "false")
- return err
-}
-
-func btrfsSubVolumesGet(path string) ([]string, error) {
- result := []string{}
-
- if !strings.HasSuffix(path, "/") {
- path = path + "/"
- }
-
- // Unprivileged users can't get to fs internals
- filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
- // Skip walk errors
- if err != nil {
- return nil
- }
-
- // Ignore the base path
- if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
- return nil
- }
-
- // Subvolumes can only be directories
- if !fi.IsDir() {
- return nil
- }
-
- // Check if a btrfs subvolume
- if isBtrfsSubVolume(fpath) {
- result = append(result, strings.TrimPrefix(fpath, path))
- }
-
- return nil
- })
-
- return result, nil
-}
-
-func (s *storageBtrfs) MigrationType() migration.MigrationFSType {
- if s.s.OS.RunningInUserNS {
- return migration.MigrationFSType_RSYNC
- }
-
- return migration.MigrationFSType_BTRFS
-}
-
-func (s *storageBtrfs) PreservesInodes() bool {
- if s.s.OS.RunningInUserNS {
- return false
- }
-
- return true
-}
-
-func (s *storageBtrfs) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- if s.s.OS.RunningInUserNS {
- return rsyncMigrationSource(args)
- }
-
- /* List all the snapshots in order of reverse creation. The idea here
- * is that we send the oldest to newest snapshot, hopefully saving on
- * xfer costs. Then, after all that, we send the container itself.
- */
- var err error
- var snapshots = []instance.Instance{}
- if !args.InstanceOnly {
- snapshots, err = args.Instance.Snapshots()
- if err != nil {
- return nil, err
- }
- }
-
- sourceDriver := &btrfsMigrationSourceDriver{
- container: args.Instance,
- snapshots: snapshots,
- btrfsSnapshotNames: []string{},
- btrfs: s,
- }
-
- if !args.InstanceOnly {
- for _, snap := range snapshots {
- btrfsPath := driver.GetSnapshotMountPoint(snap.Project(), s.pool.Name, snap.Name())
- sourceDriver.btrfsSnapshotNames = append(sourceDriver.btrfsSnapshotNames, btrfsPath)
- }
- }
-
- return sourceDriver, nil
-}
-
-func (s *storageBtrfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- if s.s.OS.RunningInUserNS {
- return rsyncMigrationSink(conn, op, args)
- }
-
- btrfsRecv := func(snapName string, btrfsPath string, targetPath string, isSnapshot bool, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
- args := []string{"receive", "-e", btrfsPath}
- cmd := exec.Command("btrfs", args...)
-
- // Remove the existing pre-created subvolume
- err := btrfsSubVolumesDelete(targetPath)
- if err != nil {
- logger.Errorf("Failed to delete pre-created BTRFS subvolume: %s: %v", btrfsPath, err)
- return err
- }
-
- stdin, err := cmd.StdinPipe()
- if err != nil {
- return err
- }
-
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return err
- }
-
- err = cmd.Start()
- if err != nil {
- return err
- }
-
- writePipe := io.WriteCloser(stdin)
- if writeWrapper != nil {
- writePipe = writeWrapper(stdin)
- }
-
- <-shared.WebsocketRecvStream(writePipe, conn)
-
- output, err := ioutil.ReadAll(stderr)
- if err != nil {
- logger.Debugf("Problem reading btrfs receive stderr %s", err)
- }
-
- err = cmd.Wait()
- if err != nil {
- logger.Errorf("Problem with btrfs receive: %s", string(output))
- return err
- }
-
- receivedSnapshot := fmt.Sprintf("%s/.migration-send", btrfsPath)
- // handle older lxd versions
- if !shared.PathExists(receivedSnapshot) {
- receivedSnapshot = fmt.Sprintf("%s/.root", btrfsPath)
- }
- if isSnapshot {
- receivedSnapshot = fmt.Sprintf("%s/%s", btrfsPath, snapName)
- err = s.btrfsPoolVolumesSnapshot(receivedSnapshot, targetPath, true, true)
- } else {
- err = s.btrfsPoolVolumesSnapshot(receivedSnapshot, targetPath, false, true)
- }
- if err != nil {
- logger.Errorf("Problem with btrfs snapshot: %s", err)
- return err
- }
-
- err = btrfsSubVolumesDelete(receivedSnapshot)
- if err != nil {
- logger.Errorf("Failed to delete BTRFS subvolume \"%s\": %s", btrfsPath, err)
- return err
- }
-
- return nil
- }
-
- instanceName := args.Instance.Name()
-
- if args.Instance.Type() != instancetype.Container {
- return fmt.Errorf("Instance type must be container")
- }
-
- ct := args.Instance.(*containerLXC)
-
- _, instancePool, _ := ct.Storage().GetContainerPoolInfo()
- containersPath := driver.GetSnapshotMountPoint(args.Instance.Project(), instancePool, instanceName)
- if !args.InstanceOnly && len(args.Snapshots) > 0 {
- err := os.MkdirAll(containersPath, driver.ContainersDirMode)
- if err != nil {
- return err
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", instancePool, "containers-snapshots", project.Prefix(args.Instance.Project(), instanceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Instance.Project(), instanceName))
- if !shared.PathExists(snapshotMntPointSymlink) {
- err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- // At this point we have already figured out the parent
- // instances's root disk device so we can simply
- // retrieve it from the expanded devices.
- parentStoragePool := ""
- parentExpandedDevices := args.Instance.ExpandedDevices()
- parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := shared.GetRootDiskDevice(parentExpandedDevices.CloneNative())
- if parentLocalRootDiskDeviceKey != "" {
- parentStoragePool = parentLocalRootDiskDevice["pool"]
- }
-
- // A little neuroticism.
- if parentStoragePool == "" {
- return fmt.Errorf("Detected that the container's root device is missing the pool property during BTRFS migration")
- }
-
- if !args.InstanceOnly {
- for _, snap := range args.Snapshots {
- ctArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), instanceName, snap)
-
- // Ensure that snapshot and parent container have the
- // same storage pool in their local root disk device.
- // If the root disk device for the snapshot comes from a
- // profile on the new instance as well we don't need to
- // do anything.
- if ctArgs.Devices != nil {
- snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(ctArgs.Devices.CloneNative())
- if snapLocalRootDiskDeviceKey != "" {
- ctArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
- }
- }
-
- snapshotMntPoint := driver.GetSnapshotMountPoint(args.Instance.Project(), instancePool, ctArgs.Name)
- _, err := containerCreateEmptySnapshot(args.Instance.DaemonState(), ctArgs)
- if err != nil {
- return err
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(args.Instance.Project(), instanceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Instance.Project(), instanceName))
- err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- tmpSnapshotMntPoint, err := ioutil.TempDir(containersPath, project.Prefix(args.Instance.Project(), instanceName))
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpSnapshotMntPoint)
-
- err = os.Chmod(tmpSnapshotMntPoint, 0100)
- if err != nil {
- return err
- }
-
- wrapper := migration.ProgressWriter(op, "fs_progress", *snap.Name)
- err = btrfsRecv(*(snap.Name), tmpSnapshotMntPoint, snapshotMntPoint, true, wrapper)
- if err != nil {
- return err
- }
- }
- }
-
- /* finally, do the real instance */
- containersMntPoint := driver.GetContainerMountPoint("default", s.pool.Name, "")
- tmpContainerMntPoint, err := ioutil.TempDir(containersMntPoint, project.Prefix(args.Instance.Project(), instanceName))
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
-
- wrapper := migration.ProgressWriter(op, "fs_progress", instanceName)
- containerMntPoint := driver.GetContainerMountPoint(args.Instance.Project(), s.pool.Name, instanceName)
- err = btrfsRecv("", tmpContainerMntPoint, containerMntPoint, false, wrapper)
- if err != nil {
- return err
- }
-
- if args.Live {
- err = btrfsRecv("", tmpContainerMntPoint, containerMntPoint, false, wrapper)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageBtrfs) btrfsLookupFsUUID(fs string) (string, error) {
- output, err := shared.RunCommand(
- "btrfs",
- "filesystem",
- "show",
- "--raw",
- fs)
- if err != nil {
- return "", fmt.Errorf("failed to detect UUID")
- }
-
- outputString := output
- idx := strings.Index(outputString, "uuid: ")
- outputString = outputString[idx+6:]
- outputString = strings.TrimSpace(outputString)
- idx = strings.Index(outputString, "\t")
- outputString = outputString[:idx]
- outputString = strings.Trim(outputString, "\n")
-
- return outputString, nil
-}
-
-func (s *storageBtrfs) StorageEntitySetQuota(volumeType int, size int64, data interface{}) error {
- logger.Debugf(`Setting BTRFS quota for "%s"`, s.volume.Name)
-
- var c instance.Instance
- var subvol string
- switch volumeType {
- case storagePoolVolumeTypeContainer:
- c = data.(instance.Instance)
- subvol = driver.GetContainerMountPoint(c.Project(), s.pool.Name, c.Name())
- case storagePoolVolumeTypeCustom:
- subvol = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- qgroup, err := btrfsSubVolumeQGroup(subvol)
- if err != nil && !s.s.OS.RunningInUserNS {
- var output string
-
- if err == btrfsErrNoQuota {
- // Enable quotas
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- _, err = shared.RunCommand("btrfs", "quota", "enable", poolMntPoint)
- if err != nil {
- return fmt.Errorf("Failed to enable quotas on BTRFS pool: %v", err)
- }
-
- // Retry
- qgroup, err = btrfsSubVolumeQGroup(subvol)
- }
-
- if err == btrfsErrNoQGroup {
- // Find the volume ID
- _, err = shared.RunCommand("btrfs", "subvolume", "show", subvol)
- if err != nil {
- return fmt.Errorf("Failed to get subvol information: %v", err)
- }
-
- id := ""
- for _, line := range strings.Split(output, "\n") {
- line = strings.TrimSpace(line)
- if strings.HasPrefix(line, "Subvolume ID:") {
- fields := strings.Split(line, ":")
- id = strings.TrimSpace(fields[len(fields)-1])
- }
- }
-
- if id == "" {
- return fmt.Errorf("Failed to find subvolume id")
- }
-
- // Create qgroup
- _, err = shared.RunCommand("btrfs", "qgroup", "create", fmt.Sprintf("0/%s", id), subvol)
- if err != nil {
- return fmt.Errorf("Failed to create missing qgroup: %v", err)
- }
-
- // Retry
- qgroup, err = btrfsSubVolumeQGroup(subvol)
- }
-
- if err != nil {
- return err
- }
- }
-
- // Attempt to make the subvolume writable
- shared.RunCommand("btrfs", "property", "set", subvol, "ro", "false")
- if size > 0 {
- _, err := shared.RunCommand(
- "btrfs",
- "qgroup",
- "limit",
- "-e", fmt.Sprintf("%d", size),
- subvol)
-
- if err != nil {
- return fmt.Errorf("Failed to set btrfs quota: %v", err)
- }
- } else if qgroup != "" {
- _, err := shared.RunCommand(
- "btrfs",
- "qgroup",
- "destroy",
- qgroup,
- subvol)
-
- if err != nil {
- return fmt.Errorf("Failed to set btrfs quota: %v", err)
- }
- }
-
- logger.Debugf(`Set BTRFS quota for "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolResources() (*api.ResourcesStoragePool, error) {
- ourMount, err := s.StoragePoolMount()
- if err != nil {
- return nil, err
- }
- if ourMount {
- defer s.StoragePoolUmount()
- }
-
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
-
- // Inode allocation is dynamic so no use in reporting them.
-
- return driver.GetStorageResource(poolMntPoint)
-}
-
-func (s *storageBtrfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) error {
- logger.Infof("Copying BTRFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
- successMsg := fmt.Sprintf("Copied BTRFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
-
- // The storage pool needs to be mounted.
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
-
- if s.pool.Name != source.Pool {
- return s.doCrossPoolVolumeCopy(source.Pool, source.Name, source.VolumeOnly)
- }
-
- err = s.copyVolume(source.Pool, source.Name, s.volume.Name, source.VolumeOnly)
- if err != nil {
- logger.Errorf("Failed to create BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- if source.VolumeOnly {
- logger.Infof(successMsg)
- return nil
- }
-
- subvols, err := btrfsSubVolumesGet(s.getCustomSnapshotSubvolumePath(source.Pool))
- if err != nil {
- return err
- }
-
- for _, snapOnlyName := range subvols {
- snap := fmt.Sprintf("%s/%s", source.Name, snapOnlyName)
-
- err := s.copyVolume(source.Pool, snap, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName), false)
- if err != nil {
- logger.Errorf("Failed to create BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
- }
-
- logger.Infof(successMsg)
- return nil
-}
-
-func (s *storageBtrfs) copyVolume(sourcePool string, sourceName string, targetName string, volumeOnly bool) error {
- var customDir string
- var srcMountPoint string
- var dstMountPoint string
-
- isSrcSnapshot := shared.IsSnapshot(sourceName)
- isDstSnapshot := shared.IsSnapshot(targetName)
-
- if isSrcSnapshot {
- srcMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, sourceName)
- } else {
- srcMountPoint = driver.GetStoragePoolVolumeMountPoint(sourcePool, sourceName)
- }
-
- if isDstSnapshot {
- dstMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, targetName)
- } else {
- dstMountPoint = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, targetName)
- }
-
- // Ensure that the directories immediately preceding the subvolume directory exist.
- if isDstSnapshot {
- volName, _, _ := shared.InstanceGetParentAndSnapshotName(targetName)
- customDir = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, volName)
- } else {
- customDir = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, "")
- }
-
- if !shared.PathExists(customDir) {
- err := os.MkdirAll(customDir, driver.CustomDirMode)
- if err != nil {
- logger.Errorf("Failed to create directory \"%s\" for storage volume \"%s\" on storage pool \"%s\": %s", customDir, s.volume.Name, s.pool.Name, err)
- return err
- }
- }
-
- err := s.btrfsPoolVolumesSnapshot(srcMountPoint, dstMountPoint, false, true)
- if err != nil {
- logger.Errorf("Failed to create BTRFS snapshot for storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) doCrossPoolVolumeCopy(sourcePool string, sourceName string, volumeOnly bool) error {
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, sourceName, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
-
- err = s.StoragePoolVolumeCreate()
- if err != nil {
- return err
- }
-
- destVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- if !volumeOnly {
- // Handle snapshots
- snapshots, err := driver.VolumeSnapshotsGet(s.s, sourcePool, sourceName, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- for _, snap := range snapshots {
- srcSnapshotMntPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, snap.Name)
-
- _, err = rsync.LocalCopy(srcSnapshotMntPoint, destVolumeMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- // create snapshot
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
-
- err = s.doVolumeSnapshotCreate(s.pool.Name, s.volume.Name, fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName))
- if err != nil {
- return err
- }
- }
- }
-
- var srcVolumeMntPoint string
-
- if shared.IsSnapshot(sourceName) {
- // copy snapshot to volume
- srcVolumeMntPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, sourceName)
- } else {
- // copy volume to volume
- srcVolumeMntPoint = driver.GetStoragePoolVolumeMountPoint(sourcePool, sourceName)
- }
-
- _, err = rsync.LocalCopy(srcVolumeMntPoint, destVolumeMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into BTRFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageBtrfs) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncStorageMigrationSource(args)
-}
-
-func (s *storageBtrfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncStorageMigrationSink(conn, op, args)
-}
-
-func (s *storageBtrfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
- logger.Infof("Creating BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- err := s.doVolumeSnapshotCreate(s.pool.Name, s.volume.Name, target.Name)
- if err != nil {
- return err
- }
-
- logger.Infof("Created BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) doVolumeSnapshotCreate(sourcePool string, sourceName string, targetName string) error {
- // Create subvolume path on the storage pool.
- customSubvolumePath := s.getCustomSubvolumePath(s.pool.Name)
-
- err := os.MkdirAll(customSubvolumePath, 0700)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- _, _, ok := shared.InstanceGetParentAndSnapshotName(targetName)
- if !ok {
- return err
- }
-
- customSnapshotSubvolumeName := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
-
- err = os.MkdirAll(customSnapshotSubvolumeName, driver.SnapshotsDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- sourcePath := driver.GetStoragePoolVolumeMountPoint(sourcePool, sourceName)
- targetPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, targetName)
-
- return s.btrfsPoolVolumesSnapshot(sourcePath, targetPath, true, true)
-}
-
-func (s *storageBtrfs) StoragePoolVolumeSnapshotDelete() error {
- logger.Infof("Deleting BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- snapshotSubvolumeName := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- if shared.PathExists(snapshotSubvolumeName) && isBtrfsSubVolume(snapshotSubvolumeName) {
- err := btrfsSubVolumesDelete(snapshotSubvolumeName)
- if err != nil {
- return err
- }
- }
-
- err := os.RemoveAll(snapshotSubvolumeName)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- storageVolumeSnapshotPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
- empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
- if err == nil && empty {
- err := os.RemoveAll(storageVolumeSnapshotPath)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for BTRFS storage volume "%s" on storage pool "%s"`,
- s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted BTRFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageBtrfs) StoragePoolVolumeSnapshotRename(newName string) error {
- sourceName, _, ok := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
-
- logger.Infof("Renaming BTRFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- oldPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
-
- err := os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof("Renamed BTRFS storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
-}
diff --git a/lxd/storage_migration_btrfs.go b/lxd/storage_migration_btrfs.go
deleted file mode 100644
index 1f52f3076e..0000000000
--- a/lxd/storage_migration_btrfs.go
+++ /dev/null
@@ -1,195 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
-
- "github.com/gorilla/websocket"
-
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- driver "github.com/lxc/lxd/lxd/storage"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/logger"
-)
-
-type btrfsMigrationSourceDriver struct {
- container instance.Instance
- snapshots []instance.Instance
- btrfsSnapshotNames []string
- btrfs *storageBtrfs
- runningSnapName string
- stoppedSnapName string
-}
-
-func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string, btrfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
- args := []string{"send"}
- if btrfsParent != "" {
- args = append(args, "-p", btrfsParent)
- }
- args = append(args, btrfsPath)
-
- cmd := exec.Command("btrfs", args...)
-
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return err
- }
-
- readPipe := io.ReadCloser(stdout)
- if readWrapper != nil {
- readPipe = readWrapper(stdout)
- }
-
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return err
- }
-
- err = cmd.Start()
- if err != nil {
- return err
- }
-
- <-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
-
- output, err := ioutil.ReadAll(stderr)
- if err != nil {
- logger.Errorf("Problem reading btrfs send stderr: %s", err)
- }
-
- err = cmd.Wait()
- if err != nil {
- logger.Errorf("Problem with btrfs send: %s", string(output))
- }
-
- return err
-}
-
-func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
- if s.container.Type() != instancetype.Container {
- return fmt.Errorf("Instance type must be container")
- }
-
- ct := s.container.(*containerLXC)
-
- _, containerPool, _ := ct.Storage().GetContainerPoolInfo()
- containerName := s.container.Name()
- containersPath := driver.GetContainerMountPoint("default", containerPool, "")
- sourceName := containerName
-
- // Deal with sending a snapshot to create a container on another LXD
- // instance.
- if s.container.IsSnapshot() {
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
- snapshotsPath := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, sourceName)
- tmpContainerMntPoint, err := ioutil.TempDir(snapshotsPath, sourceName)
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
-
- migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
- snapshotMntPoint := driver.GetSnapshotMountPoint(s.container.Project(), containerPool, containerName)
- err = s.btrfs.btrfsPoolVolumesSnapshot(snapshotMntPoint, migrationSendSnapshot, true, true)
- if err != nil {
- return err
- }
- defer btrfsSubVolumesDelete(migrationSendSnapshot)
-
- wrapper := migration.ProgressReader(op, "fs_progress", containerName)
- return s.send(conn, migrationSendSnapshot, "", wrapper)
- }
-
- if !containerOnly {
- for i, snap := range s.snapshots {
- prev := ""
- if i > 0 {
- prev = driver.GetSnapshotMountPoint(snap.Project(), containerPool, s.snapshots[i-1].Name())
- }
-
- snapMntPoint := driver.GetSnapshotMountPoint(snap.Project(), containerPool, snap.Name())
- wrapper := migration.ProgressReader(op, "fs_progress", snap.Name())
- if err := s.send(conn, snapMntPoint, prev, wrapper); err != nil {
- return err
- }
- }
- }
-
- tmpContainerMntPoint, err := ioutil.TempDir(containersPath, containerName)
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
-
- migrationSendSnapshot := fmt.Sprintf("%s/.migration-send", tmpContainerMntPoint)
- containerMntPoint := driver.GetContainerMountPoint(s.container.Project(), containerPool, sourceName)
- err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPoint, migrationSendSnapshot, true, true)
- if err != nil {
- return err
- }
- defer btrfsSubVolumesDelete(migrationSendSnapshot)
-
- btrfsParent := ""
- if len(s.btrfsSnapshotNames) > 0 {
- btrfsParent = s.btrfsSnapshotNames[len(s.btrfsSnapshotNames)-1]
- }
-
- wrapper := migration.ProgressReader(op, "fs_progress", containerName)
- return s.send(conn, migrationSendSnapshot, btrfsParent, wrapper)
-}
-
-func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
- tmpPath := driver.GetSnapshotMountPoint(s.container.Project(), s.btrfs.pool.Name,
- fmt.Sprintf("%s/.migration-send", s.container.Name()))
- err := os.MkdirAll(tmpPath, 0711)
- if err != nil {
- return err
- }
-
- err = os.Chmod(tmpPath, 0100)
- if err != nil {
- return err
- }
-
- s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
- parentName, _, _ := shared.InstanceGetParentAndSnapshotName(s.container.Name())
- containerMntPt := driver.GetContainerMountPoint(s.container.Project(), s.btrfs.pool.Name, parentName)
- err = s.btrfs.btrfsPoolVolumesSnapshot(containerMntPt, s.stoppedSnapName, true, true)
- if err != nil {
- return err
- }
-
- return s.send(conn, s.stoppedSnapName, s.runningSnapName, nil)
-}
-
-func (s *btrfsMigrationSourceDriver) Cleanup() {
- if s.stoppedSnapName != "" {
- btrfsSubVolumesDelete(s.stoppedSnapName)
- }
-
- if s.runningSnapName != "" {
- btrfsSubVolumesDelete(s.runningSnapName)
- }
-}
-
-func (s *btrfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
- msg := fmt.Sprintf("Function not implemented")
- logger.Errorf(msg)
- return fmt.Errorf(msg)
-}
From 3ce8f37f6bdc9aa8915505e8a26cd5c4701a646b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 8 Jan 2020 16:34:52 -0500
Subject: [PATCH 04/17] lxd/storage: Remove legacy zfs implementation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/api_internal.go | 1 -
lxd/container_lxc.go | 19 +-
lxd/main_init_interactive.go | 14 -
lxd/migrate_container.go | 15 -
lxd/migrate_storage_volumes.go | 12 -
lxd/patches_utils.go | 185 +-
lxd/storage.go | 27 +-
lxd/storage_migration_zfs.go | 149 --
lxd/storage_zfs.go | 3342 --------------------------------
lxd/storage_zfs_utils.go | 839 --------
10 files changed, 201 insertions(+), 4402 deletions(-)
delete mode 100644 lxd/storage_migration_zfs.go
delete mode 100644 lxd/storage_zfs.go
delete mode 100644 lxd/storage_zfs_utils.go
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 8e6cab8f35..b41fbf4406 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -627,7 +627,6 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
v[len("snapshot-"):])
}
}
-
}
}
diff --git a/lxd/container_lxc.go b/lxd/container_lxc.go
index edc5f95cb1..f88e9e2044 100644
--- a/lxd/container_lxc.go
+++ b/lxd/container_lxc.go
@@ -1863,6 +1863,19 @@ func (c *containerLXC) expandDevices(profiles []api.Profile) error {
return nil
}
+func shiftZfsSkipper(dir string, absPath string, fi os.FileInfo) bool {
+ strippedPath := absPath
+ if dir != "" {
+ strippedPath = absPath[len(dir):]
+ }
+
+ if fi.IsDir() && strippedPath == "/.zfs/snapshot" {
+ return true
+ }
+
+ return false
+}
+
func shiftBtrfsRootfs(path string, diskIdmap *idmap.IdmapSet, shift bool) error {
var err error
roSubvols := []string{}
@@ -1959,7 +1972,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
if diskIdmap != nil {
if storageType == "zfs" {
- err = diskIdmap.UnshiftRootfs(c.RootfsPath(), zfsIdmapSetSkipper)
+ err = diskIdmap.UnshiftRootfs(c.RootfsPath(), shiftZfsSkipper)
} else if storageType == "btrfs" {
err = UnshiftBtrfsRootfs(c.RootfsPath(), diskIdmap)
} else {
@@ -1975,7 +1988,7 @@ func (c *containerLXC) startCommon() (string, []func() error, error) {
if nextIdmap != nil && !c.state.OS.Shiftfs {
if storageType == "zfs" {
- err = nextIdmap.ShiftRootfs(c.RootfsPath(), zfsIdmapSetSkipper)
+ err = nextIdmap.ShiftRootfs(c.RootfsPath(), shiftZfsSkipper)
} else if storageType == "btrfs" {
err = ShiftBtrfsRootfs(c.RootfsPath(), nextIdmap)
} else {
@@ -4994,7 +5007,7 @@ func (c *containerLXC) Migrate(args *CriuMigrationArgs) error {
}
if storageType == "zfs" {
- err = idmapset.ShiftRootfs(args.stateDir, zfsIdmapSetSkipper)
+ err = idmapset.ShiftRootfs(args.stateDir, shiftZfsSkipper)
} else if storageType == "btrfs" {
err = ShiftBtrfsRootfs(args.stateDir, idmapset)
} else {
diff --git a/lxd/main_init_interactive.go b/lxd/main_init_interactive.go
index 28e4ee4967..c85cc1a1a6 100644
--- a/lxd/main_init_interactive.go
+++ b/lxd/main_init_interactive.go
@@ -499,13 +499,6 @@ func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.InstanceServer, pool
}
if cli.AskBool(fmt.Sprintf("Create a new %s pool? (yes/no) [default=yes]: ", strings.ToUpper(pool.Driver)), "yes") {
- if pool.Driver == "zfs" && os.Geteuid() == 0 {
- poolVolumeExists, err := zfsPoolVolumeExists(pool.Name)
- if err == nil && poolVolumeExists {
- return fmt.Errorf("'%s' ZFS pool already exists", pool.Name)
- }
- }
-
if pool.Driver == "ceph" {
// Ask for the name of the cluster
pool.Config["ceph.cluster_name"] = cli.AskString("Name of the existing CEPH cluster [default=ceph]: ", "ceph", nil)
@@ -581,13 +574,6 @@ func (c *cmdInit) askStoragePool(config *cmdInitData, d lxd.InstanceServer, pool
question := fmt.Sprintf("Name of the existing %s pool or dataset: ", strings.ToUpper(pool.Driver))
pool.Config["source"] = cli.AskString(question, "", nil)
}
-
- if pool.Driver == "zfs" && os.Geteuid() == 0 {
- poolVolumeExists, err := zfsPoolVolumeExists(pool.Config["source"])
- if err == nil && !poolVolumeExists {
- return fmt.Errorf("'%s' ZFS pool or dataset does not exist", pool.Config["source"])
- }
- }
}
if pool.Driver == "lvm" {
diff --git a/lxd/migrate_container.go b/lxd/migrate_container.go
index 8af9bd8b38..39ded5915a 100644
--- a/lxd/migrate_container.go
+++ b/lxd/migrate_container.go
@@ -387,12 +387,6 @@ func (s *migrationSourceWs) Do(state *state.State, migrateOp *operations.Operati
Bidirectional: &hasFeature,
},
}
-
- if len(zfsVersion) >= 3 && zfsVersion[0:3] != "0.6" {
- offerHeader.ZfsFeatures = &migration.ZfsFeatures{
- Compress: &hasFeature,
- }
- }
} else {
return fmt.Errorf("Instance type not supported")
}
@@ -1037,15 +1031,6 @@ func (c *migrationSink) Do(state *state.State, migrateOp *operations.Operation)
}
}
- // Return those ZFS features we know about (with the value sent by the remote).
- if len(zfsVersion) >= 3 && zfsVersion[0:3] != "0.6" {
- if offerHeader.ZfsFeatures != nil && offerHeader.ZfsFeatures.Compress != nil {
- respHeader.ZfsFeatures = &migration.ZfsFeatures{
- Compress: offerHeader.ZfsFeatures.Compress,
- }
- }
- }
-
// If refresh mode or the storage type the source has doesn't match what we have,
// then we have to use rsync.
if c.refresh || *offerHeader.Fs != *respHeader.Fs {
diff --git a/lxd/migrate_storage_volumes.go b/lxd/migrate_storage_volumes.go
index 52a5dde446..ad2c5d15e1 100644
--- a/lxd/migrate_storage_volumes.go
+++ b/lxd/migrate_storage_volumes.go
@@ -79,12 +79,6 @@ func (s *migrationSourceWs) DoStorage(state *state.State, poolName string, volNa
},
}
- if len(zfsVersion) >= 3 && zfsVersion[0:3] != "0.6" {
- offerHeader.ZfsFeatures = &migration.ZfsFeatures{
- Compress: &hasFeature,
- }
- }
-
// Storage needs to start unconditionally now, since we need to initialize a new
// storage interface.
ourMount, err := s.storage.StoragePoolVolumeMount()
@@ -406,12 +400,6 @@ func (c *migrationSink) DoStorage(state *state.State, poolName string, req *api.
},
}
- if len(zfsVersion) >= 3 && zfsVersion[0:3] != "0.6" {
- respHeader.ZfsFeatures = &migration.ZfsFeatures{
- Compress: &hasFeature,
- }
- }
-
// If the storage type the source has doesn't match what we have, then we have to
// use rsync.
if *offerHeader.Fs != *respHeader.Fs {
diff --git a/lxd/patches_utils.go b/lxd/patches_utils.go
index 217d684973..17775e6105 100644
--- a/lxd/patches_utils.go
+++ b/lxd/patches_utils.go
@@ -8,13 +8,14 @@ import (
"sort"
"strings"
+ "github.com/pborman/uuid"
+ "github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/logger"
)
// For 'dir' storage backend.
@@ -64,7 +65,6 @@ func btrfsSubVolumeCreate(subvol string) error {
"create",
subvol)
if err != nil {
- logger.Errorf("Failed to create BTRFS subvolume \"%s\": %v", subvol, err)
return err
}
@@ -282,3 +282,184 @@ func btrfsSubVolumesGet(path string) ([]string, error) {
return result, nil
}
+
+// For 'zfs' storage backend.
+func zfsPoolListSnapshots(pool string, path string) ([]string, error) {
+ path = strings.TrimRight(path, "/")
+ fullPath := pool
+ if path != "" {
+ fullPath = fmt.Sprintf("%s/%s", pool, path)
+ }
+
+ output, err := shared.RunCommand("zfs", "list", "-t", "snapshot", "-o", "name", "-H", "-d", "1", "-s", "creation", "-r", fullPath)
+ if err != nil {
+ return []string{}, errors.Wrap(err, "Failed to list ZFS snapshots")
+ }
+
+ children := []string{}
+ for _, entry := range strings.Split(output, "\n") {
+ if entry == "" {
+ continue
+ }
+
+ if entry == fullPath {
+ continue
+ }
+
+ children = append(children, strings.SplitN(entry, "@", 2)[1])
+ }
+
+ return children, nil
+}
+
+func zfsSnapshotDeleteInternal(projectName, poolName string, ctName string, onDiskPoolName string) error {
+ sourceContainerName, sourceContainerSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(ctName)
+ snapName := fmt.Sprintf("snapshot-%s", sourceContainerSnapOnlyName)
+
+ if zfsFilesystemEntityExists(onDiskPoolName,
+ fmt.Sprintf("containers/%s@%s",
+ project.Prefix(projectName, sourceContainerName), snapName)) {
+ removable, err := zfsPoolVolumeSnapshotRemovable(onDiskPoolName,
+ fmt.Sprintf("containers/%s",
+ project.Prefix(projectName, sourceContainerName)),
+ snapName)
+ if err != nil {
+ return err
+ }
+
+ if removable {
+ err = zfsPoolVolumeSnapshotDestroy(onDiskPoolName,
+ fmt.Sprintf("containers/%s",
+ project.Prefix(projectName, sourceContainerName)),
+ snapName)
+ } else {
+ err = zfsPoolVolumeSnapshotRename(onDiskPoolName,
+ fmt.Sprintf("containers/%s",
+ project.Prefix(projectName, sourceContainerName)),
+ snapName,
+ fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
+ }
+ if err != nil {
+ return err
+ }
+ }
+
+ // Delete the snapshot on its storage pool:
+ // ${POOL}/snapshots/<snapshot_name>
+ snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, ctName)
+ if shared.PathExists(snapshotContainerMntPoint) {
+ err := os.RemoveAll(snapshotContainerMntPoint)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Check if we can remove the snapshot symlink:
+ // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
+ // by checking if the directory is empty.
+ snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
+ empty, _ := shared.PathIsEmpty(snapshotContainerPath)
+ if empty == true {
+ // Remove the snapshot directory for the container:
+ // ${POOL}/snapshots/<source_container_name>
+ err := os.Remove(snapshotContainerPath)
+ if err != nil {
+ return err
+ }
+
+ snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
+ if shared.PathExists(snapshotSymlink) {
+ err := os.Remove(snapshotSymlink)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ // Legacy
+ snapPath := shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", project.Prefix(projectName, sourceContainerName), sourceContainerSnapOnlyName))
+ if shared.PathExists(snapPath) {
+ err := os.Remove(snapPath)
+ if err != nil {
+ return err
+ }
+ }
+
+ // Legacy
+ parent := shared.VarPath(fmt.Sprintf("snapshots/%s", project.Prefix(projectName, sourceContainerName)))
+ if ok, _ := shared.PathIsEmpty(parent); ok {
+ err := os.Remove(parent)
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func zfsFilesystemEntityExists(pool string, path string) bool {
+ vdev := pool
+ if path != "" {
+ vdev = fmt.Sprintf("%s/%s", pool, path)
+ }
+
+ output, err := shared.RunCommand("zfs", "get", "-H", "-o", "name", "type", vdev)
+ if err != nil {
+ return false
+ }
+
+ detectedName := strings.TrimSpace(output)
+ return detectedName == vdev
+}
+
+func zfsPoolVolumeSnapshotRemovable(pool string, path string, name string) (bool, error) {
+ var snap string
+ if name == "" {
+ snap = path
+ } else {
+ snap = fmt.Sprintf("%s@%s", path, name)
+ }
+
+ clones, err := zfsFilesystemEntityPropertyGet(pool, snap, "clones")
+ if err != nil {
+ return false, err
+ }
+
+ if clones == "-" || clones == "" {
+ return true, nil
+ }
+
+ return false, nil
+}
+
+func zfsFilesystemEntityPropertyGet(pool string, path string, key string) (string, error) {
+ entity := pool
+ if path != "" {
+ entity = fmt.Sprintf("%s/%s", pool, path)
+ }
+
+ output, err := shared.RunCommand("zfs", "get", "-H", "-p", "-o", "value", key, entity)
+ if err != nil {
+ return "", errors.Wrap(err, "Failed to get ZFS config")
+ }
+
+ return strings.TrimRight(output, "\n"), nil
+}
+
+func zfsPoolVolumeSnapshotDestroy(pool, path string, name string) error {
+ _, err := shared.RunCommand("zfs", "destroy", "-r", fmt.Sprintf("%s/%s@%s", pool, path, name))
+ if err != nil {
+ return errors.Wrap(err, "Failed to destroy ZFS snapshot")
+ }
+
+ return nil
+}
+
+func zfsPoolVolumeSnapshotRename(pool string, path string, oldName string, newName string) error {
+ _, err := shared.RunCommand("zfs", "rename", "-r", fmt.Sprintf("%s/%s@%s", pool, path, oldName), fmt.Sprintf("%s/%s@%s", pool, path, newName))
+ if err != nil {
+ return errors.Wrap(err, "Failed to rename ZFS snapshot")
+ }
+
+ return nil
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index b58034a345..61d2d6ed2f 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -99,7 +99,6 @@ const (
storageTypeCeph storageType = iota
storageTypeLvm
storageTypeMock
- storageTypeZfs
)
var supportedStoragePoolDrivers = []string{"btrfs", "ceph", "cephfs", "dir", "lvm", "zfs"}
@@ -112,8 +111,6 @@ func storageTypeToString(sType storageType) (string, error) {
return "lvm", nil
case storageTypeMock:
return "mock", nil
- case storageTypeZfs:
- return "zfs", nil
}
return "", fmt.Errorf("Invalid storage type")
@@ -127,8 +124,6 @@ func storageStringToType(sName string) (storageType, error) {
return storageTypeLvm, nil
case "mock":
return storageTypeMock, nil
- case "zfs":
- return storageTypeZfs, nil
}
return -1, fmt.Errorf("Invalid storage type name")
@@ -268,13 +263,6 @@ func storageCoreInit(driver string) (storage, error) {
return nil, err
}
return &mock, nil
- case storageTypeZfs:
- zfs := storageZfs{}
- err = zfs.StorageCoreInit()
- if err != nil {
- return nil, err
- }
- return &zfs, nil
}
return nil, fmt.Errorf("invalid storage type")
@@ -342,17 +330,6 @@ func storageInit(s *state.State, project, poolName, volumeName string, volumeTyp
return nil, err
}
return &mock, nil
- case storageTypeZfs:
- zfs := storageZfs{}
- zfs.poolID = poolID
- zfs.pool = pool
- zfs.volume = volume
- zfs.s = s
- err = zfs.StoragePoolInit()
- if err != nil {
- return nil, err
- }
- return &zfs, nil
}
return nil, fmt.Errorf("invalid storage type")
@@ -468,7 +445,7 @@ func storagePoolVolumeAttachPrepare(s *state.State, poolName string, volumeName
var err error
if pool.Driver == "zfs" {
- err = lastIdmap.UnshiftRootfs(remapPath, zfsIdmapSetSkipper)
+ err = lastIdmap.UnshiftRootfs(remapPath, shiftZfsSkipper)
} else {
err = lastIdmap.UnshiftRootfs(remapPath, nil)
}
@@ -486,7 +463,7 @@ func storagePoolVolumeAttachPrepare(s *state.State, poolName string, volumeName
var err error
if pool.Driver == "zfs" {
- err = nextIdmap.ShiftRootfs(remapPath, zfsIdmapSetSkipper)
+ err = nextIdmap.ShiftRootfs(remapPath, shiftZfsSkipper)
} else {
err = nextIdmap.ShiftRootfs(remapPath, nil)
}
diff --git a/lxd/storage_migration_zfs.go b/lxd/storage_migration_zfs.go
deleted file mode 100644
index 8bbb56a511..0000000000
--- a/lxd/storage_migration_zfs.go
+++ /dev/null
@@ -1,149 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os/exec"
-
- "github.com/gorilla/websocket"
- "github.com/pborman/uuid"
-
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/logger"
-)
-
-type zfsMigrationSourceDriver struct {
- instance instance.Instance
- snapshots []instance.Instance
- zfsSnapshotNames []string
- zfs *storageZfs
- runningSnapName string
- stoppedSnapName string
- zfsFeatures []string
-}
-
-func (s *zfsMigrationSourceDriver) send(conn *websocket.Conn, zfsName string, zfsParent string, readWrapper func(io.ReadCloser) io.ReadCloser) error {
- sourceParentName, _, _ := shared.InstanceGetParentAndSnapshotName(s.instance.Name())
- poolName := s.zfs.getOnDiskPoolName()
- args := []string{"send"}
-
- // Negotiated options
- if s.zfsFeatures != nil && len(s.zfsFeatures) > 0 {
- if shared.StringInSlice("compress", s.zfsFeatures) {
- args = append(args, "-c")
- args = append(args, "-L")
- }
- }
-
- args = append(args, []string{fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.instance.Project(), sourceParentName), zfsName)}...)
- if zfsParent != "" {
- args = append(args, "-i", fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(s.instance.Project(), s.instance.Name()), zfsParent))
- }
-
- cmd := exec.Command("zfs", args...)
-
- stdout, err := cmd.StdoutPipe()
- if err != nil {
- return err
- }
-
- readPipe := io.ReadCloser(stdout)
- if readWrapper != nil {
- readPipe = readWrapper(stdout)
- }
-
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return err
- }
-
- if err := cmd.Start(); err != nil {
- return err
- }
-
- <-shared.WebsocketSendStream(conn, readPipe, 4*1024*1024)
-
- output, err := ioutil.ReadAll(stderr)
- if err != nil {
- logger.Errorf("Problem reading zfs send stderr: %s", err)
- }
-
- err = cmd.Wait()
- if err != nil {
- logger.Errorf("Problem with zfs send: %s", string(output))
- }
-
- return err
-}
-
-func (s *zfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn, op *operations.Operation, bwlimit string, containerOnly bool) error {
- if s.instance.IsSnapshot() {
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(s.instance.Name())
- snapshotName := fmt.Sprintf("snapshot-%s", snapOnlyName)
- wrapper := migration.ProgressReader(op, "fs_progress", s.instance.Name())
- return s.send(conn, snapshotName, "", wrapper)
- }
-
- lastSnap := ""
- if !containerOnly {
- for i, snap := range s.zfsSnapshotNames {
- prev := ""
- if i > 0 {
- prev = s.zfsSnapshotNames[i-1]
- }
-
- lastSnap = snap
-
- wrapper := migration.ProgressReader(op, "fs_progress", snap)
- if err := s.send(conn, snap, prev, wrapper); err != nil {
- return err
- }
- }
- }
-
- s.runningSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
- if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.instance.Project(), s.instance.Name())), s.runningSnapName); err != nil {
- return err
- }
-
- wrapper := migration.ProgressReader(op, "fs_progress", s.instance.Name())
- if err := s.send(conn, s.runningSnapName, lastSnap, wrapper); err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *zfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn, bwlimit string) error {
- s.stoppedSnapName = fmt.Sprintf("migration-send-%s", uuid.NewRandom().String())
- if err := zfsPoolVolumeSnapshotCreate(s.zfs.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(s.instance.Project(), s.instance.Name())), s.stoppedSnapName); err != nil {
- return err
- }
-
- if err := s.send(conn, s.stoppedSnapName, s.runningSnapName, nil); err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *zfsMigrationSourceDriver) Cleanup() {
- poolName := s.zfs.getOnDiskPoolName()
- if s.stoppedSnapName != "" {
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.instance.Project(), s.instance.Name())), s.stoppedSnapName)
- }
- if s.runningSnapName != "" {
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(s.instance.Project(), s.instance.Name())), s.runningSnapName)
- }
-}
-
-func (s *zfsMigrationSourceDriver) SendStorageVolume(conn *websocket.Conn, op *operations.Operation, bwlimit string, storage storage, volumeOnly bool) error {
- msg := fmt.Sprintf("Function not implemented")
- logger.Errorf(msg)
- return fmt.Errorf(msg)
-}
diff --git a/lxd/storage_zfs.go b/lxd/storage_zfs.go
deleted file mode 100644
index 3231dc85c3..0000000000
--- a/lxd/storage_zfs.go
+++ /dev/null
@@ -1,3342 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/gorilla/websocket"
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/lxc/lxd/lxd/backup"
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/lxd/rsync"
- driver "github.com/lxc/lxd/lxd/storage"
- "github.com/lxc/lxd/lxd/util"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/ioprogress"
- "github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/units"
-
- "github.com/pborman/uuid"
-)
-
-// Global defaults
-var zfsUseRefquota = "false"
-var zfsRemoveSnapshots = "false"
-
-// Cache
-var zfsVersion = ""
-
-type storageZfs struct {
- dataset string
- storageShared
-}
-
-func (s *storageZfs) getOnDiskPoolName() string {
- if s.dataset != "" {
- return s.dataset
- }
-
- return s.pool.Name
-}
-
-// Only initialize the minimal information we need about a given storage type.
-func (s *storageZfs) StorageCoreInit() error {
- s.sType = storageTypeZfs
- typeName, err := storageTypeToString(s.sType)
- if err != nil {
- return err
- }
- s.sTypeName = typeName
-
- if zfsVersion != "" {
- s.sTypeVersion = zfsVersion
- return nil
- }
-
- util.LoadModule("zfs")
-
- if !zfsIsEnabled() {
- return fmt.Errorf("The \"zfs\" tool is not enabled")
- }
-
- s.sTypeVersion, err = zfsToolVersionGet()
- if err != nil {
- s.sTypeVersion, err = zfsModuleVersionGet()
- if err != nil {
- return err
- }
- }
-
- zfsVersion = s.sTypeVersion
-
- return nil
-}
-
-// Functions dealing with storage pools.
-func (s *storageZfs) StoragePoolInit() error {
- err := s.StorageCoreInit()
- if err != nil {
- return err
- }
-
- // Detect whether we have been given a zfs dataset as source.
- if s.pool.Config["zfs.pool_name"] != "" {
- s.dataset = s.pool.Config["zfs.pool_name"]
- }
-
- return nil
-}
-
-func (s *storageZfs) StoragePoolCheck() error {
- logger.Debugf("Checking ZFS storage pool \"%s\"", s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- poolName := s.getOnDiskPoolName()
- purePoolName := strings.Split(poolName, "/")[0]
- exists := zfsFilesystemEntityExists(purePoolName, "")
- if exists {
- return nil
- }
-
- logger.Debugf("ZFS storage pool \"%s\" does not exist, trying to import it", poolName)
-
- var err error
- if filepath.IsAbs(source) {
- disksPath := shared.VarPath("disks")
- _, err = shared.RunCommand("zpool", "import", "-f", "-d", disksPath, poolName)
- } else {
- _, err = shared.RunCommand("zpool", "import", purePoolName)
- }
-
- if err != nil {
- return fmt.Errorf("ZFS storage pool \"%s\" could not be imported: %s", poolName, err)
- }
-
- logger.Debugf("ZFS storage pool \"%s\" successfully imported", poolName)
- return nil
-}
-
-func (s *storageZfs) StoragePoolCreate() error {
- logger.Infof("Creating ZFS storage pool \"%s\"", s.pool.Name)
-
- err := s.zfsPoolCreate()
- if err != nil {
- return err
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.StoragePoolDelete()
- }()
-
- storagePoolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- err = os.MkdirAll(storagePoolMntPoint, 0711)
- if err != nil {
- return err
- }
-
- err = s.StoragePoolCheck()
- if err != nil {
- return err
- }
-
- revert = false
-
- logger.Infof("Created ZFS storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) zfsPoolCreate() error {
- s.pool.Config["volatile.initial_source"] = s.pool.Config["source"]
-
- zpoolName := s.getOnDiskPoolName()
- vdev := s.pool.Config["source"]
- defaultVdev := filepath.Join(shared.VarPath("disks"), fmt.Sprintf("%s.img", s.pool.Name))
- if vdev == "" || vdev == defaultVdev {
- vdev = defaultVdev
- s.pool.Config["source"] = vdev
-
- if s.pool.Config["zfs.pool_name"] == "" {
- s.pool.Config["zfs.pool_name"] = zpoolName
- }
-
- f, err := os.Create(vdev)
- if err != nil {
- return fmt.Errorf("Failed to open %s: %s", vdev, err)
- }
- defer f.Close()
-
- err = f.Chmod(0600)
- if err != nil {
- return fmt.Errorf("Failed to chmod %s: %s", vdev, err)
- }
-
- size, err := units.ParseByteSizeString(s.pool.Config["size"])
- if err != nil {
- return err
- }
- err = f.Truncate(size)
- if err != nil {
- return fmt.Errorf("Failed to create sparse file %s: %s", vdev, err)
- }
-
- err = zfsPoolCreate(zpoolName, vdev)
- if err != nil {
- return err
- }
- } else {
- // Unset size property since it doesn't make sense.
- s.pool.Config["size"] = ""
-
- if filepath.IsAbs(vdev) {
- if !shared.IsBlockdevPath(vdev) {
- return fmt.Errorf("Custom loop file locations are not supported")
- }
-
- if s.pool.Config["zfs.pool_name"] == "" {
- s.pool.Config["zfs.pool_name"] = zpoolName
- }
-
- // This is a block device. Note, that we do not store the
- // block device path or UUID or PARTUUID or similar in
- // the database. All of those might change or might be
- // used in a special way (For example, zfs uses a single
- // UUID in a multi-device pool for all devices.). The
- // safest way is to just store the name of the zfs pool
- // we create.
- s.pool.Config["source"] = zpoolName
- err := zfsPoolCreate(zpoolName, vdev)
- if err != nil {
- return err
- }
- } else {
- if s.pool.Config["zfs.pool_name"] != "" && s.pool.Config["zfs.pool_name"] != vdev {
- return fmt.Errorf("Invalid combination of \"source\" and \"zfs.pool_name\" property")
- }
-
- s.pool.Config["zfs.pool_name"] = vdev
- s.dataset = vdev
-
- if strings.Contains(vdev, "/") {
- if !zfsFilesystemEntityExists(vdev, "") {
- err := zfsPoolCreate("", vdev)
- if err != nil {
- return err
- }
- }
- } else {
- err := zfsPoolCheck(vdev)
- if err != nil {
- return err
- }
- }
-
- subvols, err := zfsPoolListSubvolumes(zpoolName, vdev)
- if err != nil {
- return err
- }
-
- if len(subvols) > 0 {
- return fmt.Errorf("Provided ZFS pool (or dataset) isn't empty")
- }
-
- err = zfsPoolApplyDefaults(vdev)
- if err != nil {
- return err
- }
- }
- }
-
- // Create default dummy datasets to avoid zfs races during container
- // creation.
- poolName := s.getOnDiskPoolName()
- dataset := fmt.Sprintf("%s/containers", poolName)
- msg, err := zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create containers dataset: %s", msg)
- return err
- }
-
- fixperms := shared.VarPath("storage-pools", s.pool.Name, "containers")
- err = os.MkdirAll(fixperms, driver.ContainersDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- err = os.Chmod(fixperms, driver.ContainersDirMode)
- if err != nil {
- logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(driver.ContainersDirMode), 8), err)
- }
-
- dataset = fmt.Sprintf("%s/images", poolName)
- msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create images dataset: %s", msg)
- return err
- }
-
- fixperms = shared.VarPath("storage-pools", s.pool.Name, "images")
- err = os.MkdirAll(fixperms, driver.ImagesDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- err = os.Chmod(fixperms, driver.ImagesDirMode)
- if err != nil {
- logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(driver.ImagesDirMode), 8), err)
- }
-
- dataset = fmt.Sprintf("%s/custom", poolName)
- msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create custom dataset: %s", msg)
- return err
- }
-
- fixperms = shared.VarPath("storage-pools", s.pool.Name, "custom")
- err = os.MkdirAll(fixperms, driver.CustomDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- err = os.Chmod(fixperms, driver.CustomDirMode)
- if err != nil {
- logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(driver.CustomDirMode), 8), err)
- }
-
- dataset = fmt.Sprintf("%s/deleted", poolName)
- msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create deleted dataset: %s", msg)
- return err
- }
-
- dataset = fmt.Sprintf("%s/snapshots", poolName)
- msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create snapshots dataset: %s", msg)
- return err
- }
-
- fixperms = shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots")
- err = os.MkdirAll(fixperms, driver.SnapshotsDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- err = os.Chmod(fixperms, driver.SnapshotsDirMode)
- if err != nil {
- logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(driver.SnapshotsDirMode), 8), err)
- }
-
- dataset = fmt.Sprintf("%s/custom-snapshots", poolName)
- msg, err = zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create snapshots dataset: %s", msg)
- return err
- }
-
- fixperms = shared.VarPath("storage-pools", s.pool.Name, "custom-snapshots")
- err = os.MkdirAll(fixperms, driver.SnapshotsDirMode)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
- err = os.Chmod(fixperms, driver.SnapshotsDirMode)
- if err != nil {
- logger.Warnf("Failed to chmod \"%s\" to \"0%s\": %s", fixperms, strconv.FormatInt(int64(driver.SnapshotsDirMode), 8), err)
- }
-
- return nil
-}
-
-func (s *storageZfs) StoragePoolDelete() error {
- logger.Infof("Deleting ZFS storage pool \"%s\"", s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- if zfsFilesystemEntityExists(poolName, "") {
- err := zfsFilesystemEntityDelete(s.pool.Config["source"], poolName)
- if err != nil {
- return err
- }
- }
-
- storagePoolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- if shared.PathExists(storagePoolMntPoint) {
- err := os.RemoveAll(storagePoolMntPoint)
- if err != nil {
- return err
- }
- }
-
- logger.Infof("Deleted ZFS storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolMount() (bool, error) {
- return true, nil
-}
-
-func (s *storageZfs) StoragePoolUmount() (bool, error) {
- return true, nil
-}
-
-func (s *storageZfs) StoragePoolVolumeCreate() error {
- logger.Infof("Creating ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- isSnapshot := shared.IsSnapshot(s.volume.Name)
-
- var fs string
-
- if isSnapshot {
- fs = fmt.Sprintf("custom-snapshots/%s", s.volume.Name)
- } else {
- fs = fmt.Sprintf("custom/%s", s.volume.Name)
- }
- poolName := s.getOnDiskPoolName()
- dataset := fmt.Sprintf("%s/%s", poolName, fs)
-
- var customPoolVolumeMntPoint string
-
- if isSnapshot {
- customPoolVolumeMntPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- } else {
- customPoolVolumeMntPoint = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- msg, err := zfsPoolVolumeCreate(dataset, "mountpoint=none", "canmount=noauto")
- if err != nil {
- logger.Errorf("Failed to create ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, msg)
- return err
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.StoragePoolVolumeDelete()
- }()
-
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", customPoolVolumeMntPoint)
- if err != nil {
- return err
- }
-
- if !shared.IsMountPoint(customPoolVolumeMntPoint) {
- err := zfsMount(poolName, fs)
- if err != nil {
- return err
- }
- defer zfsUmount(poolName, fs, customPoolVolumeMntPoint)
- }
-
- // apply quota
- if s.volume.Config["size"] != "" {
- size, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
-
- revert = false
-
- logger.Infof("Created ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeDelete() error {
- logger.Infof("Deleting ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- fs := fmt.Sprintf("custom/%s", s.volume.Name)
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- poolName := s.getOnDiskPoolName()
- if zfsFilesystemEntityExists(poolName, fs) {
- removable := true
- snaps, err := zfsPoolListSnapshots(poolName, fs)
- if err != nil {
- return err
- }
-
- for _, snap := range snaps {
- var err error
- removable, err = zfsPoolVolumeSnapshotRemovable(poolName, fs, snap)
- if err != nil {
- return err
- }
-
- if !removable {
- break
- }
- }
-
- if removable {
- origin, err := zfsFilesystemEntityPropertyGet(poolName, fs, "origin")
- if err != nil {
- return err
- }
- poolName := s.getOnDiskPoolName()
- origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", poolName))
-
- err = zfsPoolVolumeDestroy(poolName, fs)
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeCleanup(poolName, origin)
- if err != nil {
- return err
- }
- } else {
- err := zfsPoolVolumeSet(poolName, fs, "mountpoint", "none")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeRename(poolName, fs, fmt.Sprintf("deleted/custom/%s", uuid.NewRandom().String()), true)
- if err != nil {
- return err
- }
- }
- }
-
- if shared.PathExists(customPoolVolumeMntPoint) {
- err := os.RemoveAll(customPoolVolumeMntPoint)
- if err != nil {
- return err
- }
- }
-
- err := s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for ZFS storage volume "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeMount() (bool, error) {
- logger.Debugf("Mounting ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- fs := fmt.Sprintf("custom/%s", s.volume.Name)
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- customMountLockID := getCustomMountLockID(s.pool.Name, s.volume.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[customMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var customerr error
- ourMount := false
- if !shared.IsMountPoint(customPoolVolumeMntPoint) {
- customerr = zfsMount(s.getOnDiskPoolName(), fs)
- ourMount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, customMountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if customerr != nil {
- return false, customerr
- }
-
- logger.Debugf("Mounted ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourMount, nil
-}
-
-func (s *storageZfs) StoragePoolVolumeUmount() (bool, error) {
- logger.Debugf("Unmounting ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- fs := fmt.Sprintf("custom/%s", s.volume.Name)
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- customUmountLockID := getCustomUmountLockID(s.pool.Name, s.volume.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[customUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var customerr error
- ourUmount := false
- if shared.IsMountPoint(customPoolVolumeMntPoint) {
- customerr = zfsUmount(s.getOnDiskPoolName(), fs, customPoolVolumeMntPoint)
- ourUmount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, customUmountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if customerr != nil {
- return false, customerr
- }
-
- logger.Debugf("Unmounted ZFS storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourUmount, nil
-}
-
-func (s *storageZfs) GetContainerPoolInfo() (int64, string, string) {
- return s.poolID, s.pool.Name, s.getOnDiskPoolName()
-}
-
-func (s *storageZfs) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
- logger.Infof(`Updating ZFS storage pool "%s"`, s.pool.Name)
-
- changeable := changeableStoragePoolProperties["zfs"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolError(unchangeable, "zfs")
- }
-
- // "rsync.bwlimit" requires no on-disk modifications.
- // "volume.zfs.remove_snapshots" requires no on-disk modifications.
- // "volume.zfs.use_refquota" requires no on-disk modifications.
-
- logger.Infof(`Updated ZFS storage pool "%s"`, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeUpdate(writable *api.StorageVolumePut, changedConfig []string) error {
- if writable.Restore != "" {
- logger.Infof(`Restoring ZFS storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
-
- // Check that we can remove the snapshot
- poolID, err := s.s.Cluster.StoragePoolGetID(s.pool.Name)
- if err != nil {
- return err
- }
-
- // Get the names of all storage volume snapshots of a given volume
- volumes, err := s.s.Cluster.StoragePoolVolumeSnapshotsGetType(s.volume.Name, storagePoolVolumeTypeCustom, poolID)
- if err != nil {
- return err
- }
-
- if volumes[len(volumes)-1].Name != fmt.Sprintf("%s/%s", s.volume.Name, writable.Restore) {
- return fmt.Errorf("ZFS can only restore from the latest snapshot. Delete newer snapshots or copy the snapshot into a new volume instead")
- }
-
- s.volume.Description = writable.Description
- s.volume.Config = writable.Config
-
- targetSnapshotDataset := fmt.Sprintf("%s/custom/%s at snapshot-%s", s.getOnDiskPoolName(), s.volume.Name, writable.Restore)
- msg, err := shared.RunCommand("zfs", "rollback", "-r", "-R", targetSnapshotDataset)
- if err != nil {
- logger.Errorf("Failed to rollback ZFS dataset: %s", msg)
- return err
- }
-
- logger.Infof(`Restored ZFS storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
- return nil
- }
-
- logger.Infof(`Updating ZFS storage volume "%s"`, s.volume.Name)
-
- changeable := changeableStoragePoolVolumeProperties["zfs"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolVolumeError(unchangeable, "zfs")
- }
-
- if shared.StringInSlice("size", changedConfig) {
- if s.volume.Type != storagePoolVolumeTypeNameCustom {
- return updateStoragePoolVolumeError([]string{"size"}, "zfs")
- }
-
- if s.volume.Config["size"] != writable.Config["size"] {
- size, err := units.ParseByteSizeString(writable.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
- }
-
- logger.Infof(`Updated ZFS storage volume "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeRename(newName string) error {
- logger.Infof(`Renaming ZFS storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- usedBy, err := storagePoolVolumeUsedByInstancesGet(s.s, "default", s.pool.Name, s.volume.Name)
- if err != nil {
- return err
- }
- if len(usedBy) > 0 {
- return fmt.Errorf(`ZFS storage volume "%s" on storage pool "%s" is attached to containers`,
- s.volume.Name, s.pool.Name)
- }
-
- isSnapshot := shared.IsSnapshot(s.volume.Name)
-
- var oldPath string
- var newPath string
-
- if isSnapshot {
- oldPath = fmt.Sprintf("custom-snapshots/%s", s.volume.Name)
- newPath = fmt.Sprintf("custom-snapshots/%s", newName)
- } else {
- oldPath = fmt.Sprintf("custom/%s", s.volume.Name)
- newPath = fmt.Sprintf("custom/%s", newName)
- }
- poolName := s.getOnDiskPoolName()
- err = zfsPoolVolumeRename(poolName, oldPath, newPath, false)
- if err != nil {
- return err
- }
-
- logger.Infof(`Renamed ZFS storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, newName,
- storagePoolVolumeTypeCustom, s.poolID)
-}
-
-// Things we don't need to care about
-func (s *storageZfs) ContainerMount(c instance.Instance) (bool, error) {
- return s.doContainerMount(c.Project(), c.Name(), c.IsPrivileged())
-}
-
-func (s *storageZfs) ContainerUmount(c instance.Instance, path string) (bool, error) {
- logger.Debugf("Unmounting ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- name := c.Name()
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(c.Project(), name))
- containerPoolVolumeMntPoint := driver.GetContainerMountPoint(c.Project(), s.pool.Name, name)
-
- containerUmountLockID := getContainerUmountLockID(s.pool.Name, name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[containerUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var imgerr error
- ourUmount := false
- if shared.IsMountPoint(containerPoolVolumeMntPoint) {
- imgerr = zfsUmount(s.getOnDiskPoolName(), fs, containerPoolVolumeMntPoint)
- ourUmount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, containerUmountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if imgerr != nil {
- return false, imgerr
- }
-
- logger.Debugf("Unmounted ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourUmount, nil
-}
-
-// Things we do have to care about
-func (s *storageZfs) ContainerStorageReady(container instance.Instance) bool {
- volumeName := project.Prefix(container.Project(), container.Name())
- fs := fmt.Sprintf("containers/%s", volumeName)
- return zfsFilesystemEntityExists(s.getOnDiskPoolName(), fs)
-}
-
-func (s *storageZfs) ContainerCreate(container instance.Instance) error {
- err := s.doContainerCreate(container.Project(), container.Name(), container.IsPrivileged())
- if err != nil {
- s.doContainerDelete(container.Project(), container.Name())
- return err
- }
-
- ourMount, err := s.ContainerMount(container)
- if err != nil {
- return err
- }
- if ourMount {
- defer s.ContainerUmount(container, container.Path())
- }
-
- err = container.DeferTemplateApply("create")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) ContainerCreateFromImage(container instance.Instance, fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- containerPath := container.Path()
- containerName := container.Name()
- volumeName := project.Prefix(container.Project(), containerName)
- fs := fmt.Sprintf("containers/%s", volumeName)
- containerPoolVolumeMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
-
- poolName := s.getOnDiskPoolName()
- fsImage := fmt.Sprintf("images/%s", fingerprint)
-
- imageStoragePoolLockID := getImageCreateLockID(s.pool.Name, fingerprint)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- } else {
- lxdStorageOngoingOperationMap[imageStoragePoolLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var imgerr error
- if !zfsFilesystemEntityExists(poolName, fmt.Sprintf("%s at readonly", fsImage)) {
- imgerr = s.ImageCreate(fingerprint, tracker)
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, imageStoragePoolLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if imgerr != nil {
- return imgerr
- }
- }
-
- err := zfsPoolVolumeClone(container.Project(), poolName, fsImage, "readonly", fs, containerPoolVolumeMntPoint)
- if err != nil {
- return err
- }
-
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.ContainerDelete(container)
- }()
-
- ourMount, err := s.ContainerMount(container)
- if err != nil {
- return err
- }
- if ourMount {
- defer s.ContainerUmount(container, containerPath)
- }
-
- privileged := container.IsPrivileged()
- err = driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
- if err != nil {
- return err
- }
-
- err = container.DeferTemplateApply("create")
- if err != nil {
- return err
- }
-
- revert = false
-
- logger.Debugf("Created ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) ContainerDelete(container instance.Instance) error {
- err := s.doContainerDelete(container.Project(), container.Name())
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) copyWithoutSnapshotsSparse(target instance.Instance, source instance.Instance) error {
- poolName := s.getOnDiskPoolName()
-
- sourceContainerName := source.Name()
- sourceContainerPath := source.Path()
-
- targetContainerName := target.Name()
- targetContainerPath := target.Path()
- targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, targetContainerName)
-
- sourceZfsDataset := ""
- sourceZfsDatasetSnapshot := ""
- sourceName, sourceSnapOnlyName, isSnapshotName := shared.InstanceGetParentAndSnapshotName(sourceContainerName)
-
- targetZfsDataset := fmt.Sprintf("containers/%s", project.Prefix(target.Project(), targetContainerName))
-
- if isSnapshotName {
- sourceZfsDatasetSnapshot = sourceSnapOnlyName
- }
-
- revert := true
- if sourceZfsDatasetSnapshot == "" {
- if zfsFilesystemEntityExists(poolName, fmt.Sprintf("containers/%s", project.Prefix(source.Project(), sourceName))) {
- sourceZfsDatasetSnapshot = fmt.Sprintf("copy-%s", uuid.NewRandom().String())
- sourceZfsDataset = fmt.Sprintf("containers/%s", project.Prefix(source.Project(), sourceName))
- err := zfsPoolVolumeSnapshotCreate(poolName, sourceZfsDataset, sourceZfsDatasetSnapshot)
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- zfsPoolVolumeSnapshotDestroy(poolName, sourceZfsDataset, sourceZfsDatasetSnapshot)
- }()
- }
- } else {
- if zfsFilesystemEntityExists(poolName, fmt.Sprintf("containers/%s at snapshot-%s", project.Prefix(source.Project(), sourceName), sourceZfsDatasetSnapshot)) {
- sourceZfsDataset = fmt.Sprintf("containers/%s", project.Prefix(source.Project(), sourceName))
- sourceZfsDatasetSnapshot = fmt.Sprintf("snapshot-%s", sourceZfsDatasetSnapshot)
- }
- }
-
- if sourceZfsDataset != "" {
- err := zfsPoolVolumeClone(target.Project(), poolName, sourceZfsDataset, sourceZfsDatasetSnapshot, targetZfsDataset, targetContainerMountPoint)
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- zfsPoolVolumeDestroy(poolName, targetZfsDataset)
- }()
-
- ourMount, err := s.ContainerMount(target)
- if err != nil {
- return err
- }
- if ourMount {
- defer s.ContainerUmount(target, targetContainerPath)
- }
-
- err = driver.CreateContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- deleteContainerMountpoint(targetContainerMountPoint, targetContainerPath, s.GetStorageTypeName())
- }()
- } else {
- err := s.ContainerCreate(target)
- if err != nil {
- return err
- }
- defer func() {
- if !revert {
- return
- }
- s.ContainerDelete(target)
- }()
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerPath, targetContainerPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("rsync failed: %s", string(output))
- }
- }
-
- revert = false
-
- return nil
-}
-
-func (s *storageZfs) copyWithoutSnapshotFull(target instance.Instance, source instance.Instance) error {
- logger.Debugf("Creating full ZFS copy \"%s\" to \"%s\"", source.Name(), target.Name())
-
- sourceIsSnapshot := source.IsSnapshot()
- poolName := s.getOnDiskPoolName()
-
- sourceName := source.Name()
- sourceDataset := ""
- snapshotSuffix := ""
-
- targetName := target.Name()
- targetDataset := fmt.Sprintf("%s/containers/%s", poolName, project.Prefix(target.Project(), targetName))
- targetSnapshotDataset := ""
-
- if sourceIsSnapshot {
- sourceParentName, sourceSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source.Name())
- snapshotSuffix = fmt.Sprintf("snapshot-%s", sourceSnapOnlyName)
- sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), sourceParentName), snapshotSuffix)
- targetSnapshotDataset = fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(target.Project(), targetName), sourceSnapOnlyName)
- } else {
- snapshotSuffix = uuid.NewRandom().String()
- sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), sourceName), snapshotSuffix)
- targetSnapshotDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(target.Project(), targetName), snapshotSuffix)
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(source.Project(), sourceName))
- err := zfsPoolVolumeSnapshotCreate(poolName, fs, snapshotSuffix)
- if err != nil {
- return err
- }
- defer func() {
- err := zfsPoolVolumeSnapshotDestroy(poolName, fs, snapshotSuffix)
- if err != nil {
- logger.Warnf("Failed to delete temporary ZFS snapshot \"%s\", manual cleanup needed", sourceDataset)
- }
- }()
- }
-
- zfsSendCmd := exec.Command("zfs", "send", sourceDataset)
-
- zfsRecvCmd := exec.Command("zfs", "receive", targetDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err := zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
-
- msg, err := shared.RunCommand("zfs", "rollback", "-r", "-R", targetSnapshotDataset)
- if err != nil {
- logger.Errorf("Failed to rollback ZFS dataset: %s", msg)
- return err
- }
-
- targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, targetName)
- targetfs := fmt.Sprintf("containers/%s", project.Prefix(target.Project(), targetName))
-
- err = zfsPoolVolumeSet(poolName, targetfs, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, targetfs, "mountpoint", targetContainerMountPoint)
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSnapshotDestroy(poolName, targetfs, snapshotSuffix)
- if err != nil {
- return err
- }
-
- ourMount, err := s.ContainerMount(target)
- if err != nil {
- return err
- }
- if ourMount {
- defer s.ContainerUmount(target, targetContainerMountPoint)
- }
-
- err = driver.CreateContainerMountpoint(targetContainerMountPoint, target.Path(), target.IsPrivileged())
- if err != nil {
- return err
- }
-
- logger.Debugf("Created full ZFS copy \"%s\" to \"%s\"", source.Name(), target.Name())
- return nil
-}
-
-func (s *storageZfs) copyWithSnapshots(target instance.Instance, source instance.Instance, parentSnapshot string) error {
- sourceName := source.Name()
- targetParentName, targetSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(target.Name())
- containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
- err := driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- poolName := s.getOnDiskPoolName()
- sourceParentName, sourceSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(sourceName)
- currentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), sourceParentName), sourceSnapOnlyName)
- args := []string{"send", currentSnapshotDataset}
- if parentSnapshot != "" {
- parentName, parentSnaponlyName, _ := shared.InstanceGetParentAndSnapshotName(parentSnapshot)
- parentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), parentName), parentSnaponlyName)
- args = append(args, "-i", parentSnapshotDataset)
- }
-
- zfsSendCmd := exec.Command("zfs", args...)
- targetSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(target.Project(), targetParentName), targetSnapOnlyName)
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", targetSnapshotDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err = zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) doCrossPoolContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool, refresh bool, refreshSnapshots []instance.Instance) error {
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
-
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, source.Name(), storagePoolVolumeTypeContainer)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
-
- targetPool, err := target.StoragePool()
- if err != nil {
- return err
- }
-
- var snapshots []instance.Instance
-
- if refresh {
- snapshots = refreshSnapshots
- } else {
- snapshots, err = source.Snapshots()
- if err != nil {
- return err
- }
-
- // create the main container
- err = s.doContainerCreate(target.Project(), target.Name(), target.IsPrivileged())
- if err != nil {
- return err
- }
- }
-
- _, err = s.doContainerMount(target.Project(), target.Name(), target.IsPrivileged())
- if err != nil {
- return err
- }
- defer s.ContainerUmount(target, shared.VarPath("containers", project.Prefix(target.Project(), target.Name())))
-
- destContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
- bwlimit := s.pool.Config["rsync.bwlimit"]
- if !containerOnly {
- for _, snap := range snapshots {
- srcSnapshotMntPoint := driver.GetSnapshotMountPoint(target.Project(), sourcePool, snap.Name())
- _, err = rsync.LocalCopy(srcSnapshotMntPoint, destContainerMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- // create snapshot
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- err = s.doContainerSnapshotCreate(snap.Project(), fmt.Sprintf("%s/%s", target.Name(), snapOnlyName), target.Name())
- if err != nil {
- return err
- }
- }
- }
-
- srcContainerMntPoint := driver.GetContainerMountPoint(source.Project(), sourcePool, source.Name())
- _, err = rsync.LocalCopy(srcContainerMntPoint, destContainerMntPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) ContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool) error {
- logger.Debugf("Copying ZFS container storage %s to %s", source.Name(), target.Name())
-
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- if source.Type() != instancetype.Container {
- return fmt.Errorf("Source Instance type must be container")
- }
-
- if target.Type() != instancetype.Container {
- return fmt.Errorf("Target Instance type must be container")
- }
-
- srcCt := source.(*containerLXC)
- targetCt := target.(*containerLXC)
-
- sourcePool, err := srcCt.StoragePool()
- if err != nil {
- return err
- }
-
- targetPool, err := targetCt.StoragePool()
- if err != nil {
- return err
- }
-
- if sourcePool != targetPool {
- err := s.doCrossPoolContainerCopy(target, source, containerOnly, false, nil)
- if err != nil {
- return err
- }
-
- return target.DeferTemplateApply("copy")
- }
-
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- if containerOnly || len(snapshots) == 0 {
- if s.pool.Config["zfs.clone_copy"] != "" && !shared.IsTrue(s.pool.Config["zfs.clone_copy"]) {
- err = s.copyWithoutSnapshotFull(target, source)
- if err != nil {
- return err
- }
- } else {
- err = s.copyWithoutSnapshotsSparse(target, source)
- if err != nil {
- return err
- }
- }
- } else {
- targetContainerName := target.Name()
- targetContainerPath := target.Path()
- targetContainerMountPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, targetContainerName)
- err = driver.CreateContainerMountpoint(targetContainerMountPoint, targetContainerPath, target.IsPrivileged())
- if err != nil {
- return err
- }
-
- prev := ""
- prevSnapOnlyName := ""
- for i, snap := range snapshots {
- if i > 0 {
- prev = snapshots[i-1].Name()
- }
-
- sourceSnapshot, err := instance.LoadByProjectAndName(s.s, source.Project(), snap.Name())
- if err != nil {
- return err
- }
-
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- prevSnapOnlyName = snapOnlyName
- newSnapName := fmt.Sprintf("%s/%s", target.Name(), snapOnlyName)
- targetSnapshot, err := instance.LoadByProjectAndName(s.s, target.Project(), newSnapName)
- if err != nil {
- return err
- }
-
- err = s.copyWithSnapshots(targetSnapshot, sourceSnapshot, prev)
- if err != nil {
- return err
- }
- }
-
- poolName := s.getOnDiskPoolName()
-
- // send actual container
- tmpSnapshotName := fmt.Sprintf("copy-send-%s", uuid.NewRandom().String())
- err = zfsPoolVolumeSnapshotCreate(poolName, fmt.Sprintf("containers/%s", project.Prefix(source.Project(), source.Name())), tmpSnapshotName)
- if err != nil {
- return err
- }
-
- currentSnapshotDataset := fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), source.Name()), tmpSnapshotName)
- args := []string{"send", currentSnapshotDataset}
- if prevSnapOnlyName != "" {
- parentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), source.Name()), prevSnapOnlyName)
- args = append(args, "-i", parentSnapshotDataset)
- }
-
- zfsSendCmd := exec.Command("zfs", args...)
- targetSnapshotDataset := fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(target.Project(), target.Name()), tmpSnapshotName)
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", targetSnapshotDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err = zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
-
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(source.Project(), source.Name())), tmpSnapshotName)
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(target.Project(), target.Name())), tmpSnapshotName)
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(target.Project(), target.Name()))
- err = zfsPoolVolumeSet(poolName, fs, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", targetContainerMountPoint)
- if err != nil {
- return err
- }
- }
-
- err = target.DeferTemplateApply("copy")
- if err != nil {
- return err
- }
-
- logger.Debugf("Copied ZFS container storage %s to %s", source.Name(), target.Name())
- return nil
-}
-
-func (s *storageZfs) ContainerRefresh(target instance.Instance, source instance.Instance, snapshots []instance.Instance) error {
- logger.Debugf("Refreshing ZFS container storage for %s from %s", target.Name(), source.Name())
-
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- return s.doCrossPoolContainerCopy(target, source, len(snapshots) == 0, true, snapshots)
-}
-
-func (s *storageZfs) ContainerRename(container instance.Instance, newName string) error {
- logger.Debugf("Renaming ZFS storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
-
- poolName := s.getOnDiskPoolName()
- oldName := container.Name()
-
- // Unmount the dataset.
- _, err := s.ContainerUmount(container, "")
- if err != nil {
- return err
- }
-
- // Rename the dataset.
- oldZfsDataset := fmt.Sprintf("containers/%s", project.Prefix(container.Project(), oldName))
- newZfsDataset := fmt.Sprintf("containers/%s", project.Prefix(container.Project(), newName))
- err = zfsPoolVolumeRename(poolName, oldZfsDataset, newZfsDataset, false)
- if err != nil {
- return err
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- s.ContainerRename(container, oldName)
- }()
-
- // Set the new mountpoint for the dataset.
- newContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, newName)
- err = zfsPoolVolumeSet(poolName, newZfsDataset, "mountpoint", newContainerMntPoint)
- if err != nil {
- return err
- }
-
- // Unmount the dataset.
- container.(*containerLXC).name = newName
- _, err = s.ContainerUmount(container, "")
- if err != nil {
- return err
- }
-
- // Create new mountpoint on the storage pool.
- oldContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, oldName)
- oldContainerMntPointSymlink := container.Path()
- newContainerMntPointSymlink := shared.VarPath("containers", project.Prefix(container.Project(), newName))
- err = renameContainerMountpoint(oldContainerMntPoint, oldContainerMntPointSymlink, newContainerMntPoint, newContainerMntPointSymlink)
- if err != nil {
- return err
- }
-
- // Rename the snapshot mountpoint on the storage pool.
- oldSnapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, oldName)
- newSnapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, newName)
- if shared.PathExists(oldSnapshotMntPoint) {
- err := os.Rename(oldSnapshotMntPoint, newSnapshotMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Remove old symlink.
- oldSnapshotPath := shared.VarPath("snapshots", project.Prefix(container.Project(), oldName))
- if shared.PathExists(oldSnapshotPath) {
- err := os.Remove(oldSnapshotPath)
- if err != nil {
- return err
- }
- }
-
- // Create new symlink.
- newSnapshotPath := shared.VarPath("snapshots", project.Prefix(container.Project(), newName))
- if shared.PathExists(newSnapshotPath) {
- err := os.Symlink(newSnapshotMntPoint, newSnapshotPath)
- if err != nil {
- return err
- }
- }
-
- revert = false
-
- logger.Debugf("Renamed ZFS storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-func (s *storageZfs) ContainerRestore(target instance.Instance, source instance.Instance) error {
- logger.Debugf("Restoring ZFS storage volume for container \"%s\" from %s to %s", s.volume.Name, source.Name(), target.Name())
-
- snaps, err := target.Snapshots()
- if err != nil {
- return err
- }
-
- if snaps[len(snaps)-1].Name() != source.Name() {
- if s.pool.Config["volume.zfs.remove_snapshots"] != "" {
- zfsRemoveSnapshots = s.pool.Config["volume.zfs.remove_snapshots"]
- }
-
- if s.volume.Config["zfs.remove_snapshots"] != "" {
- zfsRemoveSnapshots = s.volume.Config["zfs.remove_snapshots"]
- }
-
- if !shared.IsTrue(zfsRemoveSnapshots) {
- return fmt.Errorf("ZFS can only restore from the latest snapshot. Delete newer snapshots or copy the snapshot into a new container instead")
- }
- }
-
- // Start storage for source container
- ourSourceStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourSourceStart {
- defer source.StorageStop()
- }
-
- // Start storage for target container
- ourTargetStart, err := target.StorageStart()
- if err != nil {
- return err
- }
- if ourTargetStart {
- defer target.StorageStop()
- }
-
- for i := len(snaps) - 1; i != 0; i-- {
- if snaps[i].Name() == source.Name() {
- break
- }
-
- err := snaps[i].Delete()
- if err != nil {
- return err
- }
- }
-
- // Restore the snapshot
- cName, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source.Name())
- snapName := fmt.Sprintf("snapshot-%s", snapOnlyName)
-
- err = zfsPoolVolumeSnapshotRestore(s.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(source.Project(), cName)), snapName)
- if err != nil {
- return err
- }
-
- logger.Debugf("Restored ZFS storage volume for container \"%s\" from %s to %s", s.volume.Name, source.Name(), target.Name())
- return nil
-}
-
-func (s *storageZfs) ContainerGetUsage(container instance.Instance) (int64, error) {
- var err error
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(container.Project(), container.Name()))
-
- property := "used"
-
- if s.pool.Config["volume.zfs.use_refquota"] != "" {
- zfsUseRefquota = s.pool.Config["volume.zfs.use_refquota"]
- }
- if s.volume.Config["zfs.use_refquota"] != "" {
- zfsUseRefquota = s.volume.Config["zfs.use_refquota"]
- }
-
- if shared.IsTrue(zfsUseRefquota) {
- property = "referenced"
- }
-
- // Shortcut for refquota
- mountpoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, container.Name())
- if property == "referenced" && shared.IsMountPoint(mountpoint) {
- var stat unix.Statfs_t
- err := unix.Statfs(mountpoint, &stat)
- if err != nil {
- return -1, err
- }
-
- return int64(stat.Blocks-stat.Bfree) * int64(stat.Bsize), nil
- }
-
- value, err := zfsFilesystemEntityPropertyGet(s.getOnDiskPoolName(), fs, property)
- if err != nil {
- return -1, err
- }
-
- valueInt, err := strconv.ParseInt(value, 10, 64)
- if err != nil {
- return -1, err
- }
-
- return valueInt, nil
-}
-
-func (s *storageZfs) doContainerSnapshotCreate(projectName, targetName string, sourceName string) error {
- snapshotContainerName := targetName
- logger.Debugf("Creating ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", snapshotContainerName, s.pool.Name)
-
- sourceContainerName := sourceName
-
- cName, snapshotSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snapshotContainerName)
- snapName := fmt.Sprintf("snapshot-%s", snapshotSnapOnlyName)
-
- sourceZfsDataset := fmt.Sprintf("containers/%s", project.Prefix(projectName, cName))
- err := zfsPoolVolumeSnapshotCreate(s.getOnDiskPoolName(), sourceZfsDataset, snapName)
- if err != nil {
- return err
- }
-
- snapshotMntPoint := driver.GetSnapshotMountPoint(projectName, s.pool.Name, snapshotContainerName)
- if !shared.PathExists(snapshotMntPoint) {
- err := os.MkdirAll(snapshotMntPoint, 0100)
- if err != nil {
- return err
- }
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(projectName, sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
- if !shared.PathExists(snapshotMntPointSymlink) {
- err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Created ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", snapshotContainerName, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) ContainerSnapshotCreate(snapshotContainer instance.Instance, sourceContainer instance.Instance) error {
- err := s.doContainerSnapshotCreate(sourceContainer.Project(), snapshotContainer.Name(), sourceContainer.Name())
- if err != nil {
- s.ContainerSnapshotDelete(snapshotContainer)
- return err
- }
- return nil
-}
-
-func zfsSnapshotDeleteInternal(projectName, poolName string, ctName string, onDiskPoolName string) error {
- sourceContainerName, sourceContainerSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(ctName)
- snapName := fmt.Sprintf("snapshot-%s", sourceContainerSnapOnlyName)
-
- if zfsFilesystemEntityExists(onDiskPoolName,
- fmt.Sprintf("containers/%s@%s",
- project.Prefix(projectName, sourceContainerName), snapName)) {
- removable, err := zfsPoolVolumeSnapshotRemovable(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName)
- if err != nil {
- return err
- }
-
- if removable {
- err = zfsPoolVolumeSnapshotDestroy(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName)
- } else {
- err = zfsPoolVolumeSnapshotRename(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName,
- fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
- }
- if err != nil {
- return err
- }
- }
-
- // Delete the snapshot on its storage pool:
- // ${POOL}/snapshots/<snapshot_name>
- snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, ctName)
- if shared.PathExists(snapshotContainerMntPoint) {
- err := os.RemoveAll(snapshotContainerMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Check if we can remove the snapshot symlink:
- // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
- // by checking if the directory is empty.
- snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
- empty, _ := shared.PathIsEmpty(snapshotContainerPath)
- if empty == true {
- // Remove the snapshot directory for the container:
- // ${POOL}/snapshots/<source_container_name>
- err := os.Remove(snapshotContainerPath)
- if err != nil {
- return err
- }
-
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- // Legacy
- snapPath := shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", project.Prefix(projectName, sourceContainerName), sourceContainerSnapOnlyName))
- if shared.PathExists(snapPath) {
- err := os.Remove(snapPath)
- if err != nil {
- return err
- }
- }
-
- // Legacy
- parent := shared.VarPath(fmt.Sprintf("snapshots/%s", project.Prefix(projectName, sourceContainerName)))
- if ok, _ := shared.PathIsEmpty(parent); ok {
- err := os.Remove(parent)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageZfs) ContainerSnapshotDelete(snapshotContainer instance.Instance) error {
- logger.Debugf("Deleting ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- err := zfsSnapshotDeleteInternal(snapshotContainer.Project(), s.pool.Name, snapshotContainer.Name(),
- poolName)
- if err != nil {
- return err
- }
-
- logger.Debugf("Deleted ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) ContainerSnapshotRename(snapshotContainer instance.Instance, newName string) error {
- logger.Debugf("Renaming ZFS storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
-
- oldName := snapshotContainer.Name()
-
- oldcName, oldSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snapshotContainer.Name())
- oldZfsDatasetName := fmt.Sprintf("snapshot-%s", oldSnapOnlyName)
-
- _, newSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(newName)
- newZfsDatasetName := fmt.Sprintf("snapshot-%s", newSnapOnlyName)
-
- if oldZfsDatasetName != newZfsDatasetName {
- err := zfsPoolVolumeSnapshotRename(
- s.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(snapshotContainer.Project(), oldcName)), oldZfsDatasetName, newZfsDatasetName)
- if err != nil {
- return err
- }
- }
- revert := true
- defer func() {
- if !revert {
- return
- }
- //s.ContainerSnapshotRename(snapshotContainer, oldName)
- }()
-
- oldStyleSnapshotMntPoint := shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", project.Prefix(snapshotContainer.Project(), oldcName), oldSnapOnlyName))
- if shared.PathExists(oldStyleSnapshotMntPoint) {
- err := os.Remove(oldStyleSnapshotMntPoint)
- if err != nil {
- return err
- }
- }
-
- oldSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, oldName)
- if shared.PathExists(oldSnapshotMntPoint) {
- err := os.Remove(oldSnapshotMntPoint)
- if err != nil {
- return err
- }
- }
-
- newSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, newName)
- if !shared.PathExists(newSnapshotMntPoint) {
- err := os.MkdirAll(newSnapshotMntPoint, 0100)
- if err != nil {
- return err
- }
- }
-
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(snapshotContainer.Project(), oldcName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(snapshotContainer.Project(), oldcName))
- if !shared.PathExists(snapshotMntPointSymlink) {
- err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- }
-
- revert = false
-
- logger.Debugf("Renamed ZFS storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newName)
- return nil
-}
-
-func (s *storageZfs) ContainerSnapshotStart(container instance.Instance) (bool, error) {
- logger.Debugf("Initializing ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- cName, sName, _ := shared.InstanceGetParentAndSnapshotName(container.Name())
- sourceFs := fmt.Sprintf("containers/%s", project.Prefix(container.Project(), cName))
- sourceSnap := fmt.Sprintf("snapshot-%s", sName)
- destFs := fmt.Sprintf("snapshots/%s/%s", project.Prefix(container.Project(), cName), sName)
-
- poolName := s.getOnDiskPoolName()
- snapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, container.Name())
- err := zfsPoolVolumeClone(container.Project(), poolName, sourceFs, sourceSnap, destFs, snapshotMntPoint)
- if err != nil {
- return false, err
- }
-
- err = zfsMount(poolName, destFs)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Initialized ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-func (s *storageZfs) ContainerSnapshotStop(container instance.Instance) (bool, error) {
- logger.Debugf("Stopping ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- cName, sName, _ := shared.InstanceGetParentAndSnapshotName(container.Name())
- destFs := fmt.Sprintf("snapshots/%s/%s", project.Prefix(container.Project(), cName), sName)
-
- err := zfsPoolVolumeDestroy(s.getOnDiskPoolName(), destFs)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Stopped ZFS storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return true, nil
-}
-
-func (s *storageZfs) ContainerSnapshotCreateEmpty(snapshotContainer instance.Instance) error {
- /* don't touch the fs yet, as migration will do that for us */
- return nil
-}
-
-func (s *storageZfs) doContainerOnlyBackup(tmpPath string, backup backup.Backup, source instance.Instance) error {
- sourceIsSnapshot := source.IsSnapshot()
- poolName := s.getOnDiskPoolName()
-
- sourceName := source.Name()
- sourceDataset := ""
- snapshotSuffix := ""
-
- if sourceIsSnapshot {
- sourceParentName, sourceSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source.Name())
- snapshotSuffix = fmt.Sprintf("backup-%s", sourceSnapOnlyName)
- sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), sourceParentName), snapshotSuffix)
- } else {
- snapshotSuffix = uuid.NewRandom().String()
- sourceDataset = fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), sourceName), snapshotSuffix)
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(source.Project(), sourceName))
- err := zfsPoolVolumeSnapshotCreate(poolName, fs, snapshotSuffix)
- if err != nil {
- return err
- }
-
- defer func() {
- err := zfsPoolVolumeSnapshotDestroy(poolName, fs, snapshotSuffix)
- if err != nil {
- logger.Warnf("Failed to delete temporary ZFS snapshot \"%s\", manual cleanup needed", sourceDataset)
- }
- }()
- }
-
- // Dump the container to a file
- backupFile := fmt.Sprintf("%s/%s", tmpPath, "container.bin")
- f, err := os.OpenFile(backupFile, os.O_RDWR|os.O_CREATE, 0644)
- if err != nil {
- return err
- }
- defer f.Close()
-
- zfsSendCmd := exec.Command("zfs", "send", sourceDataset)
- zfsSendCmd.Stdout = f
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) doSnapshotBackup(tmpPath string, backup backup.Backup, source instance.Instance, parentSnapshot string) error {
- sourceName := source.Name()
- snapshotsPath := fmt.Sprintf("%s/snapshots", tmpPath)
-
- // Create backup path for snapshots
- err := os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return err
- }
-
- poolName := s.getOnDiskPoolName()
- sourceParentName, sourceSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(sourceName)
- currentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), sourceParentName), sourceSnapOnlyName)
- args := []string{"send", currentSnapshotDataset}
- if parentSnapshot != "" {
- parentName, parentSnaponlyName, _ := shared.InstanceGetParentAndSnapshotName(parentSnapshot)
- parentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), parentName), parentSnaponlyName)
- args = append(args, "-i", parentSnapshotDataset)
- }
-
- backupFile := fmt.Sprintf("%s/%s.bin", snapshotsPath, sourceSnapOnlyName)
- f, err := os.OpenFile(backupFile, os.O_RDWR|os.O_CREATE, 0644)
- if err != nil {
- return err
- }
- defer f.Close()
-
- zfsSendCmd := exec.Command("zfs", args...)
- zfsSendCmd.Stdout = f
- return zfsSendCmd.Run()
-}
-
-func (s *storageZfs) doContainerBackupCreateOptimized(tmpPath string, backup backup.Backup, source instance.Instance) error {
- // Handle snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- if backup.InstanceOnly() || len(snapshots) == 0 {
- err = s.doContainerOnlyBackup(tmpPath, backup, source)
- } else {
- prev := ""
- prevSnapOnlyName := ""
- for i, snap := range snapshots {
- if i > 0 {
- prev = snapshots[i-1].Name()
- }
-
- sourceSnapshot, err := instance.LoadByProjectAndName(s.s, source.Project(), snap.Name())
- if err != nil {
- return err
- }
-
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- prevSnapOnlyName = snapOnlyName
- err = s.doSnapshotBackup(tmpPath, backup, sourceSnapshot, prev)
- if err != nil {
- return err
- }
- }
-
- // Dump the container to a file
- poolName := s.getOnDiskPoolName()
- tmpSnapshotName := fmt.Sprintf("backup-%s", uuid.NewRandom().String())
- err = zfsPoolVolumeSnapshotCreate(poolName, fmt.Sprintf("containers/%s", project.Prefix(source.Project(), source.Name())), tmpSnapshotName)
- if err != nil {
- return err
- }
-
- currentSnapshotDataset := fmt.Sprintf("%s/containers/%s@%s", poolName, project.Prefix(source.Project(), source.Name()), tmpSnapshotName)
- args := []string{"send", currentSnapshotDataset}
- if prevSnapOnlyName != "" {
- parentSnapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(source.Project(), source.Name()), prevSnapOnlyName)
- args = append(args, "-i", parentSnapshotDataset)
- }
-
- backupFile := fmt.Sprintf("%s/container.bin", tmpPath)
- f, err := os.OpenFile(backupFile, os.O_RDWR|os.O_CREATE, 0644)
- if err != nil {
- return err
- }
- defer f.Close()
-
- zfsSendCmd := exec.Command("zfs", args...)
- zfsSendCmd.Stdout = f
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(source.Project(), source.Name())), tmpSnapshotName)
- }
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) doContainerBackupCreateVanilla(tmpPath string, backup backup.Backup, source instance.Instance) error {
- // Prepare for rsync
- rsync := func(oldPath string, newPath string, bwlimit string) error {
- output, err := rsync.LocalCopy(oldPath, newPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync: %s: %s", string(output), err)
- }
-
- return nil
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- projectName := source.Project()
-
- // Handle snapshots
- if !backup.InstanceOnly() {
- snapshotsPath := fmt.Sprintf("%s/snapshots", tmpPath)
-
- // Retrieve the snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return errors.Wrap(err, "Retrieve snaphots")
- }
-
- // Create the snapshot path
- if len(snapshots) > 0 {
- err = os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return errors.Wrap(err, "Create snapshot path")
- }
- }
-
- for _, snap := range snapshots {
- _, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
-
- // Mount the snapshot to a usable path
- _, err := s.ContainerSnapshotStart(snap)
- if err != nil {
- return errors.Wrap(err, "Mount snapshot")
- }
-
- snapshotMntPoint := driver.GetSnapshotMountPoint(projectName, s.pool.Name, snap.Name())
- target := fmt.Sprintf("%s/%s", snapshotsPath, snapName)
-
- // Copy the snapshot
- err = rsync(snapshotMntPoint, target, bwlimit)
- s.ContainerSnapshotStop(snap)
- if err != nil {
- return errors.Wrap(err, "Copy snapshot")
- }
- }
- }
-
- // Make a temporary copy of the container
- containersPath := driver.GetContainerMountPoint("default", s.pool.Name, "")
- tmpContainerMntPoint, err := ioutil.TempDir(containersPath, source.Name())
- if err != nil {
- return errors.Wrap(err, "Create temporary copy dir")
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- err = os.Chmod(tmpContainerMntPoint, 0100)
- if err != nil {
- return errors.Wrap(err, "Change temporary mount point permissions")
- }
-
- snapshotSuffix := uuid.NewRandom().String()
- sourceName := source.Name()
- fs := fmt.Sprintf("containers/%s", project.Prefix(projectName, sourceName))
- sourceZfsDatasetSnapshot := fmt.Sprintf("snapshot-%s", snapshotSuffix)
- poolName := s.getOnDiskPoolName()
- err = zfsPoolVolumeSnapshotCreate(poolName, fs, sourceZfsDatasetSnapshot)
- if err != nil {
- return err
- }
- defer zfsPoolVolumeSnapshotDestroy(poolName, fs, sourceZfsDatasetSnapshot)
-
- targetZfsDataset := fmt.Sprintf("containers/%s", snapshotSuffix)
- err = zfsPoolVolumeClone(source.Project(), poolName, fs, sourceZfsDatasetSnapshot, targetZfsDataset, tmpContainerMntPoint)
- if err != nil {
- return errors.Wrap(err, "Clone volume")
- }
- defer zfsPoolVolumeDestroy(poolName, targetZfsDataset)
-
- // Mount the temporary copy
- if !shared.IsMountPoint(tmpContainerMntPoint) {
- err = zfsMount(poolName, targetZfsDataset)
- if err != nil {
- return errors.Wrap(err, "Mount temporary copy")
- }
- defer zfsUmount(poolName, targetZfsDataset, tmpContainerMntPoint)
- }
-
- // Copy the container
- containerPath := fmt.Sprintf("%s/container", tmpPath)
- err = rsync(tmpContainerMntPoint, containerPath, bwlimit)
- if err != nil {
- return errors.Wrap(err, "Copy container")
- }
-
- return nil
-}
-
-func (s *storageZfs) ContainerBackupCreate(path string, backup backup.Backup, source instance.Instance) error {
- // Generate the actual backup
- if backup.OptimizedStorage() {
- err := s.doContainerBackupCreateOptimized(path, backup, source)
- if err != nil {
- return errors.Wrap(err, "Optimized backup")
- }
- } else {
- err := s.doContainerBackupCreateVanilla(path, backup, source)
- if err != nil {
- return errors.Wrap(err, "Vanilla backup")
- }
- }
-
- return nil
-}
-
-func (s *storageZfs) doContainerBackupLoadOptimized(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- containerName, _, _ := shared.InstanceGetParentAndSnapshotName(info.Name)
- containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, containerName)
- err := driver.CreateContainerMountpoint(containerMntPoint, driver.InstancePath(instancetype.Container, info.Project, info.Name, false), info.Privileged)
- if err != nil {
- return err
- }
-
- unpackPath := fmt.Sprintf("%s/.backup", containerMntPoint)
- err = os.MkdirAll(unpackPath, 0711)
- if err != nil {
- return err
- }
-
- err = os.Chmod(unpackPath, 0100)
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- return err
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=1",
- "-C", unpackPath, "backup",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- logger.Errorf("Failed to untar \"%s\" into \"%s\": %s", info.Name, unpackPath, err)
- return err
- }
-
- poolName := s.getOnDiskPoolName()
- for _, snapshotOnlyName := range info.Snapshots {
- snapshotBackup := fmt.Sprintf("%s/snapshots/%s.bin", unpackPath, snapshotOnlyName)
- feeder, err := os.Open(snapshotBackup)
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- return err
- }
-
- snapshotDataset := fmt.Sprintf("%s/containers/%s at snapshot-%s", poolName, project.Prefix(info.Project, containerName), snapshotOnlyName)
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", snapshotDataset)
- zfsRecvCmd.Stdin = feeder
- err = zfsRecvCmd.Run()
- feeder.Close()
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- return err
- }
-
- // create mountpoint
- snapshotMntPoint := driver.GetSnapshotMountPoint(info.Project, s.pool.Name, fmt.Sprintf("%s/%s", containerName, snapshotOnlyName))
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(info.Project, containerName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(info.Project, containerName))
- err = driver.CreateSnapshotMountpoint(snapshotMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- return err
- }
- }
-
- containerBackup := fmt.Sprintf("%s/container.bin", unpackPath)
- feeder, err := os.Open(containerBackup)
- if err != nil {
- // can't use defer because it needs to run before the mount
- os.RemoveAll(unpackPath)
- return err
- }
- defer feeder.Close()
-
- containerSnapshotDataset := fmt.Sprintf("%s/containers/%s at backup", poolName, project.Prefix(info.Project, containerName))
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", containerSnapshotDataset)
- zfsRecvCmd.Stdin = feeder
-
- err = zfsRecvCmd.Run()
- os.RemoveAll(unpackPath)
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(info.Project, containerName)), "backup")
- if err != nil {
- return err
- }
-
- fs := fmt.Sprintf("containers/%s", project.Prefix(info.Project, containerName))
- err = zfsPoolVolumeSet(poolName, fs, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", containerMntPoint)
- if err != nil {
- return err
- }
-
- _, err = s.doContainerMount(info.Project, containerName, info.Privileged)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) doContainerBackupLoadVanilla(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- // create the main container
- err := s.doContainerCreate(info.Project, info.Name, info.Privileged)
- if err != nil {
- s.doContainerDelete(info.Project, info.Name)
- return errors.Wrap(err, "Create container")
- }
-
- _, err = s.doContainerMount(info.Project, info.Name, info.Privileged)
- if err != nil {
- return errors.Wrap(err, "Mount container")
- }
-
- containerMntPoint := driver.GetContainerMountPoint(info.Project, s.pool.Name, info.Name)
- // Extract container
- for _, snap := range info.Snapshots {
- // Extract snapshots
- cur := fmt.Sprintf("backup/snapshots/%s", snap)
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--recursive-unlink",
- "--strip-components=3",
- "--xattrs-include=*",
- "-C", containerMntPoint, cur,
- }...)
-
- // Unpack
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- logger.Errorf("Failed to untar \"%s\" into \"%s\": %s", cur, containerMntPoint, err)
- return errors.Wrap(err, "Unpack")
- }
-
- // create snapshot
- err = s.doContainerSnapshotCreate(info.Project, fmt.Sprintf("%s/%s", info.Name, snap), info.Name)
- if err != nil {
- return errors.Wrap(err, "Create snapshot")
- }
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=2",
- "--xattrs-include=*",
- "-C", containerMntPoint, "backup/container",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- logger.Errorf("Failed to untar \"backup/container\" into \"%s\": %s", containerMntPoint, err)
- return errors.Wrap(err, "Extract")
- }
-
- return nil
-}
-
-func (s *storageZfs) ContainerBackupLoad(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- logger.Debugf("Loading ZFS storage volume for backup \"%s\" on storage pool \"%s\"", info.Name, s.pool.Name)
-
- if info.OptimizedStorage {
- return s.doContainerBackupLoadOptimized(info, data, tarArgs)
- }
-
- return s.doContainerBackupLoadVanilla(info, data, tarArgs)
-}
-
-// - create temporary directory ${LXD_DIR}/images/lxd_images_
-// - create new zfs volume images/<fingerprint>
-// - mount the zfs volume on ${LXD_DIR}/images/lxd_images_
-// - unpack the downloaded image in ${LXD_DIR}/images/lxd_images_
-// - mark new zfs volume images/<fingerprint> readonly
-// - remove mountpoint property from zfs volume images/<fingerprint>
-// - create read-write snapshot from zfs volume images/<fingerprint>
-func (s *storageZfs) ImageCreate(fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating ZFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- // Common variables
- poolName := s.getOnDiskPoolName()
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- fs := fmt.Sprintf("images/%s", fingerprint)
-
- // Revert flags
- revertDB := true
- revertMountpoint := true
- revertDataset := true
-
- // Deal with bad/partial unpacks
- if zfsFilesystemEntityExists(poolName, fs) {
- zfsPoolVolumeDestroy(poolName, fmt.Sprintf("%s at readonly", fs))
- zfsPoolVolumeDestroy(poolName, fs)
- s.deleteImageDbPoolVolume(fingerprint)
- }
-
- // Create the image volume entry
- err := s.createImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- defer func() {
- if !revertDB {
- return
- }
-
- s.deleteImageDbPoolVolume(fingerprint)
- }()
-
- // Create mountpoint if missing
- if !shared.PathExists(imageMntPoint) {
- err := os.MkdirAll(imageMntPoint, 0700)
- if err != nil {
- return err
- }
-
- defer func() {
- if !revertMountpoint {
- return
- }
-
- os.RemoveAll(imageMntPoint)
- }()
- }
-
- // Check for deleted images
- if zfsFilesystemEntityExists(poolName, fmt.Sprintf("deleted/%s", fmt.Sprintf("%s at readonly", fs))) {
- // Restore deleted image
- err := zfsPoolVolumeRename(poolName, fmt.Sprintf("deleted/%s", fs), fs, true)
- if err != nil {
- return err
- }
-
- // In case this is an image from an older lxd instance, wipe the mountpoint.
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", "none")
- if err != nil {
- return err
- }
-
- revertDB = false
- revertMountpoint = false
- return nil
- }
-
- // Create temporary mountpoint directory.
- tmp := driver.GetImageMountPoint(s.pool.Name, "")
- tmpImageDir, err := ioutil.TempDir(tmp, "")
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpImageDir)
-
- imagePath := shared.VarPath("images", fingerprint)
-
- // Create a new dataset for the image
- dataset := fmt.Sprintf("%s/%s", poolName, fs)
- msg, err := zfsPoolVolumeCreate(dataset, "mountpoint=none")
- if err != nil {
- logger.Errorf("Failed to create ZFS dataset \"%s\" on storage pool \"%s\": %s", dataset, s.pool.Name, msg)
- return err
- }
-
- defer func() {
- if !revertDataset {
- return
- }
-
- zfsPoolVolumeDestroy(poolName, fs)
- }()
-
- // Set a temporary mountpoint for the image.
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", tmpImageDir)
- if err != nil {
- return err
- }
-
- // Make sure that the image actually got mounted.
- if !shared.IsMountPoint(tmpImageDir) {
- zfsMount(poolName, fs)
- }
-
- // Unpack the image into the temporary mountpoint.
- err = driver.ImageUnpack(imagePath, tmpImageDir, "", false, s.s.OS.RunningInUserNS, nil)
- if err != nil {
- return err
- }
-
- // Mark the new storage volume for the image as readonly.
- if err = zfsPoolVolumeSet(poolName, fs, "readonly", "on"); err != nil {
- return err
- }
-
- // Remove the temporary mountpoint from the image storage volume.
- if err = zfsPoolVolumeSet(poolName, fs, "mountpoint", "none"); err != nil {
- return err
- }
-
- // Make sure that the image actually got unmounted.
- if shared.IsMountPoint(tmpImageDir) {
- zfsUmount(poolName, fs, tmpImageDir)
- }
-
- // Create a snapshot of that image on the storage pool which we clone for
- // container creation.
- err = zfsPoolVolumeSnapshotCreate(poolName, fs, "readonly")
- if err != nil {
- return err
- }
-
- revertDB = false
- revertMountpoint = false
- revertDataset = false
-
- logger.Debugf("Created ZFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) ImageDelete(fingerprint string) error {
- logger.Debugf("Deleting ZFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- fs := fmt.Sprintf("images/%s", fingerprint)
-
- if zfsFilesystemEntityExists(poolName, fs) {
- removable, err := zfsPoolVolumeSnapshotRemovable(poolName, fs, "readonly")
- if err != nil && zfsFilesystemEntityExists(poolName, fmt.Sprintf("%s at readonly", fs)) {
- return err
- }
-
- if removable {
- err := zfsPoolVolumeDestroy(poolName, fs)
- if err != nil {
- return err
- }
- } else {
- if err := zfsPoolVolumeSet(poolName, fs, "mountpoint", "none"); err != nil {
- return err
- }
-
- if err := zfsPoolVolumeRename(poolName, fs, fmt.Sprintf("deleted/%s", fs), true); err != nil {
- return err
- }
- }
- }
-
- err := s.deleteImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if shared.PathExists(imageMntPoint) {
- err := os.RemoveAll(imageMntPoint)
- if err != nil {
- return err
- }
- }
-
- if shared.PathExists(shared.VarPath(fs + ".zfs")) {
- err := os.RemoveAll(shared.VarPath(fs + ".zfs"))
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Deleted ZFS storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) ImageMount(fingerprint string) (bool, error) {
- return true, nil
-}
-
-func (s *storageZfs) ImageUmount(fingerprint string) (bool, error) {
- return true, nil
-}
-
-func (s *storageZfs) MigrationType() migration.MigrationFSType {
- return migration.MigrationFSType_ZFS
-}
-
-func (s *storageZfs) PreservesInodes() bool {
- return true
-}
-
-func (s *storageZfs) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- /* If the container is a snapshot, let's just send that; we don't need
- * to send anything else, because that's all the user asked for.
- */
- if args.Instance.IsSnapshot() {
- return &zfsMigrationSourceDriver{instance: args.Instance, zfs: s, zfsFeatures: args.ZfsFeatures}, nil
- }
-
- driver := zfsMigrationSourceDriver{
- instance: args.Instance,
- snapshots: []instance.Instance{},
- zfsSnapshotNames: []string{},
- zfs: s,
- zfsFeatures: args.ZfsFeatures,
- }
-
- if args.InstanceOnly {
- return &driver, nil
- }
-
- /* List all the snapshots in order of reverse creation. The idea here
- * is that we send the oldest to newest snapshot, hopefully saving on
- * xfer costs. Then, after all that, we send the container itself.
- */
- snapshots, err := zfsPoolListSnapshots(s.getOnDiskPoolName(), fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name())))
- if err != nil {
- return nil, err
- }
-
- for _, snap := range snapshots {
- /* In the case of e.g. multiple copies running at the same
- * time, we will have potentially multiple migration-send
- * snapshots. (Or in the case of the test suite, sometimes one
- * will take too long to delete.)
- */
- if !strings.HasPrefix(snap, "snapshot-") {
- continue
- }
-
- lxdName := fmt.Sprintf("%s%s%s", args.Instance.Name(), shared.SnapshotDelimiter, snap[len("snapshot-"):])
- snapshot, err := instance.LoadByProjectAndName(s.s, args.Instance.Project(), lxdName)
- if err != nil {
- return nil, err
- }
-
- driver.snapshots = append(driver.snapshots, snapshot)
- driver.zfsSnapshotNames = append(driver.zfsSnapshotNames, snap)
- }
-
- return &driver, nil
-}
-
-func (s *storageZfs) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- poolName := s.getOnDiskPoolName()
- zfsName := fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name()))
- zfsRecv := func(zfsName string, writeWrapper func(io.WriteCloser) io.WriteCloser) error {
- zfsFsName := fmt.Sprintf("%s/%s", poolName, zfsName)
- args := []string{"receive", "-F", "-u", zfsFsName}
- cmd := exec.Command("zfs", args...)
-
- stdin, err := cmd.StdinPipe()
- if err != nil {
- return err
- }
-
- stderr, err := cmd.StderrPipe()
- if err != nil {
- return err
- }
-
- if err := cmd.Start(); err != nil {
- return err
- }
-
- writePipe := io.WriteCloser(stdin)
- if writeWrapper != nil {
- writePipe = writeWrapper(stdin)
- }
-
- <-shared.WebsocketRecvStream(writePipe, conn)
-
- output, err := ioutil.ReadAll(stderr)
- if err != nil {
- logger.Debugf("Problem reading zfs recv stderr %s", err)
- }
-
- err = cmd.Wait()
- if err != nil {
- logger.Errorf("Problem with zfs recv: %s", string(output))
- return err
- }
-
- if !strings.Contains(zfsName, "@") {
- err = zfsPoolVolumeSet(poolName, zfsName, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, zfsName, "mountpoint", "none")
- if err != nil {
- return err
- }
- }
-
- return nil
- }
-
- // Destroy the pre-existing (empty) dataset, this avoids issues with encryption
- err := zfsPoolVolumeDestroy(poolName, zfsName)
- if err != nil {
- return err
- }
-
- if len(args.Snapshots) > 0 {
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(args.Instance.Project(), s.volume.Name))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(args.Instance.Project(), args.Instance.Name()))
- if !shared.PathExists(snapshotMntPointSymlink) {
- err := os.Symlink(snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- // At this point we have already figured out the parent
- // container's root disk device so we can simply
- // retrieve it from the expanded devices.
- parentStoragePool := ""
- parentExpandedDevices := args.Instance.ExpandedDevices()
- parentLocalRootDiskDeviceKey, parentLocalRootDiskDevice, _ := shared.GetRootDiskDevice(parentExpandedDevices.CloneNative())
- if parentLocalRootDiskDeviceKey != "" {
- parentStoragePool = parentLocalRootDiskDevice["pool"]
- }
-
- // A little neuroticism.
- if parentStoragePool == "" {
- return fmt.Errorf("detected that the container's root device is missing the pool property during BTRFS migration")
- }
-
- for _, snap := range args.Snapshots {
- ctArgs := snapshotProtobufToInstanceArgs(args.Instance.Project(), args.Instance.Name(), snap)
-
- // Ensure that snapshot and parent container have the
- // same storage pool in their local root disk device.
- // If the root disk device for the snapshot comes from a
- // profile on the new instance as well we don't need to
- // do anything.
- if ctArgs.Devices != nil {
- snapLocalRootDiskDeviceKey, _, _ := shared.GetRootDiskDevice(ctArgs.Devices.CloneNative())
- if snapLocalRootDiskDeviceKey != "" {
- ctArgs.Devices[snapLocalRootDiskDeviceKey]["pool"] = parentStoragePool
- }
- }
- _, err := containerCreateEmptySnapshot(args.Instance.DaemonState(), ctArgs)
- if err != nil {
- return err
- }
-
- wrapper := migration.ProgressWriter(op, "fs_progress", snap.GetName())
- name := fmt.Sprintf("containers/%s at snapshot-%s", project.Prefix(args.Instance.Project(), args.Instance.Name()), snap.GetName())
- if err := zfsRecv(name, wrapper); err != nil {
- return err
- }
-
- snapshotMntPoint := driver.GetSnapshotMountPoint(args.Instance.Project(), poolName, fmt.Sprintf("%s/%s", args.Instance.Name(), *snap.Name))
- if !shared.PathExists(snapshotMntPoint) {
- err := os.MkdirAll(snapshotMntPoint, 0100)
- if err != nil {
- return err
- }
- }
- }
-
- defer func() {
- /* clean up our migration-send snapshots that we got from recv. */
- zfsSnapshots, err := zfsPoolListSnapshots(poolName, fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name())))
- if err != nil {
- logger.Errorf("Failed listing snapshots post migration: %s", err)
- return
- }
-
- for _, snap := range zfsSnapshots {
- // If we received a bunch of snapshots, remove the migration-send-* ones, if not, wipe any snapshot we got
- if args.Snapshots != nil && len(args.Snapshots) > 0 && !strings.HasPrefix(snap, "migration-send") {
- continue
- }
-
- zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("containers/%s", project.Prefix(args.Instance.Project(), args.Instance.Name())), snap)
- }
- }()
-
- /* finally, do the real container */
- wrapper := migration.ProgressWriter(op, "fs_progress", args.Instance.Name())
- if err := zfsRecv(zfsName, wrapper); err != nil {
- return err
- }
-
- if args.Live {
- /* and again for the post-running snapshot if this was a live migration */
- wrapper := migration.ProgressWriter(op, "fs_progress", args.Instance.Name())
- if err := zfsRecv(zfsName, wrapper); err != nil {
- return err
- }
- }
-
- /* Sometimes, zfs recv mounts this anyway, even if we pass -u
- * (https://forums.freebsd.org/threads/zfs-receive-u-shouldnt-mount-received-filesystem-right.36844/)
- * but sometimes it doesn't. Let's try to mount, but not complain about
- * failure.
- */
- zfsMount(poolName, zfsName)
- return nil
-}
-
-func (s *storageZfs) StorageEntitySetQuota(volumeType int, size int64, data interface{}) error {
- logger.Debugf(`Setting ZFS quota for "%s"`, s.volume.Name)
-
- if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return fmt.Errorf("Invalid storage type")
- }
-
- var c instance.Instance
- var fs string
- switch volumeType {
- case storagePoolVolumeTypeContainer:
- c = data.(instance.Instance)
- fs = fmt.Sprintf("containers/%s", project.Prefix(c.Project(), c.Name()))
- case storagePoolVolumeTypeCustom:
- fs = fmt.Sprintf("custom/%s", s.volume.Name)
- }
-
- property := "quota"
-
- if s.pool.Config["volume.zfs.use_refquota"] != "" {
- zfsUseRefquota = s.pool.Config["volume.zfs.use_refquota"]
- }
- if s.volume.Config["zfs.use_refquota"] != "" {
- zfsUseRefquota = s.volume.Config["zfs.use_refquota"]
- }
-
- if shared.IsTrue(zfsUseRefquota) {
- property = "refquota"
- }
-
- poolName := s.getOnDiskPoolName()
- var err error
- if size > 0 {
- err = zfsPoolVolumeSet(poolName, fs, property, fmt.Sprintf("%d", size))
- } else {
- err = zfsPoolVolumeSet(poolName, fs, property, "none")
- }
-
- if err != nil {
- return err
- }
-
- logger.Debugf(`Set ZFS quota for "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolResources() (*api.ResourcesStoragePool, error) {
- poolName := s.getOnDiskPoolName()
-
- totalBuf, err := zfsFilesystemEntityPropertyGet(poolName, "", "available")
- if err != nil {
- return nil, err
- }
-
- totalStr := string(totalBuf)
- totalStr = strings.TrimSpace(totalStr)
- total, err := strconv.ParseUint(totalStr, 10, 64)
- if err != nil {
- return nil, err
- }
-
- usedBuf, err := zfsFilesystemEntityPropertyGet(poolName, "", "used")
- if err != nil {
- return nil, err
- }
-
- usedStr := string(usedBuf)
- usedStr = strings.TrimSpace(usedStr)
- used, err := strconv.ParseUint(usedStr, 10, 64)
- if err != nil {
- return nil, err
- }
-
- res := api.ResourcesStoragePool{}
- res.Space.Total = total
- res.Space.Used = used
-
- // Inode allocation is dynamic so no use in reporting them.
-
- return &res, nil
-}
-
-func (s *storageZfs) doCrossPoolStorageVolumeCopy(source *api.StorageVolumeSource) error {
- successMsg := fmt.Sprintf("Copied ZFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- logger.Errorf("Failed to initialize ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
-
- // Create the main volume
- err = s.StoragePoolVolumeCreate()
- if err != nil {
- logger.Errorf("Failed to create ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- ourMount, err = s.StoragePoolVolumeMount()
- if err != nil {
- logger.Errorf("Failed to mount ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
- if ourMount {
- defer s.StoragePoolVolumeUmount()
- }
-
- dstMountPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- snapshots, err := driver.VolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- if !source.VolumeOnly {
- for _, snap := range snapshots {
- srcMountPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(source.Pool, snap.Name)
-
- _, err = rsync.LocalCopy(srcMountPoint, dstMountPoint, bwlimit, true)
- if err != nil {
- logger.Errorf("Failed to rsync into ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source.Name)
-
- s.StoragePoolVolumeSnapshotCreate(&api.StorageVolumeSnapshotsPost{Name: fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)})
- }
- }
-
- var srcMountPoint string
-
- if strings.Contains(source.Name, "/") {
- srcMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(source.Pool, source.Name)
- } else {
- srcMountPoint = driver.GetStoragePoolVolumeMountPoint(source.Pool, source.Name)
- }
-
- _, err = rsync.LocalCopy(srcMountPoint, dstMountPoint, bwlimit, true)
- if err != nil {
- os.RemoveAll(dstMountPoint)
- logger.Errorf("Failed to rsync into ZFS storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- logger.Infof(successMsg)
- return nil
-}
-
-func (s *storageZfs) copyVolumeWithoutSnapshotsFull(source *api.StorageVolumeSource) error {
- sourceIsSnapshot := shared.IsSnapshot(source.Name)
-
- var snapshotSuffix string
- var sourceDataset string
- var targetDataset string
- var targetSnapshotDataset string
-
- poolName := s.getOnDiskPoolName()
-
- if sourceIsSnapshot {
- sourceVolumeName, sourceSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source.Name)
- snapshotSuffix = fmt.Sprintf("snapshot-%s", sourceSnapOnlyName)
- sourceDataset = fmt.Sprintf("%s/custom/%s@%s", source.Pool, sourceVolumeName, snapshotSuffix)
- targetSnapshotDataset = fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, s.volume.Name, sourceSnapOnlyName)
- } else {
- snapshotSuffix = uuid.NewRandom().String()
- sourceDataset = fmt.Sprintf("%s/custom/%s@%s", poolName, source.Name, snapshotSuffix)
- targetSnapshotDataset = fmt.Sprintf("%s/custom/%s@%s", poolName, s.volume.Name, snapshotSuffix)
-
- fs := fmt.Sprintf("custom/%s", source.Name)
- err := zfsPoolVolumeSnapshotCreate(poolName, fs, snapshotSuffix)
- if err != nil {
- return err
- }
- defer func() {
- err := zfsPoolVolumeSnapshotDestroy(poolName, fs, snapshotSuffix)
- if err != nil {
- logger.Warnf("Failed to delete temporary ZFS snapshot \"%s\", manual cleanup needed", sourceDataset)
- }
- }()
- }
-
- zfsSendCmd := exec.Command("zfs", "send", sourceDataset)
-
- zfsRecvCmd := exec.Command("zfs", "receive", targetDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err := zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
-
- msg, err := shared.RunCommand("zfs", "rollback", "-r", "-R", targetSnapshotDataset)
- if err != nil {
- logger.Errorf("Failed to rollback ZFS dataset: %s", msg)
- return err
- }
-
- targetContainerMountPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- targetfs := fmt.Sprintf("custom/%s", s.volume.Name)
-
- err = zfsPoolVolumeSet(poolName, targetfs, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, targetfs, "mountpoint", targetContainerMountPoint)
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSnapshotDestroy(poolName, targetfs, snapshotSuffix)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageZfs) copyVolumeWithoutSnapshotsSparse(source *api.StorageVolumeSource) error {
- poolName := s.getOnDiskPoolName()
-
- sourceVolumeName := source.Name
- sourceVolumePath := driver.GetStoragePoolVolumeMountPoint(source.Pool, source.Name)
-
- targetVolumeName := s.volume.Name
- targetVolumePath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- sourceZfsDataset := ""
- sourceZfsDatasetSnapshot := ""
- sourceName, sourceSnapOnlyName, isSnapshotName := shared.InstanceGetParentAndSnapshotName(sourceVolumeName)
-
- targetZfsDataset := fmt.Sprintf("custom/%s", targetVolumeName)
-
- if isSnapshotName {
- sourceZfsDatasetSnapshot = sourceSnapOnlyName
- }
-
- revert := true
- if sourceZfsDatasetSnapshot == "" {
- if zfsFilesystemEntityExists(poolName, fmt.Sprintf("custom/%s", sourceName)) {
- sourceZfsDatasetSnapshot = fmt.Sprintf("copy-%s", uuid.NewRandom().String())
- sourceZfsDataset = fmt.Sprintf("custom/%s", sourceName)
-
- err := zfsPoolVolumeSnapshotCreate(poolName, sourceZfsDataset, sourceZfsDatasetSnapshot)
- if err != nil {
- return err
- }
-
- defer func() {
- if !revert {
- return
- }
- zfsPoolVolumeSnapshotDestroy(poolName, sourceZfsDataset, sourceZfsDatasetSnapshot)
- }()
- }
- } else {
- if zfsFilesystemEntityExists(poolName, fmt.Sprintf("custom/%s at snapshot-%s", sourceName, sourceZfsDatasetSnapshot)) {
- sourceZfsDataset = fmt.Sprintf("custom/%s", sourceName)
- sourceZfsDatasetSnapshot = fmt.Sprintf("snapshot-%s", sourceZfsDatasetSnapshot)
- }
- }
-
- if sourceZfsDataset != "" {
- err := zfsPoolVolumeClone("default", poolName, sourceZfsDataset, sourceZfsDatasetSnapshot, targetZfsDataset, targetVolumePath)
- if err != nil {
- return err
- }
-
- defer func() {
- if !revert {
- return
- }
- zfsPoolVolumeDestroy(poolName, targetZfsDataset)
- }()
- } else {
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- output, err := rsync.LocalCopy(sourceVolumePath, targetVolumePath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("rsync failed: %s", string(output))
- }
- }
-
- revert = false
-
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeCopy(source *api.StorageVolumeSource) error {
- logger.Infof("Copying ZFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
- successMsg := fmt.Sprintf("Copied ZFS storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
-
- if source.Pool != s.pool.Name {
- return s.doCrossPoolStorageVolumeCopy(source)
- }
-
- var snapshots []string
-
- poolName := s.getOnDiskPoolName()
-
- if !shared.IsSnapshot(source.Name) {
- allSnapshots, err := zfsPoolListSnapshots(poolName, fmt.Sprintf("custom/%s", source.Name))
- if err != nil {
- return err
- }
-
- for _, snap := range allSnapshots {
- if strings.HasPrefix(snap, "snapshot-") {
- snapshots = append(snapshots, strings.TrimPrefix(snap, "snapshot-"))
- }
- }
- }
-
- targetStorageVolumeMountPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- fs := fmt.Sprintf("custom/%s", s.volume.Name)
-
- if source.VolumeOnly || len(snapshots) == 0 {
- var err error
-
- if s.pool.Config["zfs.clone_copy"] != "" && !shared.IsTrue(s.pool.Config["zfs.clone_copy"]) {
- err = s.copyVolumeWithoutSnapshotsFull(source)
- } else {
- err = s.copyVolumeWithoutSnapshotsSparse(source)
- }
- if err != nil {
- return err
- }
- } else {
- targetVolumeMountPoint := driver.GetStoragePoolVolumeMountPoint(poolName, s.volume.Name)
-
- err := os.MkdirAll(targetVolumeMountPoint, 0711)
- if err != nil {
- return err
- }
-
- prev := ""
- prevSnapOnlyName := ""
-
- for i, snap := range snapshots {
- if i > 0 {
- prev = snapshots[i-1]
- }
-
- sourceDataset := fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, source.Name, snap)
- targetDataset := fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, s.volume.Name, snap)
-
- snapshotMntPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(poolName, fmt.Sprintf("%s/%s", s.volume.Name, snap))
-
- err := os.MkdirAll(snapshotMntPoint, 0700)
- if err != nil {
- return err
- }
-
- prevSnapOnlyName = snap
-
- args := []string{"send", sourceDataset}
-
- if prev != "" {
- parentDataset := fmt.Sprintf("%s/custom/%s/snapshot-%s", poolName, source.Name, prev)
- args = append(args, "-i", parentDataset)
- }
-
- zfsSendCmd := exec.Command("zfs", args...)
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", targetDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err = zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
- }
-
- tmpSnapshotName := fmt.Sprintf("copy-send-%s", uuid.NewRandom().String())
-
- err = zfsPoolVolumeSnapshotCreate(poolName, fmt.Sprintf("custom/%s", source.Name), tmpSnapshotName)
- if err != nil {
- return err
- }
-
- defer zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("custom/%s", source.Name), tmpSnapshotName)
-
- currentSnapshotDataset := fmt.Sprintf("%s/custom/%s@%s", poolName, source.Name, tmpSnapshotName)
-
- args := []string{"send", currentSnapshotDataset}
-
- if prevSnapOnlyName != "" {
- args = append(args, "-i", fmt.Sprintf("%s/custom/%s at snapshot-%s", poolName, source.Name, prevSnapOnlyName))
- }
-
- zfsSendCmd := exec.Command("zfs", args...)
- targetDataset := fmt.Sprintf("%s/custom/%s", poolName, s.volume.Name)
- zfsRecvCmd := exec.Command("zfs", "receive", "-F", targetDataset)
-
- zfsRecvCmd.Stdin, _ = zfsSendCmd.StdoutPipe()
- zfsRecvCmd.Stdout = os.Stdout
- zfsRecvCmd.Stderr = os.Stderr
-
- err = zfsRecvCmd.Start()
- if err != nil {
- return err
- }
-
- err = zfsSendCmd.Run()
- if err != nil {
- return err
- }
-
- err = zfsRecvCmd.Wait()
- if err != nil {
- return err
- }
-
- defer zfsPoolVolumeSnapshotDestroy(poolName, fmt.Sprintf("custom/%s", s.volume.Name), tmpSnapshotName)
-
- err = zfsPoolVolumeSet(poolName, fs, "canmount", "noauto")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", targetStorageVolumeMountPoint)
- if err != nil {
- return err
- }
- }
-
- if !shared.IsMountPoint(targetStorageVolumeMountPoint) {
- err := zfsMount(poolName, fs)
- if err != nil {
- return err
- }
- defer zfsUmount(poolName, fs, targetStorageVolumeMountPoint)
- }
-
- // apply quota
- if s.volume.Config["size"] != "" {
- size, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
-
- logger.Infof(successMsg)
- return nil
-}
-
-func (s *storageZfs) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncStorageMigrationSource(args)
-}
-
-func (s *storageZfs) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncStorageMigrationSink(conn, op, args)
-}
-
-func (s *storageZfs) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
- logger.Infof("Creating ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- sourceOnlyName, snapshotOnlyName, ok := shared.InstanceGetParentAndSnapshotName(target.Name)
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- sourceDataset := fmt.Sprintf("custom/%s", sourceOnlyName)
- poolName := s.getOnDiskPoolName()
- snapName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
- err := zfsPoolVolumeSnapshotCreate(poolName, sourceDataset, snapName)
- if err != nil {
- return err
- }
-
- snapshotMntPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
- if !shared.PathExists(snapshotMntPoint) {
- err := os.MkdirAll(snapshotMntPoint, 0700)
- if err != nil {
- return err
- }
- }
-
- logger.Infof("Created ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeSnapshotDelete() error {
- logger.Infof("Deleting ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- sourceName, snapshotOnlyName, _ := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- snapshotName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
-
- onDiskPoolName := s.getOnDiskPoolName()
- if zfsFilesystemEntityExists(onDiskPoolName, fmt.Sprintf("custom/%s@%s", sourceName, snapshotName)) {
- removable, err := zfsPoolVolumeSnapshotRemovable(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName)
- if err != nil {
- return err
- }
-
- if removable {
- err = zfsPoolVolumeSnapshotDestroy(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName)
- } else {
- err = zfsPoolVolumeSnapshotRename(onDiskPoolName, fmt.Sprintf("custom/%s", sourceName), snapshotName, fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
- }
- if err != nil {
- return err
- }
- }
-
- storageVolumePath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- err := os.RemoveAll(storageVolumePath)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- storageVolumeSnapshotPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
- empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
- if err == nil && empty {
- os.RemoveAll(storageVolumeSnapshotPath)
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for DIR storage volume "%s" on storage pool "%s"`,
- s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted ZFS storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) StoragePoolVolumeSnapshotRename(newName string) error {
- sourceName, snapshotOnlyName, ok := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
-
- logger.Infof("Renaming ZFS storage volume snapshot on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- oldZfsDatasetName := fmt.Sprintf("snapshot-%s", snapshotOnlyName)
- newZfsDatasetName := fmt.Sprintf("snapshot-%s", newName)
- err := zfsPoolVolumeSnapshotRename(s.getOnDiskPoolName(), fmt.Sprintf("custom/%s", sourceName), oldZfsDatasetName, newZfsDatasetName)
- if err != nil {
- return err
- }
-
- logger.Infof("Renamed ZFS storage volume snapshot on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fmt.Sprintf("%s/%s", sourceName, newName), storagePoolVolumeTypeCustom, s.poolID)
-}
diff --git a/lxd/storage_zfs_utils.go b/lxd/storage_zfs_utils.go
deleted file mode 100644
index 7cefa01bab..0000000000
--- a/lxd/storage_zfs_utils.go
+++ /dev/null
@@ -1,839 +0,0 @@
-package main
-
-import (
- "fmt"
- "io/ioutil"
- "os"
- "os/exec"
- "path/filepath"
- "strings"
- "time"
-
- "github.com/pborman/uuid"
- "github.com/pkg/errors"
- "golang.org/x/sys/unix"
-
- "github.com/lxc/lxd/lxd/project"
- driver "github.com/lxc/lxd/lxd/storage"
- storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/logger"
-)
-
-// zfsIsEnabled returns whether zfs backend is supported.
-func zfsIsEnabled() bool {
- out, err := exec.LookPath("zfs")
- if err != nil || len(out) == 0 {
- return false
- }
-
- return true
-}
-
-// zfsToolVersionGet returns the ZFS tools version
-func zfsToolVersionGet() (string, error) {
- // This function is only really ever relevant on Ubuntu as the only
- // distro that ships out of sync tools and kernel modules
- out, err := shared.RunCommand("dpkg-query", "--showformat=${Version}", "--show", "zfsutils-linux")
- if err != nil {
- return "", err
- }
-
- return strings.TrimSpace(string(out)), nil
-}
-
-// zfsModuleVersionGet returns the ZFS module version
-func zfsModuleVersionGet() (string, error) {
- var zfsVersion string
-
- if shared.PathExists("/sys/module/zfs/version") {
- out, err := ioutil.ReadFile("/sys/module/zfs/version")
- if err != nil {
- return "", fmt.Errorf("Could not determine ZFS module version")
- }
-
- zfsVersion = string(out)
- } else {
- out, err := shared.RunCommand("modinfo", "-F", "version", "zfs")
- if err != nil {
- return "", fmt.Errorf("Could not determine ZFS module version")
- }
-
- zfsVersion = out
- }
-
- return strings.TrimSpace(zfsVersion), nil
-}
-
-// zfsPoolVolumeCreate creates a ZFS dataset with a set of given properties.
-func zfsPoolVolumeCreate(dataset string, properties ...string) (string, error) {
- cmd := []string{"zfs", "create"}
-
- for _, prop := range properties {
- cmd = append(cmd, []string{"-o", prop}...)
- }
-
- cmd = append(cmd, []string{"-p", dataset}...)
-
- return shared.RunCommand(cmd[0], cmd[1:]...)
-}
-
-func zfsPoolCheck(pool string) error {
- output, err := shared.RunCommand(
- "zfs", "get", "-H", "-o", "value", "type", pool)
- if err != nil {
- return err
- }
-
- poolType := strings.Split(output, "\n")[0]
- if poolType != "filesystem" {
- return fmt.Errorf("Unsupported pool type: %s", poolType)
- }
-
- return nil
-}
-
-// zfsPoolVolumeExists verifies if a specific ZFS pool or volume exists.
-func zfsPoolVolumeExists(dataset string) (bool, error) {
- output, err := shared.RunCommand(
- "zfs", "list", "-Ho", "name")
-
- if err != nil {
- return false, err
- }
-
- for _, name := range strings.Split(output, "\n") {
- if name == dataset {
- return true, nil
- }
- }
- return false, nil
-}
-
-func zfsPoolCreate(pool string, vdev string) error {
- var err error
-
- dataset := ""
-
- if pool == "" {
- _, err := shared.RunCommand(
- "zfs", "create", "-p", "-o", "mountpoint=none", vdev)
- if err != nil {
- logger.Errorf("zfs create failed: %v", err)
- return errors.Wrap(err, "Failed to create ZFS filesystem")
- }
- dataset = vdev
- } else {
- _, err = shared.RunCommand(
- "zpool", "create", "-f", "-m", "none", "-O", "compression=on", pool, vdev)
- if err != nil {
- logger.Errorf("zfs create failed: %v", err)
- return errors.Wrap(err, "Failed to create the ZFS pool")
- }
-
- dataset = pool
- }
-
- err = zfsPoolApplyDefaults(dataset)
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func zfsPoolApplyDefaults(dataset string) error {
- err := zfsPoolVolumeSet(dataset, "", "mountpoint", "none")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(dataset, "", "setuid", "on")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(dataset, "", "exec", "on")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(dataset, "", "devices", "on")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(dataset, "", "acltype", "posixacl")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeSet(dataset, "", "xattr", "sa")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func zfsPoolVolumeClone(project, pool string, source string, name string, dest string, mountpoint string) error {
- _, err := shared.RunCommand(
- "zfs",
- "clone",
- "-p",
- "-o", fmt.Sprintf("mountpoint=%s", mountpoint),
- "-o", "canmount=noauto",
- fmt.Sprintf("%s/%s@%s", pool, source, name),
- fmt.Sprintf("%s/%s", pool, dest))
- if err != nil {
- logger.Errorf("zfs clone failed: %v", err)
- return errors.Wrap(err, "Failed to clone the filesystem")
- }
-
- subvols, err := zfsPoolListSubvolumes(pool, fmt.Sprintf("%s/%s", pool, source))
- if err != nil {
- return err
- }
-
- for _, sub := range subvols {
- snaps, err := zfsPoolListSnapshots(pool, sub)
- if err != nil {
- return err
- }
-
- if !shared.StringInSlice(name, snaps) {
- continue
- }
-
- destSubvol := dest + strings.TrimPrefix(sub, source)
- snapshotMntPoint := driver.GetSnapshotMountPoint(project, pool, destSubvol)
-
- _, err = shared.RunCommand(
- "zfs",
- "clone",
- "-p",
- "-o", fmt.Sprintf("mountpoint=%s", snapshotMntPoint),
- "-o", "canmount=noauto",
- fmt.Sprintf("%s/%s@%s", pool, sub, name),
- fmt.Sprintf("%s/%s", pool, destSubvol))
- if err != nil {
- logger.Errorf("zfs clone failed: %v", err)
- return errors.Wrap(err, "Failed to clone the sub-volume")
- }
- }
-
- return nil
-}
-
-func zfsFilesystemEntityDelete(vdev string, pool string) error {
- var err error
- if strings.Contains(pool, "/") {
- // Command to destroy a zfs dataset.
- _, err = shared.RunCommand("zfs", "destroy", "-r", pool)
- } else {
- // Command to destroy a zfs pool.
- _, err = shared.RunCommand("zpool", "destroy", "-f", pool)
- }
- if err != nil {
- return errors.Wrap(err, "Failed to delete the ZFS pool")
- }
-
- // Cleanup storage
- if filepath.IsAbs(vdev) && !shared.IsBlockdevPath(vdev) {
- os.RemoveAll(vdev)
- }
-
- return nil
-}
-
-func zfsPoolVolumeDestroy(pool string, path string) error {
- mountpoint, err := zfsFilesystemEntityPropertyGet(pool, path, "mountpoint")
- if err != nil {
- return err
- }
-
- if mountpoint != "none" && shared.IsMountPoint(mountpoint) {
- // Make sure the filesystem isn't mounted anymore
- err := unix.Unmount(mountpoint, 0)
- if err != nil {
- err := unix.Unmount(mountpoint, unix.MNT_DETACH)
- if err != nil {
- return err
- }
- }
-
- // Give a chance for the kernel to notice (workaround for zfs slowness)
- time.Sleep(1 * time.Second)
- }
-
- // Due to open fds or kernel refs, this may fail for a bit, give it 10s
- _, err = shared.TryRunCommand(
- "zfs",
- "destroy",
- "-r",
- fmt.Sprintf("%s/%s", pool, path))
-
- if err != nil {
- return errors.Wrap(err, "Failed to destroy ZFS dataset")
- }
-
- return nil
-}
-
-func zfsPoolVolumeCleanup(pool string, path string) error {
- if strings.HasPrefix(path, "deleted/") {
- // Cleanup of filesystems kept for refcount reason
- removablePath, err := zfsPoolVolumeSnapshotRemovable(pool, path, "")
- if err != nil {
- return err
- }
-
- // Confirm that there are no more clones
- if removablePath {
- if strings.Contains(path, "@") {
- // Cleanup snapshots
- err = zfsPoolVolumeDestroy(pool, path)
- if err != nil {
- return err
- }
-
- // Check if the parent can now be deleted
- subPath := strings.SplitN(path, "@", 2)[0]
- snaps, err := zfsPoolListSnapshots(pool, subPath)
- if err != nil {
- return err
- }
-
- if len(snaps) == 0 {
- err := zfsPoolVolumeCleanup(pool, subPath)
- if err != nil {
- return err
- }
- }
- } else {
- // Cleanup filesystems
- origin, err := zfsFilesystemEntityPropertyGet(pool, path, "origin")
- if err != nil {
- return err
- }
- origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", pool))
-
- err = zfsPoolVolumeDestroy(pool, path)
- if err != nil {
- return err
- }
-
- // Attempt to remove its parent
- if origin != "-" {
- err := zfsPoolVolumeCleanup(pool, origin)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
- }
- } else if strings.HasPrefix(path, "containers") && strings.Contains(path, "@copy-") {
- // Just remove the copy- snapshot for copies of active containers
- err := zfsPoolVolumeDestroy(pool, path)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func zfsFilesystemEntityPropertyGet(pool string, path string, key string) (string, error) {
- entity := pool
- if path != "" {
- entity = fmt.Sprintf("%s/%s", pool, path)
- }
- output, err := shared.RunCommand(
- "zfs",
- "get",
- "-H",
- "-p",
- "-o", "value",
- key,
- entity)
- if err != nil {
- return "", errors.Wrap(err, "Failed to get ZFS config")
- }
-
- return strings.TrimRight(output, "\n"), nil
-}
-
-func zfsPoolVolumeRename(pool string, source string, dest string, ignoreMounts bool) error {
- var err error
-
- for i := 0; i < 20; i++ {
- if ignoreMounts {
- _, err = shared.RunCommand(
- "/proc/self/exe",
- "forkzfs",
- "--",
- "rename",
- "-p",
- fmt.Sprintf("%s/%s", pool, source),
- fmt.Sprintf("%s/%s", pool, dest))
- } else {
- _, err = shared.RunCommand(
- "zfs",
- "rename",
- "-p",
- fmt.Sprintf("%s/%s", pool, source),
- fmt.Sprintf("%s/%s", pool, dest))
- }
-
- // Success
- if err == nil {
- return nil
- }
-
- // zfs rename can fail because of descendants, yet still manage the rename
- if !zfsFilesystemEntityExists(pool, source) && zfsFilesystemEntityExists(pool, dest) {
- return nil
- }
-
- time.Sleep(500 * time.Millisecond)
- }
-
- // Timeout
- logger.Errorf("zfs rename failed: %v", err)
- return errors.Wrap(err, "Failed to rename ZFS filesystem")
-}
-
-func zfsPoolVolumeSet(pool string, path string, key string, value string) error {
- vdev := pool
- if path != "" {
- vdev = fmt.Sprintf("%s/%s", pool, path)
- }
- _, err := shared.RunCommand(
- "zfs",
- "set",
- fmt.Sprintf("%s=%s", key, value),
- vdev)
- if err != nil {
- logger.Errorf("zfs set failed: %v", err)
- return errors.Wrap(err, "Failed to set ZFS config")
- }
-
- return nil
-}
-
-func zfsPoolVolumeSnapshotCreate(pool string, path string, name string) error {
- _, err := shared.RunCommand(
- "zfs",
- "snapshot",
- "-r",
- fmt.Sprintf("%s/%s@%s", pool, path, name))
- if err != nil {
- logger.Errorf("zfs snapshot failed: %v", err)
- return errors.Wrap(err, "Failed to create ZFS snapshot")
- }
-
- return nil
-}
-
-func zfsPoolVolumeSnapshotDestroy(pool, path string, name string) error {
- _, err := shared.RunCommand(
- "zfs",
- "destroy",
- "-r",
- fmt.Sprintf("%s/%s@%s", pool, path, name))
- if err != nil {
- logger.Errorf("zfs destroy failed: %v", err)
- return errors.Wrap(err, "Failed to destroy ZFS snapshot")
- }
-
- return nil
-}
-
-func zfsPoolVolumeSnapshotRestore(pool string, path string, name string) error {
- _, err := shared.TryRunCommand(
- "zfs",
- "rollback",
- fmt.Sprintf("%s/%s@%s", pool, path, name))
- if err != nil {
- logger.Errorf("zfs rollback failed: %v", err)
- return errors.Wrap(err, "Failed to restore ZFS snapshot")
- }
-
- subvols, err := zfsPoolListSubvolumes(pool, fmt.Sprintf("%s/%s", pool, path))
- if err != nil {
- return err
- }
-
- for _, sub := range subvols {
- snaps, err := zfsPoolListSnapshots(pool, sub)
- if err != nil {
- return err
- }
-
- if !shared.StringInSlice(name, snaps) {
- continue
- }
-
- _, err = shared.TryRunCommand(
- "zfs",
- "rollback",
- fmt.Sprintf("%s/%s@%s", pool, sub, name))
- if err != nil {
- logger.Errorf("zfs rollback failed: %v", err)
- return errors.Wrap(err, "Failed to restore ZFS sub-volume snapshot")
- }
- }
-
- return nil
-}
-
-func zfsPoolVolumeSnapshotRename(pool string, path string, oldName string, newName string) error {
- _, err := shared.RunCommand(
- "zfs",
- "rename",
- "-r",
- fmt.Sprintf("%s/%s@%s", pool, path, oldName),
- fmt.Sprintf("%s/%s@%s", pool, path, newName))
- if err != nil {
- logger.Errorf("zfs snapshot rename failed: %v", err)
- return errors.Wrap(err, "Failed to rename ZFS snapshot")
- }
-
- return nil
-}
-
-func zfsMount(poolName string, path string) error {
- _, err := shared.TryRunCommand(
- "zfs",
- "mount",
- fmt.Sprintf("%s/%s", poolName, path))
- if err != nil {
- return errors.Wrap(err, "Failed to mount ZFS filesystem")
- }
-
- return nil
-}
-
-func zfsUmount(poolName string, path string, mountpoint string) error {
- output, err := shared.TryRunCommand(
- "zfs",
- "unmount",
- fmt.Sprintf("%s/%s", poolName, path))
- if err != nil {
- logger.Warnf("Failed to unmount ZFS filesystem via zfs unmount: %s. Trying lazy umount (MNT_DETACH)...", output)
- err := storageDrivers.TryUnmount(mountpoint, unix.MNT_DETACH)
- if err != nil {
- logger.Warnf("Failed to unmount ZFS filesystem via lazy umount (MNT_DETACH)...")
- return err
- }
- }
-
- return nil
-}
-
-func zfsPoolListSubvolumes(pool string, path string) ([]string, error) {
- output, err := shared.RunCommand(
- "zfs",
- "list",
- "-t", "filesystem",
- "-o", "name",
- "-H",
- "-r", path)
- if err != nil {
- logger.Errorf("zfs list failed: %v", err)
- return []string{}, errors.Wrap(err, "Failed to list ZFS filesystems")
- }
-
- children := []string{}
- for _, entry := range strings.Split(output, "\n") {
- if entry == "" {
- continue
- }
-
- if entry == path {
- continue
- }
-
- children = append(children, strings.TrimPrefix(entry, fmt.Sprintf("%s/", pool)))
- }
-
- return children, nil
-}
-
-func zfsPoolListSnapshots(pool string, path string) ([]string, error) {
- path = strings.TrimRight(path, "/")
- fullPath := pool
- if path != "" {
- fullPath = fmt.Sprintf("%s/%s", pool, path)
- }
-
- output, err := shared.RunCommand(
- "zfs",
- "list",
- "-t", "snapshot",
- "-o", "name",
- "-H",
- "-d", "1",
- "-s", "creation",
- "-r", fullPath)
- if err != nil {
- logger.Errorf("zfs list failed: %v", err)
- return []string{}, errors.Wrap(err, "Failed to list ZFS snapshots")
- }
-
- children := []string{}
- for _, entry := range strings.Split(output, "\n") {
- if entry == "" {
- continue
- }
-
- if entry == fullPath {
- continue
- }
-
- children = append(children, strings.SplitN(entry, "@", 2)[1])
- }
-
- return children, nil
-}
-
-func zfsPoolVolumeSnapshotRemovable(pool string, path string, name string) (bool, error) {
- var snap string
- if name == "" {
- snap = path
- } else {
- snap = fmt.Sprintf("%s@%s", path, name)
- }
-
- clones, err := zfsFilesystemEntityPropertyGet(pool, snap, "clones")
- if err != nil {
- return false, err
- }
-
- if clones == "-" || clones == "" {
- return true, nil
- }
-
- return false, nil
-}
-
-func zfsFilesystemEntityExists(pool string, path string) bool {
- vdev := pool
- if path != "" {
- vdev = fmt.Sprintf("%s/%s", pool, path)
- }
- output, err := shared.RunCommand(
- "zfs",
- "get",
- "-H",
- "-o",
- "name",
- "type",
- vdev)
- if err != nil {
- return false
- }
-
- detectedName := strings.TrimSpace(output)
- return detectedName == vdev
-}
-
-func (s *storageZfs) doContainerMount(projectName, name string, privileged bool) (bool, error) {
- logger.Debugf("Mounting ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- volumeName := project.Prefix(projectName, name)
- fs := fmt.Sprintf("containers/%s", volumeName)
- containerPoolVolumeMntPoint := driver.GetContainerMountPoint(projectName, s.pool.Name, name)
-
- containerMountLockID := getContainerMountLockID(s.pool.Name, name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[containerMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, containerMountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
-
- defer removeLockFromMap()
-
- // Since we're using mount() directly zfs will not automatically create
- // the mountpoint for us. So let's check and do it if needed.
- if !shared.PathExists(containerPoolVolumeMntPoint) {
- err := driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, shared.VarPath(fs), privileged)
- if err != nil {
- return false, err
- }
- }
-
- ourMount := false
- if !shared.IsMountPoint(containerPoolVolumeMntPoint) {
- source := fmt.Sprintf("%s/%s", s.getOnDiskPoolName(), fs)
- zfsMountOptions := fmt.Sprintf("rw,zfsutil,mntpoint=%s", containerPoolVolumeMntPoint)
- mounterr := storageDrivers.TryMount(source, containerPoolVolumeMntPoint, "zfs", 0, zfsMountOptions)
- if mounterr != nil {
- if mounterr != unix.EBUSY {
- logger.Errorf("Failed to mount ZFS dataset \"%s\" onto \"%s\": %v", source, containerPoolVolumeMntPoint, mounterr)
- return false, errors.Wrapf(mounterr, "Failed to mount ZFS dataset \"%s\" onto \"%s\"", source, containerPoolVolumeMntPoint)
- }
- // EBUSY error in zfs are related to a bug we're
- // tracking. So ignore them for now, report back that
- // the mount isn't ours and proceed.
- logger.Warnf("ZFS returned EBUSY while \"%s\" is actually not a mountpoint", containerPoolVolumeMntPoint)
- return false, mounterr
- }
- ourMount = true
- }
-
- logger.Debugf("Mounted ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourMount, nil
-}
-
-func (s *storageZfs) doContainerDelete(projectName, name string) error {
- logger.Debugf("Deleting ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- containerName := name
- fs := fmt.Sprintf("containers/%s", project.Prefix(projectName, containerName))
- containerPoolVolumeMntPoint := driver.GetContainerMountPoint(projectName, s.pool.Name, containerName)
-
- if zfsFilesystemEntityExists(poolName, fs) {
- removable := true
- snaps, err := zfsPoolListSnapshots(poolName, fs)
- if err != nil {
- return err
- }
-
- for _, snap := range snaps {
- var err error
- removable, err = zfsPoolVolumeSnapshotRemovable(poolName, fs, snap)
- if err != nil {
- return err
- }
-
- if !removable {
- break
- }
- }
-
- if removable {
- origin, err := zfsFilesystemEntityPropertyGet(poolName, fs, "origin")
- if err != nil {
- return err
- }
- poolName := s.getOnDiskPoolName()
- origin = strings.TrimPrefix(origin, fmt.Sprintf("%s/", poolName))
-
- err = zfsPoolVolumeDestroy(poolName, fs)
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeCleanup(poolName, origin)
- if err != nil {
- return err
- }
- } else {
- err := zfsPoolVolumeSet(poolName, fs, "mountpoint", "none")
- if err != nil {
- return err
- }
-
- err = zfsPoolVolumeRename(poolName, fs, fmt.Sprintf("deleted/containers/%s", uuid.NewRandom().String()), true)
- if err != nil {
- return err
- }
- }
- }
-
- err := deleteContainerMountpoint(containerPoolVolumeMntPoint, shared.VarPath("containers", project.Prefix(projectName, name)), s.GetStorageTypeName())
- if err != nil {
- return err
- }
-
- snapshotZfsDataset := fmt.Sprintf("snapshots/%s", containerName)
- zfsPoolVolumeDestroy(poolName, snapshotZfsDataset)
-
- // Delete potential leftover snapshot mountpoints.
- snapshotMntPoint := driver.GetSnapshotMountPoint(projectName, s.pool.Name, containerName)
- if shared.PathExists(snapshotMntPoint) {
- err := os.RemoveAll(snapshotMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Delete potential leftover snapshot symlinks:
- // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, containerName))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Deleted ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageZfs) doContainerCreate(projectName, name string, privileged bool) error {
- logger.Debugf("Creating empty ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- containerPath := shared.VarPath("containers", project.Prefix(projectName, name))
- containerName := name
- fs := fmt.Sprintf("containers/%s", project.Prefix(projectName, containerName))
- poolName := s.getOnDiskPoolName()
- dataset := fmt.Sprintf("%s/%s", poolName, fs)
- containerPoolVolumeMntPoint := driver.GetContainerMountPoint(projectName, s.pool.Name, containerName)
-
- // Create volume.
- msg, err := zfsPoolVolumeCreate(dataset, "mountpoint=none", "canmount=noauto")
- if err != nil {
- logger.Errorf("Failed to create ZFS storage volume for container \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, msg)
- return err
- }
-
- // Set mountpoint.
- err = zfsPoolVolumeSet(poolName, fs, "mountpoint", containerPoolVolumeMntPoint)
- if err != nil {
- return err
- }
-
- err = driver.CreateContainerMountpoint(containerPoolVolumeMntPoint, containerPath, privileged)
- if err != nil {
- return err
- }
-
- logger.Debugf("Created empty ZFS storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func zfsIdmapSetSkipper(dir string, absPath string, fi os.FileInfo) bool {
- strippedPath := absPath
- if dir != "" {
- strippedPath = absPath[len(dir):]
- }
-
- if fi.IsDir() && strippedPath == "/.zfs/snapshot" {
- return true
- }
-
- return false
-}
From c89601f5643222d225f1f2621182256436847bf5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?St=C3=A9phane=20Graber?= <stgraber at ubuntu.com>
Date: Wed, 15 Jan 2020 00:13:30 -0500
Subject: [PATCH 05/17] lxd/storage: Remove legacy lvm implementation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Signed-off-by: Stéphane Graber <stgraber at ubuntu.com>
---
lxd/api_internal.go | 6 +-
lxd/patches.go | 27 +-
lxd/patches_utils.go | 140 +++
lxd/storage.go | 23 -
lxd/storage_lvm.go | 2377 --------------------------------------
lxd/storage_lvm_utils.go | 1090 -----------------
6 files changed, 154 insertions(+), 3509 deletions(-)
delete mode 100644 lxd/storage_lvm.go
delete mode 100644 lxd/storage_lvm_utils.go
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index b41fbf4406..5073c5ec8e 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -727,11 +727,11 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
case "lvm":
ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
- ctLvmName := containerNameToLVName(fmt.Sprintf("%s/%s", project.Prefix(projectName, ctName), csName))
- ctLvName := getLVName(poolName,
+ ctLvmName := lvmNameToLVName(fmt.Sprintf("%s/%s", project.Prefix(projectName, ctName), csName))
+ ctLvName := lvmLVName(poolName,
storagePoolVolumeAPIEndpointContainers,
ctLvmName)
- exists, err := storageLVExists(ctLvName)
+ exists, err := lvmLVExists(ctLvName)
if err != nil {
return response.InternalError(err)
}
diff --git a/lxd/patches.go b/lxd/patches.go
index d2400b1763..05dbf1adf3 100644
--- a/lxd/patches.go
+++ b/lxd/patches.go
@@ -206,12 +206,7 @@ func patchRenameCustomVolumeLVs(name string, d *Daemon) error {
return err
}
- sType, err := storageStringToType(pool.Driver)
- if err != nil {
- return err
- }
-
- if sType != storageTypeLvm {
+ if pool.Driver != "lvm" {
continue
}
@@ -227,9 +222,9 @@ func patchRenameCustomVolumeLVs(name string, d *Daemon) error {
for _, volume := range volumes {
oldName := fmt.Sprintf("%s/custom_%s", vgName, volume)
- newName := fmt.Sprintf("%s/custom_%s", vgName, containerNameToLVName(volume))
+ newName := fmt.Sprintf("%s/custom_%s", vgName, lvmNameToLVName(volume))
- exists, err := storageLVExists(newName)
+ exists, err := lvmLVExists(newName)
if err != nil {
return err
}
@@ -1052,7 +1047,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
}
// Activate volume group
- err = storageVGActivate(defaultPoolName)
+ err = lvmVGActivate(defaultPoolName)
if err != nil {
logger.Errorf("Could not activate volume group \"%s\". Manual intervention needed", defaultPoolName)
return err
@@ -1168,9 +1163,9 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
// new storage api. We do os.Rename() here to preserve
// permissions and ownership.
newContainerMntPoint := driver.GetContainerMountPoint("default", defaultPoolName, ct)
- ctLvName := containerNameToLVName(ct)
+ ctLvName := lvmNameToLVName(ct)
newContainerLvName := fmt.Sprintf("%s_%s", storagePoolVolumeAPIEndpointContainers, ctLvName)
- containerLvDevPath := getLvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointContainers, ctLvName)
+ containerLvDevPath := lvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointContainers, ctLvName)
if !shared.PathExists(containerLvDevPath) {
oldLvDevPath := fmt.Sprintf("/dev/%s/%s", defaultPoolName, ctLvName)
// If the old LVM device path for the logical volume
@@ -1324,9 +1319,9 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
os.Remove(oldSnapshotMntPoint + ".lv")
// Make sure we use a valid lv name.
- csLvName := containerNameToLVName(cs)
+ csLvName := lvmNameToLVName(cs)
newSnapshotLvName := fmt.Sprintf("%s_%s", storagePoolVolumeAPIEndpointContainers, csLvName)
- snapshotLvDevPath := getLvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointContainers, csLvName)
+ snapshotLvDevPath := lvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointContainers, csLvName)
if !shared.PathExists(snapshotLvDevPath) {
oldLvDevPath := fmt.Sprintf("/dev/%s/%s", defaultPoolName, csLvName)
if shared.PathExists(oldLvDevPath) {
@@ -1506,7 +1501,7 @@ func upgradeFromStorageTypeLvm(name string, d *Daemon, defaultPoolName string, d
// Rename the logical volume device.
newImageLvName := fmt.Sprintf("%s_%s", storagePoolVolumeAPIEndpointImages, img)
- imageLvDevPath := getLvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointImages, img)
+ imageLvDevPath := lvmDevPath("default", defaultPoolName, storagePoolVolumeAPIEndpointImages, img)
oldLvDevPath := fmt.Sprintf("/dev/%s/%s", defaultPoolName, img)
// Only create logical volumes for images that have a logical
// volume on the pre-storage-api LXD instance. If not, we don't
@@ -2482,8 +2477,8 @@ func patchStorageApiDetectLVSize(name string, d *Daemon) error {
// It shouldn't be possible that false volume types
// exist in the db, so it's safe to ignore the error.
volumeTypeApiEndpoint, _ := storagePoolVolumeTypeNameToAPIEndpoint(volume.Type)
- lvmName := containerNameToLVName(volume.Name)
- lvmLvDevPath := getLvmDevPath("default", poolName, volumeTypeApiEndpoint, lvmName)
+ lvmName := lvmNameToLVName(volume.Name)
+ lvmLvDevPath := lvmDevPath("default", poolName, volumeTypeApiEndpoint, lvmName)
size, err := lvmGetLVSize(lvmLvDevPath)
if err != nil {
logger.Errorf("Failed to detect size of logical volume: %s", err)
diff --git a/lxd/patches_utils.go b/lxd/patches_utils.go
index 17775e6105..959ebb87f4 100644
--- a/lxd/patches_utils.go
+++ b/lxd/patches_utils.go
@@ -3,10 +3,13 @@ package main
import (
"fmt"
"os"
+ "os/exec"
"path"
"path/filepath"
"sort"
+ "strconv"
"strings"
+ "syscall"
"github.com/pborman/uuid"
"github.com/pkg/errors"
@@ -15,7 +18,9 @@ import (
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
driver "github.com/lxc/lxd/lxd/storage"
+ storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
"github.com/lxc/lxd/shared"
+ "github.com/lxc/lxd/shared/units"
)
// For 'dir' storage backend.
@@ -463,3 +468,138 @@ func zfsPoolVolumeSnapshotRename(pool string, path string, oldName string, newNa
return nil
}
+
+// For 'lvm' storage backend.
+func lvmLVRename(vgName string, oldName string, newName string) error {
+ _, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
+ if err != nil {
+ return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %v", oldName, newName, err)
+ }
+
+ return nil
+}
+
+func lvmLVExists(lvName string) (bool, error) {
+ _, err := shared.RunCommand("lvs", "--noheadings", "-o", "lv_attr", lvName)
+ if err != nil {
+ runErr, ok := err.(shared.RunError)
+ if ok {
+ exitError, ok := runErr.Err.(*exec.ExitError)
+ if ok {
+ waitStatus := exitError.Sys().(syscall.WaitStatus)
+ if waitStatus.ExitStatus() == 5 {
+ // logical volume not found
+ return false, nil
+ }
+ }
+ }
+
+ return false, fmt.Errorf("error checking for logical volume \"%s\"", lvName)
+ }
+
+ return true, nil
+}
+
+func lvmVGActivate(lvmVolumePath string) error {
+ _, err := shared.TryRunCommand("vgchange", "-ay", lvmVolumePath)
+ if err != nil {
+ return fmt.Errorf("could not activate volume group \"%s\": %v", lvmVolumePath, err)
+ }
+
+ return nil
+}
+
+func lvmNameToLVName(containerName string) string {
+ lvName := strings.Replace(containerName, "-", "--", -1)
+ return strings.Replace(lvName, shared.SnapshotDelimiter, "-", -1)
+}
+
+func lvmDevPath(projectName, lvmPool string, volumeType string, lvmVolume string) string {
+ lvmVolume = project.Prefix(projectName, lvmVolume)
+ if volumeType == "" {
+ return fmt.Sprintf("/dev/%s/%s", lvmPool, lvmVolume)
+ }
+
+ return fmt.Sprintf("/dev/%s/%s_%s", lvmPool, volumeType, lvmVolume)
+}
+
+func lvmGetLVSize(lvPath string) (string, error) {
+ msg, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "size", "--nosuffix", "--units", "b", lvPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to retrieve size of logical volume: %s: %s", string(msg), err)
+ }
+
+ sizeString := string(msg)
+ sizeString = strings.TrimSpace(sizeString)
+ size, err := strconv.ParseInt(sizeString, 10, 64)
+ if err != nil {
+ return "", err
+ }
+
+ detectedSize := units.GetByteSizeString(size, 0)
+
+ return detectedSize, nil
+}
+
+func lvmLVName(lvmPool string, volumeType string, lvmVolume string) string {
+ if volumeType == "" {
+ return fmt.Sprintf("%s/%s", lvmPool, lvmVolume)
+ }
+
+ return fmt.Sprintf("%s/%s_%s", lvmPool, volumeType, lvmVolume)
+}
+
+func lvmContainerDeleteInternal(projectName, poolName string, ctName string, isSnapshot bool, vgName string, ctPath string) error {
+ containerMntPoint := ""
+ containerLvmName := lvmNameToLVName(ctName)
+ if isSnapshot {
+ containerMntPoint = driver.GetSnapshotMountPoint(projectName, poolName, ctName)
+ } else {
+ containerMntPoint = driver.GetContainerMountPoint(projectName, poolName, ctName)
+ }
+
+ if shared.IsMountPoint(containerMntPoint) {
+ err := storageDrivers.TryUnmount(containerMntPoint, 0)
+ if err != nil {
+ return fmt.Errorf(`Failed to unmount container path `+
+ `"%s": %s`, containerMntPoint, err)
+ }
+ }
+
+ containerLvmDevPath := lvmDevPath(projectName, vgName,
+ storagePoolVolumeAPIEndpointContainers, containerLvmName)
+
+ lvExists, _ := lvmLVExists(containerLvmDevPath)
+ if lvExists {
+ err := lvmRemoveLV(projectName, vgName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
+ if err != nil {
+ return err
+ }
+ }
+
+ var err error
+ if isSnapshot {
+ sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(ctName)
+ snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "containers-snapshots", project.Prefix(projectName, sourceName))
+ snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
+ err = deleteSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
+ } else {
+ err = deleteContainerMountpoint(containerMntPoint, ctPath, "lvm")
+ }
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func lvmRemoveLV(project, vgName string, volumeType string, lvName string) error {
+ lvmVolumePath := lvmDevPath(project, vgName, volumeType, lvName)
+
+ _, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
+ if err != nil {
+ return fmt.Errorf("Could not remove LV named %s: %v", lvName, err)
+ }
+
+ return nil
+}
diff --git a/lxd/storage.go b/lxd/storage.go
index 61d2d6ed2f..8a87be9f59 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -97,7 +97,6 @@ type storageType int
const (
storageTypeCeph storageType = iota
- storageTypeLvm
storageTypeMock
)
@@ -107,8 +106,6 @@ func storageTypeToString(sType storageType) (string, error) {
switch sType {
case storageTypeCeph:
return "ceph", nil
- case storageTypeLvm:
- return "lvm", nil
case storageTypeMock:
return "mock", nil
}
@@ -120,8 +117,6 @@ func storageStringToType(sName string) (storageType, error) {
switch sName {
case "ceph":
return storageTypeCeph, nil
- case "lvm":
- return storageTypeLvm, nil
case "mock":
return storageTypeMock, nil
}
@@ -249,13 +244,6 @@ func storageCoreInit(driver string) (storage, error) {
return nil, err
}
return &ceph, nil
- case storageTypeLvm:
- lvm := storageLvm{}
- err = lvm.StorageCoreInit()
- if err != nil {
- return nil, err
- }
- return &lvm, nil
case storageTypeMock:
mock := storageMock{}
err = mock.StorageCoreInit()
@@ -308,17 +296,6 @@ func storageInit(s *state.State, project, poolName, volumeName string, volumeTyp
return nil, err
}
return &ceph, nil
- case storageTypeLvm:
- lvm := storageLvm{}
- lvm.poolID = poolID
- lvm.pool = pool
- lvm.volume = volume
- lvm.s = s
- err = lvm.StoragePoolInit()
- if err != nil {
- return nil, err
- }
- return &lvm, nil
case storageTypeMock:
mock := storageMock{}
mock.poolID = poolID
diff --git a/lxd/storage_lvm.go b/lxd/storage_lvm.go
deleted file mode 100644
index e82675685e..0000000000
--- a/lxd/storage_lvm.go
+++ /dev/null
@@ -1,2377 +0,0 @@
-package main
-
-import (
- "fmt"
- "io"
- "os"
- "path/filepath"
- "strconv"
- "strings"
-
- "github.com/gorilla/websocket"
- "github.com/pborman/uuid"
- "github.com/pkg/errors"
-
- "github.com/lxc/lxd/lxd/backup"
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/migration"
- "github.com/lxc/lxd/lxd/operations"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/lxd/rsync"
- driver "github.com/lxc/lxd/lxd/storage"
- storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/ioprogress"
- "github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/units"
-)
-
-type storageLvm struct {
- vgName string
- thinPoolName string
- useThinpool bool
- loopInfo *os.File
- storageShared
-}
-
-var lvmVersion = ""
-
-// Only initialize the minimal information we need about a given storage type.
-func (s *storageLvm) StorageCoreInit() error {
- s.sType = storageTypeLvm
- typeName, err := storageTypeToString(s.sType)
- if err != nil {
- return err
- }
- s.sTypeName = typeName
-
- if lvmVersion != "" {
- s.sTypeVersion = lvmVersion
- return nil
- }
-
- output, err := shared.RunCommand("lvm", "version")
- if err != nil {
- return fmt.Errorf("Error getting LVM version: %v", err)
- }
- lines := strings.Split(output, "\n")
-
- s.sTypeVersion = ""
- for idx, line := range lines {
- fields := strings.SplitAfterN(line, ":", 2)
- if len(fields) < 2 {
- continue
- }
-
- if !strings.Contains(line, "version:") {
- continue
- }
-
- if idx > 0 {
- s.sTypeVersion += " / "
- }
- s.sTypeVersion += strings.TrimSpace(fields[1])
- }
-
- lvmVersion = s.sTypeVersion
-
- return nil
-}
-
-func (s *storageLvm) StoragePoolInit() error {
- err := s.StorageCoreInit()
- if err != nil {
- return err
- }
-
- source := s.pool.Config["source"]
- s.thinPoolName = s.getLvmThinpoolName()
- s.useThinpool = s.usesThinpool()
-
- if s.pool.Config["lvm.vg_name"] != "" {
- s.vgName = s.pool.Config["lvm.vg_name"]
- }
-
- if source != "" && !filepath.IsAbs(source) {
- ok, err := storageVGExists(source)
- if err != nil {
- // Internal error.
- return err
- } else if !ok {
- // Volume group does not exist.
- return fmt.Errorf("the requested volume group \"%s\" does not exist", source)
- }
- s.vgName = source
- }
-
- return nil
-}
-
-func (s *storageLvm) StoragePoolCheck() error {
- logger.Debugf("Checking LVM storage pool \"%s\"", s.pool.Name)
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
- if s.loopInfo != nil {
- defer s.loopInfo.Close()
- defer func() { s.loopInfo = nil }()
- }
-
- poolName := s.getOnDiskPoolName()
- err = storageVGActivate(poolName)
- if err != nil {
- return err
- }
-
- logger.Debugf("Checked LVM storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolCreate() error {
- logger.Infof("Creating LVM storage pool \"%s\"", s.pool.Name)
-
- s.pool.Config["volatile.initial_source"] = s.pool.Config["source"]
-
- var globalErr error
- var pvExisted bool
- var vgExisted bool
- tryUndo := true
- poolName := s.getOnDiskPoolName()
- source := s.pool.Config["source"]
- // must be initialized
- vgName := ""
- // not initialized in all cases
- pvName := ""
-
- // Create the mountpoint for the storage pool.
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- err := os.MkdirAll(poolMntPoint, 0711)
- if err != nil {
- return err
- }
- defer func() {
- if tryUndo {
- os.Remove(poolMntPoint)
- }
- }()
-
- defaultSource := filepath.Join(shared.VarPath("disks"), fmt.Sprintf("%s.img", s.pool.Name))
- if source == "" || source == defaultSource {
- source = defaultSource
- s.pool.Config["source"] = source
-
- if s.pool.Config["lvm.vg_name"] == "" {
- s.pool.Config["lvm.vg_name"] = poolName
- }
-
- f, err := os.Create(source)
- if err != nil {
- return fmt.Errorf("Failed to open %s: %s", source, err)
- }
- defer f.Close()
-
- err = f.Chmod(0600)
- if err != nil {
- return fmt.Errorf("Failed to chmod %s: %s", source, err)
- }
-
- size, err := units.ParseByteSizeString(s.pool.Config["size"])
- if err != nil {
- return err
- }
- err = f.Truncate(size)
- if err != nil {
- return fmt.Errorf("Failed to create sparse file %s: %s", source, err)
- }
-
- _, err = s.StoragePoolMount()
- if err != nil {
- return err
- }
- defer func() {
- if tryUndo {
- os.Remove(source)
- }
- }()
- if s.loopInfo != nil {
- defer s.loopInfo.Close()
- defer func() { s.loopInfo = nil }()
- }
-
- // Check if the physical volume already exists.
- pvName = s.loopInfo.Name()
- pvExisted, globalErr = storagePVExists(pvName)
- if globalErr != nil {
- return globalErr
- }
-
- // Check if the volume group already exists.
- vgExisted, globalErr = storageVGExists(poolName)
- if globalErr != nil {
- return globalErr
- }
- } else {
- s.pool.Config["size"] = ""
- if filepath.IsAbs(source) {
- pvName = source
- if !shared.IsBlockdevPath(pvName) {
- return fmt.Errorf("Custom loop file locations are not supported")
- }
-
- if s.pool.Config["lvm.vg_name"] == "" {
- s.pool.Config["lvm.vg_name"] = poolName
- }
-
- // Set source to volume group name.
- s.pool.Config["source"] = poolName
-
- // Check if the physical volume already exists.
- pvExisted, globalErr = storagePVExists(pvName)
- if globalErr != nil {
- return globalErr
- }
-
- // Check if the volume group already exists.
- vgExisted, globalErr = storageVGExists(poolName)
- if globalErr != nil {
- return globalErr
- }
- } else {
- // The physical volume must already consist
- pvExisted = true
- vgName = source
- if s.pool.Config["lvm.vg_name"] != "" && s.pool.Config["lvm.vg_name"] != vgName {
- // User gave us something weird.
- return fmt.Errorf("Invalid combination of \"source\" and \"lvm.vg_name\" property")
- }
-
- s.pool.Config["lvm.vg_name"] = vgName
- s.vgName = vgName
-
- vgExisted, globalErr = storageVGExists(vgName)
- if globalErr != nil {
- return globalErr
- }
-
- // Volume group must exist but doesn't.
- if !vgExisted {
- return fmt.Errorf("The requested volume group \"%s\" does not exist", vgName)
- }
- }
- }
-
- if !pvExisted {
- // This is an internal error condition which should never be
- // hit.
- if pvName == "" {
- logger.Errorf("No name for physical volume detected")
- }
-
- _, err := shared.TryRunCommand("pvcreate", pvName)
- if err != nil {
- return fmt.Errorf("Failed to create the physical volume for the lvm storage pool: %v", err)
- }
- defer func() {
- if tryUndo {
- shared.TryRunCommand("pvremove", pvName)
- }
- }()
- }
-
- if vgExisted {
- // Check that the volume group is empty.
- // Otherwise we will refuse to use it.
- count, err := lvmGetLVCount(poolName)
- if err != nil {
- logger.Errorf("Failed to determine whether the volume group \"%s\" is empty", poolName)
- return err
- }
-
- empty := true
- if count > 0 && !s.useThinpool {
- empty = false
- }
-
- if count > 0 && s.useThinpool {
- ok, err := storageLVMThinpoolExists(poolName, s.thinPoolName)
- if err != nil {
- logger.Errorf("Failed to determine whether thinpool \"%s\" exists in volume group \"%s\": %s", poolName, s.thinPoolName, err)
- return err
- }
- empty = ok
- }
-
- if !empty {
- msg := fmt.Sprintf("volume group \"%s\" is not empty", poolName)
- logger.Errorf(msg)
- return fmt.Errorf(msg)
- }
-
- // Check that we don't already use this volume group.
- inUse, user, err := driver.LXDUsesPool(s.s.Cluster, poolName, s.pool.Driver, "lvm.vg_name")
- if err != nil {
- return err
- }
-
- if inUse {
- msg := fmt.Sprintf("LXD already uses volume group \"%s\" for pool \"%s\"", poolName, user)
- logger.Errorf(msg)
- return fmt.Errorf(msg)
- }
- } else {
- _, err := shared.TryRunCommand("vgcreate", poolName, pvName)
- if err != nil {
- return fmt.Errorf("failed to create the volume group for the lvm storage pool: %v", err)
- }
- }
-
- err = s.StoragePoolCheck()
- if err != nil {
- return err
- }
-
- // Deregister cleanup.
- tryUndo = false
-
- logger.Infof("Created LVM storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolDelete() error {
- logger.Infof("Deleting LVM storage pool \"%s\"", s.pool.Name)
-
- source := s.pool.Config["source"]
- if source == "" {
- return fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- _, err := s.StoragePoolMount()
- if err != nil {
- return err
- }
- if s.loopInfo != nil {
- defer s.loopInfo.Close()
- defer func() { s.loopInfo = nil }()
- }
-
- poolName := s.getOnDiskPoolName()
- poolExists, _ := storageVGExists(poolName)
-
- // Delete the thinpool.
- if s.useThinpool && poolExists {
- // Check that the thinpool actually exists. For example, it
- // won't when the user has never created a storage volume in the
- // storage pool.
- devPath := getLvmDevPath("default", poolName, "", s.thinPoolName)
- ok, _ := storageLVExists(devPath)
- if ok {
- msg, err := shared.TryRunCommand("lvremove", "-f", devPath)
- if err != nil {
- logger.Errorf("Failed to delete thinpool \"%s\" from volume group \"%s\": %s", s.thinPoolName, poolName, msg)
- return err
- }
- }
- }
-
- // Check that the count in the volume group is zero. If not, we need to
- // assume that other users are using the volume group, so don't remove
- // it. This actually goes against policy since we explicitly state: our
- // pool, and nothing but our pool but still, let's not hurt users.
- count, err := lvmGetLVCount(poolName)
- if err != nil {
- return err
- }
-
- // Remove the volume group.
- if count == 0 && poolExists {
- _, err := shared.TryRunCommand("vgremove", "-f", poolName)
- if err != nil {
- logger.Errorf("Failed to destroy the volume group for the lvm storage pool: %v", err)
- return err
- }
- }
-
- if s.loopInfo != nil {
- // Set LO_FLAGS_AUTOCLEAR before we remove the loop file
- // otherwise we will get EBADF.
- err = storageDrivers.SetAutoclearOnLoopDev(int(s.loopInfo.Fd()))
- if err != nil {
- logger.Warnf("Failed to set LO_FLAGS_AUTOCLEAR on loop device: %s, manual cleanup needed", err)
- }
-
- output, err := shared.TryRunCommand("pvremove", "-f", s.loopInfo.Name())
- if err != nil {
- logger.Warnf("Failed to destroy the physical volume for the lvm storage pool: %s", output)
- }
- }
-
- if filepath.IsAbs(source) {
- // This is a loop file so deconfigure the associated loop
- // device.
- err = os.Remove(source)
- if err != nil {
- return err
- }
- }
-
- // Delete the mountpoint for the storage pool.
- poolMntPoint := driver.GetStoragePoolMountPoint(s.pool.Name)
- err = os.RemoveAll(poolMntPoint)
- if err != nil {
- return err
- }
-
- logger.Infof("Deleted LVM storage pool \"%s\"", s.pool.Name)
- return nil
-}
-
-// Currently only used for loop-backed LVM storage pools. Can be called without
-// overhead since it is essentially a noop for non-loop-backed LVM storage
-// pools.
-func (s *storageLvm) StoragePoolMount() (bool, error) {
- source := s.pool.Config["source"]
- if source == "" {
- return false, fmt.Errorf("no \"source\" property found for the storage pool")
- }
-
- if !filepath.IsAbs(source) {
- return true, nil
- }
-
- poolMountLockID := getPoolMountLockID(s.pool.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage pool.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[poolMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- removeLockFromMap := func() {
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[poolMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, poolMountLockID)
- }
- lxdStorageMapLock.Unlock()
- }
-
- defer removeLockFromMap()
-
- if filepath.IsAbs(source) && !shared.IsBlockdevPath(source) {
- // Try to prepare new loop device.
- loopF, loopErr := storageDrivers.PrepareLoopDev(source, 0)
- if loopErr != nil {
- return false, loopErr
- }
- // Make sure that LO_FLAGS_AUTOCLEAR is unset.
- loopErr = storageDrivers.UnsetAutoclearOnLoopDev(int(loopF.Fd()))
- if loopErr != nil {
- return false, loopErr
- }
- s.loopInfo = loopF
- }
-
- return true, nil
-}
-
-func (s *storageLvm) StoragePoolUmount() (bool, error) {
- return true, nil
-}
-
-func (s *storageLvm) StoragePoolVolumeCreate() error {
- logger.Infof("Creating LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- tryUndo := true
-
- volumeLvmName := containerNameToLVName(s.volume.Name)
- poolName := s.getOnDiskPoolName()
- thinPoolName := s.getLvmThinpoolName()
- lvFsType := s.getLvmFilesystem()
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- return err
- }
-
- volumeType, err := storagePoolVolumeTypeNameToAPIEndpoint(s.volume.Type)
- if err != nil {
- return err
- }
-
- if s.useThinpool {
- err = lvmCreateThinpool(s.s, s.sTypeVersion, poolName, thinPoolName, lvFsType)
- if err != nil {
- return err
- }
- }
-
- err = lvmCreateLv("default", poolName, thinPoolName, volumeLvmName, lvFsType, lvSize, volumeType, s.useThinpool)
- if err != nil {
- return fmt.Errorf("Error Creating LVM LV for new image: %v", err)
- }
- defer func() {
- if tryUndo {
- s.StoragePoolVolumeDelete()
- }
- }()
-
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- err = os.MkdirAll(customPoolVolumeMntPoint, 0711)
- if err != nil {
- return err
- }
-
- // apply quota
- if s.volume.Config["size"] != "" {
- size, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
-
- tryUndo = false
-
- logger.Infof("Created LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeDelete() error {
- logger.Infof("Deleting LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- volumeLvmName := containerNameToLVName(s.volume.Name)
- poolName := s.getOnDiskPoolName()
- customLvmDevPath := getLvmDevPath("default", poolName,
- storagePoolVolumeAPIEndpointCustom, volumeLvmName)
- lvExists, _ := storageLVExists(customLvmDevPath)
-
- if lvExists {
- _, err := s.StoragePoolVolumeUmount()
- if err != nil {
- return err
- }
- }
-
- volumeType, err := storagePoolVolumeTypeNameToAPIEndpoint(s.volume.Type)
- if err != nil {
- return err
- }
-
- if lvExists {
- err = removeLV("default", poolName, volumeType, volumeLvmName)
- if err != nil {
- return err
- }
- }
-
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- if shared.PathExists(customPoolVolumeMntPoint) {
- err := os.RemoveAll(customPoolVolumeMntPoint)
- if err != nil {
- return err
- }
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for LVM storage volume "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeMount() (bool, error) {
- logger.Debugf("Mounting LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- volumeLvmName := containerNameToLVName(s.volume.Name)
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- poolName := s.getOnDiskPoolName()
- lvFsType := s.getLvmFilesystem()
- volumeType, err := storagePoolVolumeTypeNameToAPIEndpoint(s.volume.Type)
- if err != nil {
- return false, err
- }
- lvmVolumePath := getLvmDevPath("default", poolName, volumeType, volumeLvmName)
-
- customMountLockID := getCustomMountLockID(s.pool.Name, s.volume.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[customMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var customerr error
- ourMount := false
- if !shared.IsMountPoint(customPoolVolumeMntPoint) {
- mountFlags, mountOptions := resolveMountOptions(s.getLvmMountOptions())
- customerr = storageDrivers.TryMount(lvmVolumePath, customPoolVolumeMntPoint, lvFsType, mountFlags, mountOptions)
- ourMount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, customMountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if customerr != nil {
- return false, customerr
- }
-
- logger.Debugf("Mounted LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourMount, nil
-}
-
-func (s *storageLvm) StoragePoolVolumeUmount() (bool, error) {
- logger.Debugf("Unmounting LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- customPoolVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- customUmountLockID := getCustomUmountLockID(s.pool.Name, s.volume.Name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[customUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var customerr error
- ourUmount := false
- if shared.IsMountPoint(customPoolVolumeMntPoint) {
- customerr = storageDrivers.TryUnmount(customPoolVolumeMntPoint, 0)
- ourUmount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[customUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, customUmountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if customerr != nil {
- return false, customerr
- }
-
- logger.Debugf("Unmounted LVM storage volume \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourUmount, nil
-}
-
-func (s *storageLvm) GetContainerPoolInfo() (int64, string, string) {
- return s.poolID, s.pool.Name, s.getOnDiskPoolName()
-}
-
-func (s *storageLvm) StoragePoolUpdate(writable *api.StoragePoolPut, changedConfig []string) error {
- logger.Infof(`Updating LVM storage pool "%s"`, s.pool.Name)
-
- changeable := changeableStoragePoolProperties["lvm"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolError(unchangeable, "lvm")
- }
-
- // "volume.block.mount_options" requires no on-disk modifications.
- // "volume.block.filesystem" requires no on-disk modifications.
- // "volume.size" requires no on-disk modifications.
- // "rsync.bwlimit" requires no on-disk modifications.
-
- revert := true
-
- if shared.StringInSlice("lvm.thinpool_name", changedConfig) {
- if !s.useThinpool {
- return fmt.Errorf(`The LVM storage pool "%s" does `+
- `not use thin pools. The "lvm.thinpool_name" `+
- `property cannot be set`, s.pool.Name)
- }
-
- newThinpoolName := writable.Config["lvm.thinpool_name"]
- // Paranoia check
- if newThinpoolName == "" {
- return fmt.Errorf(`Could not rename volume group: No ` +
- `new name provided`)
- }
-
- poolName := s.getOnDiskPoolName()
- oldThinpoolName := s.getLvmThinpoolName()
- err := lvmLVRename(poolName, oldThinpoolName, newThinpoolName)
- if err != nil {
- return err
- }
-
- // Already set the new thinpool name so that any potentially
- // following operations use the correct on-disk name of the
- // volume group.
- s.setLvmThinpoolName(newThinpoolName)
- defer func() {
- if !revert {
- return
- }
-
- err = lvmLVRename(poolName, newThinpoolName, oldThinpoolName)
- if err != nil {
- logger.Warnf(`Failed to rename LVM thinpool from "%s" to "%s": %s. Manual intervention needed`, newThinpoolName, oldThinpoolName, err)
- }
- s.setLvmThinpoolName(oldThinpoolName)
- }()
- }
-
- if shared.StringInSlice("lvm.vg_name", changedConfig) {
- newName := writable.Config["lvm.vg_name"]
- // Paranoia check
- if newName == "" {
- return fmt.Errorf(`Could not rename volume group: No ` +
- `new name provided`)
- }
- writable.Config["source"] = newName
-
- oldPoolName := s.getOnDiskPoolName()
- err := lvmVGRename(oldPoolName, newName)
- if err != nil {
- return err
- }
-
- // Already set the new dataset name so that any potentially
- // following operations use the correct on-disk name of the
- // volume group.
- s.setOnDiskPoolName(newName)
- defer func() {
- if !revert {
- return
- }
-
- err := lvmVGRename(newName, oldPoolName)
- if err != nil {
- logger.Warnf(`Failed to rename LVM volume group from "%s" to "%s": %s. Manual intervention needed`, newName, oldPoolName, err)
- }
- s.setOnDiskPoolName(oldPoolName)
- }()
- }
-
- // Update succeeded.
- revert = false
-
- logger.Infof(`Updated LVM storage pool "%s"`, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeUpdate(writable *api.StorageVolumePut,
- changedConfig []string) error {
-
- if writable.Restore != "" {
- logger.Infof(`Restoring LVM storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
-
- _, err := s.StoragePoolVolumeUmount()
- if err != nil {
- return err
- }
-
- sourceLvmName := containerNameToLVName(fmt.Sprintf("%s/%s", s.volume.Name, writable.Restore))
- targetLvmName := containerNameToLVName(s.volume.Name)
-
- if s.useThinpool {
- poolName := s.getOnDiskPoolName()
-
- err := removeLV("default", poolName,
- storagePoolVolumeAPIEndpointCustom, targetLvmName)
- if err != nil {
- logger.Errorf("Failed to remove \"%s\": %s",
- targetLvmName, err)
- }
-
- _, err = s.createSnapshotLV("default", poolName, sourceLvmName,
- storagePoolVolumeAPIEndpointCustom, targetLvmName,
- storagePoolVolumeAPIEndpointCustom, false, true)
- if err != nil {
- return fmt.Errorf("Error creating snapshot LV: %v", err)
- }
- } else {
- poolName := s.getOnDiskPoolName()
- sourceName := fmt.Sprintf("%s/%s", s.volume.Name, writable.Restore)
- sourceVolumeMntPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(poolName, sourceName)
- targetVolumeMntPoint := driver.GetStoragePoolVolumeMountPoint(poolName, s.volume.Name)
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceVolumeMntPoint, targetVolumeMntPoint, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
- }
-
- logger.Infof(`Restored LVM storage volume "%s" from snapshot "%s"`,
- s.volume.Name, writable.Restore)
- return nil
- }
-
- logger.Infof(`Updating LVM storage volume "%s"`, s.volume.Name)
-
- changeable := changeableStoragePoolVolumeProperties["lvm"]
- unchangeable := []string{}
- for _, change := range changedConfig {
- if !shared.StringInSlice(change, changeable) {
- unchangeable = append(unchangeable, change)
- }
- }
-
- if len(unchangeable) > 0 {
- return updateStoragePoolVolumeError(unchangeable, "lvm")
- }
-
- if shared.StringInSlice("size", changedConfig) {
- if s.volume.Type != storagePoolVolumeTypeNameCustom {
- return updateStoragePoolVolumeError([]string{"size"}, "lvm")
- }
-
- if s.volume.Config["size"] != writable.Config["size"] {
- size, err := units.ParseByteSizeString(writable.Config["size"])
- if err != nil {
- return err
- }
-
- err = s.StorageEntitySetQuota(storagePoolVolumeTypeCustom, size, nil)
- if err != nil {
- return err
- }
- }
- }
-
- logger.Infof(`Updated LVM storage volume "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeRename(newName string) error {
- logger.Infof(`Renaming LVM storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- _, err := s.StoragePoolVolumeUmount()
- if err != nil {
- return err
- }
-
- usedBy, err := storagePoolVolumeUsedByInstancesGet(s.s, "default", s.pool.Name, s.volume.Name)
- if err != nil {
- return err
- }
- if len(usedBy) > 0 {
- return fmt.Errorf(`LVM storage volume "%s" on storage pool "%s" is attached to containers`,
- s.volume.Name, s.pool.Name)
- }
-
- sourceLVName := containerNameToLVName(s.volume.Name)
- targetLVName := containerNameToLVName(newName)
-
- err = s.renameLVByPath("default", sourceLVName, targetLVName,
- storagePoolVolumeAPIEndpointCustom)
- if err != nil {
- return fmt.Errorf(`Failed to rename logical volume from "%s" to "%s": %s`,
- s.volume.Name, newName, err)
- }
-
- sourceName, _, ok := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
- fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
- oldPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, fullSnapshotName)
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof(`Renamed ZFS storage volume on storage pool "%s" from "%s" to "%s`,
- s.pool.Name, s.volume.Name, newName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, newName,
- storagePoolVolumeTypeCustom, s.poolID)
-}
-
-func (s *storageLvm) ContainerStorageReady(container instance.Instance) bool {
- containerLvmName := containerNameToLVName(container.Name())
- poolName := s.getOnDiskPoolName()
- containerLvmPath := getLvmDevPath(container.Project(), poolName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
- ok, _ := storageLVExists(containerLvmPath)
- return ok
-}
-
-func (s *storageLvm) ContainerCreate(container instance.Instance) error {
- logger.Debugf("Creating empty LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- tryUndo := true
-
- containerName := container.Name()
- containerLvmName := containerNameToLVName(containerName)
- thinPoolName := s.getLvmThinpoolName()
- lvFsType := s.getLvmFilesystem()
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- return err
- }
-
- poolName := s.getOnDiskPoolName()
- if s.useThinpool {
- err = lvmCreateThinpool(s.s, s.sTypeVersion, poolName, thinPoolName, lvFsType)
- if err != nil {
- return err
- }
- }
-
- err = lvmCreateLv(container.Project(), poolName, thinPoolName, containerLvmName, lvFsType, lvSize, storagePoolVolumeAPIEndpointContainers, s.useThinpool)
- if err != nil {
- return err
- }
- defer func() {
- if tryUndo {
- s.ContainerDelete(container)
- }
- }()
-
- if container.IsSnapshot() {
- containerMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, containerName)
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(container.Project(), sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), sourceName))
- err := os.MkdirAll(containerMntPoint, 0711)
- if err != nil {
- return err
- }
- err = driver.CreateSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
- } else {
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
- containerPath := container.Path()
- err := os.MkdirAll(containerMntPoint, 0711)
- if err != nil {
- return err
- }
- err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
- if err != nil {
- return err
- }
- }
-
- tryUndo = false
-
- logger.Debugf("Created empty LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ContainerCreateFromImage(container instance.Instance, fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- tryUndo := true
-
- containerName := container.Name()
- containerLvmName := containerNameToLVName(containerName)
-
- var err error
- if s.useThinpool {
- err = s.containerCreateFromImageThinLv(container, fingerprint)
- } else {
- err = s.containerCreateFromImageLv(container, fingerprint)
- }
- if err != nil {
- logger.Errorf(`Failed to create LVM storage volume for container "%s" on storage pool "%s": %s`, containerName, s.pool.Name, err)
- return err
- }
- logger.Debugf(`Created LVM storage volume for container "%s" on storage pool "%s"`, containerName, s.pool.Name)
- defer func() {
- if tryUndo {
- s.ContainerDelete(container)
- }
- }()
-
- containerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, containerName)
- containerPath := container.Path()
- err = os.MkdirAll(containerMntPoint, 0711)
- if err != nil {
- return errors.Wrapf(err, "Create container mount point directory at %s", containerMntPoint)
- }
- err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, container.IsPrivileged())
- if err != nil {
- return errors.Wrap(err, "Create container mount point")
- }
-
- poolName := s.getOnDiskPoolName()
- containerLvDevPath := getLvmDevPath(container.Project(), poolName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
- // Generate a new xfs's UUID
- lvFsType := s.getLvmFilesystem()
- msg, err := driver.FSGenerateNewUUID(lvFsType, containerLvDevPath)
- if err != nil {
- logger.Errorf("Failed to create new \"%s\" UUID for container \"%s\" on storage pool \"%s\": %s", lvFsType, containerName, s.pool.Name, msg)
- return err
- }
-
- ourMount, err := s.ContainerMount(container)
- if err != nil {
- return errors.Wrap(err, "Container mount")
- }
- if ourMount {
- defer s.ContainerUmount(container, containerPath)
- }
-
- err = os.Chmod(containerMntPoint, 0100)
- if err != nil {
- return errors.Wrap(err, "Set mount point permissions")
- }
-
- err = container.DeferTemplateApply("create")
- if err != nil {
- logger.Errorf("Error in create template during ContainerCreateFromImage, continuing to unmount: %s", err)
- return err
- }
-
- tryUndo = false
-
- logger.Debugf("Created LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func lvmContainerDeleteInternal(projectName, poolName string, ctName string, isSnapshot bool, vgName string, ctPath string) error {
- containerMntPoint := ""
- containerLvmName := containerNameToLVName(ctName)
- if isSnapshot {
- containerMntPoint = driver.GetSnapshotMountPoint(projectName, poolName, ctName)
- } else {
- containerMntPoint = driver.GetContainerMountPoint(projectName, poolName, ctName)
- }
-
- if shared.IsMountPoint(containerMntPoint) {
- err := storageDrivers.TryUnmount(containerMntPoint, 0)
- if err != nil {
- return fmt.Errorf(`Failed to unmount container path `+
- `"%s": %s`, containerMntPoint, err)
- }
- }
-
- containerLvmDevPath := getLvmDevPath(projectName, vgName,
- storagePoolVolumeAPIEndpointContainers, containerLvmName)
-
- lvExists, _ := storageLVExists(containerLvmDevPath)
- if lvExists {
- err := removeLV(projectName, vgName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
- if err != nil {
- return err
- }
- }
-
- var err error
- if isSnapshot {
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(ctName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "containers-snapshots", project.Prefix(projectName, sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
- err = deleteSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- } else {
- err = deleteContainerMountpoint(containerMntPoint, ctPath, "lvm")
- }
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) ContainerDelete(container instance.Instance) error {
- logger.Debugf("Deleting LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- containerName := container.Name()
- poolName := s.getOnDiskPoolName()
- err := lvmContainerDeleteInternal(container.Project(), s.pool.Name, containerName, container.IsSnapshot(), poolName, container.Path())
- 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
-}
-
-func (s *storageLvm) ContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool) error {
- logger.Debugf("Copying LVM container storage for container %s to %s", source.Name(), target.Name())
-
- err := s.doContainerCopy(target, source, containerOnly, false, nil)
- if err != nil {
- return err
- }
-
- logger.Debugf("Copied LVM container storage for container %s to %s", source.Name(), target.Name())
- return nil
-}
-
-func (s *storageLvm) doContainerCopy(target instance.Instance, source instance.Instance, containerOnly bool, refresh bool, refreshSnapshots []instance.Instance) error {
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
- targetPool, err := target.StoragePool()
- if err != nil {
- return err
- }
- srcState := s.s
- if sourcePool != targetPool {
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", sourcePool, source.Name(), storagePoolVolumeTypeContainer)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
- srcState = srcStorage.GetState()
- }
-
- err = s.copyContainer(target, source, refresh)
- if err != nil {
- return err
- }
-
- if containerOnly {
- return nil
- }
-
- var snapshots []instance.Instance
-
- if refresh {
- snapshots = refreshSnapshots
- } else {
- snapshots, err = source.Snapshots()
- if err != nil {
- return err
- }
- }
-
- if len(snapshots) == 0 {
- return nil
- }
-
- for _, snap := range snapshots {
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- newSnapName := fmt.Sprintf("%s/%s", target.Name(), snapOnlyName)
-
- logger.Debugf("Copying LVM container storage for snapshot %s to %s", snap.Name(), newSnapName)
-
- sourceSnapshot, err := instance.LoadByProjectAndName(srcState, source.Project(), snap.Name())
- if err != nil {
- return err
- }
-
- targetSnapshot, err := instance.LoadByProjectAndName(s.s, source.Project(), newSnapName)
- if err != nil {
- return err
- }
-
- err = s.copySnapshot(targetSnapshot, sourceSnapshot, refresh)
- if err != nil {
- return err
- }
-
- logger.Debugf("Copied LVM container storage for snapshot %s to %s", snap.Name(), newSnapName)
- }
-
- return nil
-}
-
-func (s *storageLvm) ContainerRefresh(target instance.Instance, source instance.Instance, snapshots []instance.Instance) error {
- logger.Debugf("Refreshing LVM container storage for %s from %s", target.Name(), source.Name())
-
- err := s.doContainerCopy(target, source, len(snapshots) == 0, true, snapshots)
- if err != nil {
- return err
- }
-
- logger.Debugf("Refreshed LVM container storage for %s from %s", target.Name(), source.Name())
- return nil
-}
-
-func (s *storageLvm) ContainerMount(c instance.Instance) (bool, error) {
- return s.doContainerMount(c.Project(), c.Name(), false)
-}
-
-func (s *storageLvm) doContainerMount(project, name string, snap bool) (bool, error) {
- logger.Debugf("Mounting LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- containerLvmName := containerNameToLVName(name)
- lvFsType := s.getLvmFilesystem()
- poolName := s.getOnDiskPoolName()
- containerLvmPath := getLvmDevPath(project, poolName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
- containerMntPoint := driver.GetContainerMountPoint(project, s.pool.Name, name)
- if shared.IsSnapshot(name) {
- containerMntPoint = driver.GetSnapshotMountPoint(project, s.pool.Name, name)
- }
-
- containerMountLockID := getContainerMountLockID(s.pool.Name, name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerMountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in mounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[containerMountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var mounterr error
- ourMount := false
- if !shared.IsMountPoint(containerMntPoint) {
- mountFlags, mountOptions := resolveMountOptions(s.getLvmMountOptions())
- if snap && lvFsType == "xfs" {
- idx := strings.Index(mountOptions, "nouuid")
- if idx < 0 {
- mountOptions += ",nouuid"
- }
- }
-
- mounterr = storageDrivers.TryMount(containerLvmPath, containerMntPoint, lvFsType, mountFlags, mountOptions)
- ourMount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerMountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, containerMountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if mounterr != nil {
- return false, errors.Wrapf(mounterr, "Mount %s onto %s", containerLvmPath, containerMntPoint)
- }
-
- logger.Debugf("Mounted LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourMount, nil
-}
-
-func (s *storageLvm) ContainerUmount(c instance.Instance, path string) (bool, error) {
- return s.umount(c.Project(), c.Name(), path)
-}
-
-func (s *storageLvm) umount(project, name string, path string) (bool, error) {
- logger.Debugf("Unmounting LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- containerMntPoint := driver.GetContainerMountPoint(project, s.pool.Name, name)
- if shared.IsSnapshot(name) {
- containerMntPoint = driver.GetSnapshotMountPoint(project, s.pool.Name, name)
- }
-
- containerUmountLockID := getContainerUmountLockID(s.pool.Name, name)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerUmountLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- // Give the benefit of the doubt and assume that the other
- // thread actually succeeded in unmounting the storage volume.
- return false, nil
- }
-
- lxdStorageOngoingOperationMap[containerUmountLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var imgerr error
- ourUmount := false
- if shared.IsMountPoint(containerMntPoint) {
- imgerr = storageDrivers.TryUnmount(containerMntPoint, 0)
- ourUmount = true
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[containerUmountLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, containerUmountLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if imgerr != nil {
- return false, imgerr
- }
-
- logger.Debugf("Unmounted LVM storage volume for container \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return ourUmount, nil
-}
-
-func (s *storageLvm) ContainerRename(container instance.Instance, newContainerName string) error {
- logger.Debugf("Renaming LVM storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newContainerName)
-
- tryUndo := true
-
- oldName := container.Name()
- oldLvmName := containerNameToLVName(oldName)
- newLvmName := containerNameToLVName(newContainerName)
-
- _, err := s.ContainerUmount(container, container.Path())
- if err != nil {
- return err
- }
-
- err = s.renameLVByPath(container.Project(), oldLvmName, newLvmName, storagePoolVolumeAPIEndpointContainers)
- if err != nil {
- return fmt.Errorf("Failed to rename a container LV, oldName='%s', newName='%s', err='%s'", oldLvmName, newLvmName, err)
- }
- defer func() {
- if tryUndo {
- s.renameLVByPath(container.Project(), newLvmName, oldLvmName, storagePoolVolumeAPIEndpointContainers)
- }
- }()
-
- // MAYBE(FIXME(brauner)): Register another cleanup function that tries to
- // rename alreday renamed snapshots back to their old name when the
- // rename fails.
- if !container.IsSnapshot() {
- snaps, err := container.Snapshots()
- if err != nil {
- return err
- }
-
- for _, snap := range snaps {
- baseSnapName := filepath.Base(snap.Name())
- newSnapshotName := newContainerName + shared.SnapshotDelimiter + baseSnapName
- err := s.ContainerRename(snap, newSnapshotName)
- if err != nil {
- return err
- }
- }
-
- oldContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, oldName)
- oldContainerMntPointSymlink := container.Path()
- newContainerMntPoint := driver.GetContainerMountPoint(container.Project(), s.pool.Name, newContainerName)
- newContainerMntPointSymlink := shared.VarPath("containers", project.Prefix(container.Project(), newContainerName))
- err = renameContainerMountpoint(oldContainerMntPoint, oldContainerMntPointSymlink, newContainerMntPoint, newContainerMntPointSymlink)
- if err != nil {
- return err
- }
-
- oldSnapshotPath := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, oldName)
- newSnapshotPath := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, newContainerName)
- if shared.PathExists(oldSnapshotPath) {
- err = os.Rename(oldSnapshotPath, newSnapshotPath)
- if err != nil {
- return err
- }
- }
-
- oldSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), oldName))
- newSnapshotSymlink := shared.VarPath("snapshots", project.Prefix(container.Project(), newContainerName))
- if shared.PathExists(oldSnapshotSymlink) {
- err := os.Remove(oldSnapshotSymlink)
- if err != nil {
- return err
- }
-
- err = os.Symlink(newSnapshotPath, newSnapshotSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- tryUndo = false
-
- logger.Debugf("Renamed LVM storage volume for container \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newContainerName)
- return nil
-}
-
-func (s *storageLvm) ContainerRestore(target instance.Instance, source instance.Instance) error {
- logger.Debugf("Restoring LVM storage volume for container \"%s\" from %s to %s", s.volume.Name, source.Name(), target.Name())
-
- if source.Type() != instancetype.Container {
- return fmt.Errorf("Source Instance type must be container")
- }
-
- if target.Type() != instancetype.Container {
- return fmt.Errorf("Target Instance type must be container")
- }
-
- srcCt := source.(*containerLXC)
- targetCt := target.(*containerLXC)
-
- _, sourcePool, _ := srcCt.Storage().GetContainerPoolInfo()
- if s.pool.Name != sourcePool {
- return fmt.Errorf("containers must be on the same pool to be restored")
- }
-
- sourceName := source.Name()
- sourceLvmName := containerNameToLVName(sourceName)
-
- targetName := target.Name()
- targetLvmName := containerNameToLVName(targetName)
- targetPath := target.Path()
- if s.useThinpool {
- ourUmount, err := targetCt.Storage().ContainerUmount(target, targetPath)
- if err != nil {
- return err
- }
- if ourUmount {
- defer targetCt.Storage().ContainerMount(target)
- }
-
- poolName := s.getOnDiskPoolName()
-
- err = removeLV(target.Project(), poolName,
- storagePoolVolumeAPIEndpointContainers, targetLvmName)
- if err != nil {
- logger.Errorf("Failed to remove \"%s\": %s",
- targetLvmName, err)
- }
-
- _, err = s.createSnapshotLV(source.Project(), poolName, sourceLvmName,
- storagePoolVolumeAPIEndpointContainers, targetLvmName,
- storagePoolVolumeAPIEndpointContainers, false, true)
- if err != nil {
- return fmt.Errorf("Error creating snapshot LV: %v", err)
- }
- } else {
- ourStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer source.StorageStop()
- }
-
- ourStart, err = target.StorageStart()
- if err != nil {
- return err
- }
- if ourStart {
- defer target.StorageStop()
- }
-
- poolName := s.getOnDiskPoolName()
- sourceName := source.Name()
- targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), poolName, targetName)
- sourceContainerMntPoint := driver.GetContainerMountPoint(target.Project(), poolName, sourceName)
- if source.IsSnapshot() {
- sourceContainerMntPoint = driver.GetSnapshotMountPoint(target.Project(), poolName, sourceName)
- }
-
- err = target.Freeze()
- if err != nil {
- }
- defer target.Unfreeze()
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
- }
-
- logger.Debugf("Restored LVM storage volume for container \"%s\" from %s to %s", s.volume.Name, sourceName, targetName)
- return nil
-}
-
-func (s *storageLvm) ContainerGetUsage(container instance.Instance) (int64, error) {
- return -1, fmt.Errorf("the LVM container backend doesn't support quotas")
-}
-
-func (s *storageLvm) ContainerSnapshotCreate(snapshotContainer instance.Instance, sourceContainer instance.Instance) error {
- logger.Debugf("Creating LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- err := s.createSnapshotContainer(snapshotContainer, sourceContainer, true)
- if err != nil {
- return err
- }
-
- logger.Debugf("Created LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ContainerSnapshotDelete(snapshotContainer instance.Instance) error {
- logger.Debugf("Deleting LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- err := s.ContainerDelete(snapshotContainer)
- if err != nil {
- return fmt.Errorf("Error deleting snapshot %s: %s", snapshotContainer.Name(), err)
- }
-
- logger.Debugf("Deleted LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ContainerSnapshotRename(snapshotContainer instance.Instance, newContainerName string) error {
- logger.Debugf("Renaming LVM storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newContainerName)
-
- tryUndo := true
-
- oldName := snapshotContainer.Name()
- oldLvmName := containerNameToLVName(oldName)
- newLvmName := containerNameToLVName(newContainerName)
-
- err := s.renameLVByPath(snapshotContainer.Project(), oldLvmName, newLvmName, storagePoolVolumeAPIEndpointContainers)
- if err != nil {
- return fmt.Errorf("Failed to rename a container LV, oldName='%s', newName='%s', err='%s'", oldLvmName, newLvmName, err)
- }
- defer func() {
- if tryUndo {
- s.renameLVByPath(snapshotContainer.Project(), newLvmName, oldLvmName, storagePoolVolumeAPIEndpointContainers)
- }
- }()
-
- oldSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, oldName)
- newSnapshotMntPoint := driver.GetSnapshotMountPoint(snapshotContainer.Project(), s.pool.Name, newContainerName)
- err = os.Rename(oldSnapshotMntPoint, newSnapshotMntPoint)
- if err != nil {
- return err
- }
-
- tryUndo = false
-
- logger.Debugf("Renamed LVM storage volume for snapshot \"%s\" from %s to %s", s.volume.Name, s.volume.Name, newContainerName)
- return nil
-}
-
-func (s *storageLvm) ContainerSnapshotStart(container instance.Instance) (bool, error) {
- logger.Debugf(`Initializing LVM storage volume for snapshot "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- containerName := container.Name()
- containerLvmName := containerNameToLVName(containerName)
- containerLvmPath := getLvmDevPath(container.Project(), poolName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
-
- wasWritableAtCheck, err := lvmLvIsWritable(containerLvmPath)
- if err != nil {
- return false, err
- }
-
- if !wasWritableAtCheck {
- _, err := shared.TryRunCommand("lvchange", "-prw", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointContainers, project.Prefix(container.Project(), containerLvmName)))
- if err != nil {
- logger.Errorf("Failed to make LVM snapshot \"%s\" read-write: %v", containerName, err)
- return false, err
- }
- }
-
- lvFsType := s.getLvmFilesystem()
- containerMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, containerName)
- if !shared.IsMountPoint(containerMntPoint) {
- mntOptString := s.getLvmMountOptions()
- mountFlags, mountOptions := resolveMountOptions(mntOptString)
-
- if lvFsType == "xfs" {
- idx := strings.Index(mountOptions, "nouuid")
- if idx < 0 {
- mountOptions += ",nouuid"
- }
- }
-
- err = storageDrivers.TryMount(containerLvmPath, containerMntPoint, lvFsType, mountFlags, mountOptions)
- if err != nil {
- logger.Errorf(`Failed to mount LVM snapshot "%s" with filesystem "%s" options "%s" onto "%s": %s`, s.volume.Name, lvFsType, mntOptString, containerMntPoint, err)
- return false, err
- }
- }
-
- logger.Debugf(`Initialized LVM storage volume for snapshot "%s" on storage pool "%s"`, s.volume.Name, s.pool.Name)
-
- if wasWritableAtCheck {
- return false, nil
- }
-
- return true, nil
-}
-
-func (s *storageLvm) ContainerSnapshotStop(container instance.Instance) (bool, error) {
- logger.Debugf("Stopping LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- containerName := container.Name()
- snapshotMntPoint := driver.GetSnapshotMountPoint(container.Project(), s.pool.Name, containerName)
-
- poolName := s.getOnDiskPoolName()
-
- if shared.IsMountPoint(snapshotMntPoint) {
- err := storageDrivers.TryUnmount(snapshotMntPoint, 0)
- if err != nil {
- return false, err
- }
- }
-
- containerLvmPath := getLvmDevPath(container.Project(), poolName, storagePoolVolumeAPIEndpointContainers, containerNameToLVName(containerName))
- wasWritableAtCheck, err := lvmLvIsWritable(containerLvmPath)
- if err != nil {
- return false, err
- }
-
- if wasWritableAtCheck {
- containerLvmName := containerNameToLVName(project.Prefix(container.Project(), containerName))
- _, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointContainers, containerLvmName))
- if err != nil {
- logger.Errorf("Failed to make LVM snapshot read-only: %v", err)
- return false, err
- }
- }
-
- logger.Debugf("Stopped LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- if wasWritableAtCheck {
- return false, nil
- }
-
- return true, nil
-}
-
-func (s *storageLvm) ContainerSnapshotCreateEmpty(snapshotContainer instance.Instance) error {
- logger.Debugf("Creating empty LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- err := s.ContainerCreate(snapshotContainer)
- if err != nil {
- return err
- }
-
- logger.Debugf("Created empty LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ContainerBackupCreate(path string, backup backup.Backup, source instance.Instance) error {
- poolName := s.getOnDiskPoolName()
-
- // Prepare for rsync
- rsync := func(oldPath string, newPath string, bwlimit string) error {
- output, err := rsync.LocalCopy(oldPath, newPath, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync: %s: %s", string(output), err)
- }
-
- return nil
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
-
- // Handle snapshots
- if !backup.InstanceOnly() {
- snapshotsPath := fmt.Sprintf("%s/snapshots", path)
-
- // Retrieve the snapshots
- snapshots, err := source.Snapshots()
- if err != nil {
- return err
- }
-
- // Create the snapshot path
- if len(snapshots) > 0 {
- err = os.MkdirAll(snapshotsPath, 0711)
- if err != nil {
- return err
- }
- }
-
- for _, snap := range snapshots {
- _, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
- snapshotMntPoint := driver.GetSnapshotMountPoint(snap.Project(), s.pool.Name, snap.Name())
- target := fmt.Sprintf("%s/%s", snapshotsPath, snapName)
-
- // Mount the snapshot
- _, err := s.ContainerSnapshotStart(snap)
- if err != nil {
- return err
- }
-
- // Copy the snapshot
- err = rsync(snapshotMntPoint, target, bwlimit)
- s.ContainerSnapshotStop(snap)
- if err != nil {
- return err
- }
- }
- }
-
- // Make a temporary snapshot of the container
- sourceLvmDatasetSnapshot := fmt.Sprintf("snapshot-%s", uuid.NewRandom().String())
- tmpContainerMntPoint := driver.GetContainerMountPoint(source.Project(), s.pool.Name, sourceLvmDatasetSnapshot)
- err := os.MkdirAll(tmpContainerMntPoint, 0100)
- if err != nil {
- return err
- }
- defer os.RemoveAll(tmpContainerMntPoint)
-
- _, err = s.createSnapshotLV(source.Project(), poolName, containerNameToLVName(source.Name()),
- storagePoolVolumeAPIEndpointContainers, containerNameToLVName(sourceLvmDatasetSnapshot),
- storagePoolVolumeAPIEndpointContainers, false, s.useThinpool)
- if err != nil {
- return err
- }
- defer removeLV(source.Project(), poolName, storagePoolVolumeAPIEndpointContainers,
- containerNameToLVName(sourceLvmDatasetSnapshot))
-
- // Mount the temporary snapshot
- _, err = s.doContainerMount(source.Project(), sourceLvmDatasetSnapshot, true)
- if err != nil {
- return err
- }
-
- // Copy the container
- containerPath := fmt.Sprintf("%s/container", path)
- err = rsync(tmpContainerMntPoint, containerPath, bwlimit)
- s.umount(source.Project(), sourceLvmDatasetSnapshot, "")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) ContainerBackupLoad(info backup.Info, data io.ReadSeeker, tarArgs []string) error {
- containerPath, err := s.doContainerBackupLoad(info.Project, info.Name, info.Privileged, false)
- if err != nil {
- return err
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=2",
- "--xattrs-include=*",
- "-C", containerPath, "backup/container",
- }...)
-
- // Extract container
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- return err
- }
-
- for _, snap := range info.Snapshots {
- containerPath, err := s.doContainerBackupLoad(info.Project, fmt.Sprintf("%s/%s", info.Name, snap),
- info.Privileged, true)
- if err != nil {
- return err
- }
-
- // Prepare tar arguments
- args := append(tarArgs, []string{
- "-",
- "--strip-components=3",
- "--xattrs-include=*",
- "-C", containerPath, fmt.Sprintf("backup/snapshots/%s", snap),
- }...)
-
- // Extract snapshots
- data.Seek(0, 0)
- err = shared.RunCommandWithFds(data, nil, "tar", args...)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageLvm) doContainerBackupLoad(projectName, containerName string, privileged bool,
- snapshot bool) (string, error) {
- tryUndo := true
-
- var containerPath string
- if snapshot {
- containerPath = shared.VarPath("snapshots", project.Prefix(projectName, containerName))
- } else {
- containerPath = shared.VarPath("containers", project.Prefix(projectName, containerName))
- }
- containerLvmName := containerNameToLVName(containerName)
- thinPoolName := s.getLvmThinpoolName()
- lvFsType := s.getLvmFilesystem()
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- return "", err
- }
-
- poolName := s.getOnDiskPoolName()
- if s.useThinpool {
- err = lvmCreateThinpool(s.s, s.sTypeVersion, poolName, thinPoolName, lvFsType)
- if err != nil {
- return "", err
- }
- }
-
- if !snapshot {
- err = lvmCreateLv(projectName, poolName, thinPoolName, containerLvmName, lvFsType, lvSize,
- storagePoolVolumeAPIEndpointContainers, s.useThinpool)
- } else {
- cname, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
- _, err = s.createSnapshotLV(projectName, poolName, containerNameToLVName(cname), storagePoolVolumeAPIEndpointContainers,
- containerLvmName, storagePoolVolumeAPIEndpointContainers, false, s.useThinpool)
- }
- if err != nil {
- return "", err
- }
-
- defer func() {
- if tryUndo {
- lvmContainerDeleteInternal(projectName, s.pool.Name, containerName, false, poolName,
- containerPath)
- }
- }()
-
- var containerMntPoint string
- if snapshot {
- containerMntPoint = driver.GetSnapshotMountPoint(projectName, s.pool.Name, containerName)
- } else {
- containerMntPoint = driver.GetContainerMountPoint(projectName, s.pool.Name, containerName)
- }
- err = os.MkdirAll(containerMntPoint, 0711)
- if err != nil {
- return "", err
- }
-
- if snapshot {
- cname, _, _ := shared.InstanceGetParentAndSnapshotName(containerName)
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, cname))
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(projectName, cname))
- err = driver.CreateSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget,
- snapshotMntPointSymlink)
- } else {
- err = driver.CreateContainerMountpoint(containerMntPoint, containerPath, privileged)
- }
- if err != nil {
- return "", err
- }
-
- _, err = s.doContainerMount(projectName, containerName, false)
- if err != nil {
- return "", err
- }
-
- tryUndo = false
-
- return containerPath, nil
-}
-
-func (s *storageLvm) ImageCreate(fingerprint string, tracker *ioprogress.ProgressTracker) error {
- logger.Debugf("Creating LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- tryUndo := true
- trySubUndo := true
-
- poolName := s.getOnDiskPoolName()
- thinPoolName := s.getLvmThinpoolName()
- lvFsType := s.getLvmFilesystem()
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- return err
- }
-
- err = s.createImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
- defer func() {
- if !trySubUndo {
- return
- }
- err := s.deleteImageDbPoolVolume(fingerprint)
- if err != nil {
- logger.Warnf("Could not delete image \"%s\" from storage volume database, manual intervention needed", fingerprint)
- }
- }()
-
- if s.useThinpool {
- err = lvmCreateThinpool(s.s, s.sTypeVersion, poolName, thinPoolName, lvFsType)
- if err != nil {
- return err
- }
-
- err = lvmCreateLv("default", poolName, thinPoolName, fingerprint, lvFsType, lvSize, storagePoolVolumeAPIEndpointImages, true)
- if err != nil {
- return fmt.Errorf("Error Creating LVM LV for new image: %v", err)
- }
- defer func() {
- if tryUndo {
- s.ImageDelete(fingerprint)
- }
- }()
- }
- trySubUndo = false
-
- // Create image mountpoint.
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if !shared.PathExists(imageMntPoint) {
- err := os.MkdirAll(imageMntPoint, 0700)
- if err != nil {
- return err
- }
- }
-
- if s.useThinpool {
- _, err = s.ImageMount(fingerprint)
- if err != nil {
- return err
- }
-
- imagePath := shared.VarPath("images", fingerprint)
- err = driver.ImageUnpack(imagePath, imageMntPoint, "", true, s.s.OS.RunningInUserNS, nil)
- if err != nil {
- return err
- }
-
- s.ImageUmount(fingerprint)
- }
-
- tryUndo = false
-
- logger.Debugf("Created LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ImageDelete(fingerprint string) error {
- logger.Debugf("Deleting LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- if s.useThinpool {
- poolName := s.getOnDiskPoolName()
- imageLvmDevPath := getLvmDevPath("default", poolName,
- storagePoolVolumeAPIEndpointImages, fingerprint)
- lvExists, _ := storageLVExists(imageLvmDevPath)
-
- if lvExists {
- _, err := s.ImageUmount(fingerprint)
- if err != nil {
- return err
- }
-
- err = removeLV("default", poolName, storagePoolVolumeAPIEndpointImages, fingerprint)
- if err != nil {
- return err
- }
- }
- }
-
- err := s.deleteImageDbPoolVolume(fingerprint)
- if err != nil {
- return err
- }
-
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if shared.PathExists(imageMntPoint) {
- err := os.Remove(imageMntPoint)
- if err != nil {
- return err
- }
- }
-
- logger.Debugf("Deleted LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) ImageMount(fingerprint string) (bool, error) {
- logger.Debugf("Mounting LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if shared.IsMountPoint(imageMntPoint) {
- return false, nil
- }
-
- // Shouldn't happen.
- lvmFstype := s.getLvmFilesystem()
- if lvmFstype == "" {
- return false, fmt.Errorf("no filesystem type specified")
- }
-
- poolName := s.getOnDiskPoolName()
- lvmVolumePath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointImages, fingerprint)
- mountFlags, mountOptions := resolveMountOptions(s.getLvmMountOptions())
- err := storageDrivers.TryMount(lvmVolumePath, imageMntPoint, lvmFstype, mountFlags, mountOptions)
- if err != nil {
- logger.Errorf(fmt.Sprintf("Error mounting image LV for unpacking: %s", err))
- return false, fmt.Errorf("Error mounting image LV: %v", err)
- }
-
- logger.Debugf("Mounted LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return true, nil
-}
-
-func (s *storageLvm) ImageUmount(fingerprint string) (bool, error) {
- logger.Debugf("Unmounting LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
-
- imageMntPoint := driver.GetImageMountPoint(s.pool.Name, fingerprint)
- if !shared.IsMountPoint(imageMntPoint) {
- return false, nil
- }
-
- err := storageDrivers.TryUnmount(imageMntPoint, 0)
- if err != nil {
- return false, err
- }
-
- logger.Debugf("Unmounted LVM storage volume for image \"%s\" on storage pool \"%s\"", fingerprint, s.pool.Name)
- return true, nil
-}
-
-func (s *storageLvm) MigrationType() migration.MigrationFSType {
- return migration.MigrationFSType_RSYNC
-}
-
-func (s *storageLvm) PreservesInodes() bool {
- return false
-}
-
-func (s *storageLvm) MigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncMigrationSource(args)
-}
-
-func (s *storageLvm) MigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncMigrationSink(conn, op, args)
-}
-
-func (s *storageLvm) StorageEntitySetQuota(volumeType int, size int64, data interface{}) error {
- logger.Debugf(`Setting LVM quota for "%s"`, s.volume.Name)
-
- if !shared.IntInSlice(volumeType, supportedVolumeTypes) {
- return fmt.Errorf("Invalid storage type")
- }
-
- poolName := s.getOnDiskPoolName()
- var c instance.Instance
- fsType := s.getLvmFilesystem()
- lvDevPath := ""
- mountpoint := ""
- switch volumeType {
- case storagePoolVolumeTypeContainer:
- c = data.(instance.Instance)
- ctName := c.Name()
- if c.IsRunning() {
- msg := fmt.Sprintf(`Cannot resize LVM storage volume `+
- `for container "%s" when it is running`,
- ctName)
- logger.Errorf(msg)
- return fmt.Errorf(msg)
- }
-
- ctLvmName := containerNameToLVName(ctName)
- lvDevPath = getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointContainers, ctLvmName)
- mountpoint = driver.GetContainerMountPoint(c.Project(), s.pool.Name, ctName)
- default:
- customLvmName := containerNameToLVName(s.volume.Name)
- lvDevPath = getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointCustom, customLvmName)
- mountpoint = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
- }
-
- oldSize, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return err
- }
-
- // The right disjunct just means that someone unset the size property in
- // the container's config. We obviously cannot resize to 0.
- if oldSize == size || size == 0 {
- return nil
- }
-
- if size < oldSize {
- err = s.lvReduce(lvDevPath, size, fsType, mountpoint, volumeType, data)
- } else if size > oldSize {
- err = s.lvExtend(lvDevPath, size, fsType, mountpoint, volumeType, data)
- }
- if err != nil {
- return err
- }
-
- // Update the database
- s.volume.Config["size"] = units.GetByteSizeString(size, 0)
- err = s.s.Cluster.StoragePoolVolumeUpdateByProject(
- "default",
- s.volume.Name,
- volumeType,
- s.poolID,
- s.volume.Description,
- s.volume.Config)
- if err != nil {
- return err
- }
-
- logger.Debugf(`Set LVM quota for "%s"`, s.volume.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolResources() (*api.ResourcesStoragePool, error) {
- res := api.ResourcesStoragePool{}
-
- // Thinpools will always report zero free space on the volume group, so calculate approx
- // used space using the thinpool logical volume allocated (data and meta) percentages.
- if s.useThinpool {
- args := []string{fmt.Sprintf("%s/%s", s.vgName, s.thinPoolName), "--noheadings",
- "--units", "b", "--nosuffix", "--separator", ",", "-o", "lv_size,data_percent,metadata_percent"}
-
- out, err := shared.TryRunCommand("lvs", args...)
- if err != nil {
- return nil, err
- }
-
- parts := strings.Split(strings.TrimSpace(out), ",")
- if len(parts) < 3 {
- return nil, fmt.Errorf("Unexpected output from lvs command")
- }
-
- total, err := strconv.ParseUint(parts[0], 10, 64)
- if err != nil {
- return nil, err
- }
-
- res.Space.Total = total
-
- dataPerc, err := strconv.ParseFloat(parts[1], 64)
- if err != nil {
- return nil, err
- }
-
- metaPerc, err := strconv.ParseFloat(parts[2], 64)
- if err != nil {
- return nil, err
- }
-
- res.Space.Used = uint64(float64(total) * ((dataPerc + metaPerc) / 100))
- } else {
- // If thinpools are not in use, calculate used space in volume group.
- args := []string{s.vgName, "--noheadings",
- "--units", "b", "--nosuffix", "--separator", ",", "-o", "vg_size,vg_free"}
-
- out, err := shared.TryRunCommand("vgs", args...)
- if err != nil {
- return nil, err
- }
-
- parts := strings.Split(strings.TrimSpace(out), ",")
- if len(parts) < 2 {
- return nil, fmt.Errorf("Unexpected output from vgs command")
- }
-
- total, err := strconv.ParseUint(parts[0], 10, 64)
- if err != nil {
- return nil, err
- }
-
- res.Space.Total = total
-
- free, err := strconv.ParseUint(parts[1], 10, 64)
- if err != nil {
- return nil, err
- }
- res.Space.Used = total - free
- }
-
- return &res, nil
-}
-
-func (s *storageLvm) StoragePoolVolumeCopy(source *api.StorageVolumeSource) error {
- logger.Infof("Copying LVM storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
- successMsg := fmt.Sprintf("Copied LVM storage volume \"%s\" on storage pool \"%s\" as \"%s\" to storage pool \"%s\"", source.Name, source.Pool, s.volume.Name, s.pool.Name)
-
- if s.pool.Name != source.Pool {
- // Cross-pool copy
- // setup storage for the source volume
- srcStorage, err := storagePoolVolumeInit(s.s, "default", source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- ourMount, err := srcStorage.StoragePoolMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer srcStorage.StoragePoolUmount()
- }
- }
-
- err := s.copyVolume(source.Pool, source.Name)
- if err != nil {
- return err
- }
-
- if source.VolumeOnly {
- logger.Infof(successMsg)
- return nil
- }
-
- snapshots, err := driver.VolumeSnapshotsGet(s.s, source.Pool, source.Name, storagePoolVolumeTypeCustom)
- if err != nil {
- return err
- }
-
- if len(snapshots) == 0 {
- return nil
- }
-
- for _, snap := range snapshots {
- err = s.copyVolumeSnapshot(source.Pool, snap.Name)
- if err != nil {
- return err
- }
- }
-
- logger.Infof(successMsg)
- return nil
-}
-
-func (s *storageLvm) StorageMigrationSource(args MigrationSourceArgs) (MigrationStorageSourceDriver, error) {
- return rsyncStorageMigrationSource(args)
-}
-
-func (s *storageLvm) StorageMigrationSink(conn *websocket.Conn, op *operations.Operation, args MigrationSinkArgs) error {
- return rsyncStorageMigrationSink(conn, op, args)
-}
-
-func (s *storageLvm) StoragePoolVolumeSnapshotCreate(target *api.StorageVolumeSnapshotsPost) error {
- logger.Debugf("Creating LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- poolName := s.getOnDiskPoolName()
- sourceOnlyName, _, ok := shared.InstanceGetParentAndSnapshotName(target.Name)
- if !ok {
- return fmt.Errorf("Not a snapshot")
- }
-
- sourceLvmName := containerNameToLVName(sourceOnlyName)
- targetLvmName := containerNameToLVName(target.Name)
-
- _, err := s.createSnapshotLV("default", poolName, sourceLvmName, storagePoolVolumeAPIEndpointCustom, targetLvmName, storagePoolVolumeAPIEndpointCustom, true, s.useThinpool)
- if err != nil {
- return fmt.Errorf("Failed to create snapshot logical volume %s", err)
- }
-
- targetPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target.Name)
- err = os.MkdirAll(targetPath, driver.SnapshotsDirMode)
- if err != nil {
- logger.Errorf("Failed to create mountpoint \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s", targetPath, s.volume.Name, s.pool.Name, err)
- return err
- }
-
- logger.Debugf("Created LVM storage volume for snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeSnapshotDelete() error {
- logger.Infof("Deleting LVM storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
-
- snapshotLVName := containerNameToLVName(s.volume.Name)
- storageVolumeSnapshotPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- if shared.IsMountPoint(storageVolumeSnapshotPath) {
- err := storageDrivers.TryUnmount(storageVolumeSnapshotPath, 0)
- if err != nil {
- return fmt.Errorf("Failed to unmount snapshot path \"%s\": %s", storageVolumeSnapshotPath, err)
- }
- }
-
- poolName := s.getOnDiskPoolName()
- snapshotLVDevPath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointCustom, snapshotLVName)
- lvExists, _ := storageLVExists(snapshotLVDevPath)
- if lvExists {
- err := removeLV("default", poolName, storagePoolVolumeAPIEndpointCustom, snapshotLVName)
- if err != nil {
- return err
- }
- }
-
- err := os.Remove(storageVolumeSnapshotPath)
- if err != nil && !os.IsNotExist(err) {
- return err
- }
-
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- storageVolumeSnapshotPath = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, sourceName)
- empty, err := shared.PathIsEmpty(storageVolumeSnapshotPath)
- if err == nil && empty {
- os.RemoveAll(storageVolumeSnapshotPath)
- }
-
- err = s.s.Cluster.StoragePoolVolumeDelete(
- "default",
- s.volume.Name,
- storagePoolVolumeTypeCustom,
- s.poolID)
- if err != nil {
- logger.Errorf(`Failed to delete database entry for LVM storage volume "%s" on storage pool "%s"`,
- s.volume.Name, s.pool.Name)
- }
-
- logger.Infof("Deleted LVM storage volume snapshot \"%s\" on storage pool \"%s\"", s.volume.Name, s.pool.Name)
- return nil
-}
-
-func (s *storageLvm) StoragePoolVolumeSnapshotRename(newName string) error {
- sourceName, _, ok := shared.InstanceGetParentAndSnapshotName(s.volume.Name)
- fullSnapshotName := fmt.Sprintf("%s%s%s", sourceName, shared.SnapshotDelimiter, newName)
-
- logger.Infof("Renaming LVM storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- _, err := s.StoragePoolVolumeUmount()
- if err != nil {
- return err
- }
-
- if !ok {
- return fmt.Errorf("Not a snapshot name")
- }
-
- sourceLVName := containerNameToLVName(s.volume.Name)
- targetLVName := containerNameToLVName(fullSnapshotName)
-
- err = s.renameLVByPath("default", sourceLVName, targetLVName, storagePoolVolumeAPIEndpointCustom)
- if err != nil {
- return fmt.Errorf("Failed to rename logical volume from \"%s\" to \"%s\": %s", s.volume.Name, fullSnapshotName, err)
- }
-
- oldPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, s.volume.Name)
- newPath := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, fullSnapshotName)
- err = os.Rename(oldPath, newPath)
- if err != nil {
- return err
- }
-
- logger.Infof("Renamed LVM storage volume on storage pool \"%s\" from \"%s\" to \"%s\"", s.pool.Name, s.volume.Name, fullSnapshotName)
-
- return s.s.Cluster.StoragePoolVolumeRename("default", s.volume.Name, fullSnapshotName, storagePoolVolumeTypeCustom, s.poolID)
-}
diff --git a/lxd/storage_lvm_utils.go b/lxd/storage_lvm_utils.go
deleted file mode 100644
index 5448118d3b..0000000000
--- a/lxd/storage_lvm_utils.go
+++ /dev/null
@@ -1,1090 +0,0 @@
-package main
-
-import (
- "fmt"
- "os"
- "os/exec"
- "strconv"
- "strings"
- "syscall"
-
- "github.com/pkg/errors"
-
- "github.com/lxc/lxd/lxd/db"
- "github.com/lxc/lxd/lxd/instance"
- "github.com/lxc/lxd/lxd/instance/instancetype"
- "github.com/lxc/lxd/lxd/project"
- "github.com/lxc/lxd/lxd/rsync"
- "github.com/lxc/lxd/lxd/state"
- driver "github.com/lxc/lxd/lxd/storage"
- "github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
- "github.com/lxc/lxd/shared/logger"
- "github.com/lxc/lxd/shared/units"
- "github.com/lxc/lxd/shared/version"
-)
-
-func (s *storageLvm) lvExtend(lvPath string, lvSize int64, fsType string, fsMntPoint string, volumeType int, data interface{}) error {
- // Round the size to closest 512 bytes
- lvSize = int64(lvSize/512) * 512
- lvSizeString := units.GetByteSizeString(lvSize, 0)
-
- msg, err := shared.TryRunCommand(
- "lvextend",
- "-L", lvSizeString,
- "-f",
- lvPath)
- if err != nil {
- logger.Errorf("Could not extend LV \"%s\": %s", lvPath, msg)
- return fmt.Errorf("could not extend LV \"%s\": %s", lvPath, msg)
- }
-
- switch volumeType {
- case storagePoolVolumeTypeContainer:
- c := data.(instance.Instance)
- ourMount, err := c.StorageStart()
- if err != nil {
- return err
- }
- if ourMount {
- defer c.StorageStop()
- }
- case storagePoolVolumeTypeCustom:
- ourMount, err := s.StoragePoolVolumeMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer s.StoragePoolVolumeUmount()
- }
- default:
- return fmt.Errorf(`Resizing not implemented for storage `+
- `volume type %d`, volumeType)
- }
-
- return driver.GrowFileSystem(fsType, lvPath, fsMntPoint)
-}
-
-func (s *storageLvm) lvReduce(lvPath string, lvSize int64, fsType string, fsMntPoint string, volumeType int, data interface{}) error {
- var err error
- var msg string
-
- // Round the size to closest 512 bytes
- lvSize = int64(lvSize/512) * 512
- lvSizeString := units.GetByteSizeString(lvSize, 0)
-
- cleanupFunc, err := shrinkVolumeFilesystem(s, volumeType, fsType, lvPath, fsMntPoint, lvSize, data)
- if cleanupFunc != nil {
- defer cleanupFunc()
- }
- if err != nil {
- return err
- }
-
- msg, err = shared.TryRunCommand(
- "lvreduce",
- "-L", lvSizeString,
- "-f",
- lvPath)
- if err != nil {
- logger.Errorf("Could not reduce LV \"%s\": %s", lvPath, msg)
- return fmt.Errorf("could not reduce LV \"%s\": %s", lvPath, msg)
- }
-
- logger.Debugf("Reduced underlying %s filesystem for LV \"%s\"", fsType, lvPath)
- return nil
-}
-
-func (s *storageLvm) getLvmMountOptions() string {
- if s.volume.Config["block.mount_options"] != "" {
- return s.volume.Config["block.mount_options"]
- }
-
- if s.pool.Config["volume.block.mount_options"] != "" {
- return s.pool.Config["volume.block.mount_options"]
- }
-
- if s.getLvmFilesystem() == "btrfs" {
- return "user_subvol_rm_allowed,discard"
- }
-
- return "discard"
-}
-
-func (s *storageLvm) getLvmFilesystem() string {
- if s.volume.Config["block.filesystem"] != "" {
- return s.volume.Config["block.filesystem"]
- }
-
- if s.pool.Config["volume.block.filesystem"] != "" {
- return s.pool.Config["volume.block.filesystem"]
- }
-
- return "ext4"
-}
-
-func (s *storageLvm) getLvmVolumeSize() (string, error) {
- sz, err := units.ParseByteSizeString(s.volume.Config["size"])
- if err != nil {
- return "", err
- }
-
- // Safety net: Set to default value.
- if sz == 0 {
- sz, _ = units.ParseByteSizeString("10GB")
- }
-
- return fmt.Sprintf("%d", sz), nil
-}
-
-func (s *storageLvm) getLvmThinpoolName() string {
- if s.pool.Config["lvm.thinpool_name"] != "" {
- return s.pool.Config["lvm.thinpool_name"]
- }
-
- return "LXDThinPool"
-}
-
-func (s *storageLvm) usesThinpool() bool {
- // Default is to use a thinpool.
- if s.pool.Config["lvm.use_thinpool"] == "" {
- return true
- }
-
- return shared.IsTrue(s.pool.Config["lvm.use_thinpool"])
-}
-
-func (s *storageLvm) setLvmThinpoolName(newThinpoolName string) {
- s.pool.Config["lvm.thinpool_name"] = newThinpoolName
-}
-
-func (s *storageLvm) getOnDiskPoolName() string {
- if s.vgName != "" {
- return s.vgName
- }
-
- return s.pool.Name
-}
-
-func (s *storageLvm) setOnDiskPoolName(newName string) {
- s.vgName = newName
- s.pool.Config["source"] = newName
-}
-
-func (s *storageLvm) renameLVByPath(project, oldName string, newName string, volumeType string) error {
- oldLvmName := getPrefixedLvName(project, volumeType, oldName)
- newLvmName := getPrefixedLvName(project, volumeType, newName)
- poolName := s.getOnDiskPoolName()
- return lvmLVRename(poolName, oldLvmName, newLvmName)
-}
-
-func removeLV(project, vgName string, volumeType string, lvName string) error {
- lvmVolumePath := getLvmDevPath(project, vgName, volumeType, lvName)
-
- _, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
- if err != nil {
- logger.Errorf("Could not remove LV \"%s\": %v", lvName, err)
- return fmt.Errorf("Could not remove LV named %s: %v", lvName, err)
- }
-
- return nil
-}
-
-func (s *storageLvm) createSnapshotLV(project, vgName string, origLvName string, origVolumeType string, lvName string, volumeType string, readonly bool, makeThinLv bool) (string, error) {
- sourceProject := project
- if origVolumeType == storagePoolVolumeAPIEndpointImages {
- // Image volumes are shared across projects.
- sourceProject = "default"
- }
-
- sourceLvmVolumePath := getLvmDevPath(sourceProject, vgName, origVolumeType, origLvName)
- isRecent, err := lvmVersionIsAtLeast(s.sTypeVersion, "2.02.99")
- if err != nil {
- return "", fmt.Errorf("Error checking LVM version: %v", err)
- }
-
- lvmPoolVolumeName := getPrefixedLvName(project, volumeType, lvName)
- args := []string{"-n", lvmPoolVolumeName, "-s", sourceLvmVolumePath}
- if isRecent {
- args = append(args, "-kn")
- }
-
- // If the source is not a thin volume the size needs to be specified.
- // According to LVM tools 15-20% of the original volume should be
- // sufficient. However, let's not be stingy at first otherwise we might
- // force users to fiddle around with lvextend.
- if !makeThinLv {
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- return "", err
- }
-
- // Round the size to closest 512 bytes
- lvSizeInt, err := units.ParseByteSizeString(lvSize)
- if err != nil {
- return "", err
- }
-
- lvSizeInt = int64(lvSizeInt/512) * 512
- lvSizeString := units.GetByteSizeString(lvSizeInt, 0)
-
- args = append(args, "--size", lvSizeString)
- }
-
- if readonly {
- args = append(args, "-pr")
- } else {
- args = append(args, "-prw")
- }
-
- _, err = shared.TryRunCommand("lvcreate", args...)
- if err != nil {
- logger.Errorf("Could not create LV snapshot: %s to %s: %v", origLvName, lvName, err)
- return "", fmt.Errorf("Could not create snapshot LV named %s: %v", lvName, err)
- }
-
- targetLvmVolumePath := getLvmDevPath(project, vgName, volumeType, lvName)
- if makeThinLv {
- // Snapshots of thin logical volumes can be directly activated.
- // Normal snapshots will complain about changing the origin
- // (Which they never do.), so skip the activation since the
- // logical volume will be automatically activated anyway.
- err := storageLVActivate(targetLvmVolumePath)
- if err != nil {
- return "", errors.Wrap(err, "Activate LVM volume")
- }
- }
-
- return targetLvmVolumePath, nil
-}
-
-func (s *storageLvm) createSnapshotContainer(snapshotContainer instance.Instance, sourceContainer instance.Instance, readonly bool) error {
- tryUndo := true
-
- sourceContainerName := sourceContainer.Name()
- targetContainerName := snapshotContainer.Name()
- sourceContainerLvmName := containerNameToLVName(sourceContainerName)
- targetContainerLvmName := containerNameToLVName(targetContainerName)
- logger.Debugf("Creating snapshot: %s to %s", sourceContainerName, targetContainerName)
-
- poolName := s.getOnDiskPoolName()
- _, err := s.createSnapshotLV(sourceContainer.Project(), poolName, sourceContainerLvmName, storagePoolVolumeAPIEndpointContainers, targetContainerLvmName, storagePoolVolumeAPIEndpointContainers, readonly, s.useThinpool)
- if err != nil {
- return fmt.Errorf("Error creating snapshot LV: %s", err)
- }
- defer func() {
- if tryUndo {
- s.ContainerDelete(snapshotContainer)
- }
- }()
-
- targetContainerMntPoint := ""
- targetContainerPath := snapshotContainer.Path()
- targetIsSnapshot := snapshotContainer.IsSnapshot()
- targetPool, err := snapshotContainer.StoragePool()
- if err != nil {
- return errors.Wrap(err, "Get snapshot storage pool")
- }
- if targetIsSnapshot {
- targetContainerMntPoint = driver.GetSnapshotMountPoint(sourceContainer.Project(), s.pool.Name, targetContainerName)
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(sourceContainerName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(sourceContainer.Project(), sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(sourceContainer.Project(), sourceName))
- err = driver.CreateSnapshotMountpoint(targetContainerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- } else {
- targetContainerMntPoint = driver.GetContainerMountPoint(sourceContainer.Project(), targetPool, targetContainerName)
- err = driver.CreateContainerMountpoint(targetContainerMntPoint, targetContainerPath, snapshotContainer.IsPrivileged())
- }
- if err != nil {
- return errors.Wrap(err, "Create mount point")
- }
-
- tryUndo = false
-
- return nil
-}
-
-// Copy a container on a storage pool that does use a thinpool.
-func (s *storageLvm) copyContainerThinpool(target instance.Instance, source instance.Instance, readonly bool) error {
- err := s.createSnapshotContainer(target, source, readonly)
- if err != nil {
- logger.Errorf("Error creating snapshot LV for copy: %s", err)
- return err
- }
-
- // Generate a new xfs's UUID
- LVFilesystem := s.getLvmFilesystem()
- poolName := s.getOnDiskPoolName()
- containerName := target.Name()
- containerLvmName := containerNameToLVName(containerName)
- containerLvDevPath := getLvmDevPath(target.Project(), poolName,
- storagePoolVolumeAPIEndpointContainers, containerLvmName)
-
- // If btrfstune sees two btrfs filesystems with the same UUID it
- // gets confused and wants both of them unmounted. So unmount
- // the source as well.
- if LVFilesystem == "btrfs" {
- ourUmount, err := s.ContainerUmount(source, source.Path())
- if err != nil {
- return err
- }
-
- if ourUmount {
- defer s.ContainerMount(source)
- }
- }
-
- msg, err := driver.FSGenerateNewUUID(LVFilesystem, containerLvDevPath)
- if err != nil {
- logger.Errorf("Failed to create new \"%s\" UUID for container \"%s\" on storage pool \"%s\": %s", LVFilesystem, containerName, s.pool.Name, msg)
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) copySnapshot(target instance.Instance, source instance.Instance, refresh bool) error {
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
-
- targetParentName, _, _ := shared.InstanceGetParentAndSnapshotName(target.Name())
- containersPath := driver.GetSnapshotMountPoint(target.Project(), s.pool.Name, targetParentName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", s.pool.Name, "containers-snapshots", project.Prefix(target.Project(), targetParentName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(target.Project(), targetParentName))
- err = driver.CreateSnapshotMountpoint(containersPath, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- if err != nil {
- return err
- }
-
- if s.useThinpool && sourcePool == s.pool.Name && !refresh {
- err = s.copyContainerThinpool(target, source, true)
- } else {
- err = s.copyContainerLv(target, source, true, refresh)
- }
- if err != nil {
- logger.Errorf("Error creating snapshot LV for copy: %s", err)
- return err
- }
-
- return nil
-}
-
-// Copy a container on a storage pool that does not use a thinpool.
-func (s *storageLvm) copyContainerLv(target instance.Instance, source instance.Instance, readonly bool, refresh bool) error {
- exists, err := storageLVExists(getLvmDevPath(target.Project(), s.getOnDiskPoolName(),
- storagePoolVolumeAPIEndpointContainers, containerNameToLVName(target.Name())))
- if err != nil {
- return err
- }
-
- // Only create container/snapshot if it doesn't already exist
- if !exists {
- err := s.ContainerCreate(target)
- if err != nil {
- return err
- }
- }
-
- targetName := target.Name()
- targetStart, err := target.StorageStart()
- if err != nil {
- return err
- }
- if targetStart {
- defer target.StorageStop()
- }
-
- sourceName := source.Name()
- sourceStart, err := source.StorageStart()
- if err != nil {
- return err
- }
- if sourceStart {
- defer source.StorageStop()
- }
-
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
- sourceContainerMntPoint := driver.GetContainerMountPoint(source.Project(), sourcePool, sourceName)
- if source.IsSnapshot() {
- sourceContainerMntPoint = driver.GetSnapshotMountPoint(source.Project(), sourcePool, sourceName)
- }
-
- targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), s.pool.Name, targetName)
- if target.IsSnapshot() {
- targetContainerMntPoint = driver.GetSnapshotMountPoint(source.Project(), s.pool.Name, targetName)
- }
-
- if source.IsRunning() {
- err = source.Freeze()
- if err != nil {
- return err
- }
- defer source.Unfreeze()
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- output, err := rsync.LocalCopy(sourceContainerMntPoint, targetContainerMntPoint, bwlimit, true)
- if err != nil {
- return fmt.Errorf("Failed to rsync container: %s: %s", string(output), err)
- }
-
- if readonly {
- targetLvmName := containerNameToLVName(targetName)
- poolName := s.getOnDiskPoolName()
- _, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointContainers, targetLvmName))
- if err != nil {
- logger.Errorf("Failed to make LVM snapshot \"%s\" read-write: %v", targetName, err)
- return err
- }
- }
-
- return nil
-}
-
-// Copy an lvm container.
-func (s *storageLvm) copyContainer(target instance.Instance, source instance.Instance, refresh bool) error {
- targetPool, err := target.StoragePool()
- if err != nil {
- return err
- }
-
- targetContainerMntPoint := driver.GetContainerMountPoint(target.Project(), targetPool, target.Name())
- err = driver.CreateContainerMountpoint(targetContainerMntPoint, target.Path(), target.IsPrivileged())
- if err != nil {
- return err
- }
-
- sourcePool, err := source.StoragePool()
- if err != nil {
- return err
- }
-
- if s.useThinpool && targetPool == sourcePool && !refresh {
- // If the storage pool uses a thinpool we can have snapshots of
- // snapshots.
- err = s.copyContainerThinpool(target, source, false)
- } else {
- // If the storage pools does not use a thinpool we need to
- // perform full copies.
- err = s.copyContainerLv(target, source, false, refresh)
- }
- if err != nil {
- return err
- }
-
- err = target.DeferTemplateApply("copy")
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) containerCreateFromImageLv(c instance.Instance, fp string) error {
- containerName := c.Name()
-
- err := s.ContainerCreate(c)
- if err != nil {
- logger.Errorf(`Failed to create non-thinpool LVM storage volume for container "%s" on storage pool "%s": %s`, containerName, s.pool.Name, err)
- return err
- }
- logger.Debugf(`Created non-thinpool LVM storage volume for container "%s" on storage pool "%s"`, containerName, s.pool.Name)
-
- containerPath := c.Path()
- _, err = s.ContainerMount(c)
- if err != nil {
- logger.Errorf(`Failed to mount non-thinpool LVM storage volume for container "%s" on storage pool "%s": %s`, containerName, s.pool.Name, err)
- return err
- }
- logger.Debugf(`Mounted non-thinpool LVM storage volume for container "%s" on storage pool "%s"`, containerName, s.pool.Name)
-
- imagePath := shared.VarPath("images", fp)
- containerMntPoint := driver.GetContainerMountPoint(c.Project(), s.pool.Name, containerName)
- err = driver.ImageUnpack(imagePath, containerMntPoint, "", true, s.s.OS.RunningInUserNS, nil)
- if err != nil {
- logger.Errorf(`Failed to unpack image "%s" into non-thinpool LVM storage volume "%s" for container "%s" on storage pool "%s": %s`, imagePath, containerMntPoint, containerName, s.pool.Name, err)
- return err
- }
- logger.Debugf(`Unpacked image "%s" into non-thinpool LVM storage volume "%s" for container "%s" on storage pool "%s"`, imagePath, containerMntPoint, containerName, s.pool.Name)
-
- s.ContainerUmount(c, containerPath)
-
- return nil
-}
-
-func (s *storageLvm) containerCreateFromImageThinLv(c instance.Instance, fp string) error {
- poolName := s.getOnDiskPoolName()
- // Check if the image already exists.
- imageLvmDevPath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointImages, fp)
-
- imageStoragePoolLockID := getImageCreateLockID(poolName, fp)
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- lxdStorageMapLock.Unlock()
- if _, ok := <-waitChannel; ok {
- logger.Warnf("Received value over semaphore, this should not have happened")
- }
- } else {
- lxdStorageOngoingOperationMap[imageStoragePoolLockID] = make(chan bool)
- lxdStorageMapLock.Unlock()
-
- var imgerr error
- ok, _ := storageLVExists(imageLvmDevPath)
- if ok {
- _, volume, err := s.s.Cluster.StoragePoolNodeVolumeGetType(fp, db.StoragePoolVolumeTypeImage, s.poolID)
- if err != nil {
- return errors.Wrapf(err, "Fetch image volume %s", fp)
- }
- if volume.Config["block.filesystem"] != s.getLvmFilesystem() {
- // The storage pool volume.blockfilesystem property has changed, re-import the image
- err := s.ImageDelete(fp)
- if err != nil {
- return errors.Wrap(err, "Image delete")
- }
- ok = false
- }
- }
-
- if !ok {
- imgerr = s.ImageCreate(fp, nil)
- }
-
- lxdStorageMapLock.Lock()
- if waitChannel, ok := lxdStorageOngoingOperationMap[imageStoragePoolLockID]; ok {
- close(waitChannel)
- delete(lxdStorageOngoingOperationMap, imageStoragePoolLockID)
- }
- lxdStorageMapLock.Unlock()
-
- if imgerr != nil {
- return errors.Wrap(imgerr, "Image create")
- }
- }
-
- containerName := c.Name()
- containerLvmName := containerNameToLVName(containerName)
- _, err := s.createSnapshotLV(c.Project(), poolName, fp, storagePoolVolumeAPIEndpointImages, containerLvmName, storagePoolVolumeAPIEndpointContainers, false, s.useThinpool)
- if err != nil {
- return errors.Wrap(err, "Create snapshot")
- }
-
- return nil
-}
-
-func lvmGetLVCount(vgName string) (int, error) {
- output, err := shared.TryRunCommand("vgs", "--noheadings", "-o", "lv_count", vgName)
- if err != nil {
- return -1, err
- }
-
- output = strings.TrimSpace(output)
- return strconv.Atoi(output)
-}
-
-func lvmLvIsWritable(lvName string) (bool, error) {
- output, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "lv_attr", lvName)
- if err != nil {
- return false, errors.Wrapf(err, "Error retrieving attributes for logical volume %q", lvName)
- }
-
- output = strings.TrimSpace(output)
- return rune(output[1]) == 'w', nil
-}
-
-func storageVGActivate(lvmVolumePath string) error {
- _, err := shared.TryRunCommand("vgchange", "-ay", lvmVolumePath)
- if err != nil {
- return fmt.Errorf("could not activate volume group \"%s\": %v", lvmVolumePath, err)
- }
-
- return nil
-}
-
-func storageLVActivate(lvmVolumePath string) error {
- _, err := shared.TryRunCommand("lvchange", "-ay", lvmVolumePath)
- if err != nil {
- return fmt.Errorf("could not activate logival volume \"%s\": %v", lvmVolumePath, err)
- }
-
- return nil
-}
-
-func storagePVExists(pvName string) (bool, error) {
- _, err := shared.RunCommand("pvs", "--noheadings", "-o", "lv_attr", pvName)
- if err != nil {
- runErr, ok := err.(shared.RunError)
- if ok {
- exitError, ok := runErr.Err.(*exec.ExitError)
- if ok {
- waitStatus := exitError.Sys().(syscall.WaitStatus)
- if waitStatus.ExitStatus() == 5 {
- // physical volume not found
- return false, nil
- }
- }
- }
- return false, fmt.Errorf("error checking for physical volume \"%s\"", pvName)
- }
-
- return true, nil
-}
-
-func storageVGExists(vgName string) (bool, error) {
- _, err := shared.RunCommand("vgs", "--noheadings", "-o", "lv_attr", vgName)
- if err != nil {
- runErr, ok := err.(shared.RunError)
- if ok {
- exitError, ok := runErr.Err.(*exec.ExitError)
- if ok {
- waitStatus := exitError.Sys().(syscall.WaitStatus)
- if waitStatus.ExitStatus() == 5 {
- // volume group not found
- return false, nil
- }
- }
- }
-
- return false, fmt.Errorf("error checking for volume group \"%s\"", vgName)
- }
-
- return true, nil
-}
-
-func storageLVExists(lvName string) (bool, error) {
- _, err := shared.RunCommand("lvs", "--noheadings", "-o", "lv_attr", lvName)
- if err != nil {
- runErr, ok := err.(shared.RunError)
- if ok {
- exitError, ok := runErr.Err.(*exec.ExitError)
- if ok {
- waitStatus := exitError.Sys().(syscall.WaitStatus)
- if waitStatus.ExitStatus() == 5 {
- // logical volume not found
- return false, nil
- }
- }
- }
-
- return false, fmt.Errorf("error checking for logical volume \"%s\"", lvName)
- }
-
- return true, nil
-}
-
-func lvmGetLVSize(lvPath string) (string, error) {
- msg, err := shared.TryRunCommand("lvs", "--noheadings", "-o", "size", "--nosuffix", "--units", "b", lvPath)
- if err != nil {
- return "", fmt.Errorf("failed to retrieve size of logical volume: %s: %s", string(msg), err)
- }
-
- sizeString := string(msg)
- sizeString = strings.TrimSpace(sizeString)
- size, err := strconv.ParseInt(sizeString, 10, 64)
- if err != nil {
- return "", err
- }
-
- detectedSize := units.GetByteSizeString(size, 0)
-
- return detectedSize, nil
-}
-
-func storageLVMThinpoolExists(vgName string, poolName string) (bool, error) {
- output, err := shared.RunCommand("vgs", "--noheadings", "-o", "lv_attr", fmt.Sprintf("%s/%s", vgName, poolName))
- if err != nil {
- runErr, ok := err.(shared.RunError)
- if ok {
- exitError, ok := runErr.Err.(*exec.ExitError)
- if ok {
- waitStatus := exitError.Sys().(syscall.WaitStatus)
- if waitStatus.ExitStatus() == 5 {
- // pool LV was not found
- return false, nil
- }
- }
- }
-
- return false, fmt.Errorf("error checking for pool \"%s\"", poolName)
- }
- // Found LV named poolname, check type:
- attrs := strings.TrimSpace(string(output[:]))
- if strings.HasPrefix(attrs, "t") {
- return true, nil
- }
-
- return false, fmt.Errorf("pool named \"%s\" exists but is not a thin pool", poolName)
-}
-
-func storageLVMGetThinPoolUsers(s *state.State) ([]string, error) {
- results := []string{}
-
- cNames, err := s.Cluster.ContainersNodeList(instancetype.Container)
- if err != nil {
- return results, err
- }
-
- for _, cName := range cNames {
- var lvLinkPath string
- if strings.Contains(cName, shared.SnapshotDelimiter) {
- lvLinkPath = shared.VarPath("snapshots", fmt.Sprintf("%s.lv", cName))
- } else {
- lvLinkPath = shared.VarPath("containers", fmt.Sprintf("%s.lv", cName))
- }
-
- if shared.PathExists(lvLinkPath) {
- results = append(results, cName)
- }
- }
-
- imageNames, err := s.Cluster.ImagesGet("default", false)
- if err != nil {
- return results, err
- }
-
- for _, imageName := range imageNames {
- imageLinkPath := shared.VarPath("images", fmt.Sprintf("%s.lv", imageName))
- if shared.PathExists(imageLinkPath) {
- results = append(results, imageName)
- }
- }
-
- return results, nil
-}
-
-func storageLVMValidateThinPoolName(s *state.State, vgName string, value string) error {
- users, err := storageLVMGetThinPoolUsers(s)
- if err != nil {
- return fmt.Errorf("error checking if a pool is already in use: %v", err)
- }
-
- if len(users) > 0 {
- return fmt.Errorf("can not change LVM config. Images or containers are still using LVs: %v", users)
- }
-
- if value != "" {
- if vgName == "" {
- return fmt.Errorf("can not set lvm.thinpool_name without lvm.vg_name set")
- }
-
- poolExists, err := storageLVMThinpoolExists(vgName, value)
- if err != nil {
- return fmt.Errorf("error checking for thin pool \"%s\" in \"%s\": %v", value, vgName, err)
- }
-
- if !poolExists {
- return fmt.Errorf("pool \"'%s\" does not exist in Volume Group \"%s\"", value, vgName)
- }
- }
-
- return nil
-}
-
-func lvmVGRename(oldName string, newName string) error {
- _, err := shared.TryRunCommand("vgrename", oldName, newName)
- if err != nil {
- return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %v", oldName, newName, err)
- }
-
- return nil
-}
-
-func lvmLVRename(vgName string, oldName string, newName string) error {
- _, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
- if err != nil {
- return fmt.Errorf("could not rename volume group from \"%s\" to \"%s\": %v", oldName, newName, err)
- }
-
- return nil
-}
-
-func containerNameToLVName(containerName string) string {
- lvName := strings.Replace(containerName, "-", "--", -1)
- return strings.Replace(lvName, shared.SnapshotDelimiter, "-", -1)
-}
-
-func getLvmDevPath(projectName, lvmPool string, volumeType string, lvmVolume string) string {
- lvmVolume = project.Prefix(projectName, lvmVolume)
- if volumeType == "" {
- return fmt.Sprintf("/dev/%s/%s", lvmPool, lvmVolume)
- }
-
- return fmt.Sprintf("/dev/%s/%s_%s", lvmPool, volumeType, lvmVolume)
-}
-
-func getLVName(lvmPool string, volumeType string, lvmVolume string) string {
- if volumeType == "" {
- return fmt.Sprintf("%s/%s", lvmPool, lvmVolume)
- }
-
- return fmt.Sprintf("%s/%s_%s", lvmPool, volumeType, lvmVolume)
-}
-
-func getPrefixedLvName(projectName, volumeType string, lvmVolume string) string {
- lvmVolume = project.Prefix(projectName, lvmVolume)
- return fmt.Sprintf("%s_%s", volumeType, lvmVolume)
-}
-
-func lvmCreateLv(projectName, vgName string, thinPoolName string, lvName string, lvFsType string, lvSize string, volumeType string, makeThinLv bool) error {
- var output string
- var err error
-
- // Round the size to closest 512 bytes
- lvSizeInt, err := units.ParseByteSizeString(lvSize)
- if err != nil {
- return err
- }
-
- lvSizeInt = int64(lvSizeInt/512) * 512
- lvSizeString := units.GetByteSizeString(lvSizeInt, 0)
-
- lvmPoolVolumeName := getPrefixedLvName(projectName, volumeType, lvName)
- if makeThinLv {
- targetVg := fmt.Sprintf("%s/%s", vgName, thinPoolName)
- _, err = shared.TryRunCommand("lvcreate", "-Wy", "--yes", "--thin", "-n", lvmPoolVolumeName, "--virtualsize", lvSizeString, targetVg)
- } else {
- _, err = shared.TryRunCommand("lvcreate", "-Wy", "--yes", "-n", lvmPoolVolumeName, "--size", lvSizeString, vgName)
- }
- if err != nil {
- logger.Errorf("Could not create LV \"%s\": %v", lvmPoolVolumeName, err)
- return fmt.Errorf("Could not create thin LV named %s: %v", lvmPoolVolumeName, err)
- }
-
- fsPath := getLvmDevPath(projectName, vgName, volumeType, lvName)
-
- output, err = makeFSType(fsPath, lvFsType, nil)
- if err != nil {
- logger.Errorf("Filesystem creation failed: %v (%s)", err, output)
- return fmt.Errorf("Error making filesystem on image LV: %v (%s)", err, output)
- }
-
- return nil
-}
-
-func lvmCreateThinpool(s *state.State, sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
- exists, err := storageLVMThinpoolExists(vgName, thinPoolName)
- if err != nil {
- return err
- }
-
- if exists {
- return nil
- }
-
- err = createDefaultThinPool(sTypeVersion, vgName, thinPoolName, lvFsType)
- if err != nil {
- return err
- }
-
- err = storageLVMValidateThinPoolName(s, vgName, thinPoolName)
- if err != nil {
- logger.Errorf("Setting thin pool name: %s", err)
- return fmt.Errorf("Error setting LVM thin pool config: %v", err)
- }
-
- return nil
-}
-
-func createDefaultThinPool(sTypeVersion string, vgName string, thinPoolName string, lvFsType string) error {
- isRecent, err := lvmVersionIsAtLeast(sTypeVersion, "2.02.99")
- if err != nil {
- return fmt.Errorf("Error checking LVM version: %s", err)
- }
-
- // Create the thin pool
- lvmThinPool := fmt.Sprintf("%s/%s", vgName, thinPoolName)
- if isRecent {
- _, err = shared.TryRunCommand(
- "lvcreate",
- "-Wy", "--yes",
- "--poolmetadatasize", "1G",
- "-l", "100%FREE",
- "--thinpool", lvmThinPool)
- } else {
- _, err = shared.TryRunCommand(
- "lvcreate",
- "-Wy", "--yes",
- "--poolmetadatasize", "1G",
- "-L", "1G",
- "--thinpool", lvmThinPool)
- }
-
- if err != nil {
- logger.Errorf("Could not create thin pool \"%s\": %v", thinPoolName, err)
- return fmt.Errorf("Could not create LVM thin pool named %s: %v", thinPoolName, err)
- }
-
- if !isRecent {
- // Grow it to the maximum VG size (two step process required by old LVM)
- _, err = shared.TryRunCommand("lvextend", "--alloc", "anywhere", "-l", "100%FREE", lvmThinPool)
-
- if err != nil {
- logger.Errorf("Could not grow thin pool: \"%s\": %v", thinPoolName, err)
- return fmt.Errorf("Could not grow LVM thin pool named %s: %v", thinPoolName, err)
- }
- }
-
- return nil
-}
-
-func lvmVersionIsAtLeast(sTypeVersion string, versionString string) (bool, error) {
- lvmVersionString := strings.Split(sTypeVersion, "/")[0]
-
- lvmVersion, err := version.Parse(lvmVersionString)
- if err != nil {
- return false, err
- }
-
- inVersion, err := version.Parse(versionString)
- if err != nil {
- return false, err
- }
-
- if lvmVersion.Compare(inVersion) < 0 {
- return false, nil
- }
-
- return true, nil
-}
-
-// Copy an LVM custom volume.
-func (s *storageLvm) copyVolume(sourcePool string, source string) error {
- targetMntPoint := driver.GetStoragePoolVolumeMountPoint(s.pool.Name, s.volume.Name)
-
- err := os.MkdirAll(targetMntPoint, 0711)
- if err != nil {
- return err
- }
-
- if s.useThinpool && sourcePool == s.pool.Name {
- err = s.copyVolumeThinpool(source, s.volume.Name, false)
- } else {
- err = s.copyVolumeLv(sourcePool, source, s.volume.Name, false)
- }
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) copyVolumeSnapshot(sourcePool string, source string) error {
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(source)
- target := fmt.Sprintf("%s/%s", s.volume.Name, snapOnlyName)
- targetMntPoint := driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
-
- err := os.MkdirAll(targetMntPoint, 0711)
- if err != nil {
- return err
- }
-
- if s.useThinpool && sourcePool == s.pool.Name {
- err = s.copyVolumeThinpool(source, target, true)
- } else {
- err = s.copyVolumeLv(sourcePool, source, target, true)
- }
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func (s *storageLvm) copyVolumeLv(sourcePool string, source string, target string, readOnly bool) error {
- var srcMountPoint string
- var dstMountPoint string
-
- sourceIsSnapshot := shared.IsSnapshot(source)
-
- if sourceIsSnapshot {
- srcMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(sourcePool, source)
- } else {
- srcMountPoint = driver.GetStoragePoolVolumeMountPoint(sourcePool, source)
-
- }
-
- targetIsSnapshot := shared.IsSnapshot(target)
-
- if targetIsSnapshot {
- dstMountPoint = driver.GetStoragePoolVolumeSnapshotMountPoint(s.pool.Name, target)
- } else {
- dstMountPoint = driver.GetStoragePoolVolumeMountPoint(s.pool.Name, target)
- }
-
- var err error
-
- if targetIsSnapshot {
- err = s.StoragePoolVolumeSnapshotCreate(&api.StorageVolumeSnapshotsPost{Name: target})
- } else {
- err = s.StoragePoolVolumeCreate()
- }
- if err != nil {
- logger.Errorf("Failed to create LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- ourMount, err := s.StoragePoolVolumeMount()
- if err != nil {
- return err
- }
- if ourMount {
- defer s.StoragePoolVolumeUmount()
- }
-
- bwlimit := s.pool.Config["rsync.bwlimit"]
- _, err = rsync.LocalCopy(srcMountPoint, dstMountPoint, bwlimit, true)
- if err != nil {
- os.RemoveAll(dstMountPoint)
- logger.Errorf("Failed to rsync into LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- // Snapshot are already read-only, and this will fail if trying to set them
- // read-only again.
- if readOnly && !targetIsSnapshot {
- targetLvmName := containerNameToLVName(target)
- poolName := s.getOnDiskPoolName()
-
- _, err := shared.TryRunCommand("lvchange", "-pr", fmt.Sprintf("%s/%s_%s", poolName, storagePoolVolumeAPIEndpointCustom, targetLvmName))
- if err != nil {
- logger.Errorf("Failed to make LVM snapshot \"%s\" read-only: %v", s.volume.Name, err)
- return err
- }
- }
-
- return nil
-}
-
-func (s *storageLvm) copyVolumeThinpool(source string, target string, readOnly bool) error {
- sourceLvmName := containerNameToLVName(source)
- targetLvmName := containerNameToLVName(target)
-
- poolName := s.getOnDiskPoolName()
- lvFsType := s.getLvmFilesystem()
-
- lvSize, err := s.getLvmVolumeSize()
- if lvSize == "" {
- logger.Errorf("Failed to get size for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- _, err = s.createSnapshotLV("default", poolName, sourceLvmName, storagePoolVolumeAPIEndpointCustom, targetLvmName, storagePoolVolumeAPIEndpointCustom, readOnly, s.useThinpool)
- if err != nil {
- logger.Errorf("Failed to create snapshot for LVM storage volume \"%s\" on storage pool \"%s\": %s", s.volume.Name, s.pool.Name, err)
- return err
- }
-
- lvDevPath := getLvmDevPath("default", poolName, storagePoolVolumeAPIEndpointCustom, targetLvmName)
-
- msg, err := driver.FSGenerateNewUUID(lvFsType, lvDevPath)
- if err != nil {
- logger.Errorf("Failed to create new UUID for filesystem \"%s\" for RBD storage volume \"%s\" on storage pool \"%s\": %s: %s", lvFsType, s.volume.Name, s.pool.Name, msg, err)
- return err
- }
-
- return nil
-}
From 7d619e57cb8eafdcfb8696599089eef52c1ddcf3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Tue, 18 Feb 2020 14:43:15 +0000
Subject: [PATCH 06/17] lxd/storage: Removes unused getPoolMountLockID
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage.go | 6 ------
1 file changed, 6 deletions(-)
diff --git a/lxd/storage.go b/lxd/storage.go
index 8a87be9f59..89f02e758b 100644
--- a/lxd/storage.go
+++ b/lxd/storage.go
@@ -52,12 +52,6 @@ var lxdStorageOngoingOperationMap = map[string]chan bool{}
// lxdStorageMapLock is used to access lxdStorageOngoingOperationMap.
var lxdStorageMapLock sync.Mutex
-// The following functions are used to construct simple operation codes that are
-// unique.
-func getPoolMountLockID(poolName string) string {
- return fmt.Sprintf("mount/pool/%s", poolName)
-}
-
func getImageCreateLockID(poolName string, fingerprint string) string {
return fmt.Sprintf("create/image/%s/%s", poolName, fingerprint)
}
From e8bd3985b4ca8faa97b63096b2116ef05db20c26 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 14:02:57 +0000
Subject: [PATCH 07/17] lxd/storage/pools/utils: Comment on storagePoolDBCreate
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage_pools_utils.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage_pools_utils.go b/lxd/storage_pools_utils.go
index 92fa2fbb09..500ac48b20 100644
--- a/lxd/storage_pools_utils.go
+++ b/lxd/storage_pools_utils.go
@@ -179,6 +179,7 @@ func profilesUsingPoolGetNames(db *db.Cluster, project string, poolName string)
return usedBy, nil
}
+// storagePoolDBCreate creates a storage pool DB entry and returns the created Pool ID.
func storagePoolDBCreate(s *state.State, poolName, poolDescription string, driver string, config map[string]string) (int64, error) {
// Check that the storage pool does not already exist.
_, err := s.Cluster.StoragePoolGetID(poolName)
From 619516d2a7f01a68455c0842f33ee275d020ca9c Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 15:13:30 +0000
Subject: [PATCH 08/17] lxd/api/internal: Removes legacy storage pool loading
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 104 ++++++++++++--------------------------------
1 file changed, 29 insertions(+), 75 deletions(-)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 5073c5ec8e..63ed0eff58 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -26,7 +26,6 @@ import (
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/response"
storagePools "github.com/lxc/lxd/lxd/storage"
- storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/api"
log "github.com/lxc/lxd/shared/log15"
@@ -462,78 +461,41 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}
- // Update snapshot names to include container name (if needed)
+ // Update snapshot names to include container name (if needed).
for i, snap := range backup.Snapshots {
if !strings.Contains(snap.Name, "/") {
backup.Snapshots[i].Name = fmt.Sprintf("%s/%s", backup.Container.Name, snap.Name)
}
}
- // Try to retrieve the storage pool the container supposedly lives on.
- var poolErr error
- poolID, pool, poolErr := d.cluster.StoragePoolGet(containerPoolName)
- if poolErr != nil {
- if poolErr != db.ErrNoSuchObject {
- return response.SmartError(poolErr)
- }
- }
-
if backup.Pool == nil {
// We don't know what kind of storage type the pool is.
return response.BadRequest(fmt.Errorf(`No storage pool struct in the backup file found. The storage pool needs to be recovered manually`))
}
- if poolErr == db.ErrNoSuchObject {
+ // Try to retrieve the storage pool the container supposedly lives on.
+ pool, err := storagePools.GetPoolByName(d.State(), containerPoolName)
+ if err == db.ErrNoSuchObject {
// Create the storage pool db entry if it doesn't exist.
- _, err := storagePoolDBCreate(d.State(), containerPoolName, "",
- backup.Pool.Driver, backup.Pool.Config)
+ _, err = storagePoolDBCreate(d.State(), containerPoolName, "", backup.Pool.Driver, backup.Pool.Config)
if err != nil {
- err = errors.Wrap(err, "Create storage pool database entry")
- return response.SmartError(err)
+ return response.SmartError(errors.Wrap(err, "Create storage pool database entry"))
}
- poolID, err = d.cluster.StoragePoolGetID(containerPoolName)
+ pool, err = storagePools.GetPoolByName(d.State(), containerPoolName)
if err != nil {
- return response.SmartError(err)
- }
- } else {
- if backup.Pool.Name != containerPoolName {
- return response.BadRequest(fmt.Errorf(`The storage pool %q the instance was detected on does not match the storage pool %q specified in the backup file`, containerPoolName, backup.Pool.Name))
- }
-
- if backup.Pool.Driver != pool.Driver {
- return response.BadRequest(fmt.Errorf(`The storage pool's %q driver %q conflicts with the driver %q recorded in the instance's backup file`, containerPoolName, pool.Driver, backup.Pool.Driver))
+ return response.SmartError(errors.Wrap(err, "Load storage pool database entry"))
}
+ } else if err != nil {
+ return response.SmartError(errors.Wrap(err, "Find storage pool database entry"))
}
- var poolName string
- _, err = storagePools.GetPoolByName(d.State(), backup.Pool.Name)
- if err != storageDrivers.ErrUnknownDriver && err != db.ErrNoSuchObject {
- if err != nil {
- return response.InternalError(err)
- }
-
- // FIXME: In the new world, we don't expose the on-disk pool
- // name, instead we need to change the per-driver logic below to using
- // clean storage functions.
- poolName = backup.Pool.Name
- } else {
- initPool, err := storagePoolInit(d.State(), backup.Pool.Name)
- if err != nil {
- err = errors.Wrap(err, "Initialize storage")
- return response.InternalError(err)
- }
-
- ourMount, err := initPool.StoragePoolMount()
- if err != nil {
- return response.InternalError(err)
- }
- if ourMount {
- defer initPool.StoragePoolUmount()
- }
+ if backup.Pool.Name != containerPoolName {
+ return response.BadRequest(fmt.Errorf(`The storage pool %q the instance was detected on does not match the storage pool %q specified in the backup file`, containerPoolName, backup.Pool.Name))
+ }
- // retrieve on-disk pool name
- _, _, poolName = initPool.GetContainerPoolInfo()
+ if backup.Pool.Driver != pool.Driver().Info().Name {
+ return response.BadRequest(fmt.Errorf(`The storage pool's %q driver %q conflicts with the driver %q recorded in the instance's backup file`, containerPoolName, pool.Driver().Info().Name, backup.Pool.Driver))
}
existingSnapshots := []*api.InstanceSnapshot{}
@@ -544,7 +506,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
if len(backup.Snapshots) > 0 {
switch backup.Pool.Driver {
case "btrfs":
- snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, poolName, req.Name)
+ snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, pool.Name(), req.Name)
snapshotsDir, err := os.Open(snapshotsDirPath)
if err != nil {
return response.InternalError(err)
@@ -556,7 +518,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
snapshotsDir.Close()
case "dir":
- snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, poolName, req.Name)
+ snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, pool.Name(), req.Name)
snapshotsDir, err := os.Open(snapshotsDirPath)
if err != nil {
return response.InternalError(err)
@@ -664,19 +626,18 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
switch backup.Pool.Driver {
case "btrfs":
snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = btrfsSnapshotDeleteInternal(projectName, poolName, snapName)
+ err = btrfsSnapshotDeleteInternal(projectName, pool.Name(), snapName)
case "dir":
snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = dirSnapshotDeleteInternal(projectName, poolName, snapName)
+ err = dirSnapshotDeleteInternal(projectName, pool.Name(), snapName)
case "lvm":
onDiskPoolName := backup.Pool.Config["lvm.vg_name"]
if onDiskPoolName == "" {
- onDiskPoolName = poolName
+ onDiskPoolName = pool.Name()
}
snapName := fmt.Sprintf("%s/%s", req.Name, od)
snapPath := storagePools.InstancePath(instancetype.Container, projectName, snapName, true)
- err = lvmContainerDeleteInternal(projectName, poolName, req.Name,
- true, onDiskPoolName, snapPath)
+ err = lvmContainerDeleteInternal(projectName, pool.Name(), req.Name, true, onDiskPoolName, snapPath)
case "ceph":
clusterName := "ceph"
if backup.Pool.Config["ceph.cluster_name"] != "" {
@@ -690,17 +651,14 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
onDiskPoolName := backup.Pool.Config["ceph.osd.pool_name"]
snapName := fmt.Sprintf("snapshot_%s", od)
- ret := cephContainerSnapshotDelete(clusterName,
- onDiskPoolName, project.Prefix(projectName, req.Name),
- storagePoolVolumeTypeNameContainer, snapName, userName)
+ ret := cephContainerSnapshotDelete(clusterName, onDiskPoolName, project.Prefix(projectName, req.Name), storagePoolVolumeTypeNameContainer, snapName, userName)
if ret < 0 {
err = fmt.Errorf(`Failed to delete snapshot`)
}
case "zfs":
onDiskPoolName := backup.Pool.Config["zfs.pool_name"]
snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = zfsSnapshotDeleteInternal(projectName, poolName, snapName,
- onDiskPoolName)
+ err = zfsSnapshotDeleteInternal(projectName, pool.Name(), snapName, onDiskPoolName)
}
if err != nil {
logger.Warnf(`Failed to delete snapshot`)
@@ -728,9 +686,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
case "lvm":
ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
ctLvmName := lvmNameToLVName(fmt.Sprintf("%s/%s", project.Prefix(projectName, ctName), csName))
- ctLvName := lvmLVName(poolName,
- storagePoolVolumeAPIEndpointContainers,
- ctLvmName)
+ ctLvName := lvmLVName(pool.Name(), storagePoolVolumeAPIEndpointContainers, ctLvmName)
exists, err := lvmLVExists(ctLvName)
if err != nil {
return response.InternalError(err)
@@ -772,9 +728,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
snapshotName := fmt.Sprintf("snapshot-%s", csName)
- exists := zfsFilesystemEntityExists(poolName,
- fmt.Sprintf("containers/%s@%s", project.Prefix(projectName, ctName),
- snapshotName))
+ exists := zfsFilesystemEntityExists(pool.Name(), fmt.Sprintf("containers/%s@%s", project.Prefix(projectName, ctName), snapshotName))
if !exists {
if req.Force {
continue
@@ -787,7 +741,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
// Check if a storage volume entry for the container already exists.
- _, volume, ctVolErr := d.cluster.StoragePoolNodeVolumeGetTypeByProject(projectName, req.Name, storagePoolVolumeTypeContainer, poolID)
+ _, volume, ctVolErr := d.cluster.StoragePoolNodeVolumeGetTypeByProject(projectName, req.Name, storagePoolVolumeTypeContainer, pool.ID())
if ctVolErr != nil {
if ctVolErr != db.ErrNoSuchObject {
return response.SmartError(ctVolErr)
@@ -826,7 +780,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
// Remove the storage volume db entry for the container since force was specified.
- err := d.cluster.StoragePoolVolumeDelete(projectName, req.Name, storagePoolVolumeTypeContainer, poolID)
+ err := d.cluster.StoragePoolVolumeDelete(projectName, req.Name, storagePoolVolumeTypeContainer, pool.ID())
if err != nil {
return response.SmartError(err)
}
@@ -928,7 +882,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
// Check if a storage volume entry for the snapshot already exists.
- _, _, csVolErr := d.cluster.StoragePoolNodeVolumeGetTypeByProject(projectName, snap.Name, storagePoolVolumeTypeContainer, poolID)
+ _, _, csVolErr := d.cluster.StoragePoolNodeVolumeGetTypeByProject(projectName, snap.Name, storagePoolVolumeTypeContainer, pool.ID())
if csVolErr != nil {
if csVolErr != db.ErrNoSuchObject {
return response.SmartError(csVolErr)
@@ -948,7 +902,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
if csVolErr == nil {
- err := d.cluster.StoragePoolVolumeDelete(projectName, snap.Name, storagePoolVolumeTypeContainer, poolID)
+ err := d.cluster.StoragePoolVolumeDelete(projectName, snap.Name, storagePoolVolumeTypeContainer, pool.ID())
if err != nil {
return response.SmartError(err)
}
From c201549f3ece5fab7c0c3f58a8a71127cf99ab60 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 15:13:55 +0000
Subject: [PATCH 09/17] lxd/api/internal: Consistent comment style
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 19 +++++++++----------
1 file changed, 9 insertions(+), 10 deletions(-)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 63ed0eff58..8ab0e8c4a4 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -442,8 +442,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.BadRequest(fmt.Errorf(`The instance %q does not seem to exist on any storage pool`, req.Name))
}
- // User needs to make sure that we can access the directory where
- // backup.yaml lives.
+ // User needs to make sure that we can access the directory where backup.yaml lives.
containerMntPoint := containerMntPoints[0]
isEmpty, err := shared.PathIsEmpty(containerMntPoint)
if err != nil {
@@ -540,7 +539,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
snaps := strings.Fields(msg)
prefix := fmt.Sprintf("containers_%s-", project.Prefix(projectName, req.Name))
for _, v := range snaps {
- // ignore zombies
+ // Ignore zombies.
if strings.HasPrefix(v, prefix) {
onDiskSnapshots = append(onDiskSnapshots,
v[len(prefix):])
@@ -568,7 +567,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
for _, v := range snaps {
- // ignore zombies
+ // Ignore zombies.
if strings.HasPrefix(v, "snapshot_") {
onDiskSnapshots = append(onDiskSnapshots,
v[len("snapshot_"):])
@@ -583,7 +582,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
for _, v := range snaps {
- // ignore zombies
+ // Ignore zombies.
if strings.HasPrefix(v, "snapshot-") {
onDiskSnapshots = append(onDiskSnapshots,
v[len("snapshot-"):])
@@ -600,7 +599,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
}
- // delete snapshots that do not exist in backup.yaml
+ // Delete snapshots that do not exist in backup.yaml.
od := ""
for _, od = range onDiskSnapshots {
inBackupFile := false
@@ -794,13 +793,13 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
}
- // Prepare root disk entry if needed
+ // Prepare root disk entry if needed.
rootDev := map[string]string{}
rootDev["type"] = "disk"
rootDev["path"] = "/"
rootDev["pool"] = containerPoolName
- // Mark the filesystem as going through an import
+ // Mark the filesystem as going through an import.
importingFilePath := storagePools.InstanceImportingFilePath(instancetype.Container, containerPoolName, projectName, req.Name)
fd, err := os.Create(importingFilePath)
if err != nil {
@@ -811,7 +810,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
baseImage := backup.Container.Config["volatile.base_image"]
- // Add root device if missing
+ // Add root device if missing.
root, _, _ := shared.GetRootDiskDevice(backup.Container.Devices)
if root == "" {
if backup.Container.Devices == nil {
@@ -915,7 +914,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}
- // Add root device if missing
+ // Add root device if missing.
root, _, _ := shared.GetRootDiskDevice(snap.Devices)
if root == "" {
if snap.Devices == nil {
From 8fbf840f609f2b27813d3f317c7a5191134b8670 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 16:54:47 +0000
Subject: [PATCH 10/17] lxd/api/internal: Stops using backup pkg name as
variable
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 102 ++++++++++++++++++++++----------------------
1 file changed, 51 insertions(+), 51 deletions(-)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 8ab0e8c4a4..d1d9ecc6f4 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -455,19 +455,19 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
// Read in the backup.yaml file.
backupYamlPath := filepath.Join(containerMntPoint, "backup.yaml")
- backup, err := backup.ParseInstanceConfigYamlFile(backupYamlPath)
+ backupConf, err := backup.ParseInstanceConfigYamlFile(backupYamlPath)
if err != nil {
return response.SmartError(err)
}
// Update snapshot names to include container name (if needed).
- for i, snap := range backup.Snapshots {
+ for i, snap := range backupConf.Snapshots {
if !strings.Contains(snap.Name, "/") {
- backup.Snapshots[i].Name = fmt.Sprintf("%s/%s", backup.Container.Name, snap.Name)
+ backupConf.Snapshots[i].Name = fmt.Sprintf("%s/%s", backupConf.Container.Name, snap.Name)
}
}
- if backup.Pool == nil {
+ if backupConf.Pool == nil {
// We don't know what kind of storage type the pool is.
return response.BadRequest(fmt.Errorf(`No storage pool struct in the backup file found. The storage pool needs to be recovered manually`))
}
@@ -476,7 +476,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
pool, err := storagePools.GetPoolByName(d.State(), containerPoolName)
if err == db.ErrNoSuchObject {
// Create the storage pool db entry if it doesn't exist.
- _, err = storagePoolDBCreate(d.State(), containerPoolName, "", backup.Pool.Driver, backup.Pool.Config)
+ _, err = storagePoolDBCreate(d.State(), containerPoolName, "", backupConf.Pool.Driver, backupConf.Pool.Config)
if err != nil {
return response.SmartError(errors.Wrap(err, "Create storage pool database entry"))
}
@@ -489,12 +489,12 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.SmartError(errors.Wrap(err, "Find storage pool database entry"))
}
- if backup.Pool.Name != containerPoolName {
- return response.BadRequest(fmt.Errorf(`The storage pool %q the instance was detected on does not match the storage pool %q specified in the backup file`, containerPoolName, backup.Pool.Name))
+ if backupConf.Pool.Name != containerPoolName {
+ return response.BadRequest(fmt.Errorf(`The storage pool %q the instance was detected on does not match the storage pool %q specified in the backup file`, containerPoolName, backupConf.Pool.Name))
}
- if backup.Pool.Driver != pool.Driver().Info().Name {
- return response.BadRequest(fmt.Errorf(`The storage pool's %q driver %q conflicts with the driver %q recorded in the instance's backup file`, containerPoolName, pool.Driver().Info().Name, backup.Pool.Driver))
+ if backupConf.Pool.Driver != pool.Driver().Info().Name {
+ return response.BadRequest(fmt.Errorf(`The storage pool's %q driver %q conflicts with the driver %q recorded in the instance's backup file`, containerPoolName, pool.Driver().Info().Name, backupConf.Pool.Driver))
}
existingSnapshots := []*api.InstanceSnapshot{}
@@ -591,7 +591,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
}
- if len(backup.Snapshots) != len(onDiskSnapshots) {
+ if len(backupConf.Snapshots) != len(onDiskSnapshots) {
if !req.Force {
msg := `There are either snapshots that don't exist on disk anymore or snapshots that are not recorded in the "backup.yaml" file. Pass "force" to remove them`
logger.Errorf(msg)
@@ -603,7 +603,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
od := ""
for _, od = range onDiskSnapshots {
inBackupFile := false
- for _, ib := range backup.Snapshots {
+ for _, ib := range backupConf.Snapshots {
_, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(ib.Name)
if od == snapOnlyName {
inBackupFile = true
@@ -622,7 +622,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
var err error
- switch backup.Pool.Driver {
+ switch backupConf.Pool.Driver {
case "btrfs":
snapName := fmt.Sprintf("%s/%s", req.Name, od)
err = btrfsSnapshotDeleteInternal(projectName, pool.Name(), snapName)
@@ -630,7 +630,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
snapName := fmt.Sprintf("%s/%s", req.Name, od)
err = dirSnapshotDeleteInternal(projectName, pool.Name(), snapName)
case "lvm":
- onDiskPoolName := backup.Pool.Config["lvm.vg_name"]
+ onDiskPoolName := backupConf.Pool.Config["lvm.vg_name"]
if onDiskPoolName == "" {
onDiskPoolName = pool.Name()
}
@@ -639,23 +639,23 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
err = lvmContainerDeleteInternal(projectName, pool.Name(), req.Name, true, onDiskPoolName, snapPath)
case "ceph":
clusterName := "ceph"
- if backup.Pool.Config["ceph.cluster_name"] != "" {
- clusterName = backup.Pool.Config["ceph.cluster_name"]
+ if backupConf.Pool.Config["ceph.cluster_name"] != "" {
+ clusterName = backupConf.Pool.Config["ceph.cluster_name"]
}
userName := "admin"
- if backup.Pool.Config["ceph.user.name"] != "" {
- userName = backup.Pool.Config["ceph.user.name"]
+ if backupConf.Pool.Config["ceph.user.name"] != "" {
+ userName = backupConf.Pool.Config["ceph.user.name"]
}
- onDiskPoolName := backup.Pool.Config["ceph.osd.pool_name"]
+ onDiskPoolName := backupConf.Pool.Config["ceph.osd.pool_name"]
snapName := fmt.Sprintf("snapshot_%s", od)
ret := cephContainerSnapshotDelete(clusterName, onDiskPoolName, project.Prefix(projectName, req.Name), storagePoolVolumeTypeNameContainer, snapName, userName)
if ret < 0 {
err = fmt.Errorf(`Failed to delete snapshot`)
}
case "zfs":
- onDiskPoolName := backup.Pool.Config["zfs.pool_name"]
+ onDiskPoolName := backupConf.Pool.Config["zfs.pool_name"]
snapName := fmt.Sprintf("%s/%s", req.Name, od)
err = zfsSnapshotDeleteInternal(projectName, pool.Name(), snapName, onDiskPoolName)
}
@@ -664,10 +664,10 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
}
- for _, snap := range backup.Snapshots {
- switch backup.Pool.Driver {
+ for _, snap := range backupConf.Snapshots {
+ switch backupConf.Pool.Driver {
case "btrfs":
- snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backup.Pool.Name, snap.Name)
+ snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name, snap.Name)
if !shared.PathExists(snpMntPt) || !btrfsIsSubVolume(snpMntPt) {
if req.Force {
continue
@@ -675,7 +675,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.BadRequest(needForce)
}
case "dir":
- snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backup.Pool.Name, snap.Name)
+ snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name, snap.Name)
if !shared.PathExists(snpMntPt) {
if req.Force {
continue
@@ -699,16 +699,16 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
case "ceph":
clusterName := "ceph"
- if backup.Pool.Config["ceph.cluster_name"] != "" {
- clusterName = backup.Pool.Config["ceph.cluster_name"]
+ if backupConf.Pool.Config["ceph.cluster_name"] != "" {
+ clusterName = backupConf.Pool.Config["ceph.cluster_name"]
}
userName := "admin"
- if backup.Pool.Config["ceph.user.name"] != "" {
- userName = backup.Pool.Config["ceph.user.name"]
+ if backupConf.Pool.Config["ceph.user.name"] != "" {
+ userName = backupConf.Pool.Config["ceph.user.name"]
}
- onDiskPoolName := backup.Pool.Config["ceph.osd.pool_name"]
+ onDiskPoolName := backupConf.Pool.Config["ceph.osd.pool_name"]
ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
ctName = project.Prefix(projectName, ctName)
snapshotName := fmt.Sprintf("snapshot_%s", csName)
@@ -765,17 +765,17 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.BadRequest(fmt.Errorf(`Entry for instance %q already exists in the database. Set "force" to overwrite`, req.Name))
}
- if backup.Volume == nil {
+ if backupConf.Volume == nil {
return response.BadRequest(fmt.Errorf(`No storage volume struct in the backup file found. The storage volume needs to be recovered manually`))
}
if ctVolErr == nil {
- if volume.Name != backup.Volume.Name {
+ if volume.Name != backupConf.Volume.Name {
return response.BadRequest(fmt.Errorf(`The name %q of the storage volume is not identical to the instance's name "%s"`, volume.Name, req.Name))
}
- if volume.Type != backup.Volume.Type {
- return response.BadRequest(fmt.Errorf(`The type %q of the storage volume is not identical to the instance's type %q`, volume.Type, backup.Volume.Type))
+ if volume.Type != backupConf.Volume.Type {
+ return response.BadRequest(fmt.Errorf(`The type %q of the storage volume is not identical to the instance's type %q`, volume.Type, backupConf.Volume.Type))
}
// Remove the storage volume db entry for the container since force was specified.
@@ -808,28 +808,28 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
fd.Close()
defer os.Remove(fd.Name())
- baseImage := backup.Container.Config["volatile.base_image"]
+ baseImage := backupConf.Container.Config["volatile.base_image"]
// Add root device if missing.
- root, _, _ := shared.GetRootDiskDevice(backup.Container.Devices)
+ root, _, _ := shared.GetRootDiskDevice(backupConf.Container.Devices)
if root == "" {
- if backup.Container.Devices == nil {
- backup.Container.Devices = map[string]map[string]string{}
+ if backupConf.Container.Devices == nil {
+ backupConf.Container.Devices = map[string]map[string]string{}
}
rootDevName := "root"
for i := 0; i < 100; i++ {
- if backup.Container.Devices[rootDevName] == nil {
+ if backupConf.Container.Devices[rootDevName] == nil {
break
}
rootDevName = fmt.Sprintf("root%d", i)
continue
}
- backup.Container.Devices[rootDevName] = rootDev
+ backupConf.Container.Devices[rootDevName] = rootDev
}
- arch, err := osarch.ArchitectureId(backup.Container.Architecture)
+ arch, err := osarch.ArchitectureId(backupConf.Container.Architecture)
if err != nil {
return response.SmartError(err)
}
@@ -837,16 +837,16 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
Project: projectName,
Architecture: arch,
BaseImage: baseImage,
- Config: backup.Container.Config,
- CreationDate: backup.Container.CreatedAt,
+ Config: backupConf.Container.Config,
+ CreationDate: backupConf.Container.CreatedAt,
Type: instancetype.Container,
- Description: backup.Container.Description,
- Devices: deviceConfig.NewDevices(backup.Container.Devices),
- Ephemeral: backup.Container.Ephemeral,
- LastUsedDate: backup.Container.LastUsedAt,
- Name: backup.Container.Name,
- Profiles: backup.Container.Profiles,
- Stateful: backup.Container.Stateful,
+ Description: backupConf.Container.Description,
+ Devices: deviceConfig.NewDevices(backupConf.Container.Devices),
+ Ephemeral: backupConf.Container.Ephemeral,
+ LastUsedDate: backupConf.Container.LastUsedAt,
+ Name: backupConf.Container.Name,
+ Profiles: backupConf.Container.Profiles,
+ Stateful: backupConf.Container.Stateful,
})
if err != nil {
err = errors.Wrap(err, "Create container")
@@ -855,7 +855,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
containerPath := storagePools.InstancePath(instancetype.Container, projectName, req.Name, false)
isPrivileged := false
- if backup.Container.Config["security.privileged"] == "" {
+ if backupConf.Container.Config["security.privileged"] == "" {
isPrivileged = true
}
err = storagePools.CreateContainerMountpoint(containerMntPoint, containerPath,
@@ -953,11 +953,11 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
// Recreate missing mountpoints and symlinks.
- snapshotMountPoint := storagePools.GetSnapshotMountPoint(projectName, backup.Pool.Name,
+ snapshotMountPoint := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name,
snap.Name)
sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
sourceName = project.Prefix(projectName, sourceName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", backup.Pool.Name, "containers-snapshots", sourceName)
+ snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", backupConf.Pool.Name, "containers-snapshots", sourceName)
snapshotMntPointSymlink := shared.VarPath("snapshots", sourceName)
err = storagePools.CreateSnapshotMountpoint(snapshotMountPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
if err != nil {
From 17d7b05aceb38188744df0d191c252bf162e0de2 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Wed, 19 Feb 2020 16:55:06 +0000
Subject: [PATCH 11/17] lxd/api/internal: Switches internalImport to use
pool.CheckInstanceBackupFileSnapshots
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 251 ++------------------------------------------
1 file changed, 8 insertions(+), 243 deletions(-)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index d1d9ecc6f4..8eedb78c74 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -27,7 +27,6 @@ import (
"github.com/lxc/lxd/lxd/response"
storagePools "github.com/lxc/lxd/lxd/storage"
"github.com/lxc/lxd/shared"
- "github.com/lxc/lxd/shared/api"
log "github.com/lxc/lxd/shared/log15"
"github.com/lxc/lxd/shared/logger"
"github.com/lxc/lxd/shared/osarch"
@@ -497,246 +496,14 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.BadRequest(fmt.Errorf(`The storage pool's %q driver %q conflicts with the driver %q recorded in the instance's backup file`, containerPoolName, pool.Driver().Info().Name, backupConf.Pool.Driver))
}
- existingSnapshots := []*api.InstanceSnapshot{}
- needForce := fmt.Errorf(`The snapshot does not exist on disk. Pass "force" to discard non-existing snapshots`)
-
- // Retrieve all snapshots that exist on disk.
- onDiskSnapshots := []string{}
- if len(backup.Snapshots) > 0 {
- switch backup.Pool.Driver {
- case "btrfs":
- snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, pool.Name(), req.Name)
- snapshotsDir, err := os.Open(snapshotsDirPath)
- if err != nil {
- return response.InternalError(err)
- }
- onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
- if err != nil {
- snapshotsDir.Close()
- return response.InternalError(err)
- }
- snapshotsDir.Close()
- case "dir":
- snapshotsDirPath := storagePools.GetSnapshotMountPoint(projectName, pool.Name(), req.Name)
- snapshotsDir, err := os.Open(snapshotsDirPath)
- if err != nil {
- return response.InternalError(err)
- }
- onDiskSnapshots, err = snapshotsDir.Readdirnames(-1)
- if err != nil {
- snapshotsDir.Close()
- return response.InternalError(err)
- }
- snapshotsDir.Close()
- case "lvm":
- onDiskPoolName := backup.Pool.Config["lvm.vg_name"]
- msg, err := shared.RunCommand("lvs", "-o", "lv_name",
- onDiskPoolName, "--noheadings")
- if err != nil {
- return response.InternalError(err)
- }
-
- snaps := strings.Fields(msg)
- prefix := fmt.Sprintf("containers_%s-", project.Prefix(projectName, req.Name))
- for _, v := range snaps {
- // Ignore zombies.
- if strings.HasPrefix(v, prefix) {
- onDiskSnapshots = append(onDiskSnapshots,
- v[len(prefix):])
- }
- }
- case "ceph":
- clusterName := "ceph"
- if backup.Pool.Config["ceph.cluster_name"] != "" {
- clusterName = backup.Pool.Config["ceph.cluster_name"]
- }
-
- userName := "admin"
- if backup.Pool.Config["ceph.user.name"] != "" {
- userName = backup.Pool.Config["ceph.user.name"]
- }
-
- onDiskPoolName := backup.Pool.Config["ceph.osd.pool_name"]
- snaps, err := cephRBDVolumeListSnapshots(clusterName,
- onDiskPoolName, project.Prefix(projectName, req.Name),
- storagePoolVolumeTypeNameContainer, userName)
- if err != nil {
- if err != db.ErrNoSuchObject {
- return response.InternalError(err)
- }
- }
-
- for _, v := range snaps {
- // Ignore zombies.
- if strings.HasPrefix(v, "snapshot_") {
- onDiskSnapshots = append(onDiskSnapshots,
- v[len("snapshot_"):])
- }
- }
- case "zfs":
- onDiskPoolName := backup.Pool.Config["zfs.pool_name"]
- snaps, err := zfsPoolListSnapshots(onDiskPoolName,
- fmt.Sprintf("containers/%s", project.Prefix(projectName, req.Name)))
- if err != nil {
- return response.InternalError(err)
- }
-
- for _, v := range snaps {
- // Ignore zombies.
- if strings.HasPrefix(v, "snapshot-") {
- onDiskSnapshots = append(onDiskSnapshots,
- v[len("snapshot-"):])
- }
- }
- }
- }
-
- if len(backupConf.Snapshots) != len(onDiskSnapshots) {
- if !req.Force {
- msg := `There are either snapshots that don't exist on disk anymore or snapshots that are not recorded in the "backup.yaml" file. Pass "force" to remove them`
- logger.Errorf(msg)
- return response.InternalError(fmt.Errorf(msg))
- }
- }
-
- // Delete snapshots that do not exist in backup.yaml.
- od := ""
- for _, od = range onDiskSnapshots {
- inBackupFile := false
- for _, ib := range backupConf.Snapshots {
- _, snapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(ib.Name)
- if od == snapOnlyName {
- inBackupFile = true
- break
- }
- }
-
- if inBackupFile {
- continue
- }
-
- if !req.Force {
- msg := `There are snapshots that are not recorded in the "backup.yaml" file. Pass "force" to remove them`
- logger.Errorf(msg)
- return response.InternalError(fmt.Errorf(msg))
- }
-
- var err error
- switch backupConf.Pool.Driver {
- case "btrfs":
- snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = btrfsSnapshotDeleteInternal(projectName, pool.Name(), snapName)
- case "dir":
- snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = dirSnapshotDeleteInternal(projectName, pool.Name(), snapName)
- case "lvm":
- onDiskPoolName := backupConf.Pool.Config["lvm.vg_name"]
- if onDiskPoolName == "" {
- onDiskPoolName = pool.Name()
- }
- snapName := fmt.Sprintf("%s/%s", req.Name, od)
- snapPath := storagePools.InstancePath(instancetype.Container, projectName, snapName, true)
- err = lvmContainerDeleteInternal(projectName, pool.Name(), req.Name, true, onDiskPoolName, snapPath)
- case "ceph":
- clusterName := "ceph"
- if backupConf.Pool.Config["ceph.cluster_name"] != "" {
- clusterName = backupConf.Pool.Config["ceph.cluster_name"]
- }
-
- userName := "admin"
- if backupConf.Pool.Config["ceph.user.name"] != "" {
- userName = backupConf.Pool.Config["ceph.user.name"]
- }
-
- onDiskPoolName := backupConf.Pool.Config["ceph.osd.pool_name"]
- snapName := fmt.Sprintf("snapshot_%s", od)
- ret := cephContainerSnapshotDelete(clusterName, onDiskPoolName, project.Prefix(projectName, req.Name), storagePoolVolumeTypeNameContainer, snapName, userName)
- if ret < 0 {
- err = fmt.Errorf(`Failed to delete snapshot`)
- }
- case "zfs":
- onDiskPoolName := backupConf.Pool.Config["zfs.pool_name"]
- snapName := fmt.Sprintf("%s/%s", req.Name, od)
- err = zfsSnapshotDeleteInternal(projectName, pool.Name(), snapName, onDiskPoolName)
- }
- if err != nil {
- logger.Warnf(`Failed to delete snapshot`)
- }
- }
-
- for _, snap := range backupConf.Snapshots {
- switch backupConf.Pool.Driver {
- case "btrfs":
- snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name, snap.Name)
- if !shared.PathExists(snpMntPt) || !btrfsIsSubVolume(snpMntPt) {
- if req.Force {
- continue
- }
- return response.BadRequest(needForce)
- }
- case "dir":
- snpMntPt := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name, snap.Name)
- if !shared.PathExists(snpMntPt) {
- if req.Force {
- continue
- }
- return response.BadRequest(needForce)
- }
- case "lvm":
- ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
- ctLvmName := lvmNameToLVName(fmt.Sprintf("%s/%s", project.Prefix(projectName, ctName), csName))
- ctLvName := lvmLVName(pool.Name(), storagePoolVolumeAPIEndpointContainers, ctLvmName)
- exists, err := lvmLVExists(ctLvName)
- if err != nil {
- return response.InternalError(err)
- }
-
- if !exists {
- if req.Force {
- continue
- }
- return response.BadRequest(needForce)
- }
- case "ceph":
- clusterName := "ceph"
- if backupConf.Pool.Config["ceph.cluster_name"] != "" {
- clusterName = backupConf.Pool.Config["ceph.cluster_name"]
- }
-
- userName := "admin"
- if backupConf.Pool.Config["ceph.user.name"] != "" {
- userName = backupConf.Pool.Config["ceph.user.name"]
- }
-
- onDiskPoolName := backupConf.Pool.Config["ceph.osd.pool_name"]
- ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
- ctName = project.Prefix(projectName, ctName)
- snapshotName := fmt.Sprintf("snapshot_%s", csName)
-
- exists := cephRBDSnapshotExists(clusterName,
- onDiskPoolName, ctName,
- storagePoolVolumeTypeNameContainer,
- snapshotName, userName)
- if !exists {
- if req.Force {
- continue
- }
- return response.BadRequest(needForce)
- }
- case "zfs":
- ctName, csName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
- snapshotName := fmt.Sprintf("snapshot-%s", csName)
-
- exists := zfsFilesystemEntityExists(pool.Name(), fmt.Sprintf("containers/%s@%s", project.Prefix(projectName, ctName), snapshotName))
- if !exists {
- if req.Force {
- continue
- }
- return response.BadRequest(needForce)
- }
+ // Check snapshots are consistent, and if not, if req.Force is true, then delete snapshots that do not exist in backup.yaml.
+ existingSnapshots, err := pool.CheckInstanceBackupFileSnapshots(backupConf, projectName, req.Force, nil)
+ if err != nil {
+ if errors.Cause(err) == storagePools.ErrBackupSnapshotsMismatch {
+ return response.InternalError(fmt.Errorf(`%s. Set "force" to discard non-existing snapshots`, err))
}
- existingSnapshots = append(existingSnapshots, snap)
+ return response.InternalError(errors.Wrap(err, "Checking snapshots"))
}
// Check if a storage volume entry for the container already exists.
@@ -858,8 +625,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
if backupConf.Container.Config["security.privileged"] == "" {
isPrivileged = true
}
- err = storagePools.CreateContainerMountpoint(containerMntPoint, containerPath,
- isPrivileged)
+ err = storagePools.CreateContainerMountpoint(containerMntPoint, containerPath, isPrivileged)
if err != nil {
return response.InternalError(err)
}
@@ -953,8 +719,7 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
}
// Recreate missing mountpoints and symlinks.
- snapshotMountPoint := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name,
- snap.Name)
+ snapshotMountPoint := storagePools.GetSnapshotMountPoint(projectName, backupConf.Pool.Name, snap.Name)
sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snap.Name)
sourceName = project.Prefix(projectName, sourceName)
snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", backupConf.Pool.Name, "containers-snapshots", sourceName)
From 4736f6a99fc4ebd3df566e6aa26d88e4d2be5fc3 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 12:55:36 +0000
Subject: [PATCH 12/17] lxd/storage/pool/interface: Adds
CheckInstanceBackupFileSnapshots
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/pool_interface.go | 1 +
1 file changed, 1 insertion(+)
diff --git a/lxd/storage/pool_interface.go b/lxd/storage/pool_interface.go
index 40a995ed2b..1808b6a037 100644
--- a/lxd/storage/pool_interface.go
+++ b/lxd/storage/pool_interface.go
@@ -37,6 +37,7 @@ type Pool interface {
DeleteInstance(inst instance.Instance, op *operations.Operation) error
UpdateInstance(inst instance.Instance, newDesc string, newConfig map[string]string, op *operations.Operation) error
UpdateInstanceBackupFile(inst instance.Instance, op *operations.Operation) error
+ CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error)
MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error
RefreshInstance(inst instance.Instance, src instance.Instance, srcSnapshots []instance.Instance, op *operations.Operation) error
From de5b89ef652d7c971fa1ed32a19f52eb769d1b21 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 12:55:59 +0000
Subject: [PATCH 13/17] lxd/storage/errors: Adds ErrBackupSnapshotsMismatch
error
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/errors.go | 3 +++
1 file changed, 3 insertions(+)
diff --git a/lxd/storage/errors.go b/lxd/storage/errors.go
index 64df5f1579..b328c28f07 100644
--- a/lxd/storage/errors.go
+++ b/lxd/storage/errors.go
@@ -12,3 +12,6 @@ var ErrNotImplemented = fmt.Errorf("Not implemented")
// ErrRunningQuotaResizeNotSupported is the "Running quota resize not supported" error.
var ErrRunningQuotaResizeNotSupported = fmt.Errorf("Running quota resize not supported")
+
+// ErrBackupSnapshotsMismatch is the "Backup snapshots mismatch" error.
+var ErrBackupSnapshotsMismatch = fmt.Errorf("Backup snapshots mismatch")
From 8f1111b6cc775269be9f9bfb540f73e95b7a7b43 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 12:56:15 +0000
Subject: [PATCH 14/17] lxd/storage/backend/mock: Adds
CheckInstanceBackupFileSnapshots
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/backend_mock.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lxd/storage/backend_mock.go b/lxd/storage/backend_mock.go
index 90f15951ff..c54563ae4f 100644
--- a/lxd/storage/backend_mock.go
+++ b/lxd/storage/backend_mock.go
@@ -100,6 +100,10 @@ func (b *mockBackend) UpdateInstanceBackupFile(inst instance.Instance, op *opera
return nil
}
+func (b *mockBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) {
+ return nil, nil
+}
+
func (b *mockBackend) MigrateInstance(inst instance.Instance, conn io.ReadWriteCloser, args *migration.VolumeSourceArgs, op *operations.Operation) error {
return nil
}
From 24e12a6e80ee0d846159bc4be4e60742d7768663 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 12:56:35 +0000
Subject: [PATCH 15/17] lxd/storage/backend/lxd: Adds
CheckInstanceBackupFileSnapshots implementation
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/storage/backend_lxd.go | 97 ++++++++++++++++++++++++++++++++++++++
1 file changed, 97 insertions(+)
diff --git a/lxd/storage/backend_lxd.go b/lxd/storage/backend_lxd.go
index b42036f22d..4925ad1119 100644
--- a/lxd/storage/backend_lxd.go
+++ b/lxd/storage/backend_lxd.go
@@ -2935,3 +2935,100 @@ func (b *lxdBackend) UpdateInstanceBackupFile(inst instance.Instance, op *operat
return err
}
+
+// CheckInstanceBackupFileSnapshots compares the snapshots on the storage device to those defined in the backup
+// config supplied and returns an error if they do not match (if deleteMissing argument is false).
+// If deleteMissing argument is true, then any snapshots that exist on the storage device but not in the backup
+// config are removed from the storage device, and any snapshots that exist in the backup config but do not exist
+// on the storage device are ignored. The remaining set of snapshots that exist on both the storage device and the
+// backup config are returned. They set can be used to re-create the snapshot database entries when importing.
+func (b *lxdBackend) CheckInstanceBackupFileSnapshots(backupConf *backup.InstanceConfig, projectName string, deleteMissing bool, op *operations.Operation) ([]*api.InstanceSnapshot, error) {
+ logger := logging.AddContext(b.logger, log.Ctx{"project": projectName, "instance": backupConf.Container.Name, "deleteMissing": deleteMissing})
+ logger.Debug("CheckInstanceBackupFileSnapshots started")
+ defer logger.Debug("CheckInstanceBackupFileSnapshots finished")
+
+ instType, err := instancetype.New(string(backupConf.Container.Type))
+ if err != nil {
+ return nil, err
+ }
+
+ volType, err := InstanceTypeToVolumeType(instType)
+ if err != nil {
+ return nil, err
+ }
+
+ // Get the volume name on storage.
+ volStorageName := project.Prefix(projectName, backupConf.Container.Name)
+
+ // We don't need to use the volume's config for mounting so set to nil.
+ vol := b.newVolume(volType, drivers.ContentTypeFS, volStorageName, nil)
+
+ // Get a list of snapshots that exist on storage device.
+ driverSnapshots, err := vol.Snapshots(op)
+ if err != nil {
+ return nil, err
+ }
+
+ if len(backupConf.Snapshots) != len(driverSnapshots) {
+ if !deleteMissing {
+ return nil, errors.Wrap(ErrBackupSnapshotsMismatch, "Snapshot count in backup config and storage device are different")
+ }
+ }
+
+ // Delete snapshots that do not exist in backup config.
+ for _, driverSnapVol := range driverSnapshots {
+ _, driverSnapOnly, _ := shared.InstanceGetParentAndSnapshotName(driverSnapVol.Name())
+
+ inBackupFile := false
+ for _, backupFileSnap := range backupConf.Snapshots {
+ _, backupFileSnapOnly, _ := shared.InstanceGetParentAndSnapshotName(backupFileSnap.Name)
+ if driverSnapOnly == backupFileSnapOnly {
+ inBackupFile = true
+ break
+ }
+ }
+
+ if inBackupFile {
+ continue
+ }
+
+ if !deleteMissing {
+ return nil, errors.Wrapf(ErrBackupSnapshotsMismatch, "Snapshot %q exists on storage device but not in backup config", driverSnapOnly)
+ }
+
+ err = b.driver.DeleteVolumeSnapshot(driverSnapVol, op)
+ if err != nil {
+ return nil, errors.Wrapf(err, "Failed to delete snapshot %q", driverSnapOnly)
+ }
+
+ logger.Debug("Deleted snapshot as not present in backup config", log.Ctx{"snapshot": driverSnapOnly})
+ }
+
+ // Check the snapshots in backup config exist on storage device.
+ existingSnapshots := []*api.InstanceSnapshot{}
+ for _, backupFileSnap := range backupConf.Snapshots {
+ _, backupFileSnapOnly, _ := shared.InstanceGetParentAndSnapshotName(backupFileSnap.Name)
+
+ onStorageDevice := false
+ for _, driverSnapVol := range driverSnapshots {
+ _, driverSnapOnly, _ := shared.InstanceGetParentAndSnapshotName(driverSnapVol.Name())
+ if driverSnapOnly == backupFileSnapOnly {
+ onStorageDevice = true
+ break
+ }
+ }
+
+ if !onStorageDevice {
+ if !deleteMissing {
+ return nil, errors.Wrapf(ErrBackupSnapshotsMismatch, "Snapshot %q exists in backup config but not on storage device", backupFileSnapOnly)
+ }
+
+ logger.Debug("Skipped snapshot in backup config as not present on storage device", log.Ctx{"snapshot": backupFileSnap})
+ continue // Skip snapshots missing on storage device.
+ }
+
+ existingSnapshots = append(existingSnapshots, backupFileSnap)
+ }
+
+ return existingSnapshots, nil
+}
From c19daf5c464b9618796d21cba6ee1edbad04d868 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 13:34:29 +0000
Subject: [PATCH 16/17] lxd/patches/utils: Removes unused functions
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/patches_utils.go | 308 -------------------------------------------
1 file changed, 308 deletions(-)
diff --git a/lxd/patches_utils.go b/lxd/patches_utils.go
index 959ebb87f4..c58f4b2007 100644
--- a/lxd/patches_utils.go
+++ b/lxd/patches_utils.go
@@ -11,49 +11,14 @@ import (
"strings"
"syscall"
- "github.com/pborman/uuid"
- "github.com/pkg/errors"
"golang.org/x/sys/unix"
"github.com/lxc/lxd/lxd/project"
"github.com/lxc/lxd/lxd/state"
- driver "github.com/lxc/lxd/lxd/storage"
- storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
"github.com/lxc/lxd/shared"
"github.com/lxc/lxd/shared/units"
)
-// For 'dir' storage backend.
-func dirSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
- snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
- if shared.PathExists(snapshotContainerMntPoint) {
- err := os.RemoveAll(snapshotContainerMntPoint)
- if err != nil {
- return err
- }
- }
-
- sourceContainerName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
- snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
- empty, _ := shared.PathIsEmpty(snapshotContainerPath)
- if empty == true {
- err := os.Remove(snapshotContainerPath)
- if err != nil {
- return err
- }
-
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- return nil
-}
-
// For 'btrfs' storage backend.
func btrfsSubVolumeCreate(subvol string) error {
parentDestPath := filepath.Dir(subvol)
@@ -76,35 +41,6 @@ func btrfsSubVolumeCreate(subvol string) error {
return nil
}
-func btrfsSnapshotDeleteInternal(projectName, poolName string, snapshotName string) error {
- snapshotSubvolumeName := driver.GetSnapshotMountPoint(projectName, poolName, snapshotName)
- // Also delete any leftover .ro snapshot.
- roSnapshotSubvolumeName := fmt.Sprintf("%s.ro", snapshotSubvolumeName)
- names := []string{snapshotSubvolumeName, roSnapshotSubvolumeName}
- for _, name := range names {
- if shared.PathExists(name) && btrfsIsSubVolume(name) {
- err := btrfsSubVolumesDelete(name)
- if err != nil {
- return err
- }
- }
- }
-
- sourceSnapshotMntPoint := shared.VarPath("snapshots", project.Prefix(projectName, snapshotName))
- os.Remove(sourceSnapshotMntPoint)
- os.Remove(snapshotSubvolumeName)
-
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(snapshotName)
- snapshotSubvolumePath := driver.GetSnapshotMountPoint(projectName, poolName, sourceName)
- os.Remove(snapshotSubvolumePath)
- if !shared.PathExists(snapshotSubvolumePath) {
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
- os.Remove(snapshotMntPointSymlink)
- }
-
- return nil
-}
-
func btrfsSubVolumeQGroup(subvol string) (string, error) {
output, err := shared.RunCommand(
"btrfs",
@@ -288,187 +224,6 @@ func btrfsSubVolumesGet(path string) ([]string, error) {
return result, nil
}
-// For 'zfs' storage backend.
-func zfsPoolListSnapshots(pool string, path string) ([]string, error) {
- path = strings.TrimRight(path, "/")
- fullPath := pool
- if path != "" {
- fullPath = fmt.Sprintf("%s/%s", pool, path)
- }
-
- output, err := shared.RunCommand("zfs", "list", "-t", "snapshot", "-o", "name", "-H", "-d", "1", "-s", "creation", "-r", fullPath)
- if err != nil {
- return []string{}, errors.Wrap(err, "Failed to list ZFS snapshots")
- }
-
- children := []string{}
- for _, entry := range strings.Split(output, "\n") {
- if entry == "" {
- continue
- }
-
- if entry == fullPath {
- continue
- }
-
- children = append(children, strings.SplitN(entry, "@", 2)[1])
- }
-
- return children, nil
-}
-
-func zfsSnapshotDeleteInternal(projectName, poolName string, ctName string, onDiskPoolName string) error {
- sourceContainerName, sourceContainerSnapOnlyName, _ := shared.InstanceGetParentAndSnapshotName(ctName)
- snapName := fmt.Sprintf("snapshot-%s", sourceContainerSnapOnlyName)
-
- if zfsFilesystemEntityExists(onDiskPoolName,
- fmt.Sprintf("containers/%s@%s",
- project.Prefix(projectName, sourceContainerName), snapName)) {
- removable, err := zfsPoolVolumeSnapshotRemovable(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName)
- if err != nil {
- return err
- }
-
- if removable {
- err = zfsPoolVolumeSnapshotDestroy(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName)
- } else {
- err = zfsPoolVolumeSnapshotRename(onDiskPoolName,
- fmt.Sprintf("containers/%s",
- project.Prefix(projectName, sourceContainerName)),
- snapName,
- fmt.Sprintf("copy-%s", uuid.NewRandom().String()))
- }
- if err != nil {
- return err
- }
- }
-
- // Delete the snapshot on its storage pool:
- // ${POOL}/snapshots/<snapshot_name>
- snapshotContainerMntPoint := driver.GetSnapshotMountPoint(projectName, poolName, ctName)
- if shared.PathExists(snapshotContainerMntPoint) {
- err := os.RemoveAll(snapshotContainerMntPoint)
- if err != nil {
- return err
- }
- }
-
- // Check if we can remove the snapshot symlink:
- // ${LXD_DIR}/snapshots/<container_name> to ${POOL}/snapshots/<container_name>
- // by checking if the directory is empty.
- snapshotContainerPath := driver.GetSnapshotMountPoint(projectName, poolName, sourceContainerName)
- empty, _ := shared.PathIsEmpty(snapshotContainerPath)
- if empty == true {
- // Remove the snapshot directory for the container:
- // ${POOL}/snapshots/<source_container_name>
- err := os.Remove(snapshotContainerPath)
- if err != nil {
- return err
- }
-
- snapshotSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceContainerName))
- if shared.PathExists(snapshotSymlink) {
- err := os.Remove(snapshotSymlink)
- if err != nil {
- return err
- }
- }
- }
-
- // Legacy
- snapPath := shared.VarPath(fmt.Sprintf("snapshots/%s/%s.zfs", project.Prefix(projectName, sourceContainerName), sourceContainerSnapOnlyName))
- if shared.PathExists(snapPath) {
- err := os.Remove(snapPath)
- if err != nil {
- return err
- }
- }
-
- // Legacy
- parent := shared.VarPath(fmt.Sprintf("snapshots/%s", project.Prefix(projectName, sourceContainerName)))
- if ok, _ := shared.PathIsEmpty(parent); ok {
- err := os.Remove(parent)
- if err != nil {
- return err
- }
- }
-
- return nil
-}
-
-func zfsFilesystemEntityExists(pool string, path string) bool {
- vdev := pool
- if path != "" {
- vdev = fmt.Sprintf("%s/%s", pool, path)
- }
-
- output, err := shared.RunCommand("zfs", "get", "-H", "-o", "name", "type", vdev)
- if err != nil {
- return false
- }
-
- detectedName := strings.TrimSpace(output)
- return detectedName == vdev
-}
-
-func zfsPoolVolumeSnapshotRemovable(pool string, path string, name string) (bool, error) {
- var snap string
- if name == "" {
- snap = path
- } else {
- snap = fmt.Sprintf("%s@%s", path, name)
- }
-
- clones, err := zfsFilesystemEntityPropertyGet(pool, snap, "clones")
- if err != nil {
- return false, err
- }
-
- if clones == "-" || clones == "" {
- return true, nil
- }
-
- return false, nil
-}
-
-func zfsFilesystemEntityPropertyGet(pool string, path string, key string) (string, error) {
- entity := pool
- if path != "" {
- entity = fmt.Sprintf("%s/%s", pool, path)
- }
-
- output, err := shared.RunCommand("zfs", "get", "-H", "-p", "-o", "value", key, entity)
- if err != nil {
- return "", errors.Wrap(err, "Failed to get ZFS config")
- }
-
- return strings.TrimRight(output, "\n"), nil
-}
-
-func zfsPoolVolumeSnapshotDestroy(pool, path string, name string) error {
- _, err := shared.RunCommand("zfs", "destroy", "-r", fmt.Sprintf("%s/%s@%s", pool, path, name))
- if err != nil {
- return errors.Wrap(err, "Failed to destroy ZFS snapshot")
- }
-
- return nil
-}
-
-func zfsPoolVolumeSnapshotRename(pool string, path string, oldName string, newName string) error {
- _, err := shared.RunCommand("zfs", "rename", "-r", fmt.Sprintf("%s/%s@%s", pool, path, oldName), fmt.Sprintf("%s/%s@%s", pool, path, newName))
- if err != nil {
- return errors.Wrap(err, "Failed to rename ZFS snapshot")
- }
-
- return nil
-}
-
// For 'lvm' storage backend.
func lvmLVRename(vgName string, oldName string, newName string) error {
_, err := shared.TryRunCommand("lvrename", vgName, oldName, newName)
@@ -540,66 +295,3 @@ func lvmGetLVSize(lvPath string) (string, error) {
return detectedSize, nil
}
-
-func lvmLVName(lvmPool string, volumeType string, lvmVolume string) string {
- if volumeType == "" {
- return fmt.Sprintf("%s/%s", lvmPool, lvmVolume)
- }
-
- return fmt.Sprintf("%s/%s_%s", lvmPool, volumeType, lvmVolume)
-}
-
-func lvmContainerDeleteInternal(projectName, poolName string, ctName string, isSnapshot bool, vgName string, ctPath string) error {
- containerMntPoint := ""
- containerLvmName := lvmNameToLVName(ctName)
- if isSnapshot {
- containerMntPoint = driver.GetSnapshotMountPoint(projectName, poolName, ctName)
- } else {
- containerMntPoint = driver.GetContainerMountPoint(projectName, poolName, ctName)
- }
-
- if shared.IsMountPoint(containerMntPoint) {
- err := storageDrivers.TryUnmount(containerMntPoint, 0)
- if err != nil {
- return fmt.Errorf(`Failed to unmount container path `+
- `"%s": %s`, containerMntPoint, err)
- }
- }
-
- containerLvmDevPath := lvmDevPath(projectName, vgName,
- storagePoolVolumeAPIEndpointContainers, containerLvmName)
-
- lvExists, _ := lvmLVExists(containerLvmDevPath)
- if lvExists {
- err := lvmRemoveLV(projectName, vgName, storagePoolVolumeAPIEndpointContainers, containerLvmName)
- if err != nil {
- return err
- }
- }
-
- var err error
- if isSnapshot {
- sourceName, _, _ := shared.InstanceGetParentAndSnapshotName(ctName)
- snapshotMntPointSymlinkTarget := shared.VarPath("storage-pools", poolName, "containers-snapshots", project.Prefix(projectName, sourceName))
- snapshotMntPointSymlink := shared.VarPath("snapshots", project.Prefix(projectName, sourceName))
- err = deleteSnapshotMountpoint(containerMntPoint, snapshotMntPointSymlinkTarget, snapshotMntPointSymlink)
- } else {
- err = deleteContainerMountpoint(containerMntPoint, ctPath, "lvm")
- }
- if err != nil {
- return err
- }
-
- return nil
-}
-
-func lvmRemoveLV(project, vgName string, volumeType string, lvName string) error {
- lvmVolumePath := lvmDevPath(project, vgName, volumeType, lvName)
-
- _, err := shared.TryRunCommand("lvremove", "-f", lvmVolumePath)
- if err != nil {
- return fmt.Errorf("Could not remove LV named %s: %v", lvName, err)
- }
-
- return nil
-}
From 323b0c56a3c1599eab5139ede35759ba815c18f7 Mon Sep 17 00:00:00 2001
From: Thomas Parrott <thomas.parrott at canonical.com>
Date: Fri, 21 Feb 2020 13:42:48 +0000
Subject: [PATCH 17/17] lxd/api/internal: Adds sanity check for instance name
in internalImport
Signed-off-by: Thomas Parrott <thomas.parrott at canonical.com>
---
lxd/api_internal.go | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/lxd/api_internal.go b/lxd/api_internal.go
index 8eedb78c74..9d1dab7c1a 100644
--- a/lxd/api_internal.go
+++ b/lxd/api_internal.go
@@ -459,6 +459,10 @@ func internalImport(d *Daemon, r *http.Request) response.Response {
return response.SmartError(err)
}
+ if req.Name != backupConf.Container.Name {
+ return response.InternalError(fmt.Errorf("Instance name in request %q doesn't match instance name in backup config %q", req.Name, backupConf.Container.Name))
+ }
+
// Update snapshot names to include container name (if needed).
for i, snap := range backupConf.Snapshots {
if !strings.Contains(snap.Name, "/") {
More information about the lxc-devel
mailing list