Skip to content

Commit 2a8b15a

Browse files
committed
rough in of tracking things that are defaults and normalizations
Signed-off-by: Steve Hawkins <shawkins@redhat.com>
1 parent f6f0183 commit 2a8b15a

File tree

2 files changed

+145
-23
lines changed

2 files changed

+145
-23
lines changed

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,18 @@ public R create(R desired, P primary, Context<P> context) {
6969
}
7070
addMetadata(false, null, desired, primary, context);
7171
final var resource = prepare(context, desired, primary, "Creating");
72-
return useSSA(context)
73-
? resource
74-
.fieldManager(context.getControllerConfiguration().fieldManager())
75-
.forceConflicts()
76-
.serverSideApply()
77-
: resource.create();
72+
var result =
73+
useSSA(context)
74+
? resource
75+
.fieldManager(context.getControllerConfiguration().fieldManager())
76+
.forceConflicts()
77+
.serverSideApply()
78+
: resource.create();
79+
if (useSSA(context)) {
80+
SSABasedGenericKubernetesResourceMatcher.getInstance()
81+
.findDefaultsAndNormalizations(result, desired, context);
82+
}
83+
return result;
7884
}
7985

8086
public R update(R actual, R desired, P primary, Context<P> context) {
@@ -92,6 +98,8 @@ public R update(R actual, R desired, P primary, Context<P> context) {
9298
.fieldManager(context.getControllerConfiguration().fieldManager())
9399
.forceConflicts()
94100
.serverSideApply();
101+
SSABasedGenericKubernetesResourceMatcher.getInstance()
102+
.findDefaultsAndNormalizations(actual, desired, context);
95103
} else {
96104
var updatedActual = GenericResourceUpdater.updateResource(actual, desired, context);
97105
updatedResource = prepare(context, updatedActual, primary, "Updating").update();

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

Lines changed: 131 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import java.util.ArrayList;
55
import java.util.Collections;
66
import java.util.HashMap;
7+
import java.util.Iterator;
78
import java.util.LinkedHashMap;
89
import java.util.List;
910
import java.util.Map;
@@ -27,6 +28,7 @@
2728
import io.javaoperatorsdk.operator.OperatorException;
2829
import io.javaoperatorsdk.operator.api.reconciler.Context;
2930
import io.javaoperatorsdk.operator.processing.LoggingUtils;
31+
import io.javaoperatorsdk.operator.processing.event.ResourceID;
3032

3133
import com.github.difflib.DiffUtils;
3234
import com.github.difflib.UnifiedDiffUtils;
@@ -60,7 +62,13 @@ public class SSABasedGenericKubernetesResourceMatcher<R extends HasMetadata> {
6062
new SSABasedGenericKubernetesResourceMatcher<>();
6163

6264
private static final List<String> IGNORED_METADATA =
63-
List.of("creationTimestamp", "deletionTimestamp", "generation", "selfLink", "uid");
65+
List.of(
66+
"creationTimestamp",
67+
"deletionTimestamp",
68+
"generation",
69+
"selfLink",
70+
"uid",
71+
"resourceVersion");
6472

6573
@SuppressWarnings("unchecked")
6674
public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L> getInstance() {
@@ -79,16 +87,134 @@ public static <L extends HasMetadata> SSABasedGenericKubernetesResourceMatcher<L
7987
private static final Logger log =
8088
LoggerFactory.getLogger(SSABasedGenericKubernetesResourceMatcher.class);
8189

82-
@SuppressWarnings("unchecked")
90+
record SSAState(Map<String, Object> desired, Map<String, Object> actual) {}
91+
92+
record CacheKey(ResourceID id, int hash) {}
93+
94+
private LinkedHashMap<CacheKey, SSAState> defaultsAndNormalizations =
95+
new LinkedHashMap<CacheKey, SSAState>();
96+
97+
public void findDefaultsAndNormalizations(R actual, R desired, Context<?> context) {
98+
SSAState state = getSSAState(actual, desired, context);
99+
100+
if (state == null) {
101+
// exception?
102+
}
103+
104+
// minimize by removing eveything that appears in both
105+
minimizeMaps(state.desired, state.actual);
106+
107+
if (!state.desired.isEmpty() || !state.actual.isEmpty()) {
108+
defaultsAndNormalizations.put(
109+
new CacheKey(ResourceID.fromResource(actual), desired.hashCode()), state);
110+
}
111+
}
112+
113+
void minimizeMaps(Map<String, Object> actual, Map<String, Object> desired) {
114+
for (Iterator<Map.Entry<String, Object>> iter = desired.entrySet().iterator();
115+
iter.hasNext(); ) {
116+
var entry = iter.next();
117+
var desiredValue = entry.getValue();
118+
var actualValue = actual.get(entry.getKey());
119+
if (Objects.equals(desiredValue, actualValue)) {
120+
iter.remove();
121+
actual.remove(entry.getKey());
122+
} else if (desiredValue instanceof Map dm) {
123+
if (actualValue instanceof Map am) {
124+
minimizeMaps(am, dm);
125+
if (am.isEmpty()) {
126+
actual.remove(entry.getKey());
127+
}
128+
}
129+
if (dm.isEmpty()) {
130+
iter.remove();
131+
}
132+
} else if (desiredValue instanceof List dl) {
133+
if (actualValue instanceof List al) {
134+
minimizeLists(al, dl);
135+
if ((dl.isEmpty() || dl.stream().allMatch(Objects::isNull))
136+
&& (al.isEmpty() || al.stream().allMatch(Objects::isNull))) {
137+
iter.remove();
138+
actual.remove(entry.getKey());
139+
}
140+
}
141+
}
142+
}
143+
}
144+
145+
void minimizeLists(List<Object> actual, List<Object> desired) {
146+
for (int i = 0; i < desired.size(); i++) {
147+
Object desiredValue = desired.get(i);
148+
if (actual.size() > i) {
149+
Object actualValue = actual.get(i);
150+
if (Objects.equals(desiredValue, actualValue)) {
151+
actual.set(i, null);
152+
desired.set(i, null);
153+
} else if (desiredValue instanceof Map dm) {
154+
if (actualValue instanceof Map am) {
155+
minimizeMaps(am, dm);
156+
if (am.isEmpty()) {
157+
actual.set(i, null);
158+
}
159+
}
160+
if (dm.isEmpty()) {
161+
desired.set(i, null);
162+
}
163+
} else if (desiredValue instanceof List dl) {
164+
if (actualValue instanceof List al) {
165+
minimizeLists(al, dl);
166+
if ((dl.isEmpty() || dl.stream().allMatch(Objects::isNull))
167+
&& (al.isEmpty() || al.stream().allMatch(Objects::isNull))) {
168+
actual.set(i, null);
169+
desired.set(i, null);
170+
}
171+
}
172+
}
173+
}
174+
}
175+
}
176+
83177
public boolean matches(R actual, R desired, Context<?> context) {
178+
SSAState state = getSSAState(actual, desired, context);
179+
180+
if (state == null) {
181+
return false;
182+
}
183+
184+
SSAState overrides = defaultsAndNormalizations.get(new CacheKey(ResourceID.fromResource(actual), desired.hashCode()));
185+
if (overrides != null) {
186+
// if containsAll(overrides.desired, state.desired) && containsAll(overrides.actual, state.actual)
187+
188+
//
189+
}
190+
191+
var matches = matches(state.actual(), state.desired(), actual, desired, context);
192+
if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) {
193+
var diff =
194+
getDiff(
195+
state.actual(), state.desired(), context.getClient().getKubernetesSerialization());
196+
log.debug(
197+
"Diff between actual and desired state for resource: {} with name: {} in namespace: {}"
198+
+ " is:\n"
199+
+ "{}",
200+
actual.getKind(),
201+
actual.getMetadata().getName(),
202+
actual.getMetadata().getNamespace(),
203+
diff);
204+
}
205+
return matches;
206+
}
207+
208+
@SuppressWarnings("unchecked")
209+
private SSAState getSSAState(R actual, R desired, Context<?> context) {
84210
var optionalManagedFieldsEntry =
85211
checkIfFieldManagerExists(actual, context.getControllerConfiguration().fieldManager());
86212
// If no field is managed by our controller, that means the controller hasn't touched the
87213
// resource yet and the resource probably doesn't match the desired state. Not matching here
88214
// means that the resource will need to be updated and since this will be done using SSA, the
89215
// fields our controller cares about will become managed by it
90216
if (optionalManagedFieldsEntry.isEmpty()) {
91-
return false;
217+
return null;
92218
}
93219

94220
var managedFieldsEntry = optionalManagedFieldsEntry.orElseThrow();
@@ -109,20 +235,8 @@ public boolean matches(R actual, R desired, Context<?> context) {
109235
objectMapper);
110236

111237
removeIrrelevantValues(desiredMap);
112-
113-
var matches = matches(prunedActual, desiredMap, actual, desired, context);
114-
if (!matches && log.isDebugEnabled() && LoggingUtils.isNotSensitiveResource(desired)) {
115-
var diff = getDiff(prunedActual, desiredMap, objectMapper);
116-
log.debug(
117-
"Diff between actual and desired state for resource: {} with name: {} in namespace: {}"
118-
+ " is:\n"
119-
+ "{}",
120-
actual.getKind(),
121-
actual.getMetadata().getName(),
122-
actual.getMetadata().getNamespace(),
123-
diff);
124-
}
125-
return matches;
238+
removeIrrelevantValues(actualMap);
239+
return new SSAState(desiredMap, prunedActual);
126240
}
127241

128242
/**

0 commit comments

Comments
 (0)