-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
e2e-tests: Add script to create e2e test targets
Factory and tag for targets are specified using environment variables. Signed-off-by: Andre Detsch <[email protected]>
- Loading branch information
Showing
1 changed file
with
342 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,342 @@ | ||
|
||
""" | ||
This script creates a set of targets that match the ones expected by e2e-tests.py script. | ||
Its use is not supported, and should be executed only by aktualizr-lite developers. | ||
It depends on fioctl, fiopush, ostree and git. | ||
USER_TOKEN, FACTORY, TAG environment variables need to be set. | ||
""" | ||
|
||
from http import HTTPStatus | ||
from requests.exceptions import HTTPError | ||
import os | ||
import requests | ||
import subprocess | ||
import sys | ||
import time | ||
import yaml | ||
|
||
local_dir = os.path.abspath("e2e-test-targets") | ||
aklite_path = os.path.abspath(os.getcwd()) | ||
|
||
fiopush_cmd = "fiopush" | ||
fioctl_cmd = "fioctl" | ||
|
||
user_token = os.getenv("USER_TOKEN") | ||
if not user_token: | ||
print("USER_TOKEN environment variable not set") | ||
sys.exit() | ||
|
||
factory = os.getenv("FACTORY") | ||
if not factory: | ||
print("FACTORY environment variable not set") | ||
sys.exit() | ||
|
||
tag = os.getenv("TAG") | ||
if not tag: | ||
print("TAG environment variable not set") | ||
sys.exit() | ||
|
||
def create_ostree_repo(): | ||
if os.path.exists(local_dir): | ||
raise Exception(f"{local_dir} directory already exists. Remove it before running") | ||
|
||
ostree_path = os.path.abspath(os.path.join(local_dir, "small-ostree")) | ||
|
||
os.mkdir(local_dir) | ||
os.mkdir(ostree_path) | ||
os.chdir(ostree_path) | ||
repo_dir = os.path.join(ostree_path, "repo") | ||
os.system(f"ostree --repo={repo_dir} init --mode=archive") | ||
|
||
tree_path = os.path.join(ostree_path, "tree") | ||
os.mkdir(tree_path) | ||
|
||
ostree_version_txt = os.path.join(tree_path, "test_ostree.txt") | ||
ostree_hashes = {} | ||
bad_ostree_versions = { 2, 5 } | ||
for ostree_version in range(1, 6): | ||
make_sys_rootfs_cmd = os.path.join(aklite_path, "tests", "make_sys_rootfs.sh") | ||
os.system(f"{make_sys_rootfs_cmd} {tree_path} {tag} intel-corei7-64 lmp") | ||
if ostree_version in bad_ostree_versions: | ||
os.system("rm -rf tree/boot") | ||
|
||
with open(ostree_version_txt, 'w') as f: | ||
f.write(f"OSTREE_{ostree_version}") | ||
|
||
sp = subprocess.run(["ostree", "--repo=repo", "commit", "--branch=main", tree_path], capture_output=True) | ||
ostree_hashes[ostree_version] = sp.stdout.decode('utf-8').strip() | ||
|
||
for ostree_version in ostree_hashes: | ||
print(f"OSTREE_HASH_{ostree_version}={ostree_hashes[ostree_version]}") | ||
return repo_dir, ostree_hashes | ||
|
||
|
||
def add_tag_to_ci(tag): | ||
os.system(f"[ -d ci-scripts ] || git clone https://source.foundries.io/factories/{factory}/ci-scripts.git") | ||
with open("ci-scripts/factory-config.yml") as stream: | ||
try: | ||
ci_script_yaml = yaml.safe_load(stream) | ||
except yaml.YAMLError as exc: | ||
print(exc) | ||
|
||
ci_script_yaml["lmp"]["tagging"][f"refs/heads/{tag}"] = [{'tag': tag}] | ||
ci_script_yaml["containers"]["tagging"][f"refs/heads/{tag}"] = [{'tag': tag}] | ||
|
||
with open("ci-scripts/factory-config.yml", "w") as stream: | ||
try: | ||
yaml.safe_dump(ci_script_yaml, stream) | ||
except yaml.YAMLError as exc: | ||
print(exc) | ||
|
||
print(ci_script_yaml) | ||
os.chdir("ci-scripts") | ||
os.system(f"git commit -s --no-gpg-sign factory-config.yml -m 'Adding tag {tag} to CI'") | ||
os.system(f"git push") | ||
os.chdir("..") | ||
|
||
def set_offline_containers(enabled): | ||
os.system(f"[ -d ci-scripts ] || git clone https://source.foundries.io/factories/{factory}/ci-scripts.git") | ||
|
||
ci_script_yaml = None | ||
with open("ci-scripts/factory-config.yml") as stream: | ||
try: | ||
ci_script_yaml = yaml.safe_load(stream) | ||
except yaml.YAMLError as exc: | ||
print(exc) | ||
|
||
ci_script_yaml["containers"]["offline"] = {"enabled": enabled} | ||
with open("ci-scripts/factory-config.yml", "w") as stream: | ||
try: | ||
yaml.safe_dump(ci_script_yaml, stream) | ||
except yaml.YAMLError as exc: | ||
print(exc) | ||
|
||
print(ci_script_yaml) | ||
os.chdir("ci-scripts") | ||
os.system(f"git commit -s --no-gpg-sign factory-config.yml -m 'Setting offline containers enabled = {enabled}'") | ||
os.system(f"git push") | ||
os.chdir("..") | ||
|
||
def write_file(filename, content): | ||
with open(filename, "w") as stream: | ||
stream.write(content) | ||
|
||
def create_app(base_http_port, http_instances_count, reference_app_base_port, break_build=False, script_suffix="", message_prefix=""): | ||
# script_suffix is used to break execution of app | ||
|
||
app_name = f"shellhttpd_base_{base_http_port}" | ||
if not os.path.exists(app_name): | ||
os.mkdir(app_name) | ||
os.chdir(app_name) | ||
|
||
if reference_app_base_port: | ||
ref_app_name = f"shellhttpd_base_{reference_app_base_port}" | ||
else: | ||
ref_app_name = app_name | ||
docker_file_str = """FROM alpine | ||
COPY shellhttpd_*.sh /usr/local/bin/ | ||
""" | ||
if break_build: | ||
docker_file_str += "BREAKING_APPS_BUILD!\n" | ||
write_file("Dockerfile", docker_file_str) | ||
|
||
with open("docker-build.conf", "w") as stream: | ||
stream.write("""# Allow CI loop to unit test the container by running a command inside it | ||
TEST_CMD="/bin/true" | ||
""") | ||
|
||
http_script = \ | ||
"""#!/bin/sh -e | ||
PORT="${PORT-8080}" | ||
MSG="${MSG-OK-$0}" | ||
RESPONSE="HTTP/1.1 200 OK\r\n\r\n${MSG}\r\n" | ||
while true; do | ||
echo -en "$RESPONSE" | nc -l -p "${PORT}" || true | ||
echo "= $(date) =============================" | ||
done | ||
""" | ||
|
||
services_str = "" | ||
for i in range(1, http_instances_count+1): | ||
|
||
if not reference_app_base_port: | ||
write_file(f"shellhttpd_cmd_{i}.sh", http_script) | ||
os.chmod(f"shellhttpd_cmd_{i}.sh", 0o775) | ||
|
||
services_str += \ | ||
f""" | ||
httpd_{i}: | ||
image: hub.foundries.io/{factory}/{ref_app_name}:latest | ||
restart: always | ||
command: /usr/local/bin/shellhttpd_cmd_{i}{script_suffix}.sh | ||
ports: | ||
- {base_http_port + i}:${{PORT-8080}} | ||
environment: | ||
MSG: "${{MSG-{message_prefix}Hello world from e2e test port {base_http_port + i}}}" | ||
""" | ||
|
||
write_file("docker-compose.yml", | ||
f"""version: '3.2' | ||
services: | ||
{services_str} | ||
""") | ||
os.chdir("..") | ||
|
||
def add_target(hash, tag, factory): | ||
cmd = [fioctl_cmd, "targets", "add", "--type", "ostree", "--tags", tag, "--src-tag", "main", "intel-corei7-64", hash, "--factory", factory] | ||
sp = subprocess.run(cmd, capture_output=True) | ||
output = sp.stdout.decode('utf-8').strip() | ||
lines = output.splitlines() | ||
version_line = [ x for x in lines if x.strip().startswith('"version":') ] | ||
if not version_line: | ||
return 0 | ||
else: | ||
return int(version_line[0].split(":")[1].strip('" ')) | ||
|
||
def push_apps(message, tag): | ||
os.system(f"git add *; git commit --no-gpg-sign -m '{message}' && git push origin {tag}") | ||
|
||
def retrieve_api_request(url, token, method="get", **kwargs): | ||
authentication = { | ||
"OSF-TOKEN": token, | ||
} | ||
retries = 3 | ||
retry_codes = [ | ||
HTTPStatus.TOO_MANY_REQUESTS, | ||
HTTPStatus.INTERNAL_SERVER_ERROR, | ||
HTTPStatus.BAD_GATEWAY, | ||
HTTPStatus.SERVICE_UNAVAILABLE, | ||
HTTPStatus.GATEWAY_TIMEOUT, | ||
] | ||
|
||
requests_method = getattr(requests, method) | ||
call_kwargs = { | ||
"headers": authentication | ||
} | ||
call_kwargs.update(kwargs) | ||
|
||
for n in range(retries): | ||
try: | ||
build_request = requests_method(url, **call_kwargs) | ||
build_request.raise_for_status() | ||
return build_request.json() | ||
except HTTPError as exc: | ||
code = exc.response.status_code | ||
if code in retry_codes: | ||
# retry after n seconds | ||
time.sleep(n) | ||
continue | ||
raise | ||
|
||
def get_api_builds(factory, user_token): | ||
# Get the first page only (which contains the latest builds) | ||
domain = "foundries.io" | ||
url = f"https://api.{domain}/projects/{factory}/lmp/builds/" | ||
return retrieve_api_request(url, user_token).get("data") | ||
|
||
def wait_jobs_execution(factory, user_token): | ||
print(f"Waiting for jobs in factory {factory} to finish") | ||
last_logged_pending_builds = None | ||
while True: | ||
reply = get_api_builds(factory, user_token) | ||
if not reply: | ||
time.sleep(5) | ||
continue | ||
|
||
pending_builds = [ x for x in reply["builds"] if x['status'] in {"RUNNING", "RUNNING_WITH_FAILURES", "PROMOTED", "QUEUED"} or [ y for y in x["runs"] if y["status"] in {"RUNNING", "RUNNING_WITH_FAILURES", "PROMOTED", "QUEUED"} ] ] | ||
if pending_builds: | ||
if last_logged_pending_builds != pending_builds: | ||
print(f"Pending builds ({len(pending_builds)}): {pending_builds}\n") | ||
last_logged_pending_builds = pending_builds | ||
if len(pending_builds) == 1: | ||
time.sleep(10) | ||
else: | ||
time.sleep(60) | ||
else: | ||
break | ||
print(f"Done waiting for jobs in factory {factory} to finish") | ||
|
||
if __name__ == "__main__": | ||
repo_dir, ostree_hashes = create_ostree_repo() | ||
|
||
os.system(f"{fiopush_cmd} -factory {factory} -repo {repo_dir} -token {user_token}") | ||
|
||
os.chdir(local_dir) | ||
|
||
add_tag_to_ci(tag) | ||
|
||
wait_jobs_execution(factory, user_token) | ||
|
||
set_offline_containers(False) | ||
|
||
os.system(f"[ -d containers ] || git clone https://source.foundries.io/factories/{factory}/containers.git") | ||
os.chdir("containers") | ||
os.system(f"git checkout {tag} || git checkout -B {tag}") | ||
|
||
os.system("git rm -r *") | ||
push_apps("Clear all apps", tag) | ||
|
||
wait_jobs_execution(factory, user_token) | ||
|
||
base_target_version = add_target(ostree_hashes[1], tag, factory) | ||
add_target(ostree_hashes[2], tag, factory) | ||
add_target(ostree_hashes[3], tag, factory) | ||
|
||
os.chdir("..") | ||
set_offline_containers(True) | ||
|
||
os.chdir("containers") | ||
create_app(10000, 5, None) | ||
push_apps("Add first app", tag) | ||
|
||
create_app(20000, 1, None) | ||
create_app(30000, 2, 10000) | ||
push_apps("Add more app", tag) | ||
|
||
create_app(20000, 1, None, False, "_wrong") | ||
push_apps("Break shellhttpd base 20000 app", tag) | ||
|
||
create_app(20000, 1, None, False, "_still_wrong", tag) | ||
push_apps("Break shellhttpd base 20000 app again", tag) | ||
|
||
create_app(20000, 1, None, True) | ||
push_apps("Break shellhttpd base 20000 app build", tag) | ||
|
||
create_app(20000, 1, None,) | ||
push_apps("Fixing shellhttpd base 20000 app", tag) | ||
|
||
create_app(20000, 1, None, False, "", "Updated ") | ||
push_apps("Updating shellhttpd base 20000 app", tag) | ||
|
||
wait_jobs_execution(factory, user_token) | ||
|
||
add_target(ostree_hashes[4], tag, factory) | ||
|
||
add_target(ostree_hashes[5], tag, factory) | ||
|
||
print(f""" | ||
Test targets successfully created | ||
* Required environment variables for e2e tests: | ||
export FACTORY={factory} | ||
export BASE_TARGET_VERSION={base_target_version} | ||
export USER_TOKEN={user_token} | ||
export TAG={tag} | ||
* Create offline bundles:" | ||
mkdir -p offline-bundles | ||
for version_offset in 0 1 2 3 4 5 6 8 9 10 11; do | ||
version=$[ $version_offset + $BASE_TARGET_VERSION ]; | ||
echo $version; | ||
fioctl targets offline-update --ostree-repo-source=e2e-test-environment/small-ostree/repo --allow-multiple-targets intel-corei7-64-lmp-$version -f ${{FACTORY}} offline-bundles/unified --tag ${{TAG}} || break; | ||
done | ||
* Run tests:" | ||
./dev-shell-e2e-test.sh pytest e2e-test.py | ||
""") |