Skip to content

Commit 6632278

Browse files
committed
wip
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 9b387ae commit 6632278

3 files changed

Lines changed: 191 additions & 0 deletions

File tree

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.baseapi.readcacheafterwrite.ownsecondaryupdate;
17+
18+
import io.fabric8.kubernetes.api.model.Namespaced;
19+
import io.fabric8.kubernetes.client.CustomResource;
20+
import io.fabric8.kubernetes.model.annotation.Group;
21+
import io.fabric8.kubernetes.model.annotation.ShortNames;
22+
import io.fabric8.kubernetes.model.annotation.Version;
23+
24+
@Group("sample.javaoperatorsdk")
25+
@Version("v1")
26+
@ShortNames("osu")
27+
public class OwnSecondaryUpdateCustomResource extends CustomResource<Void, Void>
28+
implements Namespaced {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.baseapi.readcacheafterwrite.ownsecondaryupdate;
17+
18+
import java.time.Duration;
19+
20+
import org.junit.jupiter.api.Test;
21+
import org.junit.jupiter.api.extension.RegisterExtension;
22+
23+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
24+
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.awaitility.Awaitility.await;
28+
29+
/**
30+
* Verifies that when the controller updates a secondary resource through the read-cache-after-write
31+
* path (here: {@code context.resourceOperations().serverSideApply}), the resulting watch events on
32+
* the secondary are filtered and do NOT trigger additional reconciliations. Counterpart to {@code
33+
* ExternalSecondaryUpdateIT}, which asserts the opposite for third-party updates.
34+
*/
35+
class OwnSecondaryUpdateIT {
36+
37+
static final String RESOURCE_NAME = "test-resource";
38+
39+
OwnSecondaryUpdateReconciler reconciler = new OwnSecondaryUpdateReconciler();
40+
41+
@RegisterExtension
42+
LocallyRunOperatorExtension operator =
43+
LocallyRunOperatorExtension.builder().withReconciler(reconciler).build();
44+
45+
@Test
46+
void ownUpdateOnSecondaryDoesNotTriggerReconciliation() {
47+
operator.create(testResource());
48+
49+
// Wait for the first reconciliation to have run all of its SSAs (the secondary CM exists
50+
// and carries the data of the last SSA iteration).
51+
await()
52+
.atMost(Duration.ofSeconds(30))
53+
.untilAsserted(
54+
() -> {
55+
var cm =
56+
operator
57+
.getKubernetesClient()
58+
.configMaps()
59+
.inNamespace(operator.getNamespace())
60+
.withName(RESOURCE_NAME)
61+
.get();
62+
assertThat(cm).isNotNull();
63+
assertThat(cm.getData())
64+
.containsEntry("iteration", "" + OwnSecondaryUpdateReconciler.OWN_SSA_COUNT);
65+
});
66+
67+
// Give any spurious own-write events time to reach the controller. The filter must absorb
68+
// them, so the reconciliation count must stay at 1 (the one triggered by the create).
69+
await()
70+
.pollDelay(Duration.ofSeconds(2))
71+
.atMost(Duration.ofSeconds(3))
72+
.untilAsserted(() -> assertThat(reconciler.numberOfExecutions.get()).isEqualTo(1));
73+
}
74+
75+
OwnSecondaryUpdateCustomResource testResource() {
76+
var r = new OwnSecondaryUpdateCustomResource();
77+
r.setMetadata(new ObjectMetaBuilder().withName(RESOURCE_NAME).build());
78+
return r;
79+
}
80+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright Java Operator SDK Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package io.javaoperatorsdk.operator.baseapi.readcacheafterwrite.ownsecondaryupdate;
17+
18+
import java.util.List;
19+
import java.util.Map;
20+
import java.util.concurrent.atomic.AtomicInteger;
21+
22+
import io.fabric8.kubernetes.api.model.ConfigMap;
23+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
24+
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
25+
import io.javaoperatorsdk.operator.api.config.informer.InformerEventSourceConfiguration;
26+
import io.javaoperatorsdk.operator.api.reconciler.Context;
27+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
28+
import io.javaoperatorsdk.operator.api.reconciler.EventSourceContext;
29+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
30+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
31+
import io.javaoperatorsdk.operator.processing.event.source.EventSource;
32+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
33+
34+
@ControllerConfiguration(generationAwareEventProcessing = false)
35+
public class OwnSecondaryUpdateReconciler implements Reconciler<OwnSecondaryUpdateCustomResource> {
36+
37+
static final int OWN_SSA_COUNT = 3;
38+
39+
final AtomicInteger numberOfExecutions = new AtomicInteger();
40+
41+
private InformerEventSource<ConfigMap, OwnSecondaryUpdateCustomResource> configMapEventSource;
42+
43+
@Override
44+
public UpdateControl<OwnSecondaryUpdateCustomResource> reconcile(
45+
OwnSecondaryUpdateCustomResource resource,
46+
Context<OwnSecondaryUpdateCustomResource> context) {
47+
numberOfExecutions.incrementAndGet();
48+
49+
// Issue several SSA writes on the secondary, each with distinct data so the resource
50+
// version actually advances. With the read-cache-after-write filter in place, none of the
51+
// resulting watch events should trigger a fresh reconciliation.
52+
for (int i = 1; i <= OWN_SSA_COUNT; i++) {
53+
context.resourceOperations().serverSideApply(prepareCM(resource, i), configMapEventSource);
54+
}
55+
return UpdateControl.noUpdate();
56+
}
57+
58+
@Override
59+
public List<EventSource<?, OwnSecondaryUpdateCustomResource>> prepareEventSources(
60+
EventSourceContext<OwnSecondaryUpdateCustomResource> context) {
61+
configMapEventSource =
62+
new InformerEventSource<>(
63+
InformerEventSourceConfiguration.from(
64+
ConfigMap.class, OwnSecondaryUpdateCustomResource.class)
65+
.build(),
66+
context);
67+
return List.of(configMapEventSource);
68+
}
69+
70+
private static ConfigMap prepareCM(OwnSecondaryUpdateCustomResource p, int iteration) {
71+
var cm =
72+
new ConfigMapBuilder()
73+
.withMetadata(
74+
new ObjectMetaBuilder()
75+
.withName(p.getMetadata().getName())
76+
.withNamespace(p.getMetadata().getNamespace())
77+
.build())
78+
.withData(Map.of("iteration", "" + iteration))
79+
.build();
80+
cm.addOwnerReference(p);
81+
return cm;
82+
}
83+
}

0 commit comments

Comments
 (0)