diff --git a/.github/workflows/integration.yaml b/.github/workflows/integration.yaml index e4db3473..677302bb 100644 --- a/.github/workflows/integration.yaml +++ b/.github/workflows/integration.yaml @@ -42,14 +42,13 @@ on: description: name of the test artifact output jobs: - sdk-test-suite: if: github.repository_owner == 'restatedev' runs-on: ubuntu-latest name: "Features integration test (sdk-test-suite version ${{ matrix.sdk-test-suite }})" strategy: matrix: - sdk-test-suite: [ "1.8" ] + sdk-test-suite: [ "2.0" ] permissions: contents: read issues: read diff --git a/test-services/README.md b/test-services/README.md index 4040d71d..024d2d84 100644 --- a/test-services/README.md +++ b/test-services/README.md @@ -1,11 +1,11 @@ -# Java services +# Java SDK test services ## Running the services -The Java services can be run via: +The services can be run via: ```shell SERVICES= gradle run ``` -For the list of supported services see [here](src/main/java/my/restate/e2e/services/Main.java). \ No newline at end of file +For the list of supported services see [here](src/main/kotlin/dev/restate/sdk/testservices/Main.kt). \ No newline at end of file diff --git a/test-services/src/main/kotlin/dev/restate/sdk/testservices/FailingImpl.kt b/test-services/src/main/kotlin/dev/restate/sdk/testservices/FailingImpl.kt index b58f487e..3d6a4b7f 100644 --- a/test-services/src/main/kotlin/dev/restate/sdk/testservices/FailingImpl.kt +++ b/test-services/src/main/kotlin/dev/restate/sdk/testservices/FailingImpl.kt @@ -10,10 +10,13 @@ package dev.restate.sdk.testservices import dev.restate.sdk.common.TerminalException import dev.restate.sdk.kotlin.ObjectContext +import dev.restate.sdk.kotlin.UsePreviewContext +import dev.restate.sdk.kotlin.retryPolicy import dev.restate.sdk.kotlin.runBlock import dev.restate.sdk.testservices.contracts.Failing import dev.restate.sdk.testservices.contracts.FailingClient import java.util.concurrent.atomic.AtomicInteger +import kotlin.time.Duration.Companion.milliseconds import org.apache.logging.log4j.LogManager import org.apache.logging.log4j.Logger @@ -24,6 +27,7 @@ class FailingImpl : Failing { private val eventualSuccessCalls = AtomicInteger(0) private val eventualSuccessSideEffectCalls = AtomicInteger(0) + private val eventualFailureSideEffectCalls = AtomicInteger(0) override suspend fun terminallyFailingCall(context: ObjectContext, errorMessage: String) { LOG.info("Invoked fail") @@ -55,24 +59,54 @@ class FailingImpl : Failing { } } - override suspend fun failingSideEffectWithEventualSuccess(context: ObjectContext): Int { - val successAttempt: Int = - context.runBlock { - val currentAttempt = eventualSuccessSideEffectCalls.incrementAndGet() - if (currentAttempt >= 4) { - eventualSuccessSideEffectCalls.set(0) - return@runBlock currentAttempt - } else { - throw IllegalArgumentException("Failed at attempt: $currentAttempt") - } - } - - return successAttempt - } - override suspend fun terminallyFailingSideEffect(context: ObjectContext, errorMessage: String) { context.runBlock { throw TerminalException(errorMessage) } throw IllegalStateException("Should not be reached.") } + + @OptIn(UsePreviewContext::class) + override suspend fun sideEffectSucceedsAfterGivenAttempts( + context: ObjectContext, + minimumAttempts: Int + ): Int = + context.runBlock( + name = "failing_side_effect", + retryPolicy = + retryPolicy { + initialDelay = 10.milliseconds + exponentiationFactor = 1.0f + }) { + val currentAttempt = eventualSuccessSideEffectCalls.incrementAndGet() + if (currentAttempt >= 4) { + eventualSuccessSideEffectCalls.set(0) + return@runBlock currentAttempt + } else { + throw IllegalArgumentException("Failed at attempt: $currentAttempt") + } + } + + @OptIn(UsePreviewContext::class) + override suspend fun sideEffectFailsAfterGivenAttempts( + context: ObjectContext, + retryPolicyMaxRetryCount: Int + ): Int { + try { + context.runBlock( + name = "failing_side_effect", + retryPolicy = + retryPolicy { + initialDelay = 10.milliseconds + exponentiationFactor = 1.0f + maxAttempts = retryPolicyMaxRetryCount + }) { + val currentAttempt = eventualFailureSideEffectCalls.incrementAndGet() + throw IllegalArgumentException("Failed at attempt: $currentAttempt") + } + } catch (_: TerminalException) { + return eventualFailureSideEffectCalls.get() + } + // If I reach this point, the side effect succeeded... + throw TerminalException("Expecting the side effect to fail!") + } } diff --git a/test-services/src/main/kotlin/dev/restate/sdk/testservices/Main.kt b/test-services/src/main/kotlin/dev/restate/sdk/testservices/Main.kt index 15ce2ac8..5bc16431 100644 --- a/test-services/src/main/kotlin/dev/restate/sdk/testservices/Main.kt +++ b/test-services/src/main/kotlin/dev/restate/sdk/testservices/Main.kt @@ -29,6 +29,8 @@ val KNOWN_SERVICES_FACTORIES: Map Any> = TestUtilsServiceDefinitions.SERVICE_NAME to { TestUtilsServiceImpl() }, ) +val NEEDS_EXPERIMENTAL_CONTEXT: Set = setOf(FailingDefinitions.SERVICE_NAME) + fun main(args: Array) { var env = System.getenv("SERVICES") if (env == null) { @@ -52,5 +54,9 @@ fun main(args: Array) { RestateRequestIdentityVerifier.fromKey(requestSigningKey)) } + if (env == "*" || NEEDS_EXPERIMENTAL_CONTEXT.any { env.contains(it) }) { + restateHttpEndpointBuilder.enablePreviewContext() + } + restateHttpEndpointBuilder.buildAndListen() } diff --git a/test-services/src/main/kotlin/dev/restate/sdk/testservices/contracts/Failing.kt b/test-services/src/main/kotlin/dev/restate/sdk/testservices/contracts/Failing.kt index 53ec5e44..dca2e47a 100644 --- a/test-services/src/main/kotlin/dev/restate/sdk/testservices/contracts/Failing.kt +++ b/test-services/src/main/kotlin/dev/restate/sdk/testservices/contracts/Failing.kt @@ -12,7 +12,7 @@ import dev.restate.sdk.annotation.Handler import dev.restate.sdk.annotation.VirtualObject import dev.restate.sdk.kotlin.ObjectContext -@VirtualObject +@VirtualObject(name = "Failing") interface Failing { @Handler suspend fun terminallyFailingCall(context: ObjectContext, errorMessage: String) @@ -21,7 +21,30 @@ interface Failing { @Handler suspend fun failingCallWithEventualSuccess(context: ObjectContext): Int - @Handler suspend fun failingSideEffectWithEventualSuccess(context: ObjectContext): Int - @Handler suspend fun terminallyFailingSideEffect(context: ObjectContext, errorMessage: String) + + /** + * `minimumAttempts` should be used to check when to succeed. The retry policy should be + * configured to be infinite. + * + * @return the number of executed attempts. In order to implement this count, an atomic counter in + * the service should be used. + */ + @Handler + suspend fun sideEffectSucceedsAfterGivenAttempts( + context: ObjectContext, + minimumAttempts: Int + ): Int + + /** + * `retryPolicyMaxRetryCount` should be used to configure the retry policy. + * + * @return the number of executed attempts. In order to implement this count, an atomic counter in + * the service should be used. + */ + @Handler + suspend fun sideEffectFailsAfterGivenAttempts( + context: ObjectContext, + retryPolicyMaxRetryCount: Int + ): Int }