github.com/ddev/ddev@v1.23.2-0.20240519125000-d824ffe36ff3/pkg/ddevapp/snapshot_test.go (about) 1 package ddevapp_test 2 3 import ( 4 "os" 5 "path/filepath" 6 "runtime" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/ddev/ddev/pkg/archive" 12 "github.com/ddev/ddev/pkg/ddevapp" 13 "github.com/ddev/ddev/pkg/nodeps" 14 "github.com/ddev/ddev/pkg/testcommon" 15 "github.com/ddev/ddev/pkg/util" 16 assert2 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 // TestDdevSnapshotCleanup tests creating a snapshot and deleting it. 21 func TestDdevSnapshotCleanup(t *testing.T) { 22 assert := assert2.New(t) 23 app := &ddevapp.DdevApp{} 24 site := TestSites[0] 25 switchDir := site.Chdir() 26 defer switchDir() 27 28 runTime := util.TimeTrackC("TestDdevSnapshotCleanup") 29 30 testcommon.ClearDockerEnv() 31 err := app.Init(site.Dir) 32 assert.NoError(err) 33 34 t.Cleanup(func() { 35 err = app.Stop(true, false) 36 assert.NoError(err) 37 _ = os.RemoveAll(app.GetConfigPath("db_snapshots")) 38 }) 39 40 err = app.Start() 41 assert.NoError(err) 42 43 // Make a snapshot of d7 tester test 1 44 snapshotName, err := app.Snapshot(t.Name() + "_1") 45 assert.NoError(err) 46 47 err = app.Init(site.Dir) 48 require.NoError(t, err) 49 50 err = app.Start() 51 require.NoError(t, err) 52 53 err = app.DeleteSnapshot(snapshotName) 54 assert.NoError(err) 55 56 // Snapshot data should be deleted 57 err = app.DeleteSnapshot(snapshotName) 58 assert.Error(err) 59 60 runTime() 61 } 62 63 // TestGetLatestSnapshot tests if the latest snapshot of a project is returned correctly. 64 func TestGetLatestSnapshot(t *testing.T) { 65 assert := assert2.New(t) 66 app := &ddevapp.DdevApp{} 67 site := TestSites[0] 68 origDir, _ := os.Getwd() 69 err := os.Chdir(site.Dir) 70 assert.NoError(err) 71 72 runTime := util.TimeTrackC(t.Name()) 73 74 testcommon.ClearDockerEnv() 75 err = app.Init(site.Dir) 76 assert.NoError(err) 77 78 t.Cleanup(func() { 79 err = app.Stop(true, false) 80 assert.NoError(err) 81 _ = os.RemoveAll(app.GetConfigPath("db_snapshots")) 82 err = os.Chdir(origDir) 83 assert.NoError(err) 84 }) 85 86 err = app.Start() 87 assert.NoError(err) 88 89 snapshots := []string{t.Name() + "_1", t.Name() + "_2", t.Name() + "_3"} 90 // Make three snapshots and compare the last 91 s1Name, err := app.Snapshot(snapshots[0]) 92 assert.NoError(err) 93 s2Name, err := app.Snapshot(snapshots[1]) 94 assert.NoError(err) 95 s3Name, err := app.Snapshot(snapshots[2]) // last = latest 96 assert.NoError(err) 97 98 latestSnapshot, err := app.GetLatestSnapshot() 99 assert.NoError(err) 100 assert.Equal(s3Name, latestSnapshot) 101 102 // Delete snapshot 3 103 err = app.DeleteSnapshot(s3Name) 104 assert.NoError(err) 105 latestSnapshot, err = app.GetLatestSnapshot() 106 assert.NoError(err) 107 assert.Equal(s2Name, latestSnapshot, "%s should be latest snapshot", snapshots[1]) 108 109 // Delete snapshot 2 110 err = app.DeleteSnapshot(s2Name) 111 assert.NoError(err) 112 latestSnapshot, err = app.GetLatestSnapshot() 113 assert.NoError(err) 114 assert.Equal(s1Name, latestSnapshot, "%s should now be latest snapshot", s1Name) 115 116 // Delete snapshot 1 (should be last) 117 err = app.DeleteSnapshot(s1Name) 118 assert.NoError(err) 119 latestSnapshot, _ = app.GetLatestSnapshot() 120 assert.Equal("", latestSnapshot) 121 122 runTime() 123 } 124 125 // TestDdevRestoreSnapshot tests creating a snapshot and reverting to it. 126 func TestDdevRestoreSnapshot(t *testing.T) { 127 assert := assert2.New(t) 128 129 runTime := util.TimeTrackC(t.Name()) 130 origDir, _ := os.Getwd() 131 site := TestSites[0] 132 133 d7testerTest1Dump, err := filepath.Abs(filepath.Join("testdata", t.Name(), "restore_snapshot", "d7tester_test_1.sql.gz")) 134 assert.NoError(err) 135 d7testerTest2Dump, err := filepath.Abs(filepath.Join("testdata", t.Name(), "restore_snapshot", "d7tester_test_2.sql.gz")) 136 assert.NoError(err) 137 138 testcommon.ClearDockerEnv() 139 140 app, err := ddevapp.NewApp(site.Dir, false) 141 require.NoError(t, err) 142 143 t.Cleanup(func() { 144 err = app.Stop(true, false) 145 assert.NoError(err) 146 147 app.Hooks = nil 148 app.Database.Type = nodeps.MariaDB 149 app.Database.Version = nodeps.MariaDBDefaultVersion 150 err = app.WriteConfig() 151 assert.NoError(err) 152 err = os.Chdir(origDir) 153 assert.NoError(err) 154 _ = os.RemoveAll(app.GetConfigPath("db_snapshots")) 155 testcommon.ClearDockerEnv() 156 }) 157 158 err = os.Chdir(app.AppRoot) 159 require.NoError(t, err) 160 app.Hooks = map[string][]ddevapp.YAMLTask{"post-snapshot": {{"exec-host": "touch hello-post-snapshot-" + app.Name}}, "pre-snapshot": {{"exec-host": "touch hello-pre-snapshot-" + app.Name}}} 161 162 err = app.Stop(true, false) 163 assert.NoError(err) 164 err = app.Start() 165 require.NoError(t, err) 166 167 err = app.ImportDB(d7testerTest1Dump, "", false, false, "db") 168 require.NoError(t, err, "Failed to app.ImportDB path: %s err: %v", d7testerTest1Dump, err) 169 170 stdout, _, err := app.Exec(&ddevapp.ExecOpts{ 171 Service: "db", 172 Cmd: `echo "SELECT title FROM node WHERE nid=1;" | mysql -N`, 173 }) 174 assert.NoError(err) 175 assert.Contains(stdout, "d7 tester test 1 has 1 node") 176 177 // Make a snapshot of d7 tester test 1 178 tester1Snapshot, err := app.Snapshot("d7testerTest1") 179 assert.NoError(err) 180 181 assert.Contains(tester1Snapshot, "d7testerTest1") 182 latest, err := app.GetLatestSnapshot() 183 assert.NoError(err) 184 assert.Equal(tester1Snapshot, latest) 185 186 assert.FileExists("hello-pre-snapshot-" + app.Name) 187 assert.FileExists("hello-post-snapshot-" + app.Name) 188 err = os.Remove("hello-pre-snapshot-" + app.Name) 189 assert.NoError(err) 190 err = os.Remove("hello-post-snapshot-" + app.Name) 191 assert.NoError(err) 192 193 // Make sure duplicate snapshot name gives an error 194 _, err = app.Snapshot("d7testerTest1") 195 assert.Error(err) 196 197 err = app.ImportDB(d7testerTest2Dump, "", false, false, "db") 198 assert.NoError(err, "Failed to app.ImportDB path: %s err: %v", d7testerTest2Dump, err) 199 200 stdout, _, err = app.Exec(&ddevapp.ExecOpts{ 201 Service: "db", 202 Cmd: `echo "SELECT title FROM node WHERE nid=1;" | mysql -N`, 203 }) 204 assert.NoError(err) 205 assert.Contains(stdout, "d7 tester test 2 has 2 nodes") 206 207 tester2Snapshot, err := app.Snapshot("d7testerTest2") 208 assert.NoError(err) 209 assert.Contains(tester2Snapshot, "d7testerTest2") 210 latest, err = app.GetLatestSnapshot() 211 assert.Equal(tester2Snapshot, latest) 212 213 app.Hooks = map[string][]ddevapp.YAMLTask{"post-restore-snapshot": {{"exec-host": "touch hello-post-restore-snapshot-" + app.Name}}, "pre-restore-snapshot": {{"exec-host": "touch hello-pre-restore-snapshot-" + app.Name}}} 214 215 err = app.MutagenSyncFlush() 216 require.NoError(t, err) 217 // Sleep to let sync happen if needed (M1 failure) 218 time.Sleep(2 * time.Second) 219 220 err = app.RestoreSnapshot(tester1Snapshot) 221 assert.NoError(err) 222 223 assert.FileExists("hello-pre-restore-snapshot-" + app.Name) 224 assert.FileExists("hello-post-restore-snapshot-" + app.Name) 225 err = os.Remove("hello-pre-restore-snapshot-" + app.Name) 226 assert.NoError(err) 227 err = os.Remove("hello-post-restore-snapshot-" + app.Name) 228 assert.NoError(err) 229 230 stdout, _, err = app.Exec(&ddevapp.ExecOpts{ 231 Service: "db", 232 Cmd: `echo "SELECT title FROM node WHERE nid=1;" | mysql -N`, 233 }) 234 assert.NoError(err) 235 assert.Contains(stdout, "d7 tester test 1 has 1 node") 236 237 err = app.RestoreSnapshot(tester2Snapshot) 238 assert.NoError(err) 239 240 stdout, _, err = app.Exec(&ddevapp.ExecOpts{ 241 Service: "db", 242 Cmd: `echo "SELECT title FROM node WHERE nid=1;" | mysql -N`, 243 }) 244 assert.NoError(err) 245 assert.Contains(stdout, "d7 tester test 2 has 2 nodes") 246 247 // Attempt a restore with a pre-mariadb_10.2 snapshot. It should fail and give a link. 248 oldSnapshotTarball, err := filepath.Abs(filepath.Join(origDir, "testdata", t.Name(), "restore_snapshot", "d7tester_test_1.snapshot_mariadb_10_1.tgz")) 249 assert.NoError(err) 250 251 err = archive.Untar(oldSnapshotTarball, app.GetConfigPath("db_snapshots"), "") 252 assert.NoError(err) 253 254 err = app.RestoreSnapshot("d7tester_test_1.snapshot_mariadb_10.1") 255 assert.Error(err) 256 assert.Contains(err.Error(), "is not compatible") 257 258 // Make sure that we can use old-style directory-based snapshots 259 dirSnapshots := map[string]string{ 260 "mariadb:10.3": "mariadb10.3-users", 261 "mysql:5.7": "mysql5.7-users", 262 } 263 264 // Despite much effort, and successful manual restore of the mysql5.7-users.tgz to another project, 265 // I can't get it to restore in this test. The logs show 266 // " [ERROR] InnoDB: Log block 24712 at lsn 12652032 has valid header, but checksum field contains 1825156513, should be 1116246688" 267 // Since this works everywhere else and this is legacy snapshot support, I'm going to punt 268 // and skip the MySQL snapshot. rfay 2022-02-25 269 if runtime.GOOS == "windows" { 270 delete(dirSnapshots, "mysql:5.7") 271 } 272 273 for dbDesc, dirSnapshot := range dirSnapshots { 274 oldSnapshotTarball, err = filepath.Abs(filepath.Join(origDir, "testdata", t.Name(), dirSnapshot+".tgz")) 275 assert.NoError(err) 276 fullsnapshotDir := filepath.Join(app.GetConfigPath("db_snapshots"), dirSnapshot) 277 err = os.MkdirAll(fullsnapshotDir, 0755) 278 require.NoError(t, err) 279 err = archive.Untar(oldSnapshotTarball, fullsnapshotDir, "") 280 assert.NoError(err) 281 282 err = app.Stop(true, false) 283 assert.NoError(err) 284 285 parts := strings.Split(dbDesc, ":") 286 require.Equal(t, 2, len(parts)) 287 dbType := parts[0] 288 dbVersion := parts[1] 289 app.Database.Type = dbType 290 app.Database.Version = dbVersion 291 err = app.WriteConfig() 292 assert.NoError(err) 293 294 err = app.Start() 295 assert.NoError(err) 296 297 _, _, err = app.Exec(&ddevapp.ExecOpts{ 298 Service: "db", 299 Cmd: `echo "DROP TABLE IF EXISTS users;" | mysql`, 300 }) 301 assert.NoError(err) 302 303 err = app.RestoreSnapshot(dirSnapshot) 304 if err != nil { 305 assert.NoError(err, "Failed to restore dirSnapshot '%s': %v, continuing", dirSnapshot, err) 306 continue 307 } 308 309 stdout, _, err = app.Exec(&ddevapp.ExecOpts{ 310 Service: "db", 311 Cmd: `echo "SELECT COUNT(*) FROM users;" | mysql -N`, 312 }) 313 assert.NoError(err) 314 assert.Equal(stdout, "2\n") 315 } 316 runTime() 317 }