Skip to content

Commit fb9e073

Browse files
committed
improve: dependent resources notify all owners by default (#2161)
Signed-off-by: Attila Mészáros <[email protected]>
1 parent 5e6a62a commit fb9e073

File tree

8 files changed

+221
-3
lines changed

8 files changed

+221
-3
lines changed

operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ private SecondaryToPrimaryMapper<R> getSecondaryToPrimaryMapper() {
8787
if (this instanceof SecondaryToPrimaryMapper) {
8888
return (SecondaryToPrimaryMapper<R>) this;
8989
} else if (garbageCollected) {
90-
return Mappers.fromOwnerReference();
90+
return Mappers.fromOwnerReferences(false);
9191
} else if (useNonOwnerRefBasedSecondaryToPrimaryMapping()) {
9292
return Mappers.fromDefaultAnnotations();
9393
} else {

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

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import java.util.Optional;
66

77
import io.fabric8.kubernetes.api.model.HasMetadata;
8+
import io.fabric8.kubernetes.api.model.OwnerReference;
89

910
public class ResourceID implements Serializable {
1011

@@ -21,13 +22,18 @@ public static Optional<ResourceID> fromFirstOwnerReference(HasMetadata resource,
2122
boolean clusterScoped) {
2223
var ownerReferences = resource.getMetadata().getOwnerReferences();
2324
if (!ownerReferences.isEmpty()) {
24-
return Optional.of(new ResourceID(ownerReferences.get(0).getName(),
25-
clusterScoped ? null : resource.getMetadata().getNamespace()));
25+
return Optional.of(fromOwnerReference(resource, ownerReferences.get(0), clusterScoped));
2626
} else {
2727
return Optional.empty();
2828
}
2929
}
3030

31+
public static ResourceID fromOwnerReference(HasMetadata resource, OwnerReference ownerReference,
32+
boolean clusterScoped) {
33+
return new ResourceID(ownerReference.getName(),
34+
clusterScoped ? null : resource.getMetadata().getNamespace());
35+
}
36+
3137
private final String name;
3238
private final String namespace;
3339

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,13 @@ public static <T extends HasMetadata> SecondaryToPrimaryMapper<T> fromOwnerRefer
5757
.orElseGet(Collections::emptySet);
5858
}
5959

60+
public static <T extends HasMetadata> SecondaryToPrimaryMapper<T> fromOwnerReferences(
61+
boolean clusterScope) {
62+
return resource -> resource.getMetadata().getOwnerReferences().stream()
63+
.map(or -> ResourceID.fromOwnerReference(resource, or, clusterScope))
64+
.collect(Collectors.toSet());
65+
}
66+
6067
private static <T extends HasMetadata> SecondaryToPrimaryMapper<T> fromMetadata(
6168
String nameKey, String namespaceKey, boolean isLabel) {
6269
return resource -> {
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package io.javaoperatorsdk.operator;
2+
3+
import java.util.Set;
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+
import io.javaoperatorsdk.operator.sample.multipleupdateondependent.MultipleOwnerDependentConfigMap;
12+
import io.javaoperatorsdk.operator.sample.multipleupdateondependent.MultipleOwnerDependentCustomResource;
13+
import io.javaoperatorsdk.operator.sample.multipleupdateondependent.MultipleOwnerDependentReconciler;
14+
import io.javaoperatorsdk.operator.sample.multipleupdateondependent.MultipleOwnerDependentSpec;
15+
16+
import static org.assertj.core.api.Assertions.assertThat;
17+
import static org.awaitility.Awaitility.await;
18+
19+
class MultiOwnerDependentTriggeringIT {
20+
21+
public static final String VALUE_1 = "value1";
22+
public static final String VALUE_2 = "value2";
23+
public static final String NEW_VALUE_1 = "newValue1";
24+
public static final String NEW_VALUE_2 = "newValue2";
25+
26+
@RegisterExtension
27+
LocallyRunOperatorExtension extension =
28+
LocallyRunOperatorExtension.builder()
29+
.withConfigurationService(o -> o.withDefaultNonSSAResource(Set.of()))
30+
.withReconciler(MultipleOwnerDependentReconciler.class)
31+
.build();
32+
33+
34+
@Test
35+
void multiOwnerTriggeringAndManagement() {
36+
var res1 = extension.create(testResource("res1", VALUE_1));
37+
var res2 = extension.create(testResource("res2", VALUE_2));
38+
39+
await().untilAsserted(() -> {
40+
var cm = extension.get(ConfigMap.class, MultipleOwnerDependentConfigMap.RESOURCE_NAME);
41+
42+
assertThat(cm).isNotNull();
43+
assertThat(cm.getData())
44+
.containsEntry(VALUE_1, VALUE_1)
45+
.containsEntry(VALUE_2, VALUE_2);
46+
assertThat(cm.getMetadata().getOwnerReferences()).hasSize(2);
47+
});
48+
49+
res1.getSpec().setValue(NEW_VALUE_1);
50+
extension.replace(res1);
51+
52+
await().untilAsserted(() -> {
53+
var cm = extension.get(ConfigMap.class, MultipleOwnerDependentConfigMap.RESOURCE_NAME);
54+
assertThat(cm.getData())
55+
.containsEntry(NEW_VALUE_1, NEW_VALUE_1)
56+
// note that it will still contain the old value too
57+
.containsEntry(VALUE_1, VALUE_1);
58+
assertThat(cm.getMetadata().getOwnerReferences()).hasSize(2);
59+
});
60+
61+
res2.getSpec().setValue(NEW_VALUE_2);
62+
extension.replace(res2);
63+
64+
await().untilAsserted(() -> {
65+
var cm = extension.get(ConfigMap.class, MultipleOwnerDependentConfigMap.RESOURCE_NAME);
66+
assertThat(cm.getData()).containsEntry(NEW_VALUE_2, NEW_VALUE_2);
67+
assertThat(cm.getMetadata().getOwnerReferences()).hasSize(2);
68+
});
69+
}
70+
71+
MultipleOwnerDependentCustomResource testResource(String name, String value) {
72+
var res = new MultipleOwnerDependentCustomResource();
73+
res.setMetadata(new ObjectMetaBuilder()
74+
.withName(name)
75+
.build());
76+
res.setSpec(new MultipleOwnerDependentSpec());
77+
res.getSpec().setValue(value);
78+
79+
return res;
80+
}
81+
82+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
package io.javaoperatorsdk.operator.sample.multipleupdateondependent;
2+
3+
import java.util.HashMap;
4+
import java.util.List;
5+
import java.util.Optional;
6+
7+
import io.fabric8.kubernetes.api.model.ConfigMap;
8+
import io.fabric8.kubernetes.api.model.ConfigMapBuilder;
9+
import io.javaoperatorsdk.operator.api.reconciler.Context;
10+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.BooleanWithUndefined;
11+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
12+
import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
13+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
14+
import io.javaoperatorsdk.operator.processing.event.source.informer.InformerEventSource;
15+
16+
@KubernetesDependent(useSSA = BooleanWithUndefined.TRUE)
17+
public class MultipleOwnerDependentConfigMap
18+
extends
19+
CRUDKubernetesDependentResource<ConfigMap, MultipleOwnerDependentCustomResource> {
20+
21+
public static final String RESOURCE_NAME = "test1";
22+
23+
public MultipleOwnerDependentConfigMap() {
24+
super(ConfigMap.class);
25+
}
26+
27+
@Override
28+
protected ConfigMap desired(MultipleOwnerDependentCustomResource primary,
29+
Context<MultipleOwnerDependentCustomResource> context) {
30+
31+
var cm = getSecondaryResource(primary, context);
32+
33+
var data = cm.map(ConfigMap::getData).orElse(new HashMap<>());
34+
data.put(primary.getSpec().getValue(), primary.getSpec().getValue());
35+
36+
return new ConfigMapBuilder()
37+
.withNewMetadata()
38+
.withName(RESOURCE_NAME)
39+
.withNamespace(primary.getMetadata().getNamespace())
40+
.withOwnerReferences(cm.map(c -> c.getMetadata().getOwnerReferences()).orElse(List.of()))
41+
.endMetadata()
42+
.withData(data)
43+
.build();
44+
}
45+
46+
// need to change this since owner reference is present only for the creator primary resource.
47+
@Override
48+
public Optional<ConfigMap> getSecondaryResource(MultipleOwnerDependentCustomResource primary,
49+
Context<MultipleOwnerDependentCustomResource> context) {
50+
InformerEventSource<ConfigMap, MultipleOwnerDependentCustomResource> ies =
51+
(InformerEventSource<ConfigMap, MultipleOwnerDependentCustomResource>) context
52+
.eventSourceRetriever().getResourceEventSourceFor(ConfigMap.class);
53+
return ies.get(new ResourceID(RESOURCE_NAME, primary.getMetadata().getNamespace()));
54+
}
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.javaoperatorsdk.operator.sample.multipleupdateondependent;
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("mod")
12+
public class MultipleOwnerDependentCustomResource
13+
extends CustomResource<MultipleOwnerDependentSpec, Void>
14+
implements Namespaced {
15+
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package io.javaoperatorsdk.operator.sample.multipleupdateondependent;
2+
3+
import java.util.concurrent.atomic.AtomicInteger;
4+
5+
import io.javaoperatorsdk.operator.api.reconciler.Context;
6+
import io.javaoperatorsdk.operator.api.reconciler.ControllerConfiguration;
7+
import io.javaoperatorsdk.operator.api.reconciler.Reconciler;
8+
import io.javaoperatorsdk.operator.api.reconciler.UpdateControl;
9+
import io.javaoperatorsdk.operator.api.reconciler.dependent.Dependent;
10+
import io.javaoperatorsdk.operator.support.TestExecutionInfoProvider;
11+
12+
@ControllerConfiguration(dependents = {
13+
@Dependent(type = MultipleOwnerDependentConfigMap.class)
14+
})
15+
public class MultipleOwnerDependentReconciler
16+
implements Reconciler<MultipleOwnerDependentCustomResource>,
17+
TestExecutionInfoProvider {
18+
19+
private final AtomicInteger numberOfExecutions = new AtomicInteger(0);
20+
21+
public MultipleOwnerDependentReconciler() {}
22+
23+
@Override
24+
public UpdateControl<MultipleOwnerDependentCustomResource> reconcile(
25+
MultipleOwnerDependentCustomResource resource,
26+
Context<MultipleOwnerDependentCustomResource> context) {
27+
numberOfExecutions.getAndIncrement();
28+
29+
return UpdateControl.noUpdate();
30+
}
31+
32+
33+
public int getNumberOfExecutions() {
34+
return numberOfExecutions.get();
35+
}
36+
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package io.javaoperatorsdk.operator.sample.multipleupdateondependent;
2+
3+
public class MultipleOwnerDependentSpec {
4+
5+
private String value;
6+
7+
public String getValue() {
8+
return value;
9+
}
10+
11+
public MultipleOwnerDependentSpec setValue(String value) {
12+
this.value = value;
13+
return this;
14+
}
15+
}

0 commit comments

Comments
 (0)