Skip to content
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

next (do not merge) #2157

Merged
merged 11 commits into from
Jan 10, 2024
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
2 changes: 1 addition & 1 deletion bootstrapper-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>java-operator-sdk</artifactId>
<groupId>io.javaoperatorsdk</groupId>
<version>4.6.3-SNAPSHOT</version>
<version>4.7.0-SNAPSHOT</version>
</parent>

<artifactId>bootstrapper</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ private void addJavaFiles(File projectDir, String groupId, String artifactId) {
try {
var packages = groupId.replace(".", File.separator);
var targetDir = new File(projectDir, "src/main/java/" + packages);
var targetTestDir = new File(projectDir, "src/test/java/" + packages);
FileUtils.forceMkdir(targetDir);
var classFileNamePrefix = artifactClassId(artifactId);
JAVA_FILES.forEach(f -> addTemplatedFile(projectDir, f, groupId, artifactId, targetDir,
Expand All @@ -65,6 +66,9 @@ private void addJavaFiles(File projectDir, String groupId, String artifactId) {
addTemplatedFile(projectDir, "Runner.java", groupId, artifactId, targetDir, null);
addTemplatedFile(projectDir, "ConfigMapDependentResource.java", groupId, artifactId,
targetDir, null);
addTemplatedFile(projectDir, "ReconcilerIntegrationTest.java", groupId,
artifactId,
targetTestDir, artifactClassId(artifactId) + "ReconcilerIntegrationTest.java");
} catch (IOException e) {
throw new RuntimeException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
public class ConfigMapDependentResource
extends CRUDKubernetesDependentResource<ConfigMap, {{artifactClassId}}CustomResource> {

public static final String KEY = "key";

public ConfigMapDependentResource() {
super(ConfigMap.class);
}
Expand All @@ -28,7 +30,7 @@ protected ConfigMap desired({{artifactClassId}}CustomResource primary,
.withName(primary.getMetadata().getName())
.withNamespace(primary.getMetadata().getNamespace())
.build())
.withData(Map.of("data", primary.getSpec().getValue()))
.withData(Map.of(KEY, primary.getSpec().getValue()))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package {{groupId}};

import io.fabric8.kubernetes.api.model.ConfigMap;
import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
import io.javaoperatorsdk.operator.junit.LocallyRunOperatorExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import static {{groupId}}.ConfigMapDependentResource.KEY;
import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

class {{artifactClassId}}ReconcilerIntegrationTest {

public static final String RESOURCE_NAME = "test1";
public static final String INITIAL_VALUE = "initial value";
public static final String CHANGED_VALUE = "changed value";

@RegisterExtension
LocallyRunOperatorExtension extension =
LocallyRunOperatorExtension.builder()
.withReconciler({{artifactClassId}}Reconciler.class)
.build();

@Test
void testCRUDOperations() {
var cr = extension.create(testResource());

await().untilAsserted(() -> {
var cm = extension.get(ConfigMap.class, RESOURCE_NAME);
assertThat(cm).isNotNull();
assertThat(cm.getData()).containsEntry(KEY, INITIAL_VALUE);
});

cr.getSpec().setValue(CHANGED_VALUE);
cr = extension.replace(cr);

await().untilAsserted(() -> {
var cm = extension.get(ConfigMap.class, RESOURCE_NAME);
assertThat(cm.getData()).containsEntry(KEY, CHANGED_VALUE);
});

extension.delete(cr);

await().untilAsserted(() -> {
var cm = extension.get(ConfigMap.class, RESOURCE_NAME);
assertThat(cm).isNull();
});
}

{{artifactClassId}}CustomResource testResource() {
var resource = new {{artifactClassId}}CustomResource();
resource.setMetadata(new ObjectMetaBuilder()
.withName(RESOURCE_NAME)
.build());
resource.setSpec(new {{artifactClassId}}Spec());
resource.getSpec().setValue(INITIAL_VALUE);
return resource;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,12 @@
<artifactId>operator-framework</artifactId>
<version>${josdk.version}</version>
</dependency>
<dependency>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-junit-5</artifactId>
<version>${josdk.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.fabric8</groupId>
<artifactId>crd-generator-apt</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ void copiesFilesToTarget() {
private void assertProjectCompiles() {
try {
var process = Runtime.getRuntime()
.exec("mvn clean install -f target/test-project/pom.xml");
.exec("mvn clean install -f target/test-project/pom.xml -DskipTests");

BufferedReader stdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));

Expand Down
2 changes: 1 addition & 1 deletion caffeine-bounded-cache-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>java-operator-sdk</artifactId>
<groupId>io.javaoperatorsdk</groupId>
<version>4.6.3-SNAPSHOT</version>
<version>4.7.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
10 changes: 10 additions & 0 deletions docs/documentation/dependent-resources.md
Original file line number Diff line number Diff line change
Expand Up @@ -483,6 +483,16 @@ also be created, one per dependent resource.
See [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/ExternalStateBulkIT.java)
as a sample.


## GenericKubernetesResource based Dependent Resources

In rare circumstances resource handling where there is no class representation or just typeless handling might be needed.
Fabric8 Client provides [GenericKubernetesResource](https://github.com/fabric8io/kubernetes-client/blob/main/doc/CHEATSHEET.md#resource-typeless-api)
to support that.

For dependent resource this is supported by [GenericKubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/GenericKubernetesDependentResource.java#L8-L8)
. See samples [here](https://github.com/java-operator-sdk/java-operator-sdk/tree/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/generickubernetesresource).

## Other Dependent Resource Features

### Caching and Event Handling in [KubernetesDependentResource](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/kubernetes/KubernetesDependentResource.java)
Expand Down
22 changes: 22 additions & 0 deletions docs/documentation/workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,28 @@ containing all the related exceptions.
The exceptions can be handled
by [`ErrorStatusHandler`](https://github.com/java-operator-sdk/java-operator-sdk/blob/14620657fcacc8254bb96b4293eded84c20ba685/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/api/reconciler/ErrorStatusHandler.java)

## Waiting for the actual deletion of Kubernetes Dependent Resources

Let's consider a case when a Kubernetes Dependent Resources (KDR) depends on another resource, on cleanup
the resources will be deleted in reverse order, thus the KDR will be deleted first.
However, the workflow implementation currently simply asks the Kubernetes API server to delete the resource. This is,
however, an asynchronous process, meaning that the deletion might not occur immediately, in particular if the resource
uses finalizers that block the deletion or if the deletion itself takes some time. From the SDK's perspective, though,
the deletion has been requested and it moves on to other tasks without waiting for the resource to be actually deleted
from the server (which might never occur if it uses finalizers which are not removed).
In situations like these, if your logic depends on resources being actually removed from the cluster before a
cleanup workflow can proceed correctly, you need to block the workflow progression using a delete post-condition that
checks that the resource is actually removed or that it, at least, doesn't have any finalizers any longer. JOSDK
provides such a delete post-condition implementation in the form of
[`KubernetesResourceDeletedCondition`](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework-core/src/main/java/io/javaoperatorsdk/operator/processing/dependent/workflow/KubernetesResourceDeletedCondition.java)

Also, check usage in an [integration test](https://github.com/java-operator-sdk/java-operator-sdk/blob/main/operator-framework/src/test/java/io/javaoperatorsdk/operator/sample/manageddependentdeletecondition/ManagedDependentDefaultDeleteConditionReconciler.java).

In such cases the Kubernetes Dependent Resource should extend `CRUDNoGCKubernetesDependentResource`
and NOT `CRUDKubernetesDependentResource` since otherwise the Kubernetes Garbage Collector would delete the resources.
In other words if a Kubernetes Dependent Resource depends on another dependent resource, it should not implement
`GargageCollected` interface, otherwise the deletion order won't be guaranteed.

## Notes and Caveats

- Delete is almost always called on every resource during the cleanup. However, it might be the case
Expand Down
2 changes: 1 addition & 1 deletion micrometer-support/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<artifactId>java-operator-sdk</artifactId>
<groupId>io.javaoperatorsdk</groupId>
<version>4.6.3-SNAPSHOT</version>
<version>4.7.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ private static String getScope(ResourceID resourceID) {
}

private static void addGVKTags(GroupVersionKind gvk, List<Tag> tags, boolean prefixed) {
addTagOmittingOnEmptyValue(GROUP, gvk.group, tags, prefixed);
addTag(VERSION, gvk.version, tags, prefixed);
addTag(KIND, gvk.kind, tags, prefixed);
addTagOmittingOnEmptyValue(GROUP, gvk.getGroup(), tags, prefixed);
addTag(VERSION, gvk.getVersion(), tags, prefixed);
addTag(KIND, gvk.getKind(), tags, prefixed);
}

private void incrementCounter(ResourceID id, String counterName, Map<String, Object> metadata,
Expand Down
2 changes: 1 addition & 1 deletion operator-framework-bom/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

<groupId>io.javaoperatorsdk</groupId>
<artifactId>operator-framework-bom</artifactId>
<version>4.6.3-SNAPSHOT</version>
<version>4.7.0-SNAPSHOT</version>
<name>Operator SDK - Bill of Materials</name>
<packaging>pom</packaging>
<description>Java SDK for implementing Kubernetes operators</description>
Expand Down
2 changes: 1 addition & 1 deletion operator-framework-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<parent>
<groupId>io.javaoperatorsdk</groupId>
<artifactId>java-operator-sdk</artifactId>
<version>4.6.3-SNAPSHOT</version>
<version>4.7.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Locale;
import java.util.Objects;
import java.util.function.Predicate;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import io.fabric8.kubernetes.api.builder.Builder;
import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.api.model.Namespaced;
import io.fabric8.kubernetes.client.CustomResource;
import io.fabric8.kubernetes.client.KubernetesClientException;
import io.fabric8.kubernetes.client.utils.Serialization;
Expand Down Expand Up @@ -71,6 +74,30 @@ public static String getNameFor(Class<? extends Reconciler> reconcilerClass) {
return getDefaultNameFor(reconcilerClass);
}

public static void checkIfCanAddOwnerReference(HasMetadata owner, HasMetadata resource) {
if (owner instanceof GenericKubernetesResource
|| resource instanceof GenericKubernetesResource) {
return;
}
if (owner instanceof Namespaced) {
if (!(resource instanceof Namespaced)) {
throw new OperatorException(
"Cannot add owner reference from a cluster scoped to a namespace scoped resource." +
resourcesIdentifierDescription(owner, resource));
} else if (!Objects.equals(owner.getMetadata().getNamespace(),
resource.getMetadata().getNamespace())) {
throw new OperatorException(
"Cannot add owner reference between two resource in different namespaces." +
resourcesIdentifierDescription(owner, resource));
}
}
}

private static String resourcesIdentifierDescription(HasMetadata owner, HasMetadata resource) {
return " Owner name: " + owner.getMetadata().getName() + " Kind: " + owner.getKind()
+ ", Resource name: " + resource.getMetadata().getName() + " Kind: " + resource.getKind();
}

public static String getNameFor(Reconciler reconciler) {
return getNameFor(reconciler.getClass());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import java.util.Optional;
import java.util.Set;

import io.fabric8.kubernetes.api.model.GenericKubernetesResource;
import io.fabric8.kubernetes.api.model.HasMetadata;
import io.fabric8.kubernetes.client.informers.cache.ItemStore;
import io.javaoperatorsdk.operator.ReconcilerUtils;
Expand All @@ -28,7 +29,11 @@ protected DefaultResourceConfiguration(Class<R> resourceClass,
OnUpdateFilter<? super R> onUpdateFilter, GenericFilter<? super R> genericFilter,
ItemStore<R> itemStore, Long informerListLimit) {
this.resourceClass = resourceClass;
this.resourceTypeName = ReconcilerUtils.getResourceTypeName(resourceClass);
this.resourceTypeName = resourceClass.isAssignableFrom(GenericKubernetesResource.class)
// in general this is irrelevant now for secondary resources it is used just by controller
// where GenericKubernetesResource now does not apply
? GenericKubernetesResource.class.getSimpleName()
: ReconcilerUtils.getResourceTypeName(resourceClass);
this.onAddFilter = onAddFilter;
this.onUpdateFilter = onUpdateFilter;
this.genericFilter = genericFilter;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ public static <T> void executeAndWaitForAllToComplete(Stream<T> stream,
// to find out any exceptions
f.get();
} catch (ExecutionException e) {
throw new OperatorException(e.getCause());
throw new OperatorException(e);
} catch (InterruptedException e) {
log.warn("Interrupted.", e);
Thread.currentThread().interrupt();
Expand Down
Loading
Loading