From 11669de95de7e8a9ad8b41fa5e9be3fbb845c52e Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 15 Oct 2014 17:47:46 +0000 Subject: deploy extensions: Don't crash if someone builds at the same time If a build happens, it creates a new network namespace, and if this happens while you have a disk image mounted, then you can't remove the mount-point, because the other namespace is using it. We can avoid the other namespace keeping this mount-point open by creating the disk image in a private mount namespace, so it never sees it. The nicest way to do this is to have every extension run in a private mount namespace, since you'd have to have extensions re-exec themselves, since the appropriate system calls aren't exposed very well. --- morphlib/extensions.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'morphlib') diff --git a/morphlib/extensions.py b/morphlib/extensions.py index af6ba279..ef233b6f 100644 --- a/morphlib/extensions.py +++ b/morphlib/extensions.py @@ -223,7 +223,9 @@ class ExtensionSubprocess(object): def close_read_end(): os.close(log_read_fd) p = subprocess.Popen( - [filename] + args, cwd=cwd, env=new_env, + ['unshare', '-m', '--', '/bin/sh', '-c', + 'mount --make-rprivate / && exec "$@"', '-', filename] + args, + cwd=cwd, env=new_env, stdout=subprocess.PIPE, stderr=subprocess.PIPE, preexec_fn=close_read_end) os.close(log_write_fd) -- cgit v1.2.1 From 595c92f65deb02e56414d80a7cfe8cfde508ca4d Mon Sep 17 00:00:00 2001 From: Richard Maw Date: Wed, 15 Oct 2014 18:01:55 +0000 Subject: stagingarea: Mount proc and ccache inside the namespace This works towards allowing multiple concurrent builds in the same system, which has the same problem as deployments. This is the easy bit, since linux-user-chroot has support for bind mounts and /proc mounts. We also need to get rid of the /dev/shm mount to be able to build in parallel though. --- morphlib/builder2.py | 4 +++- morphlib/stagingarea.py | 24 +++++++++++++++--------- 2 files changed, 18 insertions(+), 10 deletions(-) (limited to 'morphlib') diff --git a/morphlib/builder2.py b/morphlib/builder2.py index 596cd645..8615ed59 100644 --- a/morphlib/builder2.py +++ b/morphlib/builder2.py @@ -342,6 +342,7 @@ class ChunkBuilder(BuilderBase): relative_builddir = self.staging_area.relative(builddir) relative_destdir = self.staging_area.relative(destdir) + ccache_dir = self.staging_area.ccache_dir(self.source) extra_env = { 'DESTDIR': relative_destdir } steps = [ @@ -391,7 +392,8 @@ class ChunkBuilder(BuilderBase): cwd=relative_builddir, stdout=stdout or subprocess.PIPE, stderr=subprocess.STDOUT, - logfile=logfilepath) + logfile=logfilepath, + ccache_dir=ccache_dir) if stdout: stdout.flush() diff --git a/morphlib/stagingarea.py b/morphlib/stagingarea.py index bfe0a716..25e33b3f 100644 --- a/morphlib/stagingarea.py +++ b/morphlib/stagingarea.py @@ -192,11 +192,10 @@ class StagingArea(object): shutil.rmtree(self.dirname) to_mount = ( - ('proc', 'proc', 'none'), ('dev/shm', 'tmpfs', 'none'), ) - def mount_ccachedir(self, source): #pragma: no cover + def ccache_dir(self, source): #pragma: no cover ccache_dir = self._app.settings['compiler-cache-dir'] if not os.path.isdir(ccache_dir): os.makedirs(ccache_dir) @@ -223,10 +222,7 @@ class StagingArea(object): # to avoid breaking when faced with an empty staging area. if not os.path.isdir(ccache_destdir): os.makedirs(ccache_destdir) - # Mount it into the staging-area - self._app.runcmd(['mount', '--bind', ccache_repodir, - ccache_destdir]) - return ccache_destdir + return ccache_repodir def do_mounts(self, setup_mounts): # pragma: no cover if not setup_mounts: @@ -257,9 +253,6 @@ class StagingArea(object): self.do_mounts(setup_mounts) - if not self._app.settings['no-ccache']: - self.mounted.append(self.mount_ccachedir(source)) - return builddir, destdir def chroot_close(self): # pragma: no cover @@ -284,6 +277,7 @@ class StagingArea(object): del kwargs['cwd'] else: cwd = '/' + ccache_dir = kwargs.pop('ccache_dir', None) chroot_dir = self.dirname if self.use_chroot else '/' temp_dir = kwargs["env"].get("TMPDIR", "/tmp") @@ -304,6 +298,18 @@ class StagingArea(object): if not os.path.islink(d): real_argv += ['--mount-readonly', self.relative(d)] + if self.use_chroot: + proc_target = os.path.join(self.dirname, 'proc') + if not os.path.exists(proc_target): + os.makedirs(proc_target) + real_argv += ['--mount-proc', self.relative(proc_target)] + + if ccache_dir and not self._app.settings['no-ccache']: + ccache_target = os.path.join( + self.dirname, kwargs['env']['CCACHE_DIR'].lstrip('/')) + real_argv += ['--mount-bind', ccache_dir, + self.relative(ccache_target)] + real_argv += [chroot_dir] real_argv += argv -- cgit v1.2.1