diff --git a/Dockerfile.dapper b/Dockerfile.dapper index b0ab07d0..1a0bdde8 100644 --- a/Dockerfile.dapper +++ b/Dockerfile.dapper @@ -17,6 +17,7 @@ RUN curl -sSL https://storage.googleapis.com/golang/go1.5.2.linux-amd64.tar.gz | RUN curl -sL https://get.docker.com/builds/Linux/x86_64/docker-1.9.1 > /usr/bin/docker RUN chmod +x /usr/bin/docker +ENV DAPPER_DOCKER_SOCKET true ENV DAPPER_SOURCE /go/src/github.com/rancher/os ENV DAPPER_OUTPUT ./bin ./dist ENV DAPPER_RUN_ARGS --privileged diff --git a/config/types.go b/config/types.go index bd3024fe..419e23ed 100644 --- a/config/types.go +++ b/config/types.go @@ -104,6 +104,7 @@ type DockerConfig struct { } type StateConfig struct { + Directory string `yaml:"directory,omitempty"` FsType string `yaml:"fstype,omitempty"` Dev string `yaml:"dev,omitempty"` Required bool `yaml:"required,omitempty"` diff --git a/init/init.go b/init/init.go index 14b36b3a..2ca70e80 100644 --- a/init/init.go +++ b/init/init.go @@ -83,18 +83,17 @@ func MainInit() { } } -func mountState(cfg *config.CloudConfig) error { +func mountConfigured(display, dev, fsType, target string) error { var err error - if cfg.Rancher.State.Dev == "" { + if dev == "" { return nil } - dev := util.ResolveDevice(cfg.Rancher.State.Dev) + dev = util.ResolveDevice(dev) if dev == "" { - return fmt.Errorf("Could not resolve device %q", cfg.Rancher.State.Dev) + return fmt.Errorf("Could not resolve device %q", dev) } - fsType := cfg.Rancher.State.FsType if fsType == "auto" { fsType, err = util.GetFsType(dev) } @@ -104,8 +103,12 @@ func mountState(cfg *config.CloudConfig) error { } log.Debugf("FsType has been set to %s", fsType) - log.Infof("Mounting state device %s to %s", dev, STATE) - return util.Mount(dev, STATE, fsType, "") + log.Infof("Mounting %s device %s to %s", display, dev, target) + return util.Mount(dev, target, fsType, "") +} + +func mountState(cfg *config.CloudConfig) error { + return mountConfigured("state", cfg.Rancher.State.Dev, cfg.Rancher.State.FsType, STATE) } func tryMountState(cfg *config.CloudConfig) error { @@ -128,8 +131,9 @@ func tryMountAndBootstrap(cfg *config.CloudConfig) (*config.CloudConfig, error) return cfg, err } - log.Debugf("Switching to new root at %s", STATE) - return cfg, switchRoot(STATE, cfg.Rancher.RmUsr) + log.Debugf("Switching to new root at %s %s", STATE, cfg.Rancher.State.Directory) + err := switchRoot(STATE, cfg.Rancher.State.Directory, cfg.Rancher.RmUsr) + return cfg, err } func getLaunchConfig(cfg *config.CloudConfig, dockerCfg *config.DockerConfig) (*dockerlaunch.Config, []string) { diff --git a/init/root.go b/init/root.go index 09891c5c..d116c015 100644 --- a/init/root.go +++ b/init/root.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "path" + "strings" "syscall" log "github.com/Sirupsen/logrus" @@ -84,7 +85,7 @@ func copyMoveRoot(rootfs string, rmUsr bool) error { for _, file := range files { filename := path.Join("/", file.Name()) - if filename == rootfs { + if filename == rootfs || strings.HasPrefix(rootfs, filename+"/") { log.Debugf("Skipping Deleting %s", filename) continue } @@ -98,7 +99,23 @@ func copyMoveRoot(rootfs string, rmUsr bool) error { return nil } -func switchRoot(rootfs string, rmUsr bool) error { +func switchRoot(rootfs, subdir string, rmUsr bool) error { + if subdir != "" { + fullRootfs := path.Join(rootfs, subdir) + if _, err := os.Stat(fullRootfs); os.IsNotExist(err) { + if err := os.MkdirAll(fullRootfs, 0755); err != nil { + log.Errorf("Failed to create directory %s: %v", fullRootfs, err) + return err + } + } + + log.Debugf("Bind mounting mount %s to %s", fullRootfs, rootfs) + if err := syscall.Mount(fullRootfs, rootfs, "", syscall.MS_BIND, ""); err != nil { + log.Errorf("Failed to bind mount subdir for %s: %v", fullRootfs, err) + return err + } + } + for _, i := range []string{"/dev", "/sys", "/proc", "/run"} { log.Debugf("Moving mount %s to %s", i, path.Join(rootfs, i)) if err := os.MkdirAll(path.Join(rootfs, i), 0755); err != nil { @@ -113,23 +130,27 @@ func switchRoot(rootfs string, rmUsr bool) error { return err } + log.Debugf("chdir %s", rootfs) if err := syscall.Chdir(rootfs); err != nil { return err } + log.Debugf("mount MS_MOVE %s", rootfs) if err := syscall.Mount(rootfs, "/", "", syscall.MS_MOVE, ""); err != nil { return err } + log.Debug("chroot .") if err := syscall.Chroot("."); err != nil { return err } + log.Debug("chdir /") if err := syscall.Chdir("/"); err != nil { return err } - log.Debugf("Successfully moved to new root at %s", rootfs) + log.Debugf("Successfully moved to new root at %s", path.Join(rootfs, subdir)) os.Unsetenv("DOCKER_RAMDISK") return nil diff --git a/tests/integration/rostest/conftest.py b/tests/integration/rostest/conftest.py index 1c1524ff..43afaba5 100644 --- a/tests/integration/rostest/conftest.py +++ b/tests/integration/rostest/conftest.py @@ -2,11 +2,12 @@ import subprocess import os import pytest +import rostest @pytest.fixture(scope="session", autouse=True) def chdir_to_project_root(): - os.chdir('../..') + os.chdir(os.path.join(os.path.dirname(rostest.__file__), '../../..')) print('\nChdir to project root dir: ' + subprocess.check_output('pwd')) os.chmod('./tests/integration/assets/test.key', 0o600) print('Also, `chmod 600 tests/integration/assets/test.key` to make ssh happy') diff --git a/tests/integration/rostest/test_06_subdir.py b/tests/integration/rostest/test_06_subdir.py new file mode 100644 index 00000000..cc665bd7 --- /dev/null +++ b/tests/integration/rostest/test_06_subdir.py @@ -0,0 +1,19 @@ +import pytest +import rostest.util as u +from rostest.util import SSH + + +@pytest.fixture(scope="module") +def qemu(request): + q = u.run_qemu(request, run_args=['--append', 'rancher.state.directory=ros_subdir']) + u.flush_out(q.stdout) + return q + + +def test_system_docker_survives_custom_docker_install(qemu): + SSH(qemu).check_call('bash', '-c', ''' + set -x -e + mkdir x + sudo mount $(ros dev LABEL=RANCHER_STATE) x + [ -d x/ros_subdir/home/rancher ] + '''.strip()) diff --git a/tests/integration/rostest/util.py b/tests/integration/rostest/util.py index 4dee6045..16f90de1 100644 --- a/tests/integration/rostest/util.py +++ b/tests/integration/rostest/util.py @@ -85,3 +85,19 @@ def wait_for_ssh(qemu, ssh_command=['./scripts/ssh', '--qemu'], command=['docker print('\nWaiting for ssh and docker... ' + str(i)) time.sleep(1) assert qemu.returncode is None + + +class SSH: + def __init__(self, qemu, ssh_command=['./scripts/ssh', '--qemu']): + self._qemu = qemu + self._ssh_command = ssh_command + self._waited = False + + def check_call(self, *args, **kw): + if not self._waited: + wait_for_ssh(self._qemu, ssh_command=self._ssh_command) + self._waited = True + + kw['stderr'] = subprocess.STDOUT + kw['universal_newlines'] = True + return subprocess.check_call(self._ssh_command + list(args), **kw)