transaction_test.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188
  1. package backup
  2. import (
  3. "errors"
  4. "os"
  5. "os/exec"
  6. "path/filepath"
  7. "strings"
  8. "testing"
  9. )
  10. func TestRestoreFailureRollsBackFiles(t *testing.T) {
  11. root := t.TempDir()
  12. current := filepath.Join(root, "debtik")
  13. network := filepath.Join(root, "network")
  14. stage := filepath.Join(root, "staged")
  15. for _, dir := range []string{current, network, filepath.Join(stage, "debtik"), filepath.Join(stage, "network")} {
  16. if err := os.MkdirAll(dir, 0700); err != nil {
  17. t.Fatal(err)
  18. }
  19. }
  20. write := func(path, content string) {
  21. t.Helper()
  22. if err := os.WriteFile(path, []byte(content), 0600); err != nil {
  23. t.Fatal(err)
  24. }
  25. }
  26. write(filepath.Join(current, "old.json"), "old config")
  27. write(filepath.Join(network, "a.cfg"), "old network")
  28. write(filepath.Join(stage, "debtik", "new.json"), "new config")
  29. write(filepath.Join(stage, "network", "a.cfg"), "new network")
  30. write(filepath.Join(stage, "network", "b.cfg"), "new file")
  31. mgr := NewManager(current, network, filepath.Join(root, "backups"), nil)
  32. count := 0
  33. err := mgr.applyStagedRestore(stage, func(step string) error {
  34. if step == "network" {
  35. count++
  36. if count == 2 {
  37. return errors.New("simulated write failure")
  38. }
  39. }
  40. return nil
  41. })
  42. if err == nil || !strings.Contains(err.Error(), "previous files restored") {
  43. t.Fatalf("unexpected error %v", err)
  44. }
  45. for path, want := range map[string]string{filepath.Join(current, "old.json"): "old config", filepath.Join(network, "a.cfg"): "old network"} {
  46. data, err := os.ReadFile(path)
  47. if err != nil || string(data) != want {
  48. t.Fatalf("%s: %q %v", path, data, err)
  49. }
  50. }
  51. for _, path := range []string{filepath.Join(current, "new.json"), filepath.Join(network, "b.cfg")} {
  52. if _, err := os.Stat(path); !os.IsNotExist(err) {
  53. t.Fatalf("unexpected remaining %s: %v", path, err)
  54. }
  55. }
  56. }
  57. func TestRestoreCrashHelper(t *testing.T) {
  58. root := os.Getenv("DEBTIK_CRASH_TEST_ROOT")
  59. if root == "" {
  60. return
  61. }
  62. point := os.Getenv("DEBTIK_CRASH_TEST_POINT")
  63. mgr := NewManager(filepath.Join(root, "debtik"), filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
  64. err := mgr.applyStagedRestore(filepath.Join(root, "stage"), func(step string) error {
  65. if step == point {
  66. os.Exit(42)
  67. }
  68. return nil
  69. })
  70. t.Fatalf("failed to reach %s: %v", point, err)
  71. }
  72. func TestRecoveryAfterProcessCrash(t *testing.T) {
  73. for _, point := range []string{"old-renamed", "debtik", "network", "committed"} {
  74. t.Run(point, func(t *testing.T) {
  75. root := t.TempDir()
  76. current := filepath.Join(root, "debtik")
  77. network := filepath.Join(root, "network")
  78. stage := filepath.Join(root, "stage")
  79. for _, dir := range []string{current, network, filepath.Join(stage, "debtik"), filepath.Join(stage, "network")} {
  80. if err := os.MkdirAll(dir, 0700); err != nil {
  81. t.Fatal(err)
  82. }
  83. }
  84. for path, content := range map[string]string{filepath.Join(current, "config.json"): "old", filepath.Join(network, "a.cfg"): "old network", filepath.Join(stage, "debtik", "config.json"): "new", filepath.Join(stage, "network", "a.cfg"): "new network"} {
  85. if err := os.WriteFile(path, []byte(content), 0600); err != nil {
  86. t.Fatal(err)
  87. }
  88. }
  89. cmd := exec.Command(os.Args[0], "-test.run=^TestRestoreCrashHelper$")
  90. cmd.Env = append(os.Environ(), "DEBTIK_CRASH_TEST_ROOT="+root, "DEBTIK_CRASH_TEST_POINT="+point)
  91. if err := cmd.Run(); err == nil {
  92. t.Fatal("expected process to terminate")
  93. } else if ex, ok := err.(*exec.ExitError); !ok || ex.ExitCode() != 42 {
  94. t.Fatalf("unexpected subprocess failure: %v", err)
  95. }
  96. mgr := NewManager(current, network, filepath.Join(root, "backups"), nil)
  97. recovered, err := mgr.RecoverInterruptedRestore()
  98. if err != nil || !recovered {
  99. t.Fatalf("recover: %v %v", recovered, err)
  100. }
  101. config := "old"
  102. netConfig := "old network"
  103. if point == "committed" {
  104. config = "new"
  105. netConfig = "new network"
  106. }
  107. for path, want := range map[string]string{filepath.Join(current, "config.json"): config, filepath.Join(network, "a.cfg"): netConfig} {
  108. data, err := os.ReadFile(path)
  109. if err != nil || string(data) != want {
  110. t.Fatalf("%s: %q %v", path, data, err)
  111. }
  112. }
  113. if again, err := mgr.RecoverInterruptedRestore(); err != nil || again {
  114. t.Fatalf("recovery not idempotent: %v %v", again, err)
  115. }
  116. })
  117. }
  118. }
  119. func TestRecoveryAfterCrashWithNoPreviousConfig(t *testing.T) {
  120. root := t.TempDir()
  121. stage := filepath.Join(root, "stage")
  122. network := filepath.Join(root, "network")
  123. if err := os.MkdirAll(filepath.Join(stage, "debtik"), 0700); err != nil {
  124. t.Fatal(err)
  125. }
  126. if err := os.MkdirAll(network, 0700); err != nil {
  127. t.Fatal(err)
  128. }
  129. if err := os.WriteFile(filepath.Join(stage, "debtik", "new.json"), []byte("new"), 0600); err != nil {
  130. t.Fatal(err)
  131. }
  132. cmd := exec.Command(os.Args[0], "-test.run=^TestRestoreCrashHelper$")
  133. cmd.Env = append(os.Environ(), "DEBTIK_CRASH_TEST_ROOT="+root, "DEBTIK_CRASH_TEST_POINT=debtik")
  134. if err := cmd.Run(); err == nil {
  135. t.Fatal("expected process to terminate")
  136. } else if ex, ok := err.(*exec.ExitError); !ok || ex.ExitCode() != 42 {
  137. t.Fatal(err)
  138. }
  139. mgr := NewManager(filepath.Join(root, "debtik"), network, filepath.Join(root, "backups"), nil)
  140. if recovered, err := mgr.RecoverInterruptedRestore(); err != nil || !recovered {
  141. t.Fatalf("recovery: %v %v", recovered, err)
  142. }
  143. if _, err := os.Stat(filepath.Join(root, "debtik")); !os.IsNotExist(err) {
  144. t.Fatalf("new directory still present: %v", err)
  145. }
  146. }
  147. func TestRecoveryRejectsJournalWithUnrelatedPaths(t *testing.T) {
  148. root := t.TempDir()
  149. mgr := NewManager(filepath.Join(root, "debtik"), filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
  150. journal := &restoreJournal{DebtikDir: mgr.debtikDir, NetworkDir: mgr.networkDir, Files: []journalFile{{Path: filepath.Join(root, "outside.cfg")}}}
  151. if err := mgr.writeJournal(journal); err != nil {
  152. t.Fatal(err)
  153. }
  154. if _, err := mgr.RecoverInterruptedRestore(); err == nil {
  155. t.Fatal("accepted unrelated file")
  156. }
  157. if _, err := os.Stat(mgr.journalPath()); err != nil {
  158. t.Fatalf("journal lost after failed recovery: %v", err)
  159. }
  160. }
  161. func TestRecoveryRejectsIncompleteJournal(t *testing.T) {
  162. root := t.TempDir()
  163. current := filepath.Join(root, "debtik")
  164. if err := os.MkdirAll(current, 0700); err != nil {
  165. t.Fatal(err)
  166. }
  167. marker := filepath.Join(current, "config")
  168. if err := os.WriteFile(marker, []byte("preserve"), 0600); err != nil {
  169. t.Fatal(err)
  170. }
  171. mgr := NewManager(current, filepath.Join(root, "network"), filepath.Join(root, "backups"), nil)
  172. if err := mgr.writeJournal(&restoreJournal{DebtikDir: current, NetworkDir: mgr.networkDir, HasDebtik: true}); err != nil {
  173. t.Fatal(err)
  174. }
  175. if _, err := mgr.RecoverInterruptedRestore(); err == nil {
  176. t.Fatal("accepted incomplete journal")
  177. }
  178. if data, err := os.ReadFile(marker); err != nil || string(data) != "preserve" {
  179. t.Fatalf("config damaged: %q %v", data, err)
  180. }
  181. }