Skip to content

Commit ab62064

Browse files
ThomasMichael1811nihussmannThomas Michael
authored
Introduce integrationtests with failsafe (#252)
* Add failsafe, migrate e2e script and create additional ITs old comments: IT should run on main, everytime Create test for Cert Manager fix namespace test disable parallel try to use local maven repo enable parallel for unit tests enable some namespaces in tests disable tests for prometheus jenkins reuse maven adopt test println for namespaces retry change to 7 refactor tests add ArgoCD Test add refactor tests configure pom for Jenkins Long IT configure Jenkins Long IT typo fix Jenkinsfile refactor Jenkinsfile and long running tests remove e2e test change jenkinsfile order fix jenkinsfile add profile and jenkins option to run long running tests fix groovy delete old test add Jenkinstest clean rename class add IP direkt e2e test runs add test for argoCD E2E script integration add println for pods for tests remove plain kubectl from test test check ingress services via kubectl test comment out add systemout for information remove duplicated argocd add argocd to test GOP and add smoketests Jenkinsfile: find failsafe reports Jenkinsfile: re useLocal maven repo Jenkinsfile: Store logs when either IT fails Jenkinsfile: Run ITs in parallel Jenkinsfile: Run ITs in host network To be able to access kubeconfig via 0.0.0.0:PORT, same as in 'start gitops playground' stage adopt jenkinsfile and test for kubeconfig adopt jenkinsfile add second test for jenkins pod add first test for jenkins pod adding maven failsafe plugin for integrationtests * review changes * Update src/test/groovy/com/cloudogu/gitops/integration/features/PrometheusStackTestIT.groovy Co-authored-by: Niklas Hußmann <[email protected]> * review changes * Update docs/developers.md Co-authored-by: Niklas Hußmann <[email protected]> * Update docs/developers.md Co-authored-by: Niklas Hußmann <[email protected]> * Update docs/developers.md Co-authored-by: Niklas Hußmann <[email protected]> * Update docs/developers.md Co-authored-by: Niklas Hußmann <[email protected]> * Update docs/developers.md Co-authored-by: Niklas Hußmann <[email protected]> * Update pom.xml Co-authored-by: Niklas Hußmann <[email protected]> * Update src/test/groovy/com/cloudogu/gitops/integration/features/CertManagerTestIT.groovy Co-authored-by: Niklas Hußmann <[email protected]> * Update src/test/groovy/com/cloudogu/gitops/integration/JenkinsPipelineTestLongIT.groovy Co-authored-by: Niklas Hußmann <[email protected]> * Update src/test/groovy/com/cloudogu/gitops/integration/features/PrometheusStackTestIT.groovy Co-authored-by: Niklas Hußmann <[email protected]> * review changes --------- Co-authored-by: Niklas Hußmann <[email protected]> Co-authored-by: Thomas Michael <[email protected]>
1 parent 75a18a2 commit ab62064

File tree

9 files changed

+695
-218
lines changed

9 files changed

+695
-218
lines changed

Jenkinsfile

Lines changed: 54 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ properties([
1515
// If this happens to occur often, add the following here: disableConcurrentBuilds(),
1616

1717
parameters([
18-
booleanParam(defaultValue: false, name: 'forcePushImage', description: 'Pushes the image with the current git commit as tag, even when it is on a branch')
18+
booleanParam(defaultValue: false, name: 'forcePushImage', description: 'Pushes the image with the current git commit as tag, even when it is on a branch'),
19+
booleanParam(defaultValue: false, name: 'longRunningTests', description: 'Executes long running async integrationtests like testing ArgoCD feature deployment')
1920
])
2021
])
2122

@@ -37,38 +38,38 @@ node('high-cpu') {
3738
// Otherwise git.isTag() will not be reliable. Jenkins seems to do a sparse checkout only
3839
sh "git fetch --tags"
3940
}
41+
parallel (
42+
'Build cli': {
43+
stage('Build cli') {
44+
// Read Java version from Dockerfile (DRY)
45+
String jdkVersion = sh(returnStdout: true, script:
46+
'grep -r \'ARG JDK_VERSION\' Dockerfile | sed "s/.*JDK_VERSION=\'\\(.*\\)\'.*/\\1/" ').trim()
47+
// Groovy version is defined by micronaut version. Get it from there.
48+
String groovyVersion = sh(returnStdout: true, script:
49+
'MICRONAUT_VERSION=$(cat pom.xml | sed -n \'/<parent>/,/<\\/parent>/p\' | ' +
50+
'sed -n \'s/.*<version>\\(.*\\)<\\/version>.*/\\1/p\'); ' +
51+
'curl -s https://repo1.maven.org/maven2/io/micronaut/micronaut-core-bom/${MICRONAUT_VERSION}/micronaut-core-bom-${MICRONAUT_VERSION}.pom | ' +
52+
'sed -n \'s/.*<groovy.version>\\(.*\\)<\\/groovy.version>.*/\\1/p\'').trim()
53+
groovyImage = "groovy:${groovyVersion}-jdk${jdkVersion}"
54+
// Re-use groovy image here, even though we only need JDK
55+
mvn = new MavenWrapperInDocker(this, groovyImage)
56+
// Faster builds because mvn local repo is reused between build, unit and integration tests
57+
mvn.useLocalRepoFromJenkins = true
58+
59+
mvn 'clean test -Dmaven.test.failure.ignore=true'
60+
junit testResults: '**/target/surefire-reports/TEST-*.xml'
61+
}
62+
},
63+
'Build images': {
64+
stage('Build images') {
65+
imageNames += createImageName(git.commitHashShort)
66+
imageNames += createImageName(git.commitHashShort) + '-dev'
4067

41-
stage('Build cli') {
42-
// Read Java version from Dockerfile (DRY)
43-
String jdkVersion = sh(returnStdout: true, script:
44-
'grep -r \'ARG JDK_VERSION\' Dockerfile | sed "s/.*JDK_VERSION=\'\\(.*\\)\'.*/\\1/" ').trim()
45-
// Groovy version is defined by micronaut version. Get it from there.
46-
String groovyVersion = sh(returnStdout: true, script:
47-
'MICRONAUT_VERSION=$(cat pom.xml | sed -n \'/<parent>/,/<\\/parent>/p\' | ' +
48-
'sed -n \'s/.*<version>\\(.*\\)<\\/version>.*/\\1/p\'); ' +
49-
'curl -s https://repo1.maven.org/maven2/io/micronaut/micronaut-core-bom/${MICRONAUT_VERSION}/micronaut-core-bom-${MICRONAUT_VERSION}.pom | ' +
50-
'sed -n \'s/.*<groovy.version>\\(.*\\)<\\/groovy.version>.*/\\1/p\'').trim()
51-
groovyImage = "groovy:${groovyVersion}-jdk${jdkVersion}"
52-
// Re-use groovy image here, even though we only need JDK
53-
mvn = new MavenWrapperInDocker(this, groovyImage)
54-
55-
mvn 'clean install -DskipTests'
56-
}
57-
58-
stage('Test cli') {
59-
mvn 'test -Dmaven.test.failure.ignore=true'
60-
// Archive test results. Makes build unstable on failed tests.
61-
junit testResults: '**/target/surefire-reports/TEST-*.xml'
62-
}
63-
64-
stage('Build images') {
65-
imageNames += createImageName(git.commitHashShort)
66-
imageNames += createImageName(git.commitHashShort) + '-dev'
67-
68-
images += buildImage(imageNames[0])
69-
images += buildImage(imageNames[1], '--build-arg ENV=dev')
70-
}
71-
68+
images += buildImage(imageNames[0])
69+
images += buildImage(imageNames[1], '--build-arg ENV=dev')
70+
}
71+
}
72+
)
7273
parallel(
7374
'Scan image': {
7475
stage('Scan image') {
@@ -97,7 +98,7 @@ node('high-cpu') {
9798
.inside("-e KUBECONFIG=${env.WORKSPACE}/.kube/config " +
9899
" --network=host --entrypoint=''") {
99100
sh "/app/apply-ng --yes --trace --internal-registry-port=${registryPort} " +
100-
"--argocd --monitoring --vault=dev --ingress-nginx --mailhog --base-url=http://localhost"
101+
"--argocd --monitoring --vault=dev --ingress-nginx --mailhog --base-url=http://localhost --cert-manager"
101102
}
102103
}
103104
}
@@ -115,19 +116,28 @@ node('high-cpu') {
115116
returnStdout: true
116117
).trim()
117118

118-
int ret=0
119-
new Docker(this).image(groovyImage)
120-
// Avoids errors ("unable to resolve class") probably due to missing HOME for container in JVM.
121-
.mountJenkinsUser()
122-
.inside("--network=${k3dNetwork}") {
123-
// removing m2 and grapes avoids issues where grapes primarily resolves local m2 and fails on missing versions
124-
sh "rm -rf .m2/"
125-
sh "rm -rf .groovy/grapes"
126-
ret = sh(returnStatus: true,
127-
script: "groovy ./scripts/e2e.groovy --url http://${k3dAddress}:9090 --user admin --password admin --writeFailedLog --fail --retry 2")
119+
int ret = 0
120+
121+
122+
// long running can switch on for every branch but should run everytime on MAIN.
123+
if (params.longRunningTests || (env.BRANCH_NAME == 'main')) {
124+
withEnv([ "KUBECONFIG=${env.WORKSPACE}/.kube/config", "ADDITIONAL_DOCKER_RUN_ARGS=--network=host","K3D_ADDRESS=${k3dAddress}"]) {
125+
mvn.useLocalRepoFromJenkins = true
126+
mvn 'failsafe:integration-test -Dmaven.test.failure.ignore=true -Plong-running'
127+
// Archive test results. Makes build unstable on failed tests.
128+
junit testResults: '**/target/failsafe-reports/TEST-*.xml'
129+
}
130+
} else {
131+
132+
withEnv([ "KUBECONFIG=${env.WORKSPACE}/.kube/config", "ADDITIONAL_DOCKER_RUN_ARGS=--network=host","K3D_ADDRESS=${k3dAddress}"]) {
133+
mvn.useLocalRepoFromJenkins = true
134+
mvn 'failsafe:integration-test -Dmaven.test.failure.ignore=true'
135+
// Archive test results. Makes build unstable on failed tests.
136+
junit testResults: '**/target/failsafe-reports/TEST-*.xml'
128137
}
138+
}
129139

130-
if (ret > 0) {
140+
if (ret > 0 || currentBuild.result == 'UNSTABLE') {
131141
if (fileExists('playground-logs-of-failed-jobs')) {
132142
archiveArtifacts artifacts: 'playground-logs-of-failed-jobs/*.log'
133143
}

docs/developers.md

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,11 +49,19 @@ It provides workarounds or solutions for the given issues.
4949

5050
## Testing
5151

52-
There is an end to end testing script inside the `./scripts` folder. It scans for builds and starts them, waits until their finished or fail and returns the result.
52+
1. There are integration tests implemented by Junit. Classes marked with 'IT' and the end.
53+
2. Long running tests are marked with 'LongIT'.
54+
3. Main Branch executes both, feature-branches only IT.
5355

5456
### Usage
55-
56-
You can use it by executing `groovy ./scripts/e2e.groovy --url http://localhost:9090 --user admin --password admin`
57+
Runnable separately via maven.
58+
``
59+
mvn failsafe:integration-test -f pom.xml
60+
``
61+
To run long living test, use maven with profile: long-running
62+
``
63+
mvn failsafe:integration-test -f pom.xml -P long-running
64+
``
5765

5866
### Options
5967

pom.xml

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -263,13 +263,27 @@
263263
<scope>test</scope>
264264
</dependency>
265265

266+
<dependency>
267+
<groupId>io.kubernetes</groupId>
268+
<artifactId>client-java</artifactId>
269+
<version>22.0.0</version>
270+
<scope>test</scope>
271+
</dependency>
272+
266273
<dependency>
267274
<groupId>org.assertj</groupId>
268275
<artifactId>assertj-core</artifactId>
269276
<version>3.23.1</version>
270277
<scope>test</scope>
271278
</dependency>
272279

280+
<dependency>
281+
<groupId>com.offbytwo.jenkins</groupId>
282+
<artifactId>jenkins-client</artifactId>
283+
<version>0.3.8</version>
284+
<scope>test</scope>
285+
</dependency>
286+
273287
<dependency>
274288
<groupId>org.apache.groovy</groupId>
275289
<artifactId>groovy-test</artifactId>
@@ -283,11 +297,21 @@
283297
<scope>test</scope>
284298
</dependency>
285299

300+
<dependency>
301+
<groupId>javax.xml.bind</groupId>
302+
<artifactId>jaxb-api</artifactId>
303+
<version>2.3.1</version>
304+
<scope>test</scope>
305+
</dependency>
306+
307+
308+
286309
<dependency>
287310
<groupId>com.github.victools</groupId>
288311
<artifactId>jsonschema-generator</artifactId>
289312
<version>${jsonschema.version}</version>
290313
</dependency>
314+
291315
<dependency>
292316
<groupId>com.github.victools</groupId>
293317
<artifactId>jsonschema-module-jackson</artifactId>
@@ -467,4 +491,47 @@
467491

468492
<pluginRepositories>
469493
</pluginRepositories>
494+
<profiles>
495+
<profile>
496+
<id>default</id>
497+
<activation>
498+
<activeByDefault>true</activeByDefault>
499+
</activation>
500+
<build>
501+
<plugins>
502+
<plugin>
503+
<groupId>org.apache.maven.plugins</groupId>
504+
<artifactId>maven-failsafe-plugin</artifactId>
505+
<configuration>
506+
<excludes>
507+
<exclude>**/*LongIT</exclude>
508+
</excludes>
509+
<includes>
510+
<include>**/*IT</include>
511+
</includes>
512+
</configuration>
513+
</plugin>
514+
</plugins>
515+
</build>
516+
</profile>
517+
<profile>
518+
<id>long-running</id>
519+
<activation>
520+
<activeByDefault>false</activeByDefault>
521+
</activation>
522+
<build>
523+
<plugins>
524+
<plugin>
525+
<groupId>org.apache.maven.plugins</groupId>
526+
<artifactId>maven-failsafe-plugin</artifactId>
527+
<configuration>
528+
<includes>
529+
<include>**/*IT</include> <!-- this includes LongIT, too. Just to clearify. -->
530+
</includes>
531+
</configuration>
532+
</plugin>
533+
</plugins>
534+
</build>
535+
</profile>
536+
</profiles>
470537
</project>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
package com.cloudogu.gitops.integration
2+
3+
import com.cloudogu.gitops.integration.features.KubenetesApiTestSetup
4+
import io.kubernetes.client.openapi.models.V1NamespaceList
5+
import io.kubernetes.client.openapi.models.V1Pod
6+
import io.kubernetes.client.openapi.models.V1PodList
7+
import org.junit.jupiter.api.BeforeAll
8+
import org.junit.jupiter.api.Test
9+
10+
import static org.assertj.core.api.Assertions.assertThat
11+
12+
/**
13+
* This test ensures all Pods and Namespaces are available, runnning at a startet GOP with - more or less - defaulöt values.
14+
*/
15+
class GOPSmoketestsIT extends KubenetesApiTestSetup {
16+
17+
/**
18+
* Gets path to kubeconfig
19+
*/
20+
@BeforeAll
21+
static void labelMyTest() {
22+
println '########### K8S SMOKE TESTS ###########'
23+
}
24+
25+
26+
@Test
27+
void ensureJenkinsPodIsStarted() {
28+
29+
V1PodList list = api.listPodForAllNamespaces()
30+
.execute();
31+
// invokes the CoreV1Api client
32+
V1Pod jenkinsPod = list.getItems().findAll { it.getMetadata().getName().startsWith("jenkins") }.get(0)
33+
assertThat(jenkinsPod.getMetadata().getName()).isEqualTo("jenkins-0")
34+
}
35+
36+
@Test
37+
void ensureArgoCdPodsAreStartedExpect5() {
38+
39+
def expectedArgoCDPods = 7
40+
V1PodList list = api.listPodForAllNamespaces()
41+
.execute()
42+
List<V1Pod> argocdPodList = list.getItems().findAll { it.getMetadata().getName().startsWith("argo") }
43+
assertThat(argocdPodList.size()).isEqualTo(expectedArgoCDPods)
44+
}
45+
46+
@Test
47+
void ensureScmmPodIsStarted() {
48+
49+
V1PodList list = api.listPodForAllNamespaces()
50+
.execute();
51+
for (V1Pod item : list.getItems()) {
52+
println item.getMetadata().getName()
53+
}
54+
// invokes the CoreV1Api client
55+
V1Pod scmmPod = list.getItems().findAll { it.getMetadata().getName().startsWith("scmm-scm-manager") }.get(0)
56+
assertThat(scmmPod.getMetadata().getName()).startsWith("scmm-scm-manager")
57+
}
58+
59+
@Test
60+
void ensusreNamespacesExists() {
61+
List<String> expectedNamespaces = ["argocd",
62+
"cert-manager",
63+
"default",
64+
"example-apps-production",
65+
"example-apps-staging",
66+
"ingress-nginx",
67+
"kube-node-lease",
68+
"kube-public",
69+
"kube-system",
70+
"monitoring",
71+
"secrets"] as List<String>
72+
73+
74+
V1NamespaceList list = api.listNamespace().execute()
75+
// list.items.each {println it.getMetadata().getName()} // print namespaces
76+
List<String> listOfNamespaces = list.getItems().collect { it.getMetadata().name }
77+
assertThat(expectedNamespaces).containsAll (listOfNamespaces)
78+
79+
}
80+
81+
/**
82+
* tests searches for ingress services and ensure ingress is used as loadbalancer
83+
*/
84+
@Test
85+
// kein nginx Service am laufen am Jenkins!
86+
void ensureNginxIsOnline() {
87+
def expectedIngressServices = 2;
88+
def services = api.listServiceForAllNamespaces().execute()
89+
90+
for (def item : services.getItems()) {
91+
System.out.println("Service:" + item.getMetadata().getName())
92+
}
93+
def listOfIngessServices = services.getItems().findAll { it.getMetadata().getName().startsWith("ingress") }
94+
assertThat(listOfIngessServices.size()).isEqualTo(expectedIngressServices)
95+
def ingress = listOfIngessServices.find { it.getMetadata().getName().equals("ingress-nginx-controller") }
96+
assertThat(ingress.getStatus()).isNotNull()
97+
assertThat(ingress.getStatus().getLoadBalancer()).isNotNull()
98+
assertThat(ingress.getStatus().getLoadBalancer().getIngress()).isNotNull()
99+
}
100+
101+
@Override
102+
boolean isReadyToStartTests() {
103+
V1PodList list = api.listPodForAllNamespaces()
104+
.execute();
105+
if (list && !list.items.isEmpty()) {
106+
107+
V1Pod argoPod = list.getItems().find { it.getMetadata().getName().startsWith("argo") }
108+
if (argoPod) {
109+
return true
110+
}
111+
}
112+
return false
113+
}
114+
}

0 commit comments

Comments
 (0)