Skip to content
This repository was archived by the owner on Sep 7, 2020. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
2a778b5
PPM-30: Changes on class ALEntityDocker for compose
odkq Aug 4, 2020
9f26b8a
PPM-30: Define test entrypoint and json config
odkq Aug 4, 2020
bd59982
PPM-30: Dockerfile and docker-compose.yml
odkq Aug 4, 2020
1444905
PPM-30: dctest.py script to wrap docker-compose
odkq Aug 4, 2020
856854b
PPM-30: Add test to boardfarm-ci
odkq Aug 4, 2020
bb1645d
PPM-30: Check return error on _run_shell
odkq Aug 4, 2020
3d819a8
Added device prplmesh_compose.py
odkq Aug 4, 2020
c108e2f
boardfarm: use the name of the container for the status check
rmelotte Aug 6, 2020
7ec7a92
Use original name and docker-name for hostname in environment.py
odkq Aug 6, 2020
61cc3b7
boardfarm-ci: Remove unused code
odkq Aug 11, 2020
dd55701
boardfarm-ci: Commented and reordered Dockerfile
odkq Aug 11, 2020
0e1458d
boardfarm-ci: Scan device names
odkq Aug 11, 2020
d53c32a
boardfarm-ci: Fixes for PEP-8 compliance
odkq Aug 11, 2020
a0d8fe7
boardfarm-ci: Remove unused code
odkq Aug 12, 2020
727d836
boardfarm-ci: Remove commented out line
odkq Aug 12, 2020
9355d53
boardfarm-ci: Removed more unused code
odkq Aug 12, 2020
44af58b
boardfarm-ci: Removed commented out code
odkq Aug 12, 2020
28e53f2
boardfarm-ci: Removed commented-out code
odkq Aug 12, 2020
1df2526
boardfarm-ci: Use relative path
odkq Aug 12, 2020
0042f0e
boardfarm-ci: Added mutual exclusion in argv
odkq Aug 12, 2020
e9d90a3
tests: ALEntity: use name in place of device.docker_name
rmelotte Aug 12, 2020
a999708
boardfarm-ci: Group all apt-get commands together
odkq Aug 12, 2020
a0ae26c
boardfarm-ci: Removed unused CURRENT_ID
odkq Aug 13, 2020
157e5e4
boardfarm-ci: Commented on issue PPM-208
odkq Aug 13, 2020
344ebfe
boardfarm-ci: Better error message on _run_cmd
odkq Aug 13, 2020
b4adb53
boardfarm-ci: Fixed check_status
odkq Aug 13, 2020
d0e1415
boardfarm-ci: Set delay to the default 7 seconds
odkq Aug 13, 2020
c34ceeb
boardfarm-ci: Restored comment on ALEntityDocker's contructor
odkq Aug 13, 2020
599fc48
boardfarm-ci: Added license header to dctest.py
odkq Aug 13, 2020
356793b
boardfarm-ci: Reorder lines to improve readability
odkq Aug 13, 2020
af13310
boardfarm-ci: Fixed typo
odkq Aug 14, 2020
09e27fb
boardfarm-ci: Aesthetical changes to pass PEP-8
odkq Aug 14, 2020
a13b772
boardfarm-ci: Resolved merge conflict in Dockerfile
odkq Aug 14, 2020
62a8125
Merge branch 'master' into feature/PPM-30-boardfarm-docker-compose
odkq Aug 14, 2020
edc4b0a
boardfarm-ci: Added missing jq
odkq Aug 14, 2020
df60f75
boardfarm-ci: Fixes for the new prplmesh/prplmesh URL
odkq Aug 14, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,20 @@ bwl_dummy_unit_tests:
bcl_unit_tests:
extends: .run-test-in-docker

dctest_one_test:
stage: test
script:
- ./dctest.py
artifacts:
paths:
- logs
- results
when: always
tags:
- boardfarm-compose
needs:
- job: build-in-docker

mapf_common_encryption_tests:
extends: .run-test-in-docker

Expand Down
226 changes: 226 additions & 0 deletions dctest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
#!/usr/bin/env python3
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
#
# SPDX-FileCopyrightText: 2019-2020 the prplMesh contributors (see AUTHORS.md)
#
# This code is subject to the terms of the BSD+Patent license.
#
# See LICENSE file for more details.
#
# Launch the test suite using docker and docker-compose. This script wraps
# the creation of the bridge(s) to be able to connect external devices with
# the docker network, launching the service for boardfarm.
#
# As this script is run outside containers, it does not use anything apart
# from Python 3.5 (will work on later versions but only uses 3.5 features)
#
# The best way to make sure no Python 3.5+ features are used is running the
# script with a Python 3.5.0 interpreter. Compile it from:
#
# https://www.python.org/ftp/python/3.5.0/Python-3.5.0.tgz
#
# Also, when calling a function look for 'New in version 3.X' where X > 5
#
from __future__ import print_function # To check for python2 or < 3.5 execution
import argparse
import os
import grp
import getpass
import sys
import json
from subprocess import Popen, PIPE


if not (sys.version_info.major == 3 and sys.version_info.minor >= 5):
print("This script requires Python 3.5 or higher!")
print("You are using Python {}.{}.".format(sys.version_info.major, sys.version_info.minor))
sys.exit(1)


def check_docker_versions():
DOCKER_MAJOR = 19
DC_MAJOR = 1
DC_MINOR = 25
docker_version = os.popen('docker --version').read().split(' ')[2]
docker_major = int(docker_version.split('.')[0])
if docker_major < DOCKER_MAJOR:
fmt = "This script requires docker {}.0 or higher"
print(fmt.format(DOCKER_MAJOR))
print("You are usng version {}".format(docker_version))
sys.exit(1)
dc_version = os.popen('docker-compose --version').read().split(' ')[2]
dc_major = int(dc_version.split('.')[0])
dc_minor = int(dc_version.split('.')[1])
if dc_major < DC_MAJOR:
fmt = "This script requires docker-compose {}.{} or higher"
print(fmt.format(DC_MAJOR, DC_MINOR))
print("You are usng version {}".format(dc_version))
sys.exit(1)
if dc_minor < DC_MINOR:
fmt = "This script requires docker-compose {}.{} or higher"
print(fmt.format(DC_MAJOR, DC_MINOR))
print("You are usng version {}".format(dc_version))
sys.exit(1)


class Services:
def __init__(self, bid=None):
self.scriptdir = os.path.dirname(os.path.realpath(__file__))
os.chdir(self.scriptdir)
self.rootdir = self.scriptdir

if bid is not None:
self.build_id = bid
print('Using ID {}'.format(self.build_id))
# return
else:
self.build_id = self.get_build_id()

self.logdir = os.path.join(self.scriptdir, 'logs')
if not os.path.exists(self.logdir):
os.makedirs(self.logdir)
for device in self._get_device_names():
device_name = '{}-{}'.format(device, self.build_id)
devicedir = os.path.join(self.logdir, device_name)
if not os.path.exists(devicedir):
print('Making {}'.format(devicedir))
os.makedirs(devicedir)

def _get_device_names(self):
jspath = './tests/boardfarm_plugins/boardfarm_prplmesh/prplmesh_config_compose.json'
js = json.loads(open(jspath, 'r').read())
devices = []
for device in js['prplmesh_compose']['devices']:
devices.append(device['name'])
return devices

def get_build_id(self):
ci_pipeline_id = os.getenv('CI_PIPELINE_ID')
if ci_pipeline_id is not None:
return ci_pipeline_id

# Otherwise we are running on the local machine, just find last id
# created and add one
last_id = 0
if not os.path.exists('logs'):
return str(1)

# Search if a directory exists with logs/<device>-<X> and use X+1 as
# id. Get the first device from the json list
search_prefix = self._get_device_names()[0] + '-'
for d in os.listdir('logs'):
if d.startswith(search_prefix):
suffix = d[len(search_prefix):]
isuffix = int(suffix)
if isuffix > last_id:
last_id = isuffix
if last_id == 0:
new_id = 1
else:
new_id = last_id + 1
return str(new_id)

def dc(self, args, interactive=False):
params = ['docker-compose', '-f',
'tools/docker/boardfarm-ci/docker-compose.yml']
params += args
local_env = os.environ
local_env['ROOT_DIR'] = self.rootdir
docker_gid = grp.getgrnam('docker')[2]
local_env['CURRENT_UID_GID'] = str(os.getuid()) + ':' + str(docker_gid)
local_env['RUN_ID'] = self.build_id

if os.getenv('CI_PIPELINE_ID') is None:
# Running locally
local_env['CI_PIPELINE_ID'] = 'latest'
local_env['FINAL_ROOT_DIR'] = self.rootdir
else:
# Running inside gitlab-ci
# Setting a fixed location is needed until
# https://jira.prplfoundation.org/browse/PPM-208 is fixed.
local_env['FINAL_ROOT_DIR'] = '/builds/prpl-foundation/prplmesh/prplMesh'

if not interactive:
proc = Popen(params, stdout=PIPE, stderr=PIPE)
for line in proc.stdout:
print(line.decode(), end='')
proc.stdout.close()
else:
proc = Popen(params)
return_code = proc.wait()
return return_code


def cleanup(rc):
if rc != 0:
print('Return code !=0 -> {}'.format(rc))
if getpass.getuser() == 'gitlab-runner':
os.system('chown -R gitlab-runner:gitlab-runner .')
sys.exit(rc)


if __name__ == '__main__':
check_docker_versions()
parser = argparse.ArgumentParser(description='Dockerized test launcher')
group = parser.add_mutually_exclusive_group()
group.add_argument('--test', dest='test', type=str, help='Test to be run')
group.add_argument('--clean', dest='clean', action='store_true',
help='Clean containers images and networks')
group.add_argument('--build', dest='build', action='store_true',
help='Rebuild containers')
group.add_argument('--shell', dest='shell', action='store_true',
help='Run a shell on the bf container')
group.add_argument('--comp', dest='comp', action='store_true',
help='Pass the rest of arguments to docker-compose')
parser.add_argument('--id', dest='bid', type=str,
help='Specify the id to use for build/shell/comp/clean')
args, rest = parser.parse_known_args()

if os.getenv('CI_PIPELINE_ID') is not None:
args.bid == os.getenv('CI_PIPELINE_ID')

if args.comp:
if args.bid is None:
print('Specify --id for the --comp parameter')
sys.exit(0)
services = Services(bid=args.bid)
if len(rest) == 0:
print('Usage: dctest --id <id> --comp <arguments to docker-compose>')
sys.exit(1)
sys.exit(services.dc(rest, interactive=True))
else:
if len(rest) > 0:
print('Unknown parameters: {}'.format(rest))
sys.exit(1)

if args.clean:
if args.bid is None:
print('Specify --id for the --clean parameter')
sys.exit(0)
services = Services(bid=args.bid)
rc = services.dc(['down', '--remove-orphans', '--rmi', 'all'])
cleanup(rc)
elif args.shell:
if not args.bid:
print('Specify --id for the shell parameter')
sys.exit(0)
services = Services(bid=args.bid)
rc = services.dc(['run', '--rm', '--service-ports', '--entrypoint',
'/bin/bash', 'boardfarm'], interactive=True)
cleanup(rc)
elif args.build:
if not args.bid:
print('Specify --id for the build parameter')
sys.exit(0)
services = Services(bid=args.bid)
rc = services.dc(['build'], interactive=True)
cleanup(rc)
else:
if args.bid:
services = Services(bid=args.bid) # With new build id
else:
services = Services() # With new build id
rc = services.dc(['run', '--rm', '--service-ports', '--use-aliases',
'boardfarm'], interactive=True)
cleanup(rc)
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
# See LICENSE file for more details.

import pexpect
from typing import Dict

from boardfarm.devices import linux

Expand All @@ -16,11 +17,15 @@ class CommandError(Exception):
class PrplMeshBase(linux.LinuxDevice):
"""PrplMesh abstract device."""

def _run_shell_cmd(self, cmd: str = "", args: list = None, timeout: int = 30):
def _run_shell_cmd(self, cmd: str = "", args: list = None, timeout: int = 30,
env: Dict[str, str] = None):
"""Wrapper that executes command with specified args on host machine and logs output."""

res, exitstatus = pexpect.run(cmd, args=args, timeout=timeout, encoding="utf-8",
withexitstatus=1)
if env is not None:
res, exitstatus = pexpect.run(cmd, args=args, timeout=timeout, encoding="utf-8",
withexitstatus=1, env=env)
else:
res, exitstatus = pexpect.run(cmd, args=args, timeout=timeout, encoding="utf-8",
withexitstatus=1)
entry = " ".join((cmd, " ".join(args)))
if exitstatus != 0:
raise CommandError("Error executing {}:\n{}".format(entry, res))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
# SPDX-License-Identifier: BSD-2-Clause-Patent
# SPDX-FileCopyrightText: 2020 the prplMesh contributors (see AUTHORS.md)
# This code is subject to the terms of the BSD+Patent license.
# See LICENSE file for more details.

import os
import time

import boardfarm
from environment import ALEntityDocker, _get_bridge_interface
from .prplmesh_base import PrplMeshBase
from sniffer import Sniffer

rootdir = os.path.abspath(os.path.join(os.path.dirname(__file__), '../../../..'))


class PrplMeshCompose(PrplMeshBase):
"""Dockerized prplMesh device."""

model = ("prplmesh_compose")
agent_entity = None
controller_entity = None

def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs

config = kwargs.get("config", kwargs)

# List of device's consoles test can interact with
self.consoles = [self]

# Getting unic ID to distinguish devices and network they belong to
self.unique_id = os.getenv("RUN_ID")
self.user_id = os.getenv("SUDO_USER", os.getenv("USER", ""))

self.name = config.get("name", "prplmesh_compose")
self.docker_name = "-".join((self.name, self.unique_id))
print('config.get("name") {}'.format(config.get("name")))
self.role = config.get("role", "agent")
self.cleanup_cmd = config.get("cleanup_cmd", None)
self.conn_cmd = config.get("conn_cmd", None)
self.delay = config.get("delay", 7)
self.docker_network = "boardfarm-ci_default"

if self.role == "controller":
self._docker_compose(["-d", "--name", self.docker_name, "controller"],
"run", "start-controller-agent")
time.sleep(self.delay)
self.controller_entity = \
ALEntityDocker(self.docker_name, device=self, is_controller=True, compose=True)
else:
self._docker_compose(["-d", "--name", self.docker_name, "agent"],
"run", "start-agent")
time.sleep(self.delay)
self.agent_entity = ALEntityDocker(self.docker_name, device=self,
is_controller=False, compose=True)

self.wired_sniffer = Sniffer(_get_bridge_interface(self.docker_network),
boardfarm.config.output_dir)
self.check_status()

def _docker_compose(self, args, parameter=None, start=None):
print('_docker_compose: args {}'.format(args))
yml_path = "tools/docker/boardfarm-ci/docker-compose.yml"
full_args = ["-f", os.path.join(rootdir, yml_path)]
if parameter == "run":
log_path = os.path.join(rootdir, "logs/{}".format(self.docker_name))
if not os.path.exists(log_path):
os.mkdir(log_path)

pipeline_id = os.getenv('CI_PIPELINE_ID')
if pipeline_id is None or pipeline_id == 'latest':
vol = '{}:/tmp/{}/beerocks/logs'.format(log_path, self.user_id)
else:
vol = '{}:/tmp/beerocks/logs'.format(log_path)

full_args += ["run", "--rm", "-v", vol]
full_args += args

print('_docker_compose: {}'.format(' '.join(full_args)))
if os.getenv('CI_PIPELINE_ID') is None:
print('Setting CI_PIPELINE_ID "latest"')
os.environ['CI_PIPELINE_ID'] = 'latest'
self._run_shell_cmd("docker-compose",
full_args, env=os.environ)
else:
self._run_shell_cmd("docker-compose", full_args)

def __del__(self):
self._run_shell_cmd("docker", ["stop", self.docker_name])
self._run_shell_cmd("docker", ["container", "rm", "-f", self.docker_name])

def check_status(self):
"""Method required by boardfarm.

It is used by boardfarm to indicate that spawned device instance is ready for test
and also after test - to insure that device still operational.
"""
self._run_shell_cmd("printf",
["device_get_info", "|", "nc", "-w", "1", self.docker_name, "8002"])

def isalive(self):
"""Method required by boardfarm.

States that device is operational and its consoles are accessible.

"""
return True

def prprlmesh_status_check(self):
self.check_status()
return True
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ def check_status(self):
and also after test - to insure that device still operational.
"""
self._run_shell_cmd("printf",
["device_get_info", "|", "nc", "-w", "1", "controller-rme", "8002"])
["device_get_info", "|", "nc", "-w", "1", self.docker_name, "8002"])

def isalive(self):
"""Method required by boardfarm.
Expand Down
Loading