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  }