Skip to content

Commit

Permalink
Mutagen followup from v1.18.0-alpha1, fixes #3124 (#3125)
Browse files Browse the repository at this point in the history
* Docs updates about mutagen
* Remove var from ignored dirs and leave as comment, fixes #3124
* Remove MUTAGEN_DATA_DIRECTORY usage to lessen debugging confusion
* Add more mutagen info to test_ddev.sh
* Sync vcs directories, remove doc that said we don't
* Add diagnose_mutagen.sh script
* Improve StopMutagenDaemon and add test coverage for it
* Provide output to users about terminate and flush activities
* Improve tests
  • Loading branch information
rfay committed Jul 30, 2021
1 parent 7b926c5 commit 06ff3e5
Show file tree
Hide file tree
Showing 11 changed files with 78 additions and 58 deletions.
8 changes: 5 additions & 3 deletions cmd/ddev/cmd/dotddev_assets/mutagen.yml
Expand Up @@ -11,10 +11,12 @@ sync:
mode: "two-way-resolved"
ignore:
paths:
# /var/www/html/var does not need to sync in TYPO3
- "var"
- ".ddev"
vcs: true
# For example /var/www/html/var does not need to sync in TYPO3
# - "var"
# vcs like .git can be ignored for safety, but then some
# composer operations may fail if they use git.
# vcs: true
symlink:
mode: "portable"

2 changes: 1 addition & 1 deletion cmd/ddev/cmd/global_dotddev_assets/.gitignore
Expand Up @@ -7,8 +7,8 @@
/**/*.example
/**/README.*
/CMS
/bin
/global_config.yaml
/.mutagen
/pantheon*
/*compose.yaml
/*compose-full.yaml
Expand Down
5 changes: 0 additions & 5 deletions cmd/ddev/cmd/root.go
Expand Up @@ -66,11 +66,6 @@ Support: https://ddev.readthedocs.io/en/stable/#support`,
}
}

err = ddevapp.CheckMutagenVersion(version.MutagenVersionConstraint)
if err != nil {
util.Warning("The mutagen version currently installed does not meet ddev's requirements: %v\nThe appropriate version will be downloaded when it is needed.", err)
}

updateFile := filepath.Join(globalconfig.GetGlobalDdevDir(), ".update")

// Do periodic detection of whether an update is available for ddev users.
Expand Down
19 changes: 16 additions & 3 deletions docs/users/performance.md
Expand Up @@ -137,13 +137,19 @@ To stop using Mutagen on a project, `ddev config --mutagen-enabled=false` after

You can also enable mutagen globally (for every project) with `ddev config global --mutagen-enabled`

Note that the nfs-mount-enabled feature is automatically turned off if you're using mutagen.

### Caveats about Mutagen Integration

* Mutagen is not the right choice for every project. If filesystem consistency is your highest priority (as opposed to performance) then you'll want to walk carefully. At this point, there haven't been major issues reported, but two-way sync is a very difficult computational problem, and problems may surface. If you have backups (Time Machine!) and code under source control, you should be fine.
* Multiple mutagen versions can't coexist on one machine, so please stop any running mutagen. On macOS, `killall mutagen`.
* This is mostly for macOS users. WSL2 is already the preferred environment for Windows users, but if you're still using traditional Windows this makes a huge difference. Turning on mutagen doesn't make sense on Linux or WSL2.
* Mutagen integration ends up at least doubling the size of your project code disk usage, because the code exists both on your computer and also inside a docker volume. So take care that you have enough overall disk space, and also (on macOS) that you have enough file space set up in Docker Desktop.
* If your project is likely to change the same file on both the host and inside the container, you may be at risk for conflicts.
* Massive changes to either the host or the container are the most likely to introduce issues. This integration has been tested extensively with major changes introduced by `ddev composer` and `ddev composer create` but be aware of this issue. A script that deletes huge sections of the synced data is a related behavior that should raise caution.
* You can cause an explicit sync with `ddev mutagen sync` and see syncing status with `ddev mutagen status`. Note that `ddev start` and `ddev start` automatically force a mutagen sync.
* Massive changes to either the host or the container are the most likely to introduce issues. This integration has been tested extensively with major changes introduced by `ddev composer` and `ddev composer create` but be aware of this issue. Changing git branches or a script that deletes huge sections of the synced data are related behaviors that should raise caution.
* You can cause an explicit sync with `ddev mutagen sync` and see syncing status with `ddev mutagen status`. Note that both `ddev start` and `ddev stop` automatically force a mutagen sync.
* If you do composer actions inside the container (with `ddev ssh`) you'll probably want to do a `ddev mutagen sync` to make sure they get synced as soon as possible, although most people won't ever notice the difference and mutagen will get it synced soon enough.
* Keep backups. Mutagen syncing is an experimental feature.
* The mutagen integration by default does not sync VCS directories like .git into the container, but this can be changed with advanced configuration options. (This means that by default you cannot do git operations inside the container with mutagen turned on.)

### Advanced Mutagen configuration options

Expand All @@ -154,3 +160,10 @@ Each project by default already has a .ddev/mutagen.yml file with basic defaults
The most likely thing you'll want to do is to exclude a path from mutagen syncing, which you can do in the `paths:` section of the `ignore:` stanza in the mutagen.yml.

It is possible to exclude mutagen syncing from a path and bind-mount something from the host or a different volume on that path with a `docker-compose.*.yaml` file.

### Troubleshooting Mutagen Sync Issues

* DDEV's mutagen may not be compatible with an existing mutagen on your system. Please make sure that any mutagen installs you have are not running, or stop them. You may want to `brew uninstall mutagen-io/mutagen/mutagen mutagen-io/mutagen/mutagen-beta` to get rid of brew-installed versions.
* DDEV's mutagen is installed in ~/.ddev/bin/mutagen. You can use all the features of mutagen by running that, including `~/.ddev/bin/mutagen sync list` and `~/.ddev/bin/mutagen daemon stop`.
You can run the script [diagnose_mutagen.sh](https://raw.githubusercontent.com/drud/ddev/master/scripts/diagnose_mutagen.sh) to gather some information about the setup of mutagen. Please report its output when creating an issue or otherwise seeking support.
* If you're having trouble, we really want to hear from you to learn and try to sort it out. See the [Support channels](https://ddev.readthedocs.io/en/latest/#support-and-user-contributed-documentation).
5 changes: 2 additions & 3 deletions pkg/ddevapp/ddevapp.go
Expand Up @@ -1413,7 +1413,6 @@ func (app *DdevApp) DockerEnv() {
"DDEV_PRIMARY_URL": app.GetPrimaryURL(),
"DOCKER_SCAN_SUGGEST": "false",
"IS_DDEV_PROJECT": "true",
"MUTAGEN_DATA_DIRECTORY": globalconfig.GetMutagenDir(),
}

// Set the mariadb_local command to empty to prevent docker-compose from complaining normally.
Expand Down Expand Up @@ -1457,7 +1456,7 @@ func (app *DdevApp) Pause() error {
return err
}

_ = SyncAndTerminateMutagen(app)
_ = SyncAndTerminateMutagenSession(app)

if _, _, err := dockerutil.ComposeCmd([]string{app.DockerComposeFullRenderedYAMLPath()}, "stop"); err != nil {
return err
Expand Down Expand Up @@ -1805,7 +1804,7 @@ func (app *DdevApp) Stop(removeData bool, createSnapshot bool) error {
}
}

_ = SyncAndTerminateMutagen(app)
_ = SyncAndTerminateMutagenSession(app)

if app.SiteStatus() == SiteRunning {
err = app.Pause()
Expand Down
9 changes: 8 additions & 1 deletion pkg/ddevapp/ddevapp_test.go
Expand Up @@ -844,6 +844,8 @@ func TestDdevXhprofEnabled(t *testing.T) {
}
assert.Contains(stdout, "xhprof.output_dir", "xhprof is not enabled for %s", v)

// Dummy hit on phpinfo.php to avoid M1 "connection reset by peer"
_, _, _ = testcommon.GetLocalHTTPResponse(t, app.GetHTTPSURL()+"/phpinfo.php")
out, _, err := testcommon.GetLocalHTTPResponse(t, app.GetHTTPSURL()+"/phpinfo.php")
assert.NoError(err, "Failed to get base URL webserver_type=%s, php_version=%s", webserverKey, v)
assert.Contains(out, "module_xhprof")
Expand Down Expand Up @@ -1379,6 +1381,9 @@ func TestDdevExportDB(t *testing.T) {
assert.NoError(err)
assert.True(stringFound)

// Flush needs to be complete before purge or may conflict with mutagen on windows
err = app.MutagenSyncFlush()
assert.NoError(err)
err = fileutil.PurgeDirectory("tmp")
assert.NoError(err)

Expand Down Expand Up @@ -1724,7 +1729,9 @@ func TestDdevRestoreSnapshot(t *testing.T) {
err = os.Remove("hello-post-restore-snapshot-" + app.Name)
assert.NoError(err)

_, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPSURL(), "d7 tester test 1 has 1 node", 45)
// Dummy hit in advance to try to avoid M1 "connection reset by peer"
_, _, _ = testcommon.GetLocalHTTPResponse(t, app.GetHTTPSURL(), 60)
_, _ = testcommon.EnsureLocalHTTPContent(t, app.GetHTTPSURL(), "d7 tester test 1 has 1 node", 60)
err = app.RestoreSnapshot("d7testerTest2")
assert.NoError(err)

Expand Down
44 changes: 8 additions & 36 deletions pkg/ddevapp/mutagen.go
Expand Up @@ -2,7 +2,6 @@ package ddevapp

import (
"fmt"
"github.com/Masterminds/semver"
"github.com/drud/ddev/pkg/archive"
"github.com/drud/ddev/pkg/exec"
"github.com/drud/ddev/pkg/fileutil"
Expand Down Expand Up @@ -51,12 +50,13 @@ func TerminateMutagenSync(app *DdevApp) error {
return err
}
}
util.Success("Terminated mutagen sync session %s", syncName)
}
return nil
}

// SyncAndTerminateMutagen syncs and terminates the mutagen sync
func SyncAndTerminateMutagen(app *DdevApp) error {
// SyncAndTerminateMutagenSession syncs and terminates the mutagen sync session
func SyncAndTerminateMutagenSession(app *DdevApp) error {
if app.MutagenEnabled || app.MutagenEnabledGlobal {
syncName := MutagenSyncName(app.Name)

Expand Down Expand Up @@ -177,6 +177,7 @@ func (app *DdevApp) MutagenSyncFlush() error {
if !status || err != nil {
return err
}
util.Success("Flushed mutagen sync session %s", syncName)
}
return nil
}
Expand Down Expand Up @@ -217,7 +218,10 @@ func DownloadMutagen() error {
// StopMutagenDaemon will try to stop a running mutagen daemon
// But no problem if there wasn't one
func StopMutagenDaemon() {
_, _ = exec.RunHostCommand(globalconfig.GetMutagenPath(), "daemon", "stop")
out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "daemon", "stop")
if err != nil && !strings.Contains(out, "unable to connect to daemon") {
util.Warning("Unable to stop mutagen daemon: %v", err)
}
}

// DownloadMutagenIfNeeded downloads the proper version of mutagen
Expand All @@ -235,35 +239,3 @@ func DownloadMutagenIfNeeded(app *DdevApp) error {
}
return nil
}

// CheckMutagenVersion determines if the mutagen version of the host
//system meets the provided version constraint
func CheckMutagenVersion(versionConstraint string) error {
currentVersion, err := version.GetMutagenVersion()
if err != nil {
return fmt.Errorf("no mutagen")
}
v, err := semver.NewVersion(currentVersion)
if err != nil {
return err
}

constraint, err := semver.NewConstraint(versionConstraint)
if err != nil {
return err
}

match, errs := constraint.Validate(v)
if !match {
if len(errs) <= 1 {
return errs[0]
}

msgs := "\n"
for _, err := range errs {
msgs = fmt.Sprint(msgs, err, "\n")
}
return fmt.Errorf(msgs)
}
return nil
}
14 changes: 10 additions & 4 deletions pkg/ddevapp/mutagen_test.go
Expand Up @@ -55,7 +55,7 @@ func TestMutagenSimple(t *testing.T) {
assert.True(desc["mutagen_enabled"].(bool))

// Make sure the sync is there
_, err = exec.RunCommand(mutagenPath, []string{"sync", "list", ddevapp.MutagenSyncName(app.Name)})
_, err = exec.RunHostCommand(mutagenPath, "sync", "list", ddevapp.MutagenSyncName(app.Name))
assert.NoError(err)

// Remove the vendor directory and sync
Expand Down Expand Up @@ -83,20 +83,26 @@ func TestMutagenSimple(t *testing.T) {

// Stop app, should result in no more mutagen sync
err = app.Stop(false, false)
_, err = exec.RunCommand(mutagenPath, []string{"sync", "list", ddevapp.MutagenSyncName(app.Name)})
_, err = exec.RunHostCommand(mutagenPath, "sync", "list", ddevapp.MutagenSyncName(app.Name))
assert.Error(err)

// Make sure we can stop the daemon
ddevapp.StopMutagenDaemon()
out, err := exec.RunHostCommand(globalconfig.GetMutagenPath(), "sync", "list")
assert.NoError(err)
assert.Contains(out, "Started Mutagen daemon in background")

err = app.Start()
assert.NoError(err)

// Make sure sync is down on pause also
err = app.Pause()
_, err = exec.RunCommand(mutagenPath, []string{"sync", "list", ddevapp.MutagenSyncName(app.Name)})
_, err = exec.RunHostCommand(mutagenPath, "sync", "list", ddevapp.MutagenSyncName(app.Name))
assert.Error(err)

// And that it's re-established when we start again
err = app.Start()
_, err = exec.RunCommand(mutagenPath, []string{"sync", "list", ddevapp.MutagenSyncName(app.Name)})
_, err = exec.RunHostCommand(mutagenPath, "sync", "list", ddevapp.MutagenSyncName(app.Name))
assert.NoError(err)

runTime()
Expand Down
4 changes: 2 additions & 2 deletions pkg/globalconfig/global_config.go
Expand Up @@ -66,7 +66,7 @@ func GetGlobalConfigPath() string {

// GetMutagenDir returns the directory of the mutagen config and binary
func GetMutagenDir() string {
return filepath.Join(GetGlobalDdevDir(), ".mutagen")
return filepath.Join(GetGlobalDdevDir(), "bin")
}

// GetMutagenPath gets the full path to the mutagen binary
Expand All @@ -75,7 +75,7 @@ func GetMutagenPath() string {
if runtime.GOOS == "windows" {
mutagenBinary = mutagenBinary + ".exe"
}
return filepath.Join(GetMutagenDir(), "bin", mutagenBinary)
return filepath.Join(GetMutagenDir(), mutagenBinary)
}

// ValidateGlobalConfig validates global config
Expand Down
18 changes: 18 additions & 0 deletions scripts/diagnose_mutagen.sh
@@ -0,0 +1,18 @@
#!/bin/bash

# This script tries to diagnose mutagen issues. Please run it in the project
# directory where you're having trouble and provide its
# output in a gist at gist.github.com with any issue you open about mutagen.

if command -v mutagen >/dev/null ; then
echo "mutagen additionally installed in PATH at $(command -v mutagen), version $(mutagen version)"
fi
if killall -0 mutagen 2>/dev/null; then
echo "mutagen is running on this system:"
ps -ef | grep mutagen
fi

ddev list
ddev describe
~/.ddev/bin/mutagen sync list -l
~/.ddev/bin/mutagen version
8 changes: 8 additions & 0 deletions scripts/test_ddev.sh
Expand Up @@ -35,6 +35,14 @@ echo -n "docker location: " && ls -l "$(which docker)"
if [ ${OSTYPE%-*} != "linux" ]; then
echo -n "Docker Desktop Version: " && docker_desktop_version && echo
fi
if command -v mutagen >/dev/null ; then
echo "mutagen additionally installed in PATH at $(command -v mutagen), version $(mutagen version)"
fi
if killall -0 mutagen 2>/dev/null; then
echo "mutagen is running on this system:"
ps -ef | grep mutagen
fi

echo "Docker disk space:" && docker run --rm busybox df -h / && echo
ddev poweroff
echo "Existing docker containers: " && docker ps -a
Expand Down

0 comments on commit 06ff3e5

Please sign in to comment.