github.com/drud/ddev@v1.21.5-alpha1.0.20230226034409-94fcc4b94453/pkg/ddevapp/snapshot_test.go (about)

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