Skip to content

Commit 8eda9dd

Browse files
committed
tests and test fixes
Signed-off-by: Attila Mészáros <a_meszaros@apple.com>
1 parent 5a49799 commit 8eda9dd

4 files changed

Lines changed: 239 additions & 7 deletions

File tree

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -567,7 +567,7 @@ public <R extends HasMetadata> R jsonPatch(
567567
return resourcePatch(
568568
desired,
569569
actualResource,
570-
r -> context.getClient().resource(actualResource).edit(unaryOperator),
570+
r -> context.getClient().resource(actualResource).edit(rr -> desired),
571571
informerEventSource,
572572
options);
573573
}
@@ -704,7 +704,7 @@ public P jsonPatchPrimaryStatus(
704704
return resourcePatch(
705705
desired,
706706
actualResource,
707-
r -> context.getClient().resource(actualResource).status().edit(unaryOperator),
707+
r -> context.getClient().resource(actualResource).status().edit(rr -> desired),
708708
context.eventSourceRetriever().getControllerEventSource(),
709709
options);
710710
}

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

Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,20 @@
1717

1818
import java.util.Collections;
1919
import java.util.List;
20+
import java.util.Optional;
2021
import java.util.function.UnaryOperator;
2122

2223
import org.junit.jupiter.api.BeforeEach;
2324
import org.junit.jupiter.api.Test;
25+
import org.mockito.stubbing.Answer;
2426

2527
import io.fabric8.kubernetes.api.model.HasMetadata;
2628
import io.fabric8.kubernetes.client.KubernetesClient;
2729
import io.fabric8.kubernetes.client.KubernetesClientException;
2830
import io.fabric8.kubernetes.client.dsl.MixedOperation;
31+
import io.fabric8.kubernetes.client.dsl.NamespaceableResource;
2932
import io.fabric8.kubernetes.client.dsl.Resource;
33+
import io.fabric8.kubernetes.client.dsl.base.PatchContext;
3034
import io.fabric8.kubernetes.client.utils.KubernetesSerialization;
3135
import io.javaoperatorsdk.operator.TestUtils;
3236
import io.javaoperatorsdk.operator.api.config.Cloner;
@@ -56,6 +60,12 @@ class ResourceOperationsTest {
5660
private ControllerEventSource<TestCustomResource> controllerEventSource;
5761
private ResourceOperations<TestCustomResource> resourceOperations;
5862

63+
@SuppressWarnings("rawtypes")
64+
private ManagedInformerEventSource verbEventSource;
65+
66+
@SuppressWarnings("rawtypes")
67+
private NamespaceableResource verbClientResource;
68+
5969
@BeforeEach
6070
void setupMocks() {
6171
context = mock(Context.class);
@@ -448,4 +458,227 @@ void createRejectsMatcher() {
448458

449459
assertThat(exception.getMessage()).contains("does not support matcher");
450460
}
461+
462+
// ---------------------------------------------------------------------------
463+
// High-level update / create / patch verbs with different Options variations.
464+
// These go through the full public API (resolving the event source from the
465+
// retriever and invoking the real client verb), verifying both the routing
466+
// (filter vs cache) and which underlying Kubernetes operation is used.
467+
// ---------------------------------------------------------------------------
468+
469+
/**
470+
* Wires a {@link ManagedInformerEventSource} into the retriever and a client {@link Resource} so
471+
* that both cache paths actually run the update operation, letting us assert which client verb is
472+
* invoked. Returns the resource the client verbs are stubbed to return.
473+
*/
474+
@SuppressWarnings("rawtypes")
475+
private TestCustomResource wireVerbMocks() {
476+
verbEventSource = mock(ManagedInformerEventSource.class);
477+
when(context.eventSourceRetriever().getEventSourcesFor(TestCustomResource.class))
478+
.thenReturn(List.of(verbEventSource));
479+
480+
verbClientResource = mock(NamespaceableResource.class);
481+
when(context.getClient().resource(any(HasMetadata.class))).thenReturn(verbClientResource);
482+
483+
var updated = TestUtils.testCustomResource1();
484+
updated.getMetadata().setResourceVersion("999");
485+
when(verbClientResource.update()).thenReturn(updated);
486+
when(verbClientResource.create()).thenReturn(updated);
487+
when(verbClientResource.updateStatus()).thenReturn(updated);
488+
when(verbClientResource.patch()).thenReturn(updated);
489+
when(verbClientResource.patch(any(PatchContext.class))).thenReturn(updated);
490+
491+
// both cache paths execute the update operation so the underlying client verb runs
492+
Answer<Object> runOperation =
493+
invocation -> ((UnaryOperator) invocation.getArgument(1)).apply(invocation.getArgument(0));
494+
when(verbEventSource.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class)))
495+
.thenAnswer(runOperation);
496+
when(verbEventSource.updateAndCacheResource(any(), any(UnaryOperator.class)))
497+
.thenAnswer(runOperation);
498+
return updated;
499+
}
500+
501+
// ---- update -------------------------------------------------------------
502+
503+
@Test
504+
void updateCacheOnlyCachesAndCallsClientUpdate() {
505+
var resource = TestUtils.testCustomResource1();
506+
resource.getMetadata().setResourceVersion("1");
507+
var updated = wireVerbMocks();
508+
509+
var result = resourceOperations.update(resource, ResourceOperations.Options.cacheOnly());
510+
511+
assertThat(result).isSameAs(updated);
512+
verify(verbEventSource, times(1))
513+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
514+
verify(verbEventSource, never())
515+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
516+
verify(verbClientResource, times(1)).update();
517+
}
518+
519+
@Test
520+
void updateForceFilterFiltersAndCallsClientUpdate() {
521+
var resource = TestUtils.testCustomResource1();
522+
var updated = wireVerbMocks();
523+
524+
var result =
525+
resourceOperations.update(resource, ResourceOperations.Options.forceFilterEvents());
526+
527+
assertThat(result).isSameAs(updated);
528+
verify(verbEventSource, times(1))
529+
.eventFilteringUpdateAndCacheResource(eq(resource), any(UnaryOperator.class));
530+
verify(verbEventSource, never()).updateAndCacheResource(any(), any(UnaryOperator.class));
531+
verify(verbClientResource, times(1)).update();
532+
}
533+
534+
@Test
535+
void updateFilterIfOptimisticLockingFiltersWhenResourceVersionPresent() {
536+
var resource = TestUtils.testCustomResource1();
537+
resource.getMetadata().setResourceVersion("1");
538+
wireVerbMocks();
539+
540+
resourceOperations.update(resource, ResourceOperations.Options.filterIfOptimisticLocking());
541+
542+
verify(verbEventSource, times(1))
543+
.eventFilteringUpdateAndCacheResource(eq(resource), any(UnaryOperator.class));
544+
verify(verbEventSource, never()).updateAndCacheResource(any(), any(UnaryOperator.class));
545+
}
546+
547+
@Test
548+
void updateFilterIfOptimisticLockingCachesWhenNoResourceVersion() {
549+
var resource = TestUtils.testCustomResource1();
550+
resource.getMetadata().setResourceVersion(null);
551+
wireVerbMocks();
552+
553+
resourceOperations.update(resource, ResourceOperations.Options.filterIfOptimisticLocking());
554+
555+
verify(verbEventSource, times(1))
556+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
557+
verify(verbEventSource, never())
558+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
559+
}
560+
561+
@Test
562+
void updateWithMatcherMatchingSkipsWrite() {
563+
var desired = TestUtils.testCustomResource1();
564+
var actual = TestUtils.testCustomResource1();
565+
wireVerbMocks();
566+
when(verbEventSource.get(any())).thenReturn(Optional.of(actual));
567+
var matcher = mock(Matcher.class);
568+
when(matcher.matches(any(), any(), any())).thenReturn(true);
569+
570+
var result =
571+
resourceOperations.update(desired, ResourceOperations.Options.matchAndFilter(matcher));
572+
573+
assertThat(result).isSameAs(actual);
574+
verify(verbEventSource, never())
575+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
576+
verify(verbEventSource, never()).updateAndCacheResource(any(), any(UnaryOperator.class));
577+
verify(verbClientResource, never()).update();
578+
}
579+
580+
@Test
581+
void updateWithMatcherNotMatchingFiltersAndWrites() {
582+
var desired = TestUtils.testCustomResource1();
583+
var actual = TestUtils.testCustomResource1();
584+
var updated = wireVerbMocks();
585+
when(verbEventSource.get(any())).thenReturn(Optional.of(actual));
586+
var matcher = mock(Matcher.class);
587+
when(matcher.matches(any(), any(), any())).thenReturn(false);
588+
589+
var result =
590+
resourceOperations.update(desired, ResourceOperations.Options.matchAndFilter(matcher));
591+
592+
assertThat(result).isSameAs(updated);
593+
verify(verbEventSource, times(1))
594+
.eventFilteringUpdateAndCacheResource(eq(desired), any(UnaryOperator.class));
595+
verify(verbClientResource, times(1)).update();
596+
}
597+
598+
// ---- create -------------------------------------------------------------
599+
600+
@Test
601+
void createDefaultForceFiltersAndCallsClientCreate() {
602+
var resource = TestUtils.testCustomResource1();
603+
var updated = wireVerbMocks();
604+
605+
var result = resourceOperations.create(resource);
606+
607+
assertThat(result).isSameAs(updated);
608+
verify(verbEventSource, times(1))
609+
.eventFilteringUpdateAndCacheResource(eq(resource), any(UnaryOperator.class));
610+
verify(verbEventSource, never()).updateAndCacheResource(any(), any(UnaryOperator.class));
611+
verify(verbClientResource, times(1)).create();
612+
}
613+
614+
@Test
615+
void createCacheOnlyCachesAndCallsClientCreate() {
616+
var resource = TestUtils.testCustomResource1();
617+
var updated = wireVerbMocks();
618+
619+
var result = resourceOperations.create(resource, ResourceOperations.Options.cacheOnly());
620+
621+
assertThat(result).isSameAs(updated);
622+
verify(verbEventSource, times(1))
623+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
624+
verify(verbEventSource, never())
625+
.eventFilteringUpdateAndCacheResource(any(), any(UnaryOperator.class));
626+
verify(verbClientResource, times(1)).create();
627+
}
628+
629+
@Test
630+
void createWithNullEventSourceFallsBackToForceFilter() {
631+
var resource = TestUtils.testCustomResource1();
632+
wireVerbMocks();
633+
634+
// a null informer event source falls back to the default create(), which force-filters
635+
resourceOperations.create(resource, null, ResourceOperations.Options.cacheOnly());
636+
637+
verify(verbEventSource, times(1))
638+
.eventFilteringUpdateAndCacheResource(eq(resource), any(UnaryOperator.class));
639+
verify(verbEventSource, never()).updateAndCacheResource(any(), any(UnaryOperator.class));
640+
}
641+
642+
// ---- patch / status verbs ----------------------------------------------
643+
644+
@Test
645+
void jsonMergePatchCacheOnlyCallsClientPatch() {
646+
var resource = TestUtils.testCustomResource1();
647+
var updated = wireVerbMocks();
648+
649+
var result =
650+
resourceOperations.jsonMergePatch(resource, ResourceOperations.Options.cacheOnly());
651+
652+
assertThat(result).isSameAs(updated);
653+
verify(verbEventSource, times(1))
654+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
655+
verify(verbClientResource, times(1)).patch();
656+
}
657+
658+
@Test
659+
void serverSideApplyCacheOnlyCallsClientSsaPatch() {
660+
var resource = TestUtils.testCustomResource1();
661+
var updated = wireVerbMocks();
662+
663+
var result =
664+
resourceOperations.serverSideApply(resource, ResourceOperations.Options.cacheOnly());
665+
666+
assertThat(result).isSameAs(updated);
667+
verify(verbEventSource, times(1))
668+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
669+
verify(verbClientResource, times(1)).patch(any(PatchContext.class));
670+
}
671+
672+
@Test
673+
void updateStatusCacheOnlyCallsClientUpdateStatus() {
674+
var resource = TestUtils.testCustomResource1();
675+
var updated = wireVerbMocks();
676+
677+
var result = resourceOperations.updateStatus(resource, ResourceOperations.Options.cacheOnly());
678+
679+
assertThat(result).isSameAs(updated);
680+
verify(verbEventSource, times(1))
681+
.updateAndCacheResource(eq(resource), any(UnaryOperator.class));
682+
verify(verbClientResource, times(1)).updateStatus();
683+
}
451684
}

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceTestCustomReconciler.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,14 +43,13 @@ public UpdateControl<SubResourceTestCustomResource> reconcile(
4343
numberOfExecutions.addAndGet(1);
4444
log.info("Value: " + resource.getSpec().getValue());
4545

46-
ensureStatusExists(resource);
47-
4846
waitXms(RECONCILER_MIN_EXEC_TIME);
4947
context
5048
.resourceOperations()
5149
.jsonPatchPrimaryStatus(
5250
resource,
5351
r -> {
52+
ensureStatusExists(r);
5453
r.getStatus().setState(SubResourceTestCustomResourceStatus.State.SUCCESS);
5554
return r;
5655
});

operator-framework/src/test/java/io/javaoperatorsdk/operator/baseapi/subresource/SubResourceUpdateIT.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ void updatesSubResourceStatus() {
5959
// wait for sure, there are no more events
6060
waitXms(WAIT_AFTER_EXECUTION);
6161
// there is no event on status update processed
62-
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(2);
62+
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(1);
6363
}
6464

6565
@Test
@@ -73,7 +73,7 @@ void updatesSubResourceStatusNoFinalizer() {
7373
// wait for sure, there are no more events
7474
waitXms(WAIT_AFTER_EXECUTION);
7575
// there is no event on status update processed
76-
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(2);
76+
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(1);
7777
}
7878

7979
/** Note that we check on controller impl if there is finalizer on execution. */
@@ -87,7 +87,7 @@ void ifNoFinalizerPresentFirstAddsTheFinalizerThenExecutesControllerAgain() {
8787
// wait for sure, there are no more events
8888
waitXms(WAIT_AFTER_EXECUTION);
8989
// there is no event on status update processed
90-
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(2);
90+
assertThat(TestUtils.getNumberOfExecutions(operator)).isEqualTo(1);
9191
}
9292

9393
/**

0 commit comments

Comments
 (0)