Skip to content

Commit d3e3c45

Browse files
authored
Add Webpage Sample to V2 + fix for observed generation (#693)
1 parent 319ebee commit d3e3c45

File tree

25 files changed

+704
-69
lines changed

25 files changed

+704
-69
lines changed

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAware.java

+1-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.javaoperatorsdk.operator.api;
22

3-
import java.util.Optional;
4-
53
import io.fabric8.kubernetes.client.CustomResource;
64
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
75

@@ -24,6 +22,6 @@ public interface ObservedGenerationAware {
2422

2523
void setObservedGeneration(Long generation);
2624

27-
Optional<Long> getObservedGeneration();
25+
Long getObservedGeneration();
2826

2927
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/ObservedGenerationAwareStatus.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package io.javaoperatorsdk.operator.api;
22

3-
import java.util.Optional;
4-
53
/**
64
* A helper base class for status sub-resources classes to extend to support generate awareness.
75
*/
@@ -15,7 +13,7 @@ public void setObservedGeneration(Long generation) {
1513
}
1614

1715
@Override
18-
public Optional<Long> getObservedGeneration() {
19-
return Optional.ofNullable(observedGeneration);
16+
public Long getObservedGeneration() {
17+
return observedGeneration;
2018
}
2119
}

Diff for: operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/ReconciliationDispatcher.java

+6-6
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,9 @@ private PostExecutionControl<R> handleDispatch(ExecutionScope<R> executionScope)
7575
Context context =
7676
new DefaultContext(executionScope.getRetryInfo());
7777
if (markedForDeletion) {
78-
return handleDelete(resource, context);
78+
return handleCleanup(resource, context);
7979
} else {
80-
return handleCreateOrUpdate(executionScope, resource, context);
80+
return handleReconcile(executionScope, resource, context);
8181
}
8282
}
8383

@@ -99,7 +99,7 @@ private boolean shouldNotDispatchToDelete(R resource) {
9999
return configuration().useFinalizer() && !resource.hasFinalizer(configuration().getFinalizer());
100100
}
101101

102-
private PostExecutionControl<R> handleCreateOrUpdate(
102+
private PostExecutionControl<R> handleReconcile(
103103
ExecutionScope<R> executionScope, R resource, Context context) {
104104
if (configuration().useFinalizer() && !resource.hasFinalizer(configuration().getFinalizer())) {
105105
/*
@@ -114,7 +114,7 @@ private PostExecutionControl<R> handleCreateOrUpdate(
114114
try {
115115
var resourceForExecution =
116116
cloneResourceForErrorStatusHandlerIfNeeded(resource, context);
117-
return createOrUpdateExecution(executionScope, resourceForExecution, context);
117+
return reconcileExecution(executionScope, resourceForExecution, context);
118118
} catch (RuntimeException e) {
119119
handleLastAttemptErrorStatusHandler(resource, context, e);
120120
throw e;
@@ -137,7 +137,7 @@ private R cloneResourceForErrorStatusHandlerIfNeeded(R resource, Context context
137137
}
138138
}
139139

140-
private PostExecutionControl<R> createOrUpdateExecution(ExecutionScope<R> executionScope,
140+
private PostExecutionControl<R> reconcileExecution(ExecutionScope<R> executionScope,
141141
R resource, Context context) {
142142
log.debug(
143143
"Executing createOrUpdate for resource {} with version: {} with execution scope: {}",
@@ -222,7 +222,7 @@ private void updatePostExecutionControlWithReschedule(
222222
baseControl.getScheduleDelay().ifPresent(postExecutionControl::withReSchedule);
223223
}
224224

225-
private PostExecutionControl<R> handleDelete(R resource, Context context) {
225+
private PostExecutionControl<R> handleCleanup(R resource, Context context) {
226226
log.debug(
227227
"Executing delete for resource: {} with version: {}",
228228
getName(resource),

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public final class ResourceEventFilters {
3434
var actualGeneration = newResource.getMetadata().getGeneration();
3535
var observedGeneration = ((ObservedGenerationAware) status)
3636
.getObservedGeneration();
37-
return observedGeneration.map(aLong -> actualGeneration > aLong).orElse(true);
37+
return observedGeneration == null || actualGeneration > observedGeneration;
3838
}
3939
}
4040
return oldResource == null || !generationAware ||

Diff for: operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/ReconciliationDispatcherTest.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ void setObservedGenerationForStatusIfNeeded() {
320320

321321
PostExecutionControl<ObservedGenCustomResource> control = lDispatcher.handleExecution(
322322
executionScopeWithCREvent(observedGenResource));
323-
assertThat(control.getUpdatedCustomResource().get().getStatus().getObservedGeneration().get())
323+
assertThat(control.getUpdatedCustomResource().get().getStatus().getObservedGeneration())
324324
.isEqualTo(1L);
325325
}
326326

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.util.concurrent.TimeUnit;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.fabric8.kubernetes.api.model.ObjectMeta;
9+
import io.javaoperatorsdk.operator.config.runtime.DefaultConfigurationService;
10+
import io.javaoperatorsdk.operator.junit.OperatorExtension;
11+
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestCustomResource;
12+
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenerationTestReconciler;
13+
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.awaitility.Awaitility.await;
16+
17+
public class ObservedGenerationHandlingIT {
18+
@RegisterExtension
19+
OperatorExtension operator =
20+
OperatorExtension.builder()
21+
.withConfigurationService(DefaultConfigurationService.instance())
22+
.withReconciler(new ObservedGenerationTestReconciler())
23+
.build();
24+
25+
@Test
26+
public void testReconciliationOfNonCustomResourceAndStatusUpdate() {
27+
var resource = new ObservedGenerationTestCustomResource();
28+
resource.setMetadata(new ObjectMeta());
29+
resource.getMetadata().setName("observed-gen1");
30+
31+
var createdResource = operator.create(ObservedGenerationTestCustomResource.class, resource);
32+
33+
await().atMost(10, TimeUnit.SECONDS).untilAsserted(() -> {
34+
var d = operator.get(ObservedGenerationTestCustomResource.class,
35+
createdResource.getMetadata().getName());
36+
assertThat(d.getStatus().getObservedGeneration()).isNotNull();
37+
assertThat(d.getStatus().getObservedGeneration()).isEqualTo(1);
38+
});
39+
}
40+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.javaoperatorsdk.operator.sample.observedgeneration;
2+
3+
import io.fabric8.kubernetes.api.model.Namespaced;
4+
import io.fabric8.kubernetes.client.CustomResource;
5+
import io.fabric8.kubernetes.model.annotation.Group;
6+
import io.fabric8.kubernetes.model.annotation.Kind;
7+
import io.fabric8.kubernetes.model.annotation.ShortNames;
8+
import io.fabric8.kubernetes.model.annotation.Version;
9+
10+
@Group("sample.javaoperatorsdk")
11+
@Version("v1")
12+
@Kind("ObservedGenerationTestCustomResource")
13+
@ShortNames("og")
14+
public class ObservedGenerationTestCustomResource
15+
extends CustomResource<Void, ObservedGenerationTestCustomResourceStatus>
16+
implements Namespaced {
17+
18+
@Override
19+
protected ObservedGenerationTestCustomResourceStatus initStatus() {
20+
return new ObservedGenerationTestCustomResourceStatus();
21+
}
22+
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.javaoperatorsdk.operator.sample.observedgeneration;
2+
3+
import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;
4+
5+
public class ObservedGenerationTestCustomResourceStatus extends ObservedGenerationAwareStatus {
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.javaoperatorsdk.operator.sample.observedgeneration;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
6+
import io.javaoperatorsdk.operator.api.reconciler.*;
7+
8+
import static io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration.NO_FINALIZER;
9+
10+
@ControllerConfiguration(finalizerName = NO_FINALIZER)
11+
public class ObservedGenerationTestReconciler
12+
implements Reconciler<ObservedGenerationTestCustomResource> {
13+
14+
private static final Logger log = LoggerFactory.getLogger(ObservedGenerationTestReconciler.class);
15+
16+
@Override
17+
public UpdateControl<ObservedGenerationTestCustomResource> reconcile(
18+
ObservedGenerationTestCustomResource resource, Context context) {
19+
log.info("Reconcile ObservedGenerationTestCustomResource: {}",
20+
resource.getMetadata().getName());
21+
return UpdateControl.updateStatus(resource);
22+
}
23+
}

Diff for: sample-operators/pom.xml

+1-54
Original file line numberDiff line numberDiff line change
@@ -20,59 +20,6 @@
2020

2121
<modules>
2222
<module>tomcat-operator</module>
23+
<module>webpage</module>
2324
</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-
7825
</project>

Diff for: sample-operators/webpage/README.md

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
# WebServer Operator
2+
3+
This is a simple example of how a Custom Resource backed by an Operator can serve as
4+
an abstraction layer. This Operator will use a webserver resource, which mainly contains a
5+
static webpage definition and creates an NGINX Deployment backed by a ConfigMap which holds
6+
the HTML.
7+
8+
This is an example input:
9+
```yaml
10+
apiVersion: "sample.javaoperatorsdk/v1"
11+
kind: WebPage
12+
metadata:
13+
name: mynginx-hello
14+
spec:
15+
html: |
16+
<html>
17+
<head>
18+
<title>Webserver Operator</title>
19+
</head>
20+
<body>
21+
Hello World!
22+
</body>
23+
</html>
24+
```
25+
26+
### Try
27+
28+
The quickest way to try the operator is to run it on your local machine, while it connects to a local or remote
29+
Kubernetes cluster. When you start it, it will use the current kubectl context on your machine to connect to the cluster.
30+
31+
Before you run it you have to install the CRD on your cluster by running `kubectl apply -f k8s/crd.yaml`
32+
33+
When the Operator is running you can create some Webserver Custom Resources. You can find a sample custom resource in
34+
`k8s/webpage.yaml`. You can create it by running `kubectl apply -f k8s/webpage.yaml`
35+
36+
After the Operator has picked up the new webserver resource (see the logs) it should create the NGINX server in the
37+
same namespace where the webserver resource is created. To connect to the server using your browser you can
38+
run `kubectl get service` and view the service created by the Operator. It should have a NodePort configured. If you are
39+
running a single-node cluster (e.g. Docker for Mac or Minikube) you can connect to the VM on this port to access the
40+
page. Otherwise you can change the service to a LoadBalancer (e.g on a public cloud).
41+
42+
You can also try to change the HTML code in `k8s/webpage.yaml` and do another `kubectl apply -f k8s/webpage.yaml`.
43+
This should update the actual NGINX deployment with the new configuration.
44+
45+
### Build
46+
47+
You can build the sample using `mvn jib:dockerBuild` this will produce a Docker image you can push to the registry
48+
of your choice. The JAR file is built using your local Maven and JDK and then copied into the Docker image.
49+
50+
### Deployment
51+
52+
1. Deploy the CRD: `kubectl apply -f k8s/crd.yaml`
53+
2. Deploy the operator: `kubectl apply -f k8s/operator.yaml`

0 commit comments

Comments
 (0)