Skip to content

Commit 942a9d8

Browse files
authored
Merge pull request operator-framework#134 from ContainerSolutions/retry_and_improved_status_support
Improved Retry and Sub Resource Support
2 parents 795ebc8 + 644bed9 commit 942a9d8

File tree

40 files changed

+773
-359
lines changed

40 files changed

+773
-359
lines changed

README.md

+8-8
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,10 @@
22
![Java CI with Maven](https://github.com/ContainerSolutions/java-operator-sdk/workflows/Java%20CI%20with%20Maven/badge.svg)
33

44
SDK for building Kubernetes Operators in Java. Inspired by [operator-sdk](https://github.com/operator-framework/operator-sdk).
5-
In this first iteration we aim to provide a framework which handles the reconciliation loop by dispatching events to
6-
a Controller written by the user of the framework.
7-
8-
The Controller only contains the logic to create, update and delete the actual resources related to the CRD.
5+
User (you) only writes the logic in a Controller that creates/updates or deletes resources related to a custom resource.
6+
All the issues around are handled by the framework for you.
7+
Check out this [blog post](https://blog.container-solutions.com/a-deep-dive-into-the-java-operator-sdk)
8+
about the non-trivial yet common problems needs to be solved for every operator.
99

1010
## Join us on Discord!
1111

@@ -61,16 +61,16 @@ The Controller implements the business logic and describes all the classes neede
6161
public class WebServerController implements ResourceController<WebServer> {
6262

6363
@Override
64-
public boolean deleteResource(CustomService resource) {
64+
public boolean deleteResource(CustomService resource, Context<WebServer> context) {
6565
// ... your logic ...
6666
return true;
6767
}
6868

6969
// Return the changed resource, so it gets updated. See javadoc for details.
7070
@Override
71-
public Optional<CustomService> createOrUpdateResource(CustomService resource) {
71+
public UpdateControl<CustomService> createOrUpdateResource(CustomService resource, Context<WebServer> context) {
7272
// ... your logic ...
73-
return resource;
73+
return UpdateControl.updateStatusSubResource(resource);
7474
}
7575
}
7676
```
@@ -139,7 +139,7 @@ public class Application {
139139
}
140140
```
141141

142-
And add Spring's `@Service` annotation to your controller classes so they will be automatically registered as resource controllers.
142+
add Spring's `@Service` annotation to your controller classes so they will be automatically registered as resource controllers.
143143

144144
The Operator's Spring Boot integration leverages [Spring's configuration mechanisms](https://docs.spring.io/spring-boot/docs/1.0.1.RELEASE/reference/html/boot-features-external-config.html) to configure
145145
- [The Kubernetes client](spring-boot-starter/src/main/java/com/github/containersolutions/operator/spingboot/starter/OperatorProperties.java)

operator-framework/src/main/java/com/github/containersolutions/operator/ControllerUtils.java

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
import java.util.Map;
1414

1515

16-
class ControllerUtils {
16+
public class ControllerUtils {
1717

1818
private final static double JAVA_VERSION = Double.parseDouble(System.getProperty("java.specification.version"));
1919

@@ -78,4 +78,11 @@ static String getCrdName(ResourceController controller) {
7878
private static Controller getAnnotation(ResourceController controller) {
7979
return controller.getClass().getAnnotation(Controller.class);
8080
}
81+
82+
public static boolean hasDefaultFinalizer(CustomResource resource, String finalizer) {
83+
if (resource.getMetadata().getFinalizers() != null) {
84+
return resource.getMetadata().getFinalizers().contains(finalizer);
85+
}
86+
return false;
87+
}
8188
}

operator-framework/src/main/java/com/github/containersolutions/operator/Operator.java

+4-3
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@ private <R extends CustomResource> void registerController(ResourceController<R>
5656
Class<R> resClass = getCustomResourceClass(controller);
5757
CustomResourceDefinition crd = getCustomResourceDefinitionForController(controller);
5858
KubernetesDeserializer.registerCustomKind(getApiVersion(crd), getKind(crd), resClass);
59-
59+
String finalizer = getDefaultFinalizer(controller);
6060
MixedOperation client = k8sClient.customResources(crd, resClass, CustomResourceList.class, getCustomResourceDoneableClass(controller));
6161
EventDispatcher eventDispatcher = new EventDispatcher(controller,
62-
getDefaultFinalizer(controller), new EventDispatcher.CustomResourceReplaceFacade(client));
63-
EventScheduler eventScheduler = new EventScheduler(eventDispatcher, retry, ControllerUtils.getGenerationEventProcessing(controller));
62+
finalizer, new EventDispatcher.CustomResourceFacade(client), ControllerUtils.getGenerationEventProcessing(controller));
63+
EventScheduler eventScheduler = new EventScheduler(eventDispatcher, retry);
6464
registerWatches(controller, client, resClass, watchAllNamespaces, targetNamespaces, eventScheduler);
6565
}
6666

67+
6768
private <R extends CustomResource> void registerWatches(ResourceController<R> controller, MixedOperation client,
6869
Class<R> resClass,
6970
boolean watchAllNamespaces, String[] targetNamespaces, EventScheduler eventScheduler) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.github.containersolutions.operator.api;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
5+
public interface Context<T extends CustomResource> {
6+
7+
RetryInfo retryInfo();
8+
9+
}

operator-framework/src/main/java/com/github/containersolutions/operator/api/Controller.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
String finalizerName() default DEFAULT_FINALIZER;
2121

2222
/**
23-
* If true, will process new event only if generation increased since the last processing, otherwise will
23+
* If true, will dispatch new event to the controller if generation increased since the last processing, otherwise will
2424
* process all events.
2525
* See generation meta attribute
2626
* <a href="https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#status-subresource">here</a>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.github.containersolutions.operator.api;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
5+
public class DefaultContext<T extends CustomResource> implements Context<T> {
6+
7+
private final RetryInfo retryInfo;
8+
9+
public DefaultContext(RetryInfo retryInfo) {
10+
this.retryInfo = retryInfo;
11+
}
12+
13+
@Override
14+
public RetryInfo retryInfo() {
15+
return retryInfo;
16+
}
17+
}

operator-framework/src/main/java/com/github/containersolutions/operator/api/ResourceController.java

+2-4
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
import io.fabric8.kubernetes.client.CustomResource;
44

5-
import java.util.Optional;
6-
75
public interface ResourceController<R extends CustomResource> {
86

97
/**
@@ -17,7 +15,7 @@ public interface ResourceController<R extends CustomResource> {
1715
* @return true - so the finalizer is automatically removed after the call.
1816
* false if you don't want to remove the finalizer. Note that this is ALMOST NEVER the case.
1917
*/
20-
boolean deleteResource(R resource);
18+
boolean deleteResource(R resource, Context<R> context);
2119

2220
/**
2321
* The implementation of this operation is required to be idempotent.
@@ -28,6 +26,6 @@ public interface ResourceController<R extends CustomResource> {
2826
* this update can be skipped.
2927
* <b>However we will always call an update if there is no finalizer on object and its not marked for deletion.</b>
3028
*/
31-
Optional<R> createOrUpdateResource(R resource);
29+
UpdateControl createOrUpdateResource(R resource, Context<R> context);
3230

3331
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package com.github.containersolutions.operator.api;
2+
3+
public class RetryInfo {
4+
5+
private int retryNumber;
6+
private boolean lastAttempt;
7+
8+
public RetryInfo(int retryNumber, boolean lastAttempt) {
9+
this.retryNumber = retryNumber;
10+
this.lastAttempt = lastAttempt;
11+
}
12+
13+
public int getRetryNumber() {
14+
return retryNumber;
15+
}
16+
17+
public boolean isLastAttempt() {
18+
return lastAttempt;
19+
}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package com.github.containersolutions.operator.api;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
5+
public class UpdateControl<T extends CustomResource> {
6+
7+
private final T customResource;
8+
private final boolean updateStatusSubResource;
9+
private final boolean updateCustomResource;
10+
11+
private UpdateControl(T customResource, boolean updateStatusSubResource, boolean updateCustomResource) {
12+
if ((updateCustomResource || updateStatusSubResource) && customResource == null) {
13+
throw new IllegalArgumentException("CustomResource cannot be null in case of update");
14+
}
15+
this.customResource = customResource;
16+
this.updateStatusSubResource = updateStatusSubResource;
17+
this.updateCustomResource = updateCustomResource;
18+
}
19+
20+
public static <T extends CustomResource> UpdateControl<T> updateCustomResource(T customResource) {
21+
return new UpdateControl<>(customResource, false, true);
22+
}
23+
24+
public static <T extends CustomResource> UpdateControl<T> updateStatusSubResource(T customResource) {
25+
return new UpdateControl<>(customResource, true, false);
26+
}
27+
28+
public static <T extends CustomResource> UpdateControl<T> noUpdate() {
29+
return new UpdateControl<>(null, false, false);
30+
}
31+
32+
public T getCustomResource() {
33+
return customResource;
34+
}
35+
36+
public boolean isUpdateStatusSubResource() {
37+
return updateStatusSubResource;
38+
}
39+
40+
public boolean isUpdateCustomResource() {
41+
return updateCustomResource;
42+
}
43+
44+
}

operator-framework/src/main/java/com/github/containersolutions/operator/processing/CustomResourceEvent.java

+10-4
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,13 @@ public class CustomResourceEvent {
1414
private final CustomResource resource;
1515
private int retryCount = -1;
1616

17-
CustomResourceEvent(Watcher.Action action, CustomResource resource, Retry retry) {
17+
public CustomResourceEvent(Watcher.Action action, CustomResource resource, Retry retry) {
1818
this.action = action;
1919
this.resource = resource;
2020
this.retryExecution = retry.initExecution();
2121
}
2222

23-
Watcher.Action getAction() {
23+
public Watcher.Action getAction() {
2424
return action;
2525
}
2626

@@ -32,8 +32,6 @@ public String resourceUid() {
3232
return resource.getMetadata().getUid();
3333
}
3434

35-
36-
3735
public Optional<Long> nextBackOff() {
3836
retryCount++;
3937
return retryExecution.nextDelay();
@@ -50,4 +48,12 @@ public String toString() {
5048
" ], retriesIndex=" + retryCount +
5149
'}';
5250
}
51+
52+
public RetryExecution getRetryExecution() {
53+
return retryExecution;
54+
}
55+
56+
public int getRetryCount() {
57+
return retryCount;
58+
}
5359
}

operator-framework/src/main/java/com/github/containersolutions/operator/processing/EventConsumer.java

+1-5
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package com.github.containersolutions.operator.processing;
22

3-
import io.fabric8.kubernetes.client.CustomResource;
4-
import io.fabric8.kubernetes.client.Watcher;
53
import org.slf4j.Logger;
64
import org.slf4j.LoggerFactory;
75

@@ -34,10 +32,8 @@ public void run() {
3432

3533
@SuppressWarnings("unchecked")
3634
private boolean processEvent() {
37-
Watcher.Action action = event.getAction();
38-
CustomResource resource = event.getResource();
3935
try {
40-
eventDispatcher.handleEvent(action, resource);
36+
eventDispatcher.handleEvent(event);
4137
} catch (RuntimeException e) {
4238
log.error("Processing event {} failed.", event, e);
4339
return false;

0 commit comments

Comments
 (0)