Skip to content

Commit fec20a2

Browse files
committed
feat: distict latest resource collector
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent f105902 commit fec20a2

File tree

2 files changed

+277
-0
lines changed

2 files changed

+277
-0
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,16 @@
1616
package io.javaoperatorsdk.operator.api.reconciler;
1717

1818
import java.lang.reflect.InvocationTargetException;
19+
import java.util.ArrayList;
20+
import java.util.Collection;
21+
import java.util.HashSet;
22+
import java.util.List;
23+
import java.util.Map;
24+
import java.util.Set;
1925
import java.util.function.Predicate;
2026
import java.util.function.UnaryOperator;
27+
import java.util.stream.Collector;
28+
import java.util.stream.Collectors;
2129

2230
import org.slf4j.Logger;
2331
import org.slf4j.LoggerFactory;

operator-framework-core/src/test/java/io/javaoperatorsdk/operator/api/reconciler/ReconcileUtilsTest.java

Lines changed: 269 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,12 @@
1515
*/
1616
package io.javaoperatorsdk.operator.api.reconciler;
1717

18+
import java.util.Collection;
1819
import java.util.Collections;
1920
import java.util.List;
21+
import java.util.Set;
2022
import java.util.function.UnaryOperator;
23+
import java.util.stream.Stream;
2124

2225
import org.junit.jupiter.api.BeforeEach;
2326
import org.junit.jupiter.api.Test;
@@ -325,4 +328,270 @@ void resourcePatchThrowsWhenEventSourceIsNotManagedInformer() {
325328
assertThat(exception.getMessage()).contains("Target event source must be a subclass off");
326329
assertThat(exception.getMessage()).contains("ManagedInformerEventSource");
327330
}
331+
332+
@Test
333+
void latestDistinctKeepsOnlyLatestResourceVersion() {
334+
// Create multiple resources with same name and namespace but different versions
335+
HasMetadata pod1v1 =
336+
new PodBuilder()
337+
.withMetadata(
338+
new ObjectMetaBuilder()
339+
.withName("pod1")
340+
.withNamespace("default")
341+
.withResourceVersion("100")
342+
.build())
343+
.build();
344+
345+
HasMetadata pod1v2 =
346+
new PodBuilder()
347+
.withMetadata(
348+
new ObjectMetaBuilder()
349+
.withName("pod1")
350+
.withNamespace("default")
351+
.withResourceVersion("200")
352+
.build())
353+
.build();
354+
355+
HasMetadata pod1v3 =
356+
new PodBuilder()
357+
.withMetadata(
358+
new ObjectMetaBuilder()
359+
.withName("pod1")
360+
.withNamespace("default")
361+
.withResourceVersion("150")
362+
.build())
363+
.build();
364+
365+
// Create a resource with different name
366+
HasMetadata pod2v1 =
367+
new PodBuilder()
368+
.withMetadata(
369+
new ObjectMetaBuilder()
370+
.withName("pod2")
371+
.withNamespace("default")
372+
.withResourceVersion("100")
373+
.build())
374+
.build();
375+
376+
// Create a resource with same name but different namespace
377+
HasMetadata pod1OtherNsv1 =
378+
new PodBuilder()
379+
.withMetadata(
380+
new ObjectMetaBuilder()
381+
.withName("pod1")
382+
.withNamespace("other")
383+
.withResourceVersion("50")
384+
.build())
385+
.build();
386+
387+
Collection<HasMetadata> result =
388+
Stream.of(pod1v1, pod1v2, pod1v3, pod2v1, pod1OtherNsv1)
389+
.collect(ReconcileUtils.latestDistinct());
390+
391+
// Should have 3 resources: pod1 in default (latest version 200), pod2 in default, and pod1 in
392+
// other
393+
assertThat(result).hasSize(3);
394+
395+
// Find pod1 in default namespace - should have version 200
396+
HasMetadata pod1InDefault =
397+
result.stream()
398+
.filter(
399+
r ->
400+
"pod1".equals(r.getMetadata().getName())
401+
&& "default".equals(r.getMetadata().getNamespace()))
402+
.findFirst()
403+
.orElseThrow();
404+
assertThat(pod1InDefault.getMetadata().getResourceVersion()).isEqualTo("200");
405+
406+
// Find pod2 in default namespace - should exist
407+
HasMetadata pod2InDefault =
408+
result.stream()
409+
.filter(
410+
r ->
411+
"pod2".equals(r.getMetadata().getName())
412+
&& "default".equals(r.getMetadata().getNamespace()))
413+
.findFirst()
414+
.orElseThrow();
415+
assertThat(pod2InDefault.getMetadata().getResourceVersion()).isEqualTo("100");
416+
417+
// Find pod1 in other namespace - should exist
418+
HasMetadata pod1InOther =
419+
result.stream()
420+
.filter(
421+
r ->
422+
"pod1".equals(r.getMetadata().getName())
423+
&& "other".equals(r.getMetadata().getNamespace()))
424+
.findFirst()
425+
.orElseThrow();
426+
assertThat(pod1InOther.getMetadata().getResourceVersion()).isEqualTo("50");
427+
}
428+
429+
@Test
430+
void latestDistinctHandlesEmptyStream() {
431+
Collection<HasMetadata> result =
432+
Stream.<HasMetadata>empty().collect(ReconcileUtils.latestDistinct());
433+
434+
assertThat(result).isEmpty();
435+
}
436+
437+
@Test
438+
void latestDistinctHandlesSingleResource() {
439+
HasMetadata pod =
440+
new PodBuilder()
441+
.withMetadata(
442+
new ObjectMetaBuilder()
443+
.withName("pod1")
444+
.withNamespace("default")
445+
.withResourceVersion("100")
446+
.build())
447+
.build();
448+
449+
Collection<HasMetadata> result = Stream.of(pod).collect(ReconcileUtils.latestDistinct());
450+
451+
assertThat(result).hasSize(1);
452+
assertThat(result).contains(pod);
453+
}
454+
455+
@Test
456+
void latestDistinctComparesNumericVersionsCorrectly() {
457+
// Test that version 1000 is greater than version 999 (not lexicographic)
458+
HasMetadata podV999 =
459+
new PodBuilder()
460+
.withMetadata(
461+
new ObjectMetaBuilder()
462+
.withName("pod1")
463+
.withNamespace("default")
464+
.withResourceVersion("999")
465+
.build())
466+
.build();
467+
468+
HasMetadata podV1000 =
469+
new PodBuilder()
470+
.withMetadata(
471+
new ObjectMetaBuilder()
472+
.withName("pod1")
473+
.withNamespace("default")
474+
.withResourceVersion("1000")
475+
.build())
476+
.build();
477+
478+
Collection<HasMetadata> result =
479+
Stream.of(podV999, podV1000).collect(ReconcileUtils.latestDistinct());
480+
481+
assertThat(result).hasSize(1);
482+
HasMetadata resultPod = result.iterator().next();
483+
assertThat(resultPod.getMetadata().getResourceVersion()).isEqualTo("1000");
484+
}
485+
486+
@Test
487+
void latestDistinctListReturnsListType() {
488+
Pod pod1v1 =
489+
new PodBuilder()
490+
.withMetadata(
491+
new ObjectMetaBuilder()
492+
.withName("pod1")
493+
.withNamespace("default")
494+
.withResourceVersion("100")
495+
.build())
496+
.build();
497+
498+
Pod pod1v2 =
499+
new PodBuilder()
500+
.withMetadata(
501+
new ObjectMetaBuilder()
502+
.withName("pod1")
503+
.withNamespace("default")
504+
.withResourceVersion("200")
505+
.build())
506+
.build();
507+
508+
Pod pod2v1 =
509+
new PodBuilder()
510+
.withMetadata(
511+
new ObjectMetaBuilder()
512+
.withName("pod2")
513+
.withNamespace("default")
514+
.withResourceVersion("100")
515+
.build())
516+
.build();
517+
518+
List<Pod> result =
519+
Stream.of(pod1v1, pod1v2, pod2v1).collect(ReconcileUtils.latestDistinctList());
520+
521+
assertThat(result).isInstanceOf(List.class);
522+
assertThat(result).hasSize(2);
523+
524+
// Verify the list contains the correct resources
525+
Pod pod1 =
526+
result.stream()
527+
.filter(r -> "pod1".equals(r.getMetadata().getName()))
528+
.findFirst()
529+
.orElseThrow();
530+
assertThat(pod1.getMetadata().getResourceVersion()).isEqualTo("200");
531+
}
532+
533+
@Test
534+
void latestDistinctSetReturnsSetType() {
535+
Pod pod1v1 =
536+
new PodBuilder()
537+
.withMetadata(
538+
new ObjectMetaBuilder()
539+
.withName("pod1")
540+
.withNamespace("default")
541+
.withResourceVersion("100")
542+
.build())
543+
.build();
544+
545+
Pod pod1v2 =
546+
new PodBuilder()
547+
.withMetadata(
548+
new ObjectMetaBuilder()
549+
.withName("pod1")
550+
.withNamespace("default")
551+
.withResourceVersion("200")
552+
.build())
553+
.build();
554+
555+
Pod pod2v1 =
556+
new PodBuilder()
557+
.withMetadata(
558+
new ObjectMetaBuilder()
559+
.withName("pod2")
560+
.withNamespace("default")
561+
.withResourceVersion("100")
562+
.build())
563+
.build();
564+
565+
Set<Pod> result = Stream.of(pod1v1, pod1v2, pod2v1).collect(ReconcileUtils.latestDistinctSet());
566+
567+
assertThat(result).isInstanceOf(java.util.Set.class);
568+
assertThat(result).hasSize(2);
569+
570+
// Verify the set contains the correct resources
571+
Pod pod1 =
572+
result.stream()
573+
.filter(r -> "pod1".equals(r.getMetadata().getName()))
574+
.findFirst()
575+
.orElseThrow();
576+
assertThat(pod1.getMetadata().getResourceVersion()).isEqualTo("200");
577+
}
578+
579+
@Test
580+
void latestDistinctListHandlesEmptyStream() {
581+
List<HasMetadata> result =
582+
Stream.<HasMetadata>empty().collect(ReconcileUtils.latestDistinctList());
583+
584+
assertThat(result).isEmpty();
585+
assertThat(result).isInstanceOf(List.class);
586+
}
587+
588+
@Test
589+
void latestDistinctSetHandlesEmptyStream() {
590+
Set<HasMetadata> result =
591+
Stream.<HasMetadata>empty().collect(ReconcileUtils.latestDistinctSet());
592+
593+
assertThat(result).isEmpty();
594+
assertThat(result).isInstanceOf(Set.class);
595+
}
596+
328597
}

0 commit comments

Comments
 (0)