Skip to content

Commit 33c2eff

Browse files
csvirimetacosmadam-sandor
authored
feat: Add tomcat operator sample (#659) (#682)
fixed informer bug and related IT as part of this commit Co-authored-by: Chris Laprun <[email protected]> Co-authored-by: Attila Mészáros <[email protected]> Co-authored-by: Adam Sándor <[email protected]>
1 parent fce4e76 commit 33c2eff

File tree

43 files changed

+1288
-32
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+1288
-32
lines changed

Diff for: .github/workflows/end-to-end-tests.yml

+77
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
# End to end integration test which deploys the Tomcat operator to a Kubernetes
2+
# (Kind) cluster and creates custom resources to verify the operator's functionality
3+
name: TomcatOperator End to End test
4+
on:
5+
push:
6+
branches:
7+
- "*"
8+
jobs:
9+
tomcat_e2e_test:
10+
runs-on: ubuntu-latest
11+
env:
12+
KIND_CL_NAME: e2e-test
13+
steps:
14+
- name: Checkout
15+
uses: actions/checkout@v2
16+
17+
- name: clean resident local docker
18+
if: ${{ env.ACT }}
19+
continue-on-error: true
20+
run: |
21+
for DIMG in "$KIND_CL_NAME-control-plane "; do
22+
docker stop $DIMG ; docker rm $DIMG ;
23+
done ;
24+
sleep 1
25+
26+
- name: Create Kubernetes KinD Cluster
27+
uses: container-tools/[email protected]
28+
with:
29+
cluster_name: e2e-test
30+
registry: false
31+
32+
- name: Set up Java and Maven
33+
uses: actions/setup-java@v2
34+
with:
35+
java-version: 11
36+
distribution: adopt-hotspot
37+
cache: 'maven'
38+
39+
- name: Build SDK
40+
run: mvn install -DskipTests
41+
42+
- name: build jib
43+
working-directory: sample-operators/tomcat-operator
44+
run: |
45+
mvn --version
46+
mvn -B package jib:dockerBuild jib:buildTar -Djib-maven-image=tomcat-operator -DskipTests
47+
kind load image-archive target/jib-image.tar --name=${{ env.KIND_CL_NAME }}
48+
49+
- name: Apply CRDs
50+
working-directory: sample-operators/tomcat-operator
51+
run: |
52+
kubectl apply -f target/classes/META-INF/fabric8/tomcats.tomcatoperator.io-v1.yml
53+
kubectl apply -f target/classes/META-INF/fabric8/webapps.tomcatoperator.io-v1.yml
54+
55+
- name: Deploy Tomcat Operator
56+
working-directory: sample-operators/tomcat-operator
57+
run: |
58+
kubectl apply -f k8s/operator.yaml
59+
60+
- name: Run E2E Tests
61+
working-directory: sample-operators/tomcat-operator
62+
run: mvn -B test -P end-to-end-tests
63+
64+
- name: Dump state
65+
if: ${{ failure() }}
66+
run: |
67+
set +e
68+
echo "All namespaces"
69+
kubectl get ns
70+
echo "All objects in tomcat-operator"
71+
kubectl get all -n tomcat-operator -o yaml
72+
echo "Output of tomcat-operator pod"
73+
kubectl logs -l app=tomcat-operator -n tomcat-operator
74+
echo "All objects in tomcat-test"
75+
kubectl get deployment,pod,tomcat,webapp -n tomcat-test -o yaml
76+
echo "Output of curl command"
77+
kubectl logs curl -n tomcat-test

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSource.java

+7-2
Original file line numberDiff line numberDiff line change
@@ -161,8 +161,11 @@ public void onDelete(T resource, boolean b) {
161161

162162
@Override
163163
public Optional<T> getCustomResource(CustomResourceID resourceID) {
164-
var sharedIndexInformer =
165-
sharedIndexInformers.get(resourceID.getNamespace().orElse(ANY_NAMESPACE_MAP_KEY));
164+
var sharedIndexInformer = sharedIndexInformers.get(ANY_NAMESPACE_MAP_KEY);
165+
if (sharedIndexInformer == null) {
166+
sharedIndexInformer =
167+
sharedIndexInformers.get(resourceID.getNamespace().orElse(ANY_NAMESPACE_MAP_KEY));
168+
}
166169
var resource = sharedIndexInformer.getStore()
167170
.getByKey(Cache.namespaceKeyFunc(resourceID.getNamespace().orElse(null),
168171
resourceID.getName()));
@@ -173,6 +176,8 @@ public Optional<T> getCustomResource(CustomResourceID resourceID) {
173176
}
174177
}
175178

179+
180+
176181
/**
177182
* @return shared informers by namespace. If custom resource is not namespace scoped use
178183
* CustomResourceEventSource.ANY_NAMESPACE_MAP_KEY

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/internal/InformerEventSource.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,12 @@ public void onDelete(T t, boolean b) {
8686
}
8787

8888
private void propagateEvent(T object) {
89-
var uids = resourceToCustomResourceIDSet.apply(object);
90-
if (uids.isEmpty()) {
89+
var customResourceIDSet = resourceToCustomResourceIDSet.apply(object);
90+
if (customResourceIDSet.isEmpty()) {
9191
return;
9292
}
93-
uids.forEach(uid -> {
94-
Event event = new Event(CustomResourceID.fromResource(object));
93+
customResourceIDSet.forEach(customResourceId -> {
94+
Event event = new Event(customResourceId);
9595
/*
9696
* In fabric8 client for certain cases informers can be created on in a way that they are
9797
* automatically started, what would cause a NullPointerException here, since an event might

Diff for: operator-framework/src/test/java/io/javaoperatorsdk/operator/InformerEventSourceIT.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import io.javaoperatorsdk.operator.sample.informereventsource.InformerEventSourceTestCustomReconciler;
1414
import io.javaoperatorsdk.operator.sample.informereventsource.InformerEventSourceTestCustomResource;
1515

16-
import static io.javaoperatorsdk.operator.sample.informereventsource.InformerEventSourceTestCustomReconciler.RELATED_RESOURCE_UID;
16+
import static io.javaoperatorsdk.operator.sample.informereventsource.InformerEventSourceTestCustomReconciler.RELATED_RESOURCE_NAME;
1717
import static io.javaoperatorsdk.operator.sample.informereventsource.InformerEventSourceTestCustomReconciler.TARGET_CONFIG_MAP_KEY;
1818
import static org.assertj.core.api.Assertions.assertThat;
1919
import static org.awaitility.Awaitility.await;
@@ -36,11 +36,11 @@ public void testUsingInformerToWatchChangesOfConfigMap() {
3636
var customResource = initialCustomResource();
3737
customResource = operator.create(InformerEventSourceTestCustomResource.class, customResource);
3838
ConfigMap configMap =
39-
operator.create(ConfigMap.class, relatedConfigMap(customResource.getMetadata().getUid()));
39+
operator.create(ConfigMap.class, relatedConfigMap(customResource.getMetadata().getName()));
4040
waitForCRStatusValue(INITIAL_STATUS_MESSAGE);
4141

4242
configMap.getData().put(TARGET_CONFIG_MAP_KEY, UPDATE_STATUS_MESSAGE);
43-
operator.replace(ConfigMap.class, configMap);
43+
configMap = operator.replace(ConfigMap.class, configMap);
4444

4545
waitForCRStatusValue(UPDATE_STATUS_MESSAGE);
4646
}
@@ -51,7 +51,7 @@ private ConfigMap relatedConfigMap(String relatedResourceAnnotation) {
5151
ObjectMeta objectMeta = new ObjectMeta();
5252
objectMeta.setName(RESOURCE_NAME);
5353
objectMeta.setAnnotations(new HashMap<>());
54-
objectMeta.getAnnotations().put(RELATED_RESOURCE_UID, relatedResourceAnnotation);
54+
objectMeta.getAnnotations().put(RELATED_RESOURCE_NAME, relatedResourceAnnotation);
5555
configMap.setMetadata(objectMeta);
5656

5757
configMap.setData(new HashMap<>());

Diff for: operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/informereventsource/InformerEventSourceTestCustomReconciler.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public class InformerEventSourceTestCustomReconciler implements
2929
private static final Logger LOGGER =
3030
LoggerFactory.getLogger(InformerEventSourceTestCustomReconciler.class);
3131

32-
public static final String RELATED_RESOURCE_UID = "relatedResourceName";
32+
public static final String RELATED_RESOURCE_NAME = "relatedResourceName";
3333
public static final String TARGET_CONFIG_MAP_KEY = "targetStatus";
3434

3535
private KubernetesClient kubernetesClient;
@@ -38,7 +38,7 @@ public class InformerEventSourceTestCustomReconciler implements
3838
@Override
3939
public void prepareEventSources(EventSourceRegistry eventSourceRegistry) {
4040
eventSource = new InformerEventSource<>(kubernetesClient, ConfigMap.class,
41-
Mappers.fromAnnotation(RELATED_RESOURCE_UID));
41+
Mappers.fromAnnotation(RELATED_RESOURCE_NAME));
4242
eventSourceRegistry.registerEventSource(eventSource);
4343
}
4444

Diff for: pom.xml

+27-2
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,9 @@
7676
<module>operator-framework-core</module>
7777
<module>operator-framework-junit5</module>
7878
<module>operator-framework</module>
79-
<module>samples</module>
80-
<module>micrometer-support</module>
79+
<module>smoke-test-samples</module>
80+
<module>micrometer-support</module>
81+
<module>sample-operators</module>
8182
</modules>
8283

8384

@@ -271,6 +272,7 @@
271272
</includes>
272273
<excludes>
273274
<exclude>**/*IT.java</exclude>
275+
<exclude>**/*E2E.java</exclude>
274276
</excludes>
275277
</configuration>
276278
</plugin>
@@ -316,6 +318,7 @@
316318
<includes>
317319
<include>**/*Test.java</include>
318320
<include>**/*IT.java</include>
321+
<include>**/*E2E.java</include>
319322
</includes>
320323
</configuration>
321324
</plugin>
@@ -335,6 +338,27 @@
335338
</includes>
336339
<excludes>
337340
<exclude>**/*Test.java</exclude>
341+
<exclude>**/*E2E.java</exclude>
342+
</excludes>
343+
</configuration>
344+
</plugin>
345+
</plugins>
346+
</build>
347+
</profile>
348+
<profile>
349+
<id>end-to-end-tests</id>
350+
<build>
351+
<plugins>
352+
<plugin>
353+
<groupId>org.apache.maven.plugins</groupId>
354+
<artifactId>maven-surefire-plugin</artifactId>
355+
<configuration>
356+
<includes>
357+
<include>**/*E2E.java</include>
358+
</includes>
359+
<excludes>
360+
<exclude>**/*Test.java</exclude>
361+
<exclude>**/*IT.java</exclude>
338362
</excludes>
339363
</configuration>
340364
</plugin>
@@ -352,6 +376,7 @@
352376
<configuration>
353377
<excludes>
354378
<exclude>**/*IT.java</exclude>
379+
<exclude>**/*E2E.java</exclude>
355380
</excludes>
356381
</configuration>
357382
</plugin>

Diff for: sample-operators/pom.xml

+78
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.javaoperatorsdk</groupId>
9+
<artifactId>java-operator-sdk</artifactId>
10+
<version>2.0.0-SNAPSHOT</version>
11+
</parent>
12+
13+
<artifactId>sample-operators</artifactId>
14+
<name>Operator SDK - Samples</name>
15+
<packaging>pom</packaging>
16+
17+
<properties>
18+
<jib-maven-plugin.version>3.1.4</jib-maven-plugin.version>
19+
</properties>
20+
21+
<modules>
22+
<module>tomcat-operator</module>
23+
</modules>
24+
25+
<dependencies>
26+
<dependency>
27+
<groupId>io.javaoperatorsdk</groupId>
28+
<artifactId>operator-framework</artifactId>
29+
<version>1.9.2</version>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.apache.logging.log4j</groupId>
33+
<artifactId>log4j-slf4j-impl</artifactId>
34+
<version>2.13.3</version>
35+
</dependency>
36+
<dependency>
37+
<groupId>org.takes</groupId>
38+
<artifactId>takes</artifactId>
39+
<version>1.19</version>
40+
</dependency>
41+
<dependency>
42+
<groupId>junit</groupId>
43+
<artifactId>junit</artifactId>
44+
<version>4.13.1</version>
45+
<scope>test</scope>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.awaitility</groupId>
49+
<artifactId>awaitility</artifactId>
50+
<version>4.1.0</version>
51+
<scope>test</scope>
52+
</dependency>
53+
</dependencies>
54+
55+
<build>
56+
<plugins>
57+
<plugin>
58+
<groupId>com.google.cloud.tools</groupId>
59+
<artifactId>jib-maven-plugin</artifactId>
60+
<version>${jib-maven-plugin.version}</version>
61+
<configuration>
62+
<from>
63+
<image>gcr.io/distroless/java:11</image>
64+
</from>
65+
<to>
66+
<image>tomcat-operator</image>
67+
</to>
68+
</configuration>
69+
</plugin>
70+
<plugin>
71+
<groupId>org.apache.maven.plugins</groupId>
72+
<artifactId>maven-compiler-plugin</artifactId>
73+
<version>3.8.1</version>
74+
</plugin>
75+
</plugins>
76+
</build>
77+
78+
</project>

Diff for: sample-operators/tomcat-operator/README.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Tomcat Operator
2+
3+
Creates a Tomcat deployment from a Custom Resource, while keeping the WAR separated with another Custom Resource.
4+
5+
This sample demonstrates the following capabilities of the Java Operator SDK:
6+
* Multiple Controllers in a single Operator. The Tomcat resource is managed by the TomcatController while the Webapp
7+
resource is managed by the WebappController.
8+
* Reacting to events about resources created by the controller. The TomcatController will receive events about the
9+
Deployment resources it created. See EventSource section below for more detail.
10+
11+
## Example input for creating a Tomcat instance
12+
```
13+
apiVersion: "tomcatoperator.io/v1"
14+
kind: Tomcat
15+
metadata:
16+
name: test-tomcat1
17+
spec:
18+
version: 9.0
19+
replicas: 2
20+
```
21+
22+
## Example input for the Webapp
23+
```
24+
apiVersion: "tomcatoperator.io/v1"
25+
kind: Webapp
26+
metadata:
27+
name: sample-webapp1
28+
spec:
29+
tomcat: test-tomcat1
30+
url: http://tomcat.apache.org/tomcat-7.0-doc/appdev/sample/sample.war
31+
contextPath: mysample
32+
```
33+
34+
## Getting started / Testing
35+
36+
The quickest way to try the operator is to run it on your local machine, while it connects to a
37+
local or remote Kubernetes cluster. When you start it, it will use the current kubectl context on
38+
your machine to connect to the cluster.
39+
40+
Before you run it you have to install the CRDs on your cluster by running:
41+
- `kubectl apply -f target/classes/META-INF/fabric8/tomcats.tomcatoperator.io-v1.yml`
42+
- `kubectl apply -f target/classes/META-INF/fabric8/webapps.tomcatoperator.io-v1.yml`
43+
44+
The CRDs are generated automatically from your code by simply adding the `crd-generator-apt`
45+
dependency to your `pom.xml` file.
46+
47+
When the Operator is running you can create some Tomcat Custom Resources. You can find a sample
48+
custom resources in the k8s folder.
49+
50+
If you want the Operator to be running as a deployment in your cluster, follow the below steps.
51+
52+
## Build
53+
54+
You can build the sample using `mvn install jib:dockerBuild` this will produce a Docker image you
55+
can push to the registry of your choice. The JAR file is built using your local Maven and JDK and
56+
then copied into the Docker image.
57+
58+
## Install Operator into cluster
59+
60+
Install the CRDs as shown above if you haven't already, then
61+
run `kubectl apply -f k8s/operator.yaml`. Now you can create Tomcat instances with CRs (see examples
62+
above).
63+
64+
## EventSources
65+
The TomcatController is listening to events about Deployments created by the TomcatOperator by registering a
66+
InformerEventSource with the EventSourceManager. The InformerEventSource will in turn register a watch on
67+
all Deployments managed by the Controller (identified by the `app.kubernetes.io/managed-by` label).
68+
When an event from a Deployment is received we have to identify which Tomcat object does the Deployment
69+
belong to. This is done when the InformerEventSource creates the event.
70+
71+
The TomcatController has to take care of setting the `app.kubernetes.io/managed-by` label on the Deployment so the
72+
InformerEventSource can watch the right Deployments.
73+
The TomcatController also has to set `ownerReference` on the Deployment so later the InformerEventSource can
74+
identify which Tomcat does the Deployment belong to. This is necessary so the frameowork can call the Controller
75+
`createOrUpdate` method correctly.
76+

0 commit comments

Comments
 (0)