Skip to content

Commit 7da84b5

Browse files
documentation
1 parent acd54ca commit 7da84b5

File tree

8 files changed

+162
-43
lines changed

8 files changed

+162
-43
lines changed

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,16 @@ This catalog contains the config data for the tests run in https://testing.stack
99
* [platforms.yaml](catalog/platforms.yaml) contains the definition of the test system vendors and platforms
1010
* [operator-tests.yaml](catalog/operator-tests.yaml) defines the integration tests for our operators
1111

12+
See this [Nuclino page](https://app.nuclino.com/Stackable/Engineering/Configuring-Test-Jobs-4e993d84-19b4-4081-846d-738f9f38573d) for further information.
13+
1214
## Apps
1315

14-
Under [apps/](apps/), we maintain a bunch of Dockerized applications for the maintenance of Jenkins and to run the tests.
16+
Under [apps/](apps/README.md), we maintain a bunch of Dockerized applications for the maintenance of Jenkins and to run the tests.
1517

1618
## Seed Job
1719

1820
The [seed job pipeline](jenkins/jobbuilder.groovy) to populate the Jenkins
21+
22+
## Tools
23+
24+
The [tools folder](tools/) contains Dockerized tools to be run in the maintenance jobs. They are not strictly related to testing, but found their home here because this is the only Jenkins we have.

apps/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# testing.stackable.tech apps
2+
3+
For running our testing platform, we need to perform complex jobs in Jenkins. These jobs are coded in Dockerized apps. As they have common tasks, we maintain a shared codebase for these apps and build different Docker images for them.
4+
5+
## The Apps
6+
7+
We have these two apps:
8+
9+
* **Jenkins Job Builder** is a "seed job" which (re)creates all the jobs in our Jenkins.
10+
* **Operator Test Runner** is an app which creates/terminates K8s clusters and runs operator integration tests in them.
11+
12+
## Project Structure
13+
14+
The code is documented inline, here's the project structure:
15+
16+
* [docker/](docker/) contains the Docker build resources (`Dockerfile` + ...) for the apps
17+
* [jjb/](jjb/) contains the templates for the jobs for the **Jenkins Job Builder** app.
18+
* [src/](src/) contains the Python sources.
19+
* [src/modules/](src/modules/) contains the common Python modules of this project
20+
* [jenkins-job-builer.py](src/jenkins-job-builder.py) is the main program of the **Jenkins Job Builder** app.
21+
* [operator-test-runner.py](src/operator-test-runner.py) is the main program of the **Operator Test Runner** app.
22+
* [build.sh](build.sh) lets you build the Docker images locally.
23+
24+
## Build process
25+
26+
The [seed job](../jenkins/jobbuilder.groovy) which is created during Jenkins setup and executed every 5 minutes does build the apps as Docker images inside the Jenkins. They are tagged as `latest` and used by the Jenkins jobs, so every code change pushed to the `main` branch is reflected in near-real-time. No external Docker image registry like Nexus or Harbor is needed here.

apps/src/jenkins-job-builder.py

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,40 @@
1+
"""
2+
Main module of the Jenkins Job builder application
3+
4+
See https://jenkins-job-builder.readthedocs.io/en/latest/
5+
"""
16
import os
27
from jinja2 import Template
38
from subprocess import PIPE, Popen
49
import json
510

611
import modules.catalog as catalog
712

13+
# values of the params (given as env vars during Docker run)
814
param_jenkins_url = None
915
param_jenkins_username = None
1016
param_jenkins_password = None
1117

18+
# keys for the env vars
1219
PARAM_KEY_JENKINS_URL = 'JENKINS_URL'
1320
PARAM_KEY_JENKINS_USERNAME = 'JENKINS_USERNAME'
1421
PARAM_KEY_JENKINS_PASSWORD = 'JENKINS_PASSWORD'
1522

1623
SLACK_CHANNEL = '#team-testing'
17-
1824
CLUSTER_LOGGING_ENDPOINT = 'https://search.t2.stackable.tech'
1925
OPENSEARCH_DASHBOARDS_URL = 'https://logs.t2.stackable.tech'
2026

2127
def get_platform_metadata():
28+
"""
29+
Creates a dict containing display name and version list for a given platform.
30+
"""
2231
return { p['id']: { 'name': p['name'], 'versions': [ v for v in p['versions']] } for p in catalog.platforms }
2332

24-
2533
def init():
2634
"""
2735
Initializes this app, checks if all params are provided as environment variables.
36+
37+
Returns True if initialization succeeded, False otherwise.
2838
"""
2939
global param_jenkins_url
3040
global param_jenkins_username
@@ -48,24 +58,30 @@ def init():
4858

4959
return True
5060

51-
5261
def generate_jjb_config():
62+
"""
63+
Creates the config file for the JJB util using Jinja templating.
64+
"""
5365
with open("/jjb/jjb.conf.j2", "r") as t:
5466
template = Template(t.read())
5567
with open("/jjb/jjb.conf", "w") as f:
5668
f.write(template.render(os.environ))
5769
f.close()
5870

59-
6071
def generate_jjb_file_maintenance_jobs():
72+
"""
73+
Creates the JJB job definition file defining the maintenance jobs using Jinja templating.
74+
"""
6175
with open("/jjb/maintenance.j2", "r") as t:
6276
template = Template(t.read())
6377
with open("/jjb/maintenance.yaml", "w") as f:
6478
f.write(template.render(os.environ))
6579
f.close()
6680

67-
6881
def generate_jjb_file_operator_weekly_test_jobs(platform_metadata):
82+
"""
83+
Creates the JJB job definition files defining the weekly operator test jobs using Jinja templating.
84+
"""
6985
with open("/jjb/trigger-weekly-tests.groovy.j2", "r") as t:
7086
template = Template(t.read())
7187
with open("/jjb/trigger-weekly-tests.groovy", "w") as f:
@@ -79,6 +95,9 @@ def generate_jjb_file_operator_weekly_test_jobs(platform_metadata):
7995

8096

8197
def generate_jjb_file_operator_custom_test_jobs(platform_metadata, operator_versions):
98+
"""
99+
Creates the JJB job definition file defining the custom operator test jobs using Jinja templating.
100+
"""
82101
with open("/jjb/operator_custom_tests.j2", "r") as t:
83102
template = Template(t.read())
84103
with open("/jjb/operator_custom_tests.yaml", "w") as f:
@@ -87,6 +106,9 @@ def generate_jjb_file_operator_custom_test_jobs(platform_metadata, operator_vers
87106

88107

89108
def execute_jjb():
109+
"""
110+
Executes JJB for all job definition files
111+
"""
90112
os.system(f"jenkins-jobs --conf /jjb/jjb.conf update /jjb/maintenance.yaml")
91113
os.system(f"jenkins-jobs --conf /jjb/jjb.conf update /jjb/operator_weekly_tests.yaml")
92114
os.system(f"jenkins-jobs --conf /jjb/jjb.conf update /jjb/operator_custom_tests.yaml")

apps/src/modules/catalog.py

Lines changed: 35 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,20 @@
1-
# import sys
2-
# import os
3-
# import os.path
4-
import hiyapyco
5-
# from jinja2 import Template
6-
7-
# platform_name = None
8-
# k8s_version = None
9-
# metadata_annotations = {}
1+
"""
2+
This module enables access to the catalog
3+
"""
104

11-
# catalog_platforms = None
5+
import hiyapyco
126

7+
# variables holding the catalog data
138
providers = []
149
platforms = []
1510
operator_tests = []
1611

1712
def read_providers(logger):
13+
"""
14+
Reads the list of cluster providers from platforms.yaml
15+
16+
logger logger (String-consuming function)
17+
"""
1818
global providers
1919
platforms_yaml = hiyapyco.load("/platforms.yaml")
2020
if not 'providers' in platforms_yaml:
@@ -24,10 +24,15 @@ def read_providers(logger):
2424
if len(providers) == 0:
2525
logger('platforms.yaml does not contain providers.')
2626
return False
27-
# TODO More checks to make sure the catalog file is usable
27+
# TODO More syntax checks to make sure the catalog file is usable
2828
return True
2929

3030
def read_platforms(logger):
31+
"""
32+
Reads the list of cluster platforms from platforms.yaml
33+
34+
logger logger (String-consuming function)
35+
"""
3136
global platforms
3237
platforms = hiyapyco.load("/platforms.yaml")['platforms']
3338
platforms_yaml = hiyapyco.load("/platforms.yaml")
@@ -38,21 +43,29 @@ def read_platforms(logger):
3843
if len(platforms) == 0:
3944
logger('platforms.yaml does not contain platforms.')
4045
return False
41-
# TODO More checks to make sure the catalog file is usable
46+
# TODO More syntax checks to make sure the catalog file is usable
4247
return True
4348

44-
4549
def read_operator_tests(logger):
50+
"""
51+
Reads the list of operator test definitions from operator-tests.yaml
52+
53+
logger logger (String-consuming function)
54+
"""
4655
global operator_tests
4756
operator_tests = hiyapyco.load("/operator-tests.yaml")
4857
if len(operator_tests) == 0:
4958
logger('operator-tests.yaml does not contain any tests.')
5059
return False
51-
# TODO More checks to make sure the catalog file is usable
60+
# TODO More syntax checks to make sure the catalog file is usable
5261
return True
5362

54-
5563
def read_catalog(logger):
64+
"""
65+
Read all catalogs
66+
67+
logger logger (String-consuming function)
68+
"""
5669
if not read_providers(logger):
5770
return False
5871
if not read_platforms(logger):
@@ -64,14 +77,19 @@ def read_catalog(logger):
6477
logger(f"Read {len(operator_tests)} operator tests: [{','.join([ot['id'] for ot in operator_tests])}]")
6578
return True
6679

67-
6880
def get_platform(platform):
81+
"""
82+
Get the platform matching the platform string.
83+
If the platform string matches an ID, that platform is chosen.
84+
Otherwise, we search for a matching name attribute.
85+
86+
platform string which identifies a platform
87+
"""
6988
matching_platform = next(filter(lambda p: p['id']==platform, platforms), None)
7089
if not matching_platform:
7190
matching_platform = next(filter(lambda p: p['name']==platform, platforms), None)
7291
return matching_platform
7392

74-
7593
def get_spec_for_operator_test(operator_test, platform, logger):
7694
"""
7795
Reads the cluster spec for the given operator/platform combination.

apps/src/modules/cluster.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
"""
2+
This module is a facade to the cluster providers.
3+
4+
Currently, we offer clusters for replicated.com and IONOS.
5+
"""
6+
17
import modules.provider_ionos as ionos
28
import modules.provider_replicated as replicated
39

@@ -21,10 +27,13 @@ def create_cluster(provider_id, id, spec, platform_version, cluster_info_file, l
2127
if 'ionos' == provider_id:
2228
return ionos.create_cluster(id, spec, platform_version, cluster_info_file, logger)
2329

24-
2530
def terminate_cluster(provider_id, cluster, logger):
2631
"""
2732
Terminates the given cluster. (Blocking operation)
33+
34+
provider_id ID of the cloud provider / vendor
35+
cluster vendor-specific cluster object which was previously returned by create_cluster()
36+
logger logger (String-consuming function)
2837
"""
2938
if 'replicated' == provider_id:
3039
return replicated.terminate_cluster(cluster, logger)

apps/src/modules/cluster_logging.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
"""
2+
This module installs the "cluster logging" on a test cluster.
3+
4+
"cluster logging" refers to the ability of a cluster to forward the logs
5+
(K8s Events, Stackable Operator, Stackable Products) to our centralized
6+
log index.
7+
"""
8+
19
from modules.command import run_command
210

311
def install_cluster_logging(cluster_id, endpoint, username, password, logger):
@@ -54,4 +62,3 @@ def install_cluster_logging(cluster_id, endpoint, username, password, logger):
5462
return False
5563

5664
return True
57-

apps/src/modules/command.py

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,28 @@
1+
"""
2+
This util module helps you to run system processes
3+
"""
4+
15
from subprocess import PIPE, TimeoutExpired, Popen
26
from time import sleep
37

4-
58
def output_to_string_array(byte_stream):
9+
"""
10+
Splits the binary output of the command into an array of lines/strings
11+
"""
612
return [line.strip() for line in byte_stream.decode("utf-8").splitlines()]
713

8-
914
def run_command(command, description, timeout=60, retries=1, delay=60):
15+
"""
16+
Execute command.
17+
18+
command command
19+
description command (short) description
20+
timeout time [seconds], after which system call is killed
21+
retries number of tries to execute command if it returns <> 0
22+
delay time [seconds] between two (re)tries.
23+
24+
Returns tuple (exit_code, output)
25+
"""
1026
while retries > 0:
1127
exit_code, output = _run_command(command, description, timeout)
1228
if exit_code == 0:
@@ -15,11 +31,15 @@ def run_command(command, description, timeout=60, retries=1, delay=60):
1531
sleep(delay)
1632
return exit_code, output
1733

18-
1934
def _run_command(command, description, timeout=60):
2035
"""
21-
Execute command.
22-
Returns tuple (exit_code, output as multi-line)
36+
Execute command (module-internal)
37+
38+
command command
39+
description command (short) description
40+
timeout time [seconds], after which system call is killed
41+
42+
Returns tuple (exit_code, output)
2343
"""
2444
proc = Popen(['/bin/bash', '-c', command], stdout=PIPE, stderr=PIPE)
2545
try:

0 commit comments

Comments
 (0)