Skip to content

Commit 457c23f

Browse files
authored
fix: startup all resource indexing (#2881)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 28e18f7 commit 457c23f

File tree

7 files changed

+180
-4
lines changed

7 files changed

+180
-4
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSource.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ private InformerEventSource(
9595
parseResourceVersions);
9696
// If there is a primary to secondary mapper there is no need for primary to secondary index.
9797
primaryToSecondaryMapper = configuration.getPrimaryToSecondaryMapper();
98-
if (primaryToSecondaryMapper == null) {
98+
if (useSecondaryToPrimaryIndex()) {
9999
primaryToSecondaryIndex =
100100
// The index uses the secondary to primary mapper (always present) to build the index
101101
new DefaultPrimaryToSecondaryIndex<>(configuration.getSecondaryToPrimaryMapper());
@@ -157,6 +157,14 @@ public void onDelete(R resource, boolean b) {
157157
}
158158
}
159159

160+
@Override
161+
public synchronized void start() {
162+
super.start();
163+
// this makes sure that on first reconciliation all resources are
164+
// present on the index
165+
manager().list().forEach(primaryToSecondaryIndex::onAddOrUpdate);
166+
}
167+
160168
private synchronized void onAddOrUpdate(
161169
Operation operation, R newObject, R oldObject, Runnable superOnOp) {
162170
var resourceID = ResourceID.fromResource(newObject);

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/processing/event/source/informer/InformerEventSourceTest.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,12 @@ void setup() {
5757
.thenReturn(mock(SecondaryToPrimaryMapper.class));
5858
when(informerEventSourceConfiguration.getResourceClass()).thenReturn(Deployment.class);
5959

60-
informerEventSource = new InformerEventSource<>(informerEventSourceConfiguration, clientMock);
60+
informerEventSource =
61+
new InformerEventSource<>(informerEventSourceConfiguration, clientMock) {
62+
// mocking start
63+
@Override
64+
public synchronized void start() {}
65+
};
6166

6267
var mockControllerConfig = mock(ControllerConfiguration.class);
6368
when(mockControllerConfig.getConfigurationService()).thenReturn(new BaseConfigurationService());

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/AbstractOperatorExtension.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,10 @@ public <T extends HasMetadata> T create(T resource) {
111111
return kubernetesClient.resource(resource).inNamespace(namespace).create();
112112
}
113113

114+
public <T extends HasMetadata> T serverSideApply(T resource) {
115+
return kubernetesClient.resource(resource).inNamespace(namespace).serverSideApply();
116+
}
117+
114118
public <T extends HasMetadata> T replace(T resource) {
115119
return kubernetesClient.resource(resource).inNamespace(namespace).replace();
116120
}

operator-framework-junit5/src/main/java/io/javaoperatorsdk/operator/junit/LocallyRunOperatorExtension.java

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ public class LocallyRunOperatorExtension extends AbstractOperatorExtension {
5454
private final List<Class<? extends CustomResource>> additionalCustomResourceDefinitions;
5555
private final Map<Reconciler, RegisteredController> registeredControllers;
5656
private final Map<String, String> crdMappings;
57+
private final Consumer<LocallyRunOperatorExtension> beforeStartHook;
5758

5859
private LocallyRunOperatorExtension(
5960
List<ReconcilerSpec> reconcilers,
@@ -68,7 +69,8 @@ private LocallyRunOperatorExtension(
6869
Consumer<ConfigurationServiceOverrider> configurationServiceOverrider,
6970
Function<ExtensionContext, String> namespaceNameSupplier,
7071
Function<ExtensionContext, String> perClassNamespaceNameSupplier,
71-
List<String> additionalCrds) {
72+
List<String> additionalCrds,
73+
Consumer<LocallyRunOperatorExtension> beforeStartHook) {
7274
super(
7375
infrastructure,
7476
infrastructureTimeout,
@@ -82,6 +84,7 @@ private LocallyRunOperatorExtension(
8284
this.portForwards = portForwards;
8385
this.localPortForwards = new ArrayList<>(portForwards.size());
8486
this.additionalCustomResourceDefinitions = additionalCustomResourceDefinitions;
87+
this.beforeStartHook = beforeStartHook;
8588
configurationServiceOverrider =
8689
configurationServiceOverrider != null
8790
? configurationServiceOverrider.andThen(
@@ -298,6 +301,10 @@ protected void before(ExtensionContext context) {
298301
});
299302
crdMappings.clear();
300303

304+
if (beforeStartHook != null) {
305+
beforeStartHook.accept(this);
306+
}
307+
301308
LOGGER.debug("Starting the operator locally");
302309
this.operator.start();
303310
}
@@ -356,6 +363,7 @@ public static class Builder extends AbstractBuilder<Builder> {
356363
private final List<PortForwardSpec> portForwards;
357364
private final List<Class<? extends CustomResource>> additionalCustomResourceDefinitions;
358365
private final List<String> additionalCRDs = new ArrayList<>();
366+
private Consumer<LocallyRunOperatorExtension> beforeStartHook;
359367
private KubernetesClient kubernetesClient;
360368

361369
protected Builder() {
@@ -424,6 +432,15 @@ public Builder withAdditionalCRD(String... paths) {
424432
return this;
425433
}
426434

435+
/**
436+
* Used to initialize resources when the namespace is generated but the operator is not started
437+
* yet.
438+
*/
439+
public Builder withBeforeStartHook(Consumer<LocallyRunOperatorExtension> beforeStartHook) {
440+
this.beforeStartHook = beforeStartHook;
441+
return this;
442+
}
443+
427444
public LocallyRunOperatorExtension build() {
428445
return new LocallyRunOperatorExtension(
429446
reconcilers,
@@ -438,7 +455,8 @@ public LocallyRunOperatorExtension build() {
438455
configurationServiceOverrider,
439456
namespaceNameSupplier,
440457
perClassNamespaceNameSupplier,
441-
additionalCRDs);
458+
additionalCRDs,
459+
beforeStartHook);
442460
}
443461
}
444462

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess;
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.ShortNames;
7+
import io.fabric8.kubernetes.model.annotation.Version;
8+
9+
@Group("sample.javaoperatorsdk")
10+
@Version("v1")
11+
@ShortNames("ssac")
12+
public class StartupSecondaryAccessCustomResource extends CustomResource<Void, Void>
13+
implements Namespaced {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess;
2+
3+
import java.util.Map;
4+
5+
import org.junit.jupiter.api.Test;
6+
import org.junit.jupiter.api.extension.RegisterExtension;
7+
8+
import io.fabric8.kubernetes.api.model.ConfigMap;
9+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
10+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
11+
12+
import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessReconciler.LABEL_KEY;
13+
import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessReconciler.LABEL_VALUE;
14+
import static org.assertj.core.api.Assertions.assertThat;
15+
import static org.awaitility.Awaitility.await;
16+
17+
class StartupSecondaryAccessIT {
18+
19+
public static final int SECONDARY_NUMBER = 200;
20+
21+
@RegisterExtension
22+
static LocallyRunOperatorExtension extension =
23+
LocallyRunOperatorExtension.builder()
24+
.withReconciler(new StartupSecondaryAccessReconciler())
25+
.withBeforeStartHook(
26+
ex -> {
27+
var primary = new StartupSecondaryAccessCustomResource();
28+
primary.setMetadata(new ObjectMetaBuilder().withName("test1").build());
29+
primary = ex.serverSideApply(primary);
30+
31+
for (int i = 0; i < SECONDARY_NUMBER; i++) {
32+
ConfigMap cm = new ConfigMap();
33+
cm.setMetadata(
34+
new ObjectMetaBuilder()
35+
.withLabels(Map.of(LABEL_KEY, LABEL_VALUE))
36+
.withNamespace(ex.getNamespace())
37+
.withName("cm" + i)
38+
.build());
39+
cm.addOwnerReference(primary);
40+
ex.serverSideApply(cm);
41+
}
42+
})
43+
.build();
44+
45+
@Test
46+
void reconcilerSeeAllSecondaryResources() {
47+
var reconciler = extension.getReconcilerOfType(StartupSecondaryAccessReconciler.class);
48+
49+
await().untilAsserted(() -> assertThat(reconciler.isReconciled()).isTrue());
50+
51+
assertThat(reconciler.isSecondaryAndCacheSameAmount()).isTrue();
52+
}
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package io.javaoperatorsdk.operator.baseapi.startsecondaryaccess;
2+
3+
import java.util.List;
4+
5+
import org.slf4j.Logger;
6+
import org.slf4j.LoggerFactory;
7+
8+
import io.fabric8.kubernetes.api.model.ConfigMap;
9+
import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
10+
import io.javaoperatorsdk.operator.api.reconciler.Context;
11+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
12+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
13+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
14+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
15+
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
16+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
17+
18+
import static io.javaoperatorsdk.operator.baseapi.startsecondaryaccess.StartupSecondaryAccessIT.SECONDARY_NUMBER;
19+
20+
@ControllerConfiguration
21+
public class StartupSecondaryAccessReconciler
22+
implements Reconciler<StartupSecondaryAccessCustomResource> {
23+
24+
private static final Logger log = LoggerFactory.getLogger(StartupSecondaryAccessReconciler.class);
25+
26+
public static final String LABEL_KEY = "app";
27+
public static final String LABEL_VALUE = "secondary-test";
28+
29+
private InformerEventSource<ConfigMap, StartupSecondaryAccessCustomResource> cmInformer;
30+
31+
private boolean secondaryAndCacheSameAmount = true;
32+
private boolean reconciled = false;
33+
34+
@Override
35+
public UpdateControl<StartupSecondaryAccessCustomResource> reconcile(
36+
StartupSecondaryAccessCustomResource resource,
37+
Context<StartupSecondaryAccessCustomResource> context) {
38+
39+
var secondary = context.getSecondaryResources(ConfigMap.class);
40+
var cached = cmInformer.list().toList();
41+
42+
log.info(
43+
"Secondary number: {}, cached: {}, expected: {}",
44+
secondary.size(),
45+
cached.size(),
46+
SECONDARY_NUMBER);
47+
48+
if (secondary.size() != cached.size()) {
49+
secondaryAndCacheSameAmount = false;
50+
}
51+
reconciled = true;
52+
return UpdateControl.noUpdate();
53+
}
54+
55+
@Override
56+
public List<EventSource<?, StartupSecondaryAccessCustomResource>> prepareEventSources(
57+
EventSourceContext<StartupSecondaryAccessCustomResource> context) {
58+
cmInformer =
59+
new InformerEventSource<>(
60+
InformerEventSourceConfiguration.from(
61+
ConfigMap.class, StartupSecondaryAccessCustomResource.class)
62+
.withLabelSelector(LABEL_KEY + "=" + LABEL_VALUE)
63+
.build(),
64+
context);
65+
return List.of(cmInformer);
66+
}
67+
68+
public boolean isSecondaryAndCacheSameAmount() {
69+
return secondaryAndCacheSameAmount;
70+
}
71+
72+
public boolean isReconciled() {
73+
return reconciled;
74+
}
75+
}

0 commit comments

Comments
 (0)