Source code for benchbuild.utils.unionfs
import logging
import os
import subprocess
import psutil
from plumbum import local
from benchbuild import settings
from benchbuild.utils.container import in_container
LOG = logging.getLogger(__name__)
[docs]def unionfs(rw='rw', ro=None, union='union'):
"""
Decorator for the UnionFS feature.
This configures a unionfs for projects. The given base_dir and/or image_dir
are layered as follows:
image_dir=RW:base_dir=RO
All writes go to the image_dir, while base_dir delivers the (read-only)
versions of the rest of the filesystem.
The unified version will be provided in the project's builddir. Unmouting
is done as soon as the function completes.
Args:
rw: writeable storage area for the unified fuse filesystem.
ro: read-only storage area for the unified fuse filesystem.
union: mountpoint of the unified fuse filesystem.
"""
from functools import wraps
def wrap_in_union_fs(func):
"""
Function that wraps a given function inside the file system.
Args:
func: The function that needs to be wrapped inside the unions fs.
Return:
The file system with the function wrapped inside.
"""
@wraps(func)
def wrap_in_union_fs_func(project, *args, **kwargs):
"""
Wrap the func in the UnionFS mount stack.
We make sure that the mount points all exist and stack up the
directories for the unionfs. All directories outside of the default
build environment are tracked for deletion.
"""
container = project.container
if container is None or in_container():
return func(project, *args, **kwargs)
build_dir = local.path(project.builddir)
LOG.debug("UnionFS - Project builddir: %s", project.builddir)
if __unionfs_is_active(root=build_dir):
LOG.debug(
"UnionFS already active in %s, nesting not supported.",
build_dir)
return func(project, *args, **kwargs)
ro_dir = local.path(container.local)
rw_dir = build_dir / rw
un_dir = build_dir / union
LOG.debug("UnionFS - RW: %s", rw_dir)
unionfs_cmd = __unionfs_set_up(ro_dir, rw_dir, un_dir)
project_builddir_bak = project.builddir
project.builddir = un_dir
proc = unionfs_cmd.popen()
while (not __unionfs_is_active(root=un_dir)) and \
(proc.poll() is None):
pass
ret = None
if proc.poll() is None:
try:
with local.cwd(un_dir):
ret = func(project, *args, **kwargs)
finally:
project.builddir = project_builddir_bak
from signal import SIGINT
is_running = proc.poll() is None
while __unionfs_is_active(root=un_dir) and is_running:
try:
proc.send_signal(SIGINT)
proc.wait(timeout=3)
except subprocess.TimeoutExpired:
proc.kill()
is_running = False
LOG.debug("Unionfs shut down.")
if __unionfs_is_active(root=un_dir):
raise UnmountError()
return ret
return wrap_in_union_fs_func
return wrap_in_union_fs
def __update_cleanup_paths(new_path):
"""
Add the new path to the list of paths to clean up afterwards.
Args:
new_path: Path to the directory that need to be cleaned up.
"""
cleanup_dirs = settings.CFG["cleanup_paths"].value
cleanup_dirs = set(cleanup_dirs)
cleanup_dirs.add(new_path)
cleanup_dirs = list(cleanup_dirs)
settings.CFG["cleanup_paths"] = cleanup_dirs
def __is_outside_of_builddir(project, path_to_check):
"""Check if a project lies outside of its expected directory."""
bdir = project.builddir
cprefix = os.path.commonprefix([path_to_check, bdir])
return cprefix != bdir
def __unionfs_is_active(root):
real_root = os.path.realpath(root)
for part in psutil.disk_partitions(all=True):
if os.path.commonpath([part.mountpoint, real_root]) == real_root:
if part.fstype in ["fuse.unionfs", "fuse.unionfs-fuse"]:
return True
return False
def __unionfs_set_up(ro_dir, rw_dir, mount_dir):
"""
Setup a unionfs via unionfs-fuse.
Args:
ro_base: base_directory of the project
rw_image: virtual image of actual file system
mountpoint: location where ro_base and rw_image merge
"""
mount_dir.mkdir()
rw_dir.mkdir()
if not ro_dir.exists():
LOG.error("Base dir does not exist: '%s'", ro_dir)
raise ValueError("Base directory does not exist")
from benchbuild.utils.cmd import unionfs as unionfs_cmd
LOG.debug("Mounting UnionFS on %s with RO:%s RW:%s", mount_dir, ro_dir,
rw_dir)
return unionfs_cmd["-f", "-o", "auto_unmount,allow_other,cow", rw_dir +
"=RW:" + ro_dir + "=RO", mount_dir]
[docs]class UnmountError(BaseException):
pass