14
"github.com/gorilla/websocket"
15
"github.com/pborman/uuid"
17
"github.com/lxc/lxd/shared"
19
log "gopkg.in/inconshreveable/log15.v2"
22
type storageBtrfs struct {
28
func (s *storageBtrfs) Init(config map[string]interface{}) (storage, error) {
29
s.sType = storageTypeBtrfs
30
s.sTypeName = storageTypeToString(s.sType)
31
if err := s.initShared(); err != nil {
35
out, err := exec.LookPath("btrfs")
36
if err != nil || len(out) == 0 {
37
return s, fmt.Errorf("The 'btrfs' tool isn't available")
40
output, err := exec.Command("btrfs", "version").CombinedOutput()
42
return s, fmt.Errorf("The 'btrfs' tool isn't working properly")
45
count, err := fmt.Sscanf(strings.SplitN(string(output), " ", 2)[1], "v%s\n", &s.sTypeVersion)
46
if err != nil || count != 1 {
47
return s, fmt.Errorf("The 'btrfs' tool isn't working properly")
53
func (s *storageBtrfs) ContainerCreate(container container) error {
54
cPath := container.Path()
56
// MkdirAll the pardir of the BTRFS Subvolume.
57
if err := os.MkdirAll(filepath.Dir(cPath), 0755); err != nil {
61
// Create the BTRFS Subvolume
62
err := s.subvolCreate(cPath)
67
if container.IsPrivileged() {
68
if err := os.Chmod(cPath, 0700); err != nil {
73
return container.TemplateApply("create")
76
func (s *storageBtrfs) ContainerCreateFromImage(
77
container container, imageFingerprint string) error {
79
imageSubvol := fmt.Sprintf(
81
shared.VarPath("images", imageFingerprint))
83
// Create the btrfs subvol of the image first if it doesn exists.
84
if !shared.PathExists(imageSubvol) {
85
if err := s.ImageCreate(imageFingerprint); err != nil {
90
// Now make a snapshot of the image subvol
91
err := s.subvolsSnapshot(imageSubvol, container.Path(), false)
96
if !container.IsPrivileged() {
97
if err = s.shiftRootfs(container); err != nil {
98
s.ContainerDelete(container)
102
if err := os.Chmod(container.Path(), 0700); err != nil {
107
return container.TemplateApply("create")
110
func (s *storageBtrfs) ContainerCanRestore(container container, sourceContainer container) error {
114
func (s *storageBtrfs) ContainerDelete(container container) error {
115
cPath := container.Path()
117
// First remove the subvol (if it was one).
118
if s.isSubvolume(cPath) {
119
if err := s.subvolsDelete(cPath); err != nil {
124
// Then the directory (if it still exists).
125
err := os.RemoveAll(cPath)
127
s.log.Error("ContainerDelete: failed", log.Ctx{"cPath": cPath, "err": err})
128
return fmt.Errorf("Error cleaning up %s: %s", cPath, err)
134
func (s *storageBtrfs) ContainerCopy(container container, sourceContainer container) error {
135
subvol := sourceContainer.Path()
136
dpath := container.Path()
138
if s.isSubvolume(subvol) {
139
// Snapshot the sourcecontainer
140
err := s.subvolsSnapshot(subvol, dpath, false)
145
// Create the BTRFS Container.
146
if err := s.ContainerCreate(container); err != nil {
151
* Copy by using rsync
153
output, err := storageRsyncCopy(
154
sourceContainer.Path(),
157
s.ContainerDelete(container)
159
s.log.Error("ContainerCopy: rsync failed", log.Ctx{"output": string(output)})
160
return fmt.Errorf("rsync failed: %s", string(output))
164
if err := s.setUnprivUserAcl(sourceContainer, dpath); err != nil {
165
s.ContainerDelete(container)
169
return container.TemplateApply("copy")
172
func (s *storageBtrfs) ContainerStart(container container) error {
176
func (s *storageBtrfs) ContainerStop(container container) error {
180
func (s *storageBtrfs) ContainerRename(container container, newName string) error {
181
oldName := container.Name()
182
oldPath := container.Path()
183
newPath := containerPath(newName, false)
185
if err := os.Rename(oldPath, newPath); err != nil {
189
if shared.PathExists(shared.VarPath(fmt.Sprintf("snapshots/%s", oldName))) {
190
err := os.Rename(shared.VarPath(fmt.Sprintf("snapshots/%s", oldName)), shared.VarPath(fmt.Sprintf("snapshots/%s", newName)))
196
// TODO: No TemplateApply here?
200
func (s *storageBtrfs) ContainerRestore(
201
container container, sourceContainer container) error {
203
targetSubVol := container.Path()
204
sourceSubVol := sourceContainer.Path()
205
sourceBackupPath := container.Path() + ".back"
207
// Create a backup of the container
208
err := os.Rename(container.Path(), sourceBackupPath)
214
if s.isSubvolume(sourceSubVol) {
215
// Restore using btrfs snapshots.
216
err := s.subvolsSnapshot(sourceSubVol, targetSubVol, false)
221
// Restore using rsync but create a btrfs subvol.
222
if err := s.subvolCreate(targetSubVol); err == nil {
223
output, err := storageRsyncCopy(
229
"ContainerRestore: rsync failed",
230
log.Ctx{"output": string(output)})
239
// Now allow unprivileged users to access its data.
240
if err := s.setUnprivUserAcl(sourceContainer, targetSubVol); err != nil {
245
// Restore original container
246
s.ContainerDelete(container)
247
os.Rename(sourceBackupPath, container.Path())
249
// Remove the backup, we made
250
if s.isSubvolume(sourceBackupPath) {
251
return s.subvolDelete(sourceBackupPath)
253
os.RemoveAll(sourceBackupPath)
259
func (s *storageBtrfs) ContainerSetQuota(container container, size int64) error {
260
subvol := container.Path()
262
_, err := s.subvolQGroup(subvol)
267
output, err := exec.Command(
271
"-e", fmt.Sprintf("%d", size),
272
subvol).CombinedOutput()
275
return fmt.Errorf("Failed to set btrfs quota: %s", output)
281
func (s *storageBtrfs) ContainerGetUsage(container container) (int64, error) {
282
return s.subvolQGroupUsage(container.Path())
285
func (s *storageBtrfs) ContainerSnapshotCreate(
286
snapshotContainer container, sourceContainer container) error {
288
subvol := sourceContainer.Path()
289
dpath := snapshotContainer.Path()
291
if s.isSubvolume(subvol) {
292
// Create a readonly snapshot of the source.
293
err := s.subvolsSnapshot(subvol, dpath, true)
295
s.ContainerSnapshotDelete(snapshotContainer)
300
* Copy by using rsync
302
output, err := storageRsyncCopy(
306
s.ContainerSnapshotDelete(snapshotContainer)
309
"ContainerSnapshotCreate: rsync failed",
310
log.Ctx{"output": string(output)})
311
return fmt.Errorf("rsync failed: %s", string(output))
317
func (s *storageBtrfs) ContainerSnapshotDelete(
318
snapshotContainer container) error {
320
err := s.ContainerDelete(snapshotContainer)
322
return fmt.Errorf("Error deleting snapshot %s: %s", snapshotContainer.Name(), err)
325
oldPathParent := filepath.Dir(snapshotContainer.Path())
326
if ok, _ := shared.PathIsEmpty(oldPathParent); ok {
327
os.Remove(oldPathParent)
332
func (s *storageBtrfs) ContainerSnapshotStart(container container) error {
333
if shared.PathExists(container.Path() + ".ro") {
334
return fmt.Errorf("The snapshot is already mounted read-write.")
337
err := os.Rename(container.Path(), container.Path()+".ro")
342
err = s.subvolsSnapshot(container.Path()+".ro", container.Path(), false)
350
func (s *storageBtrfs) ContainerSnapshotStop(container container) error {
351
if !shared.PathExists(container.Path() + ".ro") {
352
return fmt.Errorf("The snapshot isn't currently mounted read-write.")
355
err := s.subvolsDelete(container.Path())
360
err = os.Rename(container.Path()+".ro", container.Path())
368
// ContainerSnapshotRename renames a snapshot of a container.
369
func (s *storageBtrfs) ContainerSnapshotRename(
370
snapshotContainer container, newName string) error {
372
oldPath := snapshotContainer.Path()
373
newPath := containerPath(newName, true)
375
// Create the new parent.
376
if !shared.PathExists(filepath.Dir(newPath)) {
377
os.MkdirAll(filepath.Dir(newPath), 0700)
380
// Now rename the snapshot.
381
if !s.isSubvolume(oldPath) {
382
if err := os.Rename(oldPath, newPath); err != nil {
386
if err := s.subvolsSnapshot(oldPath, newPath, true); err != nil {
389
if err := s.subvolsDelete(oldPath); err != nil {
394
// Remove the old parent (on container rename) if its empty.
395
if ok, _ := shared.PathIsEmpty(filepath.Dir(oldPath)); ok {
396
os.Remove(filepath.Dir(oldPath))
402
func (s *storageBtrfs) ContainerSnapshotCreateEmpty(snapshotContainer container) error {
403
dpath := snapshotContainer.Path()
404
return s.subvolCreate(dpath)
407
func (s *storageBtrfs) ImageCreate(fingerprint string) error {
408
imagePath := shared.VarPath("images", fingerprint)
409
subvol := fmt.Sprintf("%s.btrfs", imagePath)
411
if err := s.subvolCreate(subvol); err != nil {
415
if err := untarImage(imagePath, subvol); err != nil {
422
func (s *storageBtrfs) ImageDelete(fingerprint string) error {
423
imagePath := shared.VarPath("images", fingerprint)
424
subvol := fmt.Sprintf("%s.btrfs", imagePath)
426
return s.subvolDelete(subvol)
429
func (s *storageBtrfs) subvolCreate(subvol string) error {
430
parentDestPath := filepath.Dir(subvol)
431
if !shared.PathExists(parentDestPath) {
432
if err := os.MkdirAll(parentDestPath, 0700); err != nil {
437
output, err := exec.Command(
441
subvol).CombinedOutput()
444
"subvolume create failed",
445
log.Ctx{"subvol": subvol, "output": string(output)},
448
"btrfs subvolume create failed, subvol=%s, output%s",
457
func (s *storageBtrfs) subvolQGroup(subvol string) (string, error) {
458
output, err := exec.Command(
464
"-f").CombinedOutput()
467
return "", fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
471
for _, line := range strings.Split(string(output), "\n") {
472
if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
476
fields := strings.Fields(line)
477
if len(fields) != 4 {
485
return "", fmt.Errorf("Unable to find quota group")
491
func (s *storageBtrfs) subvolQGroupUsage(subvol string) (int64, error) {
492
output, err := exec.Command(
498
"-f").CombinedOutput()
501
return -1, fmt.Errorf("btrfs quotas not supported. Try enabling them with 'btrfs quota enable'.")
504
for _, line := range strings.Split(string(output), "\n") {
505
if line == "" || strings.HasPrefix(line, "qgroupid") || strings.HasPrefix(line, "---") {
509
fields := strings.Fields(line)
510
if len(fields) != 4 {
514
usage, err := strconv.ParseInt(fields[2], 10, 64)
522
return -1, fmt.Errorf("Unable to find current qgroup usage")
525
func (s *storageBtrfs) subvolDelete(subvol string) error {
526
// Attempt (but don't fail on) to delete any qgroup on the subvolume
527
qgroup, err := s.subvolQGroup(subvol)
529
output, err := exec.Command(
534
subvol).CombinedOutput()
538
"subvolume qgroup delete failed",
539
log.Ctx{"subvol": subvol, "output": string(output)},
544
// Delete the subvolume itself
545
output, err := exec.Command(
554
"subvolume delete failed",
555
log.Ctx{"subvol": subvol, "output": string(output)},
561
// subvolsDelete is the recursive variant on subvolDelete,
562
// it first deletes subvolumes of the subvolume and then the
564
func (s *storageBtrfs) subvolsDelete(subvol string) error {
565
// Delete subsubvols.
566
subsubvols, err := s.getSubVolumes(subvol)
571
for _, subsubvol := range subsubvols {
573
"Deleting subsubvol",
576
"subsubvol": subsubvol})
578
if err := s.subvolDelete(path.Join(subvol, subsubvol)); err != nil {
583
// Delete the subvol itself
584
if err := s.subvolDelete(subvol); err != nil {
592
* subvolSnapshot creates a snapshot of "source" to "dest"
593
* the result will be readonly if "readonly" is True.
595
func (s *storageBtrfs) subvolSnapshot(
596
source string, dest string, readonly bool) error {
598
parentDestPath := filepath.Dir(dest)
599
if !shared.PathExists(parentDestPath) {
600
if err := os.MkdirAll(parentDestPath, 0700); err != nil {
605
if shared.PathExists(dest) {
606
if err := os.Remove(dest); err != nil {
614
output, err = exec.Command(
620
dest).CombinedOutput()
622
output, err = exec.Command(
627
dest).CombinedOutput()
631
"subvolume snapshot failed",
632
log.Ctx{"source": source, "dest": dest, "output": string(output)},
635
"subvolume snapshot failed, source=%s, dest=%s, output=%s",
645
func (s *storageBtrfs) subvolsSnapshot(
646
source string, dest string, readonly bool) error {
648
// Get a list of subvolumes of the root
649
subsubvols, err := s.getSubVolumes(source)
654
if len(subsubvols) > 0 && readonly {
655
// A root with subvolumes can never be readonly,
656
// also don't make subvolumes readonly.
660
"Subvolumes detected, ignoring ro flag",
661
log.Ctx{"source": source, "dest": dest})
664
// First snapshot the root
665
if err := s.subvolSnapshot(source, dest, readonly); err != nil {
669
// Now snapshot all subvolumes of the root.
670
for _, subsubvol := range subsubvols {
671
if err := s.subvolSnapshot(
672
path.Join(source, subsubvol),
673
path.Join(dest, subsubvol),
674
readonly); err != nil {
684
* isSubvolume returns true if the given Path is a btrfs subvolume
687
func (s *storageBtrfs) isSubvolume(subvolPath string) bool {
689
// subvolume show is restricted to real root, use a workaround
691
fs := syscall.Statfs_t{}
692
err := syscall.Statfs(subvolPath, &fs)
697
if fs.Type != filesystemSuperMagicBtrfs {
701
parentFs := syscall.Statfs_t{}
702
err = syscall.Statfs(path.Dir(subvolPath), &parentFs)
707
if fs.Fsid == parentFs.Fsid {
714
output, err := exec.Command(
718
subvolPath).CombinedOutput()
719
if err != nil || strings.HasPrefix(string(output), "ERROR: ") {
726
// getSubVolumes returns a list of relative subvolume paths of "path".
727
func (s *storageBtrfs) getSubVolumes(path string) ([]string, error) {
731
if !strings.HasSuffix(path, "/") {
735
// Unprivileged users can't get to fs internals
736
filepath.Walk(path, func(fpath string, fi os.FileInfo, err error) error {
737
if strings.TrimRight(fpath, "/") == strings.TrimRight(path, "/") {
749
if s.isSubvolume(fpath) {
750
result = append(result, strings.TrimPrefix(fpath, path))
758
out, err := exec.Command(
762
path).CombinedOutput()
764
return result, fmt.Errorf(
765
"Unable to get btrfs rootid, path='%s', err='%s'",
769
rootid := strings.TrimRight(string(out), "\n")
771
out, err = exec.Command(
775
rootid, path).CombinedOutput()
777
return result, fmt.Errorf(
778
"Unable to resolve btrfs rootid, path='%s', err='%s'",
782
basePath := strings.TrimRight(string(out), "\n")
784
out, err = exec.Command(
789
path).CombinedOutput()
791
return result, fmt.Errorf(
792
"Unable to list subvolumes, path='%s', err='%s'",
797
lines := strings.Split(string(out), "\n")
798
for _, line := range lines {
803
cols := strings.Fields(line)
804
result = append(result, cols[8][len(basePath):])
810
type btrfsMigrationSourceDriver struct {
812
snapshots []container
813
btrfsSnapshotNames []string
815
runningSnapName string
816
stoppedSnapName string
819
func (s *btrfsMigrationSourceDriver) Snapshots() []container {
823
func (s *btrfsMigrationSourceDriver) send(conn *websocket.Conn, btrfsPath string, btrfsParent string) error {
824
args := []string{"send", btrfsPath}
825
if btrfsParent != "" {
826
args = append(args, "-p", btrfsParent)
829
cmd := exec.Command("btrfs", args...)
831
stdout, err := cmd.StdoutPipe()
836
stderr, err := cmd.StderrPipe()
841
if err := cmd.Start(); err != nil {
845
<-shared.WebsocketSendStream(conn, stdout)
847
output, err := ioutil.ReadAll(stderr)
849
shared.Log.Error("problem reading btrfs send stderr", "err", err)
854
shared.Log.Error("problem with btrfs send", "output", string(output))
859
func (s *btrfsMigrationSourceDriver) SendWhileRunning(conn *websocket.Conn) error {
860
if s.container.IsSnapshot() {
861
tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
862
err := os.MkdirAll(tmpPath, 0700)
867
btrfsPath := fmt.Sprintf("%s/.root", tmpPath)
868
if err := s.btrfs.subvolSnapshot(s.container.Path(), btrfsPath, true); err != nil {
872
defer s.btrfs.subvolDelete(btrfsPath)
874
return s.send(conn, btrfsPath, "")
877
for i, snap := range s.snapshots {
880
prev = s.snapshots[i-1].Path()
883
if err := s.send(conn, snap.Path(), prev); err != nil {
888
/* We can't send running fses, so let's snapshot the fs and send
891
tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
892
err := os.MkdirAll(tmpPath, 0700)
897
s.runningSnapName = fmt.Sprintf("%s/.root", tmpPath)
898
if err := s.btrfs.subvolSnapshot(s.container.Path(), s.runningSnapName, true); err != nil {
903
if len(s.btrfsSnapshotNames) > 0 {
904
btrfsParent = s.btrfsSnapshotNames[len(s.btrfsSnapshotNames)-1]
907
return s.send(conn, s.runningSnapName, btrfsParent)
910
func (s *btrfsMigrationSourceDriver) SendAfterCheckpoint(conn *websocket.Conn) error {
911
tmpPath := containerPath(fmt.Sprintf("%s/.migration-send-%s", s.container.Name(), uuid.NewRandom().String()), true)
912
err := os.MkdirAll(tmpPath, 0700)
917
s.stoppedSnapName = fmt.Sprintf("%s/.root", tmpPath)
918
if err := s.btrfs.subvolSnapshot(s.container.Path(), s.stoppedSnapName, true); err != nil {
922
return s.send(conn, s.stoppedSnapName, s.runningSnapName)
925
func (s *btrfsMigrationSourceDriver) Cleanup() {
926
if s.stoppedSnapName != "" {
927
s.btrfs.subvolDelete(s.stoppedSnapName)
930
if s.runningSnapName != "" {
931
s.btrfs.subvolDelete(s.runningSnapName)
935
func (s *storageBtrfs) MigrationType() MigrationFSType {
937
return MigrationFSType_RSYNC
939
return MigrationFSType_BTRFS
943
func (s *storageBtrfs) MigrationSource(c container) (MigrationStorageSourceDriver, error) {
945
return rsyncMigrationSource(c)
948
/* List all the snapshots in order of reverse creation. The idea here
949
* is that we send the oldest to newest snapshot, hopefully saving on
950
* xfer costs. Then, after all that, we send the container itself.
952
snapshots, err := c.Snapshots()
957
driver := &btrfsMigrationSourceDriver{
959
snapshots: snapshots,
960
btrfsSnapshotNames: []string{},
964
for _, snap := range snapshots {
965
btrfsPath := snap.Path()
966
driver.btrfsSnapshotNames = append(driver.btrfsSnapshotNames, btrfsPath)
972
func (s *storageBtrfs) MigrationSink(live bool, container container, snapshots []container, conn *websocket.Conn) error {
974
return rsyncMigrationSink(live, container, snapshots, conn)
977
cName := container.Name()
979
snapshotsPath := shared.VarPath(fmt.Sprintf("snapshots/%s", cName))
980
if !shared.PathExists(snapshotsPath) {
981
err := os.MkdirAll(shared.VarPath(fmt.Sprintf("snapshots/%s", cName)), 0700)
987
btrfsRecv := func(btrfsPath string, targetPath string, isSnapshot bool) error {
988
args := []string{"receive", "-e", btrfsPath}
989
cmd := exec.Command("btrfs", args...)
991
// Remove the existing pre-created subvolume
992
err := s.subvolsDelete(targetPath)
997
stdin, err := cmd.StdinPipe()
1002
stderr, err := cmd.StderrPipe()
1007
if err := cmd.Start(); err != nil {
1011
<-shared.WebsocketRecvStream(stdin, conn)
1013
output, err := ioutil.ReadAll(stderr)
1015
shared.Debugf("problem reading btrfs receive stderr %s", err)
1020
shared.Log.Error("problem with btrfs receive", log.Ctx{"output": string(output)})
1025
cPath := containerPath(fmt.Sprintf("%s/.root", cName), true)
1027
err := s.subvolSnapshot(cPath, targetPath, false)
1029
shared.Log.Error("problem with btrfs snapshot", log.Ctx{"err": err})
1033
err = s.subvolsDelete(cPath)
1035
shared.Log.Error("problem with btrfs delete", log.Ctx{"err": err})
1043
for _, snap := range snapshots {
1044
if err := btrfsRecv(containerPath(cName, true), snap.Path(), true); err != nil {
1049
/* finally, do the real container */
1050
if err := btrfsRecv(containerPath(cName, true), container.Path(), false); err != nil {
1055
if err := btrfsRecv(containerPath(cName, true), container.Path(), false); err != nil {
1061
if ok, _ := shared.PathIsEmpty(snapshotsPath); ok {
1062
err := os.Remove(snapshotsPath)