Prior to this commit, the functional tests used LXC. LXD is better for configuring a shared device between the host and container that can be written to by the container. This feature is critical for tracking code coverage in the functional tests.
Signed-off-by: Tom Hromatka <tom.hroma...@oracle.com> --- .travis.yml | 1 + tests/ftests/cgroup.py | 4 +- tests/ftests/config.py | 2 +- tests/ftests/consts.py | 10 +-- tests/ftests/container.py | 140 +++++++++++++------------------------- tests/ftests/ftests.py | 72 +++++++++++++++----- tests/ftests/run.py | 2 +- 7 files changed, 114 insertions(+), 117 deletions(-) diff --git a/.travis.yml b/.travis.yml index 055f375..4b24840 100644 --- a/.travis.yml +++ b/.travis.yml @@ -37,6 +37,7 @@ addons: apt: packages: - lxc + - lxd # perform the build and fail immediately on error install: diff --git a/tests/ftests/cgroup.py b/tests/ftests/cgroup.py index 20495af..c8bffa5 100644 --- a/tests/ftests/cgroup.py +++ b/tests/ftests/cgroup.py @@ -27,8 +27,8 @@ class Cgroup(object): @staticmethod def build_cmd_path(in_container, cmd): if in_container: - return os.path.join('/', consts.LIBCG_MOUNT_POINT, - 'src/tools/.libs/{}'.format(cmd)) + return os.path.join(consts.LIBCG_MOUNT_POINT, + 'src/tools/{}'.format(cmd)) else: return cmd diff --git a/tests/ftests/config.py b/tests/ftests/config.py index 2d32aaf..5cdc21d 100644 --- a/tests/ftests/config.py +++ b/tests/ftests/config.py @@ -32,7 +32,7 @@ class Config(object): else: # Use the default container settings self.container = Container(name=consts.DEFAULT_CONTAINER_NAME, - stop_timeout=args.timeout, arch=None, cfg_path=args.config, + stop_timeout=args.timeout, arch=None, distro=args.distro, release=args.release) self.ftest_dir = os.path.dirname(os.path.abspath(__file__)) diff --git a/tests/ftests/consts.py b/tests/ftests/consts.py index 9a1b92b..f1621b4 100644 --- a/tests/ftests/consts.py +++ b/tests/ftests/consts.py @@ -28,11 +28,13 @@ LOG_WARNING = 5 LOG_DEBUG = 8 DEFAULT_LOG_LEVEL = 5 -LIBCG_MOUNT_POINT = 'libcg' +ftest_dir = os.path.dirname(os.path.abspath(__file__)) +tests_dir = os.path.dirname(ftest_dir) +LIBCG_MOUNT_POINT = os.path.dirname(tests_dir) -DEFAULT_CONTAINER_NAME = 'test_libcg' -DEFAULT_CONTAINER_DISTRO = 'oracle' -DEFAULT_CONTAINER_RELEASE = '7' +DEFAULT_CONTAINER_NAME = 'TestLibcg' +DEFAULT_CONTAINER_DISTRO = 'ubuntu' +DEFAULT_CONTAINER_RELEASE = '18.04' DEFAULT_CONTAINER_ARCH = 'amd64' DEFAULT_CONTAINER_STOP_TIMEOUT = 5 DEFAULT_CONTAINER_CFG_PATH=os.path.join( diff --git a/tests/ftests/container.py b/tests/ftests/container.py index 6d4a100..948018e 100644 --- a/tests/ftests/container.py +++ b/tests/ftests/container.py @@ -41,11 +41,6 @@ class Container(object): else: self.arch = consts.DEFAULT_CONTAINER_ARCH - if cfg_path: - self.cfg_path = cfg_path - else: - self.cfg_path = consts.DEFAULT_CONTAINER_CFG_PATH - if distro: self.distro = distro else: @@ -58,125 +53,93 @@ class Container(object): ftest_dir = os.path.dirname(os.path.abspath(__file__)) tests_dir = os.path.dirname(ftest_dir) - libcg_dir = os.path.dirname(tests_dir) - - self.tmp_cfg_path = os.path.join(ftest_dir, consts.TEMP_CONTAINER_CFG_FILE) - try: - Run.run(['rm', '-f', self.tmp_cfg_path]) - except: - pass + # save off the path to the libcgroup source code + self.libcg_dir = os.path.dirname(tests_dir) - Run.run(['cp', self.cfg_path, self.tmp_cfg_path]) - - conf_line = 'lxc.arch = {}'.format(self.arch) - Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True) - - conf_line = 'lxc.mount.entry = {} {} none bind,ro 0 0'.format( - libcg_dir, consts.LIBCG_MOUNT_POINT) - Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True) - - if not self.privileged: - conf_line = 'lxc.idmap = u 0 100000 65536' - Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True) - conf_line = 'lxc.idmap = g 0 100000 65536' - Run.run(['echo', conf_line, '>>', self.tmp_cfg_path], shell_bool=True) def __str__(self): out_str = "{}".format(self.name) out_str += "\n\tdistro = {}".format(self.distro) out_str += "\n\trelease = {}".format(self.release) out_str += "\n\tarch = {}".format(self.arch) - out_str += "\n\tcfg_path = {}".format(self.cfg_path) out_str += "\n\tstop_timeout = {}".format(self.stop_timeout) return out_str - def create(self): - cmd = list() - - if self.privileged: - cmd.append('sudo') - - cmd.append('lxc-create') - cmd.append('-t') - cmd.append( 'download') + # configure the container to meet our needs + def config(self): + ftest_dir = os.path.dirname(os.path.abspath(__file__)) + tests_dir = os.path.dirname(ftest_dir) + libcg_dir = os.path.dirname(tests_dir) - cmd.append('-n') - cmd.append(self.name) + # map our UID and GID to the same UID/GID in the container + cmd = 'printf "uid {} 1000\ngid {} 1000" | sudo lxc config set {} raw.idmap -'.format( + os.getuid(), os.getgid(), self.name) + Run.run(cmd, shell_bool=True) + # add the libcgroup root directory (where we did the build) into + # the container + cmd2 = list() if self.privileged: - cmd.append('sudo') - cmd.append('-f') - cmd.append(self.tmp_cfg_path) - - cmd.append('--') - - cmd.append('-d') - cmd.append(self.distro) - - cmd.append('-r') - cmd.append(self.release) - - cmd.append('-a') - cmd.append(self.arch) - - return Run.run(cmd) + cmd2.append('sudo') + cmd2.append('lxc') + cmd2.append('config') + cmd2.append('device') + cmd2.append('add') + cmd2.append(self.name) + cmd2.append('libcgsrc') # arbitrary name of device + cmd2.append('disk') + # to appease gcov, mount the libcgroup source at the same path as we + # built it. This can be worked around someday by using + # GCOV_PREFIX_STRIP, but that was more difficult to setup than just + # doing this initially + cmd2.append('source={}'.format(self.libcg_dir)) + cmd2.append('path={}'.format(self.libcg_dir)) + + return Run.run(cmd2) - def destroy(self): + def create(self): cmd = list() if self.privileged: cmd.append('sudo') - cmd.append('lxc-destroy') + cmd.append('lxc') + cmd.append('init') + + cmd.append('{}:{}'.format(self.distro, self.release)) - cmd.append('-n') cmd.append(self.name) return Run.run(cmd) - def info(self, cfgname): + def delete(self): cmd = list() if self.privileged: cmd.append('sudo') - cmd.append('lxc-info') - - cmd.append('--config={}'.format(cfgname)) + cmd.append('lxc') + cmd.append('delete') - cmd.append('-n') cmd.append(self.name) return Run.run(cmd) - def rootfs(self): - # try to read lxc.rootfs.path first - ret = self.info('lxc.rootfs.path') - if len(ret.strip()) > 0: - return ret.decode() - - # older versions of lxc used lxc.rootfs. Try that. - ret = self.info('lxc.rootfs') - if len(ret.strip()) == 0: - # we failed to get the rootfs - raise ContainerError('Failed to get the rootfs') - return ret.decode() - def run(self, cntnr_cmd): cmd = list() if self.privileged: cmd.append('sudo') - cmd.append('lxc-attach') + cmd.append('lxc') + cmd.append('exec') - cmd.append('-n') cmd.append(self.name) cmd.append('--') - # concatenate the lxc-attach command with the command to be run + # concatenate the lxc exec command with the command to be run # inside the container if isinstance(cntnr_cmd, str): cmd.append(cntnr_cmd) @@ -193,37 +156,32 @@ class Container(object): if self.privileged: cmd.append('sudo') - cmd.append('lxc-start') - cmd.append('-d') + cmd.append('lxc') + cmd.append('start') - cmd.append('-n') cmd.append(self.name) return Run.run(cmd) - def stop(self, kill=True): + def stop(self, force=True): cmd = list() if self.privileged: cmd.append('sudo') - cmd.append('lxc-stop') + cmd.append('lxc') + cmd.append('stop') - cmd.append('-n') cmd.append(self.name) - if kill: - cmd.append('-k') + if force: + cmd.append('-f') else: - cmd.append('-t') + cmd.append('--timeout') cmd.append(str(self.stop_timeout)) return Run.run(cmd) - def version(self): - cmd = ['lxc-create', '--version'] - return Run.run(cmd) - class ContainerError(Exception): def __init__(self, message, ret): super(RunError, self).__init__(message) diff --git a/tests/ftests/ftests.py b/tests/ftests/ftests.py index 8f11ed6..54eb658 100755 --- a/tests/ftests/ftests.py +++ b/tests/ftests/ftests.py @@ -41,9 +41,6 @@ def parse_args(): parser.add_argument('-n', '--name', help='name of the container', required=False, type=str, default=None) - parser.add_argument('-f', '--config', - help='initial configuration file', - required=False, type=str, default=None) parser.add_argument('-d', '--distro', help='linux distribution to use as a template', required=False, type=str, default=None) @@ -70,9 +67,6 @@ def parse_args(): parser.add_argument('-s', '--suite', help='Test suite to run, e.g. cpuset', required=False, default=consts.TESTS_RUN_ALL_SUITES, type=str) - parser.add_argument('-u', '--unpriv', - help='Run the tests in an unprivileged container', - required=False, action="store_true") parser.add_argument('-v', '--verbose', help='Print all information about this test run', default=True, required=False, action="store_false") @@ -83,12 +77,55 @@ def parse_args(): log.log_level = config.args.loglevel if config.args.logfile: log.log_file = config.args.logfile - if config.args.unpriv: - raise ValueError('Unprivileged containers are not currently supported') - config.container.privileged = False return config +# this function maps the container UID to the host UID. By doing +# this, we can write to a bind-mounted device - and thus generate +# code coverage data in the LXD container +def update_host_subuid(): + subuid_line1 = 'lxd:{}:1'.format(os.getuid()) + subuid_line2 = 'root:{}:1'.format(os.getuid()) + found_line1 = False + found_line2 = False + + with open('/etc/subuid') as ufile: + for line in ufile.readlines(): + if line.strip() == subuid_line1: + found_line1 = True + elif line.strip() == subuid_line2: + found_line2 = True + + if not found_line1: + Run.run('sudo sh -c "echo {} >> /etc/subuid"'.format( + subuid_line1), shell_bool=True) + if not found_line2: + Run.run('sudo sh -c "echo {} >> /etc/subuid"'.format( + subuid_line2), shell_bool=True) + +# this function maps the container GID to the host GID. By doing +# this, we can write to a bind-mounted device - and thus generate +# code coverage data in the LXD container +def update_host_subgid(): + subgid_line1 = 'lxd:{}:1'.format(os.getgid()) + subgid_line2 = 'root:{}:1'.format(os.getgid()) + found_line1 = False + found_line2 = False + + with open('/etc/subgid') as ufile: + for line in ufile.readlines(): + if line.strip() == subgid_line1: + found_line1 = True + elif line.strip() == subgid_line2: + found_line2 = True + + if not found_line1: + Run.run('sudo sh -c "echo {} >> /etc/subgid"'.format( + subgid_line1), shell_bool=True) + if not found_line2: + Run.run('sudo sh -c "echo {} >> /etc/subgid"'.format( + subgid_line2), shell_bool=True) + def setup(config, do_teardown=True, record_time=False): global setup_time start_time = time.time() @@ -101,19 +138,18 @@ def setup(config, do_teardown=True, record_time=False): # log but ignore all exceptions Log.log_debug(e) - config.container.create() - - # make the /libcg directory in the container's rootfs - rootfs = config.container.rootfs() - container_rootfs_path = rootfs.split('=')[1].strip() - Run.run(['sudo', 'mkdir', os.path.join(container_rootfs_path, - consts.LIBCG_MOUNT_POINT)]) + # this command initializes the lxd storage, networking, etc. + Run.run(['sudo', 'lxd', 'init', '--auto']) + update_host_subuid() + update_host_subgid() + config.container.create() + config.container.config() config.container.start() # add the libcgroup library to the container's ld echo_cmd = ['bash', '-c', 'echo {} >> /etc/ld.so.conf.d/libcgroup.conf'.format( - os.path.join('/', consts.LIBCG_MOUNT_POINT, 'src/.libs'))] + os.path.join(consts.LIBCG_MOUNT_POINT, 'src/.libs'))] config.container.run(echo_cmd) config.container.run('ldconfig') if record_time: @@ -240,7 +276,7 @@ def teardown(config, record_time=False): # log but ignore all exceptions Log.log_debug(e) try: - config.container.destroy() + config.container.delete() except Exception as e: # log but ignore all exceptions Log.log_debug(e) diff --git a/tests/ftests/run.py b/tests/ftests/run.py index 0b4e068..419f38f 100644 --- a/tests/ftests/run.py +++ b/tests/ftests/run.py @@ -51,7 +51,7 @@ class Run(object): else: Log.log_debug( "run:\n\tcommand = {}\n\tret = {}\n\tstdout = {}\n\tstderr = {}".format( - ''.join(command), ret, out, err)) + ' '.join(command), ret, out, err)) if ret != 0: raise RunError("Command '{}' failed".format(''.join(command)), -- 2.21.0 _______________________________________________ Libcg-devel mailing list Libcg-devel@lists.sourceforge.net https://lists.sourceforge.net/lists/listinfo/libcg-devel