Skip to content

Commit 755a6c3

Browse files
lburgazzolimetacosm
authored andcommitted
feat: option to limit CRs the operator watches operator-framework#453
1 parent b8433ed commit 755a6c3

File tree

12 files changed

+348
-71
lines changed

12 files changed

+348
-71
lines changed

operator-framework-core/pom.xml

+12-1
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,14 @@
5050
</plugins>
5151
</build>
5252

53+
5354
<dependencies>
5455
<!-- We use the OpenShift client, because functionally it is a superset of the Kubernetes client -->
5556
<dependency>
5657
<groupId>io.fabric8</groupId>
5758
<artifactId>openshift-client</artifactId>
5859
</dependency>
59-
60+
6061
<dependency>
6162
<groupId>org.slf4j</groupId>
6263
<artifactId>slf4j-api</artifactId>
@@ -97,5 +98,15 @@
9798
<artifactId>log4j-core</artifactId>
9899
<scope>test</scope>
99100
</dependency>
101+
<dependency>
102+
<groupId>io.fabric8</groupId>
103+
<artifactId>kubernetes-server-mock</artifactId>
104+
<scope>test</scope>
105+
</dependency>
106+
<dependency>
107+
<groupId>org.awaitility</groupId>
108+
<artifactId>awaitility</artifactId>
109+
<scope>test</scope>
110+
</dependency>
100111
</dependencies>
101112
</project>

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/Controller.java

+9
Original file line numberDiff line numberDiff line change
@@ -40,4 +40,13 @@
4040
* @return the list of namespaces this controller monitors
4141
*/
4242
String[] namespaces() default {};
43+
44+
/**
45+
* Optional label selector used to identify the set of custom resources the controller will acc
46+
* upon. The label selector can be made of multiple comma separated requirements that acts as a
47+
* logical AND operator.
48+
*
49+
* @return the finalizer name
50+
*/
51+
String labelSelector() default NULL;
4352
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.javaoperatorsdk.operator.ControllerUtils;
5+
import io.javaoperatorsdk.operator.api.ResourceController;
6+
7+
public abstract class AbstractConfiguration<R extends CustomResource>
8+
implements ControllerConfiguration<R> {
9+
private final Class<? extends ResourceController<R>> controllerClass;
10+
private final Class<R> customResourceClass;
11+
private ConfigurationService service;
12+
13+
public AbstractConfiguration(
14+
Class<? extends ResourceController<R>> controllerClass, Class<R> customResourceClass) {
15+
this.controllerClass = controllerClass;
16+
this.customResourceClass = customResourceClass;
17+
}
18+
19+
@Override
20+
public String getName() {
21+
return ControllerUtils.getNameFor(controllerClass);
22+
}
23+
24+
@Override
25+
public String getCRDName() {
26+
return CustomResource.getCRDName(getCustomResourceClass());
27+
}
28+
29+
@Override
30+
public ConfigurationService getConfigurationService() {
31+
return service;
32+
}
33+
34+
@Override
35+
public void setConfigurationService(ConfigurationService service) {
36+
this.service = service;
37+
}
38+
39+
@Override
40+
public String getAssociatedControllerClassName() {
41+
return controllerClass.getCanonicalName();
42+
}
43+
44+
@Override
45+
public Class<R> getCustomResourceClass() {
46+
return customResourceClass;
47+
}
48+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/AbstractControllerConfiguration.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ public abstract class AbstractControllerConfiguration<R extends CustomResource>
1515
private final Set<String> namespaces;
1616
private final boolean watchAllNamespaces;
1717
private final RetryConfiguration retryConfiguration;
18+
private final String labelSelector;
1819
private ConfigurationService service;
1920

2021
public AbstractControllerConfiguration(
@@ -24,7 +25,8 @@ public AbstractControllerConfiguration(
2425
String finalizer,
2526
boolean generationAware,
2627
Set<String> namespaces,
27-
RetryConfiguration retryConfiguration) {
28+
RetryConfiguration retryConfiguration,
29+
String labelSelector) {
2830
this.associatedControllerClassName = associatedControllerClassName;
2931
this.name = name;
3032
this.crdName = crdName;
@@ -37,6 +39,7 @@ public AbstractControllerConfiguration(
3739
retryConfiguration == null
3840
? ControllerConfiguration.super.getRetryConfiguration()
3941
: retryConfiguration;
42+
this.labelSelector = labelSelector;
4043
}
4144

4245
@Override
@@ -88,4 +91,9 @@ public ConfigurationService getConfigurationService() {
8891
public void setConfigurationService(ConfigurationService service) {
8992
this.service = service;
9093
}
94+
95+
@Override
96+
public String getLabelSelector() {
97+
return labelSelector;
98+
}
9199
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package io.javaoperatorsdk.operator.api.config;
2+
3+
import io.fabric8.kubernetes.client.CustomResource;
4+
import io.javaoperatorsdk.operator.ControllerUtils;
5+
import io.javaoperatorsdk.operator.api.Controller;
6+
import io.javaoperatorsdk.operator.api.ResourceController;
7+
import java.util.Optional;
8+
import java.util.function.Predicate;
9+
10+
public class AnnotationConfiguration<R extends CustomResource> extends AbstractConfiguration<R> {
11+
private final Optional<Controller> annotation;
12+
13+
public AnnotationConfiguration(
14+
Class<? extends ResourceController<R>> controllerClass, Class<R> customResourceClass) {
15+
super(controllerClass, customResourceClass);
16+
this.annotation = Optional.ofNullable(controllerClass.getAnnotation(Controller.class));
17+
}
18+
19+
@Override
20+
public String getFinalizer() {
21+
return annotation
22+
.map(Controller::finalizerName)
23+
.filter(Predicate.not(String::isBlank))
24+
.orElse(ControllerUtils.getDefaultFinalizerName(getCRDName()));
25+
}
26+
27+
@Override
28+
public boolean isGenerationAware() {
29+
return annotation.map(Controller::generationAwareEventProcessing).orElse(true);
30+
}
31+
32+
@Override
33+
public String getLabelSelector() {
34+
return annotation.map(Controller::labelSelector).orElse("");
35+
}
36+
}

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfiguration.java

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ public interface ControllerConfiguration<R extends CustomResource> {
1313

1414
String getFinalizer();
1515

16+
String getLabelSelector();
17+
1618
boolean isGenerationAware();
1719

1820
Class<R> getCustomResourceClass();

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/config/ControllerConfigurationOverrider.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ public class ControllerConfigurationOverrider<R extends CustomResource> {
1111
private boolean generationAware;
1212
private Set<String> namespaces;
1313
private RetryConfiguration retry;
14+
private String labelSelector;
1415
private final ControllerConfiguration<R> original;
1516

1617
private ControllerConfigurationOverrider(ControllerConfiguration<R> original) {
1718
finalizer = original.getFinalizer();
1819
generationAware = original.isGenerationAware();
1920
namespaces = new HashSet<>(original.getNamespaces());
2021
retry = original.getRetryConfiguration();
22+
labelSelector = original.getLabelSelector();
2123
this.original = original;
2224
}
2325

@@ -57,6 +59,11 @@ public ControllerConfigurationOverrider<R> withRetry(RetryConfiguration retry) {
5759
return this;
5860
}
5961

62+
public ControllerConfigurationOverrider<R> withLabelSelector(String labelSelector) {
63+
this.labelSelector = labelSelector;
64+
return this;
65+
}
66+
6067
public ControllerConfiguration<R> build() {
6168
return new AbstractControllerConfiguration<R>(
6269
original.getAssociatedControllerClassName(),
@@ -65,7 +72,8 @@ public ControllerConfiguration<R> build() {
6572
finalizer,
6673
generationAware,
6774
namespaces,
68-
retry) {
75+
retry,
76+
labelSelector) {
6977
@Override
7078
public Class<R> getCustomResourceClass() {
7179
return original.getCustomResourceClass();

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

+15-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,14 @@
55
import static io.javaoperatorsdk.operator.processing.KubernetesResourceUtils.getVersion;
66

77
import io.fabric8.kubernetes.api.model.KubernetesResourceList;
8+
import io.fabric8.kubernetes.api.model.ListOptions;
89
import io.fabric8.kubernetes.client.CustomResource;
910
import io.fabric8.kubernetes.client.Watch;
1011
import io.fabric8.kubernetes.client.Watcher;
1112
import io.fabric8.kubernetes.client.WatcherException;
1213
import io.fabric8.kubernetes.client.dsl.MixedOperation;
1314
import io.fabric8.kubernetes.client.dsl.Resource;
15+
import io.fabric8.kubernetes.client.utils.Utils;
1416
import io.javaoperatorsdk.operator.api.config.ControllerConfiguration;
1517
import io.javaoperatorsdk.operator.processing.CustomResourceCache;
1618
import io.javaoperatorsdk.operator.processing.KubernetesResourceUtils;
@@ -33,6 +35,7 @@ public class CustomResourceEventSource<T extends CustomResource<?, ?>> extends A
3335
private final Set<String> targetNamespaces;
3436
private final boolean generationAware;
3537
private final String resourceFinalizer;
38+
private final String labelSelector;
3639
private final Map<String, Long> lastGenerationProcessedSuccessfully = new ConcurrentHashMap<>();
3740
private final List<Watch> watches;
3841
private final String resClass;
@@ -46,6 +49,7 @@ public CustomResourceEventSource(
4649
configuration.getEffectiveNamespaces(),
4750
configuration.isGenerationAware(),
4851
configuration.getFinalizer(),
52+
configuration.getLabelSelector(),
4953
configuration.getCustomResourceClass(),
5054
new CustomResourceCache(configuration.getConfigurationService().getObjectMapper()));
5155
}
@@ -55,12 +59,14 @@ public CustomResourceEventSource(
5559
Set<String> targetNamespaces,
5660
boolean generationAware,
5761
String resourceFinalizer,
62+
String labelSelector,
5863
Class<T> resClass) {
5964
this(
6065
client,
6166
targetNamespaces,
6267
generationAware,
6368
resourceFinalizer,
69+
labelSelector,
6470
resClass,
6571
new CustomResourceCache());
6672
}
@@ -70,27 +76,34 @@ public CustomResourceEventSource(
7076
Set<String> targetNamespaces,
7177
boolean generationAware,
7278
String resourceFinalizer,
79+
String labelSelector,
7380
Class<T> resClass,
7481
CustomResourceCache customResourceCache) {
7582
this.client = client;
7683
this.targetNamespaces = targetNamespaces;
7784
this.generationAware = generationAware;
7885
this.resourceFinalizer = resourceFinalizer;
86+
this.labelSelector = labelSelector;
7987
this.watches = new ArrayList<>();
8088
this.resClass = resClass.getName();
8189
this.customResourceCache = customResourceCache;
8290
}
8391

8492
@Override
8593
public void start() {
94+
var options = new ListOptions();
95+
if (Utils.isNotNullOrEmpty(labelSelector)) {
96+
options.setLabelSelector(labelSelector);
97+
}
98+
8699
if (ControllerConfiguration.allNamespacesWatched(targetNamespaces)) {
87-
var w = client.inAnyNamespace().watch(this);
100+
var w = client.inAnyNamespace().watch(options, this);
88101
watches.add(w);
89102
log.debug("Registered controller {} -> {} for any namespace", resClass, w);
90103
} else {
91104
targetNamespaces.forEach(
92105
ns -> {
93-
var w = client.inNamespace(ns).watch(this);
106+
var w = client.inNamespace(ns).watch(options, this);
94107
watches.add(w);
95108
log.debug("Registered controller {} -> {} for namespace: {}", resClass, w, ns);
96109
});

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/internal/CustomResourceEventSourceTest.java

+4-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@ class CustomResourceEventSourceTest {
2525
EventHandler eventHandler = mock(EventHandler.class);
2626

2727
private CustomResourceEventSource<TestCustomResource> customResourceEventSource =
28-
new CustomResourceEventSource<>(client, null, true, FINALIZER, TestCustomResource.class);
28+
new CustomResourceEventSource<>(
29+
client, null, true, FINALIZER, null, TestCustomResource.class);
2930

3031
@BeforeEach
3132
public void setup() {
@@ -72,7 +73,8 @@ public void normalExecutionIfGenerationChanges() {
7273
@Test
7374
public void handlesAllEventIfNotGenerationAware() {
7475
customResourceEventSource =
75-
new CustomResourceEventSource<>(client, null, false, FINALIZER, TestCustomResource.class);
76+
new CustomResourceEventSource<>(
77+
client, null, false, FINALIZER, null, TestCustomResource.class);
7678
setup();
7779

7880
TestCustomResource customResource1 = TestUtils.testCustomResource();

0 commit comments

Comments
 (0)