Skip to content

Observed generation in status support #628

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Oct 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package io.javaoperatorsdk.operator.api;

import java.util.Optional;

import io.fabric8.kubernetes.client.CustomResource;

/**
* If the custom resource's status implements this interface, the observed generation will be
* automatically handled. The last observed generation will be updated on status when the status is
* instructed to be updated (see below). In addition to that, controller configuration will be
* checked if is set to generation aware. If generation aware config is turned off, this interface
* is ignored.
*
* In order to work the status object returned by CustomResource.getStatus() should not be null. In
* addition to that from the controller that the
* {@link UpdateControl#updateStatusSubResource(CustomResource)} or
* {@link UpdateControl#updateCustomResourceAndStatus(CustomResource)} should be returned. The
* observed generation is not updated in other cases.
*
* @see ObservedGenerationAwareStatus
*/
public interface ObservedGenerationAware {

void setObservedGeneration(Long generation);

Optional<Long> getObservedGeneration();

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.javaoperatorsdk.operator.api;

import java.util.Optional;

/**
* A helper base class for status sub-resources classes to extend to support generate awareness.
*/
public class ObservedGenerationAwareStatus implements ObservedGenerationAware {

private Long observedGeneration;

@Override
public void setObservedGeneration(Long generation) {
this.observedGeneration = generation;
}

@Override
public Optional<Long> getObservedGeneration() {
return Optional.ofNullable(observedGeneration);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -117,18 +117,32 @@ private PostExecutionControl<R> handleCreateOrUpdate(
.getCustomResource()
.getMetadata()
.setResourceVersion(updatedCustomResource.getMetadata().getResourceVersion());
updatedCustomResource =
customResourceFacade.updateStatus(updateControl.getCustomResource());
updatedCustomResource = updateStatusGenerationAware(updateControl.getCustomResource());
} else if (updateControl.isUpdateStatusSubResource()) {
updatedCustomResource =
customResourceFacade.updateStatus(updateControl.getCustomResource());
updatedCustomResource = updateStatusGenerationAware(updateControl.getCustomResource());
} else if (updateControl.isUpdateCustomResource()) {
updatedCustomResource = updateCustomResource(updateControl.getCustomResource());
}
return createPostExecutionControl(updatedCustomResource, updateControl);
}
}

private R updateStatusGenerationAware(R customResource) {
updateStatusObservedGenerationIfRequired(customResource);
return customResourceFacade.updateStatus(customResource);
}

private void updateStatusObservedGenerationIfRequired(R customResource) {
if (controller.getConfiguration().isGenerationAware()) {
var status = customResource.getStatus();
// Note that if status is null we won't update the observed generation.
if (status instanceof ObservedGenerationAware) {
((ObservedGenerationAware) status)
.setObservedGeneration(customResource.getMetadata().getGeneration());
}
}
}

private PostExecutionControl<R> createPostExecutionControl(R updatedCustomResource,
UpdateControl<R> updateControl) {
PostExecutionControl<R> postExecutionControl;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.javaoperatorsdk.operator.processing.event.internal;

import io.fabric8.kubernetes.client.CustomResource;
import io.javaoperatorsdk.operator.api.ObservedGenerationAware;

/**
* Convenience implementations of, and utility methods for, {@link CustomResourceEventFilter}.
Expand All @@ -21,9 +22,18 @@ public final class CustomResourceEventFilters {
};

private static final CustomResourceEventFilter<CustomResource> GENERATION_AWARE =
(configuration, oldResource, newResource) -> oldResource == null
|| !configuration.isGenerationAware()
|| oldResource.getMetadata().getGeneration() < newResource.getMetadata().getGeneration();
(configuration, oldResource, newResource) -> {
final var status = newResource.getStatus();
final var generationAware = configuration.isGenerationAware();
if (generationAware && status instanceof ObservedGenerationAware) {
var actualGeneration = newResource.getMetadata().getGeneration();
var observedGeneration = ((ObservedGenerationAware) status)
.getObservedGeneration();
return observedGeneration.map(aLong -> actualGeneration > aLong).orElse(true);
}
return oldResource == null || !generationAware ||
oldResource.getMetadata().getGeneration() < newResource.getMetadata().getGeneration();
};

private static final CustomResourceEventFilter<CustomResource> PASSTHROUGH =
(configuration, oldResource, newResource) -> true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;

import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.CustomResource;
import io.javaoperatorsdk.operator.TestUtils;
import io.javaoperatorsdk.operator.api.Context;
Expand All @@ -23,6 +24,7 @@
import io.javaoperatorsdk.operator.processing.event.Event;
import io.javaoperatorsdk.operator.processing.event.internal.CustomResourceEvent;
import io.javaoperatorsdk.operator.processing.event.internal.ResourceAction;
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenCustomResource;

import static io.javaoperatorsdk.operator.processing.event.internal.ResourceAction.ADDED;
import static io.javaoperatorsdk.operator.processing.event.internal.ResourceAction.UPDATED;
Expand Down Expand Up @@ -320,6 +322,33 @@ void reScheduleOnDeleteWithoutFinalizerRemoval() {
assertThat(control.getReScheduleDelay().get()).isEqualTo(1000L);
}

@Test
void setObservedGenerationForStatusIfNeeded() {
var observedGenResource = createObservedGenCustomResource();

when(configuration.isGenerationAware()).thenReturn(true);
when(controller.createOrUpdateResource(eq(observedGenResource), any()))
.thenReturn(
UpdateControl.updateStatusSubResource(observedGenResource));

when(customResourceFacade.updateStatus(observedGenResource)).thenReturn(observedGenResource);

PostExecutionControl<ObservedGenCustomResource> control = eventDispatcher.handleExecution(
executionScopeWithCREvent(ADDED, observedGenResource));
assertThat(control.getUpdatedCustomResource().get().getStatus().getObservedGeneration().get())
.isEqualTo(1L);
}

@Test
private ObservedGenCustomResource createObservedGenCustomResource() {
ObservedGenCustomResource observedGenCustomResource = new ObservedGenCustomResource();
observedGenCustomResource.setMetadata(new ObjectMeta());
observedGenCustomResource.getMetadata().setGeneration(1L);
observedGenCustomResource.getMetadata().setFinalizers(new ArrayList<>());
observedGenCustomResource.getMetadata().getFinalizers().add(DEFAULT_FINALIZER);
return observedGenCustomResource;
}

private void markForDeletion(CustomResource customResource) {
customResource.getMetadata().setDeletionTimestamp("2019-8-10");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import org.junit.jupiter.api.Test;

import io.fabric8.kubernetes.api.model.KubernetesResourceList;
import io.fabric8.kubernetes.api.model.ObjectMeta;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.dsl.MixedOperation;
import io.fabric8.kubernetes.client.dsl.Resource;
import io.javaoperatorsdk.operator.TestUtils;
Expand All @@ -15,6 +17,7 @@
import io.javaoperatorsdk.operator.api.config.DefaultControllerConfiguration;
import io.javaoperatorsdk.operator.processing.ConfiguredController;
import io.javaoperatorsdk.operator.processing.event.EventHandler;
import io.javaoperatorsdk.operator.sample.observedgeneration.ObservedGenCustomResource;
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource;

import static org.mockito.Mockito.any;
Expand Down Expand Up @@ -94,6 +97,31 @@ public void eventFilteredByCustomPredicateAndGenerationAware() {
verify(eventHandler, times(1)).handleEvent(any());
}

@Test
public void observedGenerationFiltering() {
var config = new ObservedGenControllerConfig(FINALIZER, true, null);
when(config.getConfigurationService().getResourceCloner())
.thenReturn(ConfigurationService.DEFAULT_CLONER);

var controller = new ObservedGenConfiguredController(config);
var eventSource = new CustomResourceEventSource<>(controller);
eventSource.setEventHandler(eventHandler);

ObservedGenCustomResource cr = new ObservedGenCustomResource();
cr.setMetadata(new ObjectMeta());
cr.getMetadata().setFinalizers(List.of(FINALIZER));
cr.getMetadata().setGeneration(5L);
cr.getStatus().setObservedGeneration(5L);

eventSource.eventReceived(ResourceAction.UPDATED, cr, null);
verify(eventHandler, times(0)).handleEvent(any());

cr.getMetadata().setGeneration(6L);

eventSource.eventReceived(ResourceAction.UPDATED, cr, null);
verify(eventHandler, times(1)).handleEvent(any());
}

@Test
public void eventNotFilteredByCustomPredicateIfFinalizerIsRequired() {
var config = new TestControllerConfig(
Expand Down Expand Up @@ -124,11 +152,25 @@ public void eventNotFilteredByCustomPredicateIfFinalizerIsRequired() {
verify(eventHandler, times(2)).handleEvent(any());
}

private static class TestControllerConfig extends
DefaultControllerConfiguration<TestCustomResource> {

private static class TestControllerConfig extends ControllerConfig<TestCustomResource> {
public TestControllerConfig(String finalizer, boolean generationAware,
CustomResourceEventFilter<TestCustomResource> eventFilter) {
super(finalizer, generationAware, eventFilter, TestCustomResource.class);
}
}
private static class ObservedGenControllerConfig
extends ControllerConfig<ObservedGenCustomResource> {
public ObservedGenControllerConfig(String finalizer, boolean generationAware,
CustomResourceEventFilter<ObservedGenCustomResource> eventFilter) {
super(finalizer, generationAware, eventFilter, ObservedGenCustomResource.class);
}
}

private static class ControllerConfig<T extends CustomResource<?, ?>> extends
DefaultControllerConfiguration<T> {

public ControllerConfig(String finalizer, boolean generationAware,
CustomResourceEventFilter<T> eventFilter, Class<T> customResourceClass) {
super(
null,
null,
Expand All @@ -139,7 +181,7 @@ public TestControllerConfig(String finalizer, boolean generationAware,
null,
null,
eventFilter,
TestCustomResource.class,
customResourceClass,
mock(ConfigurationService.class));

when(getConfigurationService().getResourceCloner())
Expand All @@ -158,4 +200,18 @@ public MixedOperation<TestCustomResource, KubernetesResourceList<TestCustomResou
return mock(MixedOperation.class);
}
}

private static class ObservedGenConfiguredController
extends ConfiguredController<ObservedGenCustomResource> {

public ObservedGenConfiguredController(
ControllerConfiguration<ObservedGenCustomResource> configuration) {
super(null, configuration, null);
}

@Override
public MixedOperation<ObservedGenCustomResource, KubernetesResourceList<ObservedGenCustomResource>, Resource<ObservedGenCustomResource>> getCRClient() {
return mock(MixedOperation.class);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.model.annotation.Group;
import io.fabric8.kubernetes.model.annotation.Version;

@Group("sample.javaoperatorsdk.io")
@Version("v1")
public class ObservedGenCustomResource
extends CustomResource<ObservedGenSpec, ObservedGenStatus> {

@Override
protected ObservedGenSpec initSpec() {
return new ObservedGenSpec();
}

@Override
protected ObservedGenStatus initStatus() {
return new ObservedGenStatus();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

public class ObservedGenSpec {

private String value;

public String getValue() {
return value;
}

public void setValue(String value) {
this.value = value;
}

@Override
public String toString() {
return "TestCustomResourceSpec{" +
"value='" + value + '\'' +
'}';
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package io.javaoperatorsdk.operator.sample.observedgeneration;

import io.javaoperatorsdk.operator.api.ObservedGenerationAwareStatus;

public class ObservedGenStatus extends ObservedGenerationAwareStatus {

}