Skip to content
Open
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
43 changes: 43 additions & 0 deletions operator-framework-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@
<name>Operator SDK - Framework - Core</name>
<description>Core framework for implementing Kubernetes operators</description>

<properties>
<!-- Used only for test sources, to verify that the framework works properly with Kotlin. -->
<kotlin.version>2.4.10</kotlin.version>
</properties>

<dependencies>
<dependency>
<groupId>io.github.java-diff-utils</groupId>
Expand Down Expand Up @@ -101,6 +106,13 @@
<artifactId>kube-api-test-client-inject</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<!-- Test-only, used to verify JOSDK works properly when used from Kotlin. -->
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down Expand Up @@ -147,6 +159,37 @@
</execution>
</executions>
</plugin>
<plugin>
<!--
Compiles the Kotlin test sources under src/test/kotlin, used only to verify that JOSDK
works properly when used from Kotlin (see https://github.com/operator-framework/java-operator-sdk/issues/2967).
Bound to process-test-sources so the compiled Kotlin classes are on the classpath before
the regular Java test sources are compiled.
-->
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<configuration>
<jvmTarget>${java.version}</jvmTarget>
</configuration>
<executions>
<execution>
<id>kotlin-test-compile</id>
<goals>
<goal>test-compile</goal>
</goals>
<phase>process-test-sources</phase>
<configuration>
<sourceDirs>
<sourceDir>${project.basedir}/src/test/kotlin</sourceDir>
<!-- Not compiled by Kotlin, only needed so the Kotlin sources can reference the
existing Java test classes (e.g. TestCustomResource). -->
<sourceDir>${project.basedir}/src/test/java</sourceDir>
</sourceDirs>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
/*
* Copyright Java Operator SDK Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.javaoperatorsdk.operator.processing.dependent.workflow

import io.javaoperatorsdk.operator.AggregatedOperatorException
import io.javaoperatorsdk.operator.api.reconciler.Context
import io.javaoperatorsdk.operator.api.reconciler.dependent.DependentResource
import io.javaoperatorsdk.operator.api.reconciler.dependent.ReconcileResult
import io.javaoperatorsdk.operator.api.reconciler.dependent.managed.ManagedWorkflowAndDependentResourceContext
import io.javaoperatorsdk.operator.processing.event.EventSourceRetriever
import io.javaoperatorsdk.operator.sample.simple.TestCustomResource
import java.util.concurrent.Executors
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows
import org.mockito.Mockito.mock
import org.mockito.Mockito.`when`

/**
* Smoke test verifying that JOSDK works properly when used from Kotlin.
*
* Unlike Java, Kotlin does not distinguish between checked and unchecked exceptions, so a Kotlin
* [DependentResource] can throw a plain [java.lang.Exception] from `reconcile` without declaring
* it, even though the Java `DependentResource.reconcile` method does not declare `throws
* Exception`. Before https://github.com/operator-framework/java-operator-sdk/pull/2965 such an
* exception was not caught by [NodeExecutor], since it only handled [RuntimeException], so it
* would not have been recorded as an error and retries would not have been triggered. This test
* makes sure such an exception is properly caught and reported, see
* https://github.com/operator-framework/java-operator-sdk/issues/2967.
*/
class KotlinCheckedExceptionDependentResourceTest {

private class CheckedException(message: String) : Exception(message)

private class ThrowingDependentResource :
DependentResource<String, TestCustomResource> {
override fun reconcile(
primary: TestCustomResource,
context: Context<TestCustomResource>
): ReconcileResult<String> {
throw CheckedException("checked exception thrown from Kotlin")
}

override fun resourceType(): Class<String> = String::class.java
}

@Test
fun checkedExceptionThrownFromKotlinDependentResourceTriggersRetry() {
val dependentResource = ThrowingDependentResource()

val workflow =
WorkflowBuilder<TestCustomResource>()
.addDependentResource(dependentResource)
.withThrowExceptionFurther(false)
.build()

@Suppress("UNCHECKED_CAST")
val context = mock(Context::class.java) as Context<TestCustomResource>
`when`(context.managedWorkflowAndDependentResourceContext())
.thenReturn(mock(ManagedWorkflowAndDependentResourceContext::class.java))
`when`(context.workflowExecutorService).thenReturn(Executors.newCachedThreadPool())
@Suppress("UNCHECKED_CAST")
`when`(context.eventSourceRetriever())
.thenReturn(mock(EventSourceRetriever::class.java) as EventSourceRetriever<TestCustomResource>)

val result = workflow.reconcile(TestCustomResource(), context)

// the checked exception was caught by the workflow executor, not left uncaught, so it is
// reported as an error for the dependent resource, which is what allows JOSDK to retry the
// reconciliation.
assertThat(result.erroredDependents).containsOnlyKeys(dependentResource)
assertThat(result.erroredDependents[dependentResource]).isInstanceOf(CheckedException::class.java)
assertThrows<AggregatedOperatorException> { result.throwAggregateExceptionIfErrorsPresent() }
}
Comment on lines +72 to +87
}