awslabs / aws-sdk-kotlin

Multiplatform AWS SDK for Kotlin
Apache License 2.0
403 stars 50 forks source link

Timestream doesn't support running with localstack #1413

Open yibo-long opened 1 week ago

yibo-long commented 1 week ago

Describe the bug

When running with localstack-pro for Timestream, it throws below errors:


http://localhost:4566 is not a valid inet host
java.lang.IllegalArgumentException: http://localhost:4566 is not a valid inet host
    at aws.smithy.kotlin.runtime.net.HostKt.hostParseImpl(Host.kt:35)
    at aws.smithy.kotlin.runtime.net.HostKt.access$hostParseImpl(Host.kt:1)
    at aws.smithy.kotlin.runtime.net.Host$Companion.parse(Host.kt:14)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer.discoverHost(TimestreamWriteEndpointDiscoverer.kt:48)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer.access$discoverHost(TimestreamWriteEndpointDiscoverer.kt:25)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer$discoverHost$1.invokeSuspend(TimestreamWriteEndpointDiscoverer.kt)
    at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt:42)
    at aws.smithy.kotlin.runtime.http.middleware.RetryMiddleware$handle$result$outcome$1.invokeSuspend(RetryMiddleware.kt:144)
    at aws.smithy.kotlin.runtime.retries.StandardRetryStrategy.doTryLoop(StandardRetryStrategy.kt:60)
    at aws.smithy.kotlin.runtime.retries.StandardRetryStrategy.retry$suspendImpl(StandardRetryStrategy.kt:40)
    at aws.smithy.kotlin.runtime.http.middleware.RetryMiddleware.handle(RetryMiddleware.kt:50)
    at aws.smithy.kotlin.runtime.io.middleware.ModifyRequestMiddleware.handle(ModifyRequest.kt:26)
    at aws.smithy.kotlin.runtime.io.middleware.ModifyRequestMiddleware.handle(ModifyRequest.kt:26)
    at aws.smithy.kotlin.runtime.io.middleware.ModifyRequestMiddleware.handle(ModifyRequest.kt:26)
    at aws.smithy.kotlin.runtime.http.operation.SerializeHandler.call(SdkOperationExecution.kt:251)
    at aws.smithy.kotlin.runtime.http.operation.OperationHandler.call(SdkOperationExecution.kt:207)
    at aws.smithy.kotlin.runtime.http.operation.SdkHttpOperationKt$execute$$inlined$withSpan$1.invokeSuspend(SdkHttpOperation.kt:76)
    at aws.smithy.kotlin.runtime.http.operation.SdkHttpOperationKt.execute(SdkHttpOperation.kt:219)
    at io.span.services.traithandlers.metering.TimestreamSeeder$seed$2.invokeSuspend(TimestreamSeeder.kt:277)
    at io.span.services.traithandlers.metering.MeteringTraitHandlerEndpointTest$beforeAll$1.invokeSuspend(MeteringTraitHandlerEndpointTest.kt:59)
Caused by: java.lang.IllegalArgumentException: http://localhost:4566 is not a valid inet host
    at aws.smithy.kotlin.runtime.net.HostKt.hostParseImpl(Host.kt:35)
    at aws.smithy.kotlin.runtime.net.HostKt.access$hostParseImpl(Host.kt:1)
    at aws.smithy.kotlin.runtime.net.Host$Companion.parse(Host.kt:14)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer.discoverHost(TimestreamWriteEndpointDiscoverer.kt:48)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer.access$discoverHost(TimestreamWriteEndpointDiscoverer.kt:25)
    at aws.sdk.kotlin.services.timestreamwrite.endpoints.TimestreamWriteEndpointDiscoverer$discoverHost$1.invokeSuspend(TimestreamWriteEndpointDiscoverer.kt)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at kotlinx.coroutines.UndispatchedCoroutine.afterResume(CoroutineContext.kt:266)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:99)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.UndispatchedCoroutine.afterResume(CoroutineContext.kt:266)
    at kotlinx.coroutines.AbstractCoroutine.resumeWith(AbstractCoroutine.kt:99)
    at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:46)
    at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104)
    at kotlinx.coroutines.internal.LimitedDispatcher$Worker.run(LimitedDispatcher.kt:111)
    at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:99)
    at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:811)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:715)
    at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:702)

This happens for both timestream-query and timestream-write

the client is created as:

TimestreamWriteClient {
        region = awsRegion
        timestreamEndpoint?.let {
            endpointUrl = Url.parse(it)
        }
    }

Regression Issue

Expected behavior

it should be run correctly as similar codes with software.amazon.awssdk:timestreamwrite or software.amazon.awssdk:timestreamquery.

Current behavior

Throws the error.

Steps to Reproduce

run with the code below with localstack-pro testcontainer

val container: LocalStackContainer
// awsRegion and timestreamEndpoint are from localstack testcontainer
val client = TimestreamWriteClient {
    region = container.region
    endpointUrl = Url.parse(container.endpoint.toString())
}
client.createDatabase {
    databaseName = "test-db"
}

Possible Solution

It seems to be that kotlin sdk didn't disable endpoint discovery like aws-sdk-java-v2:

image

for those EndpointDiscoverer in kotlin sdk, it should not try to discover host with describeEndpoints if endpoint is already overriden.

Context

No response

AWS SDK for Kotlin version

1.3.21

Platform (JVM/JS/Native)

JDK21

Operating system and version

linux/mac

0marperez commented 1 week ago

Hi, thanks for the report. Have you tried using a custom endpoint provider with a fixed endpoint. There's an example of that here in our documentation.

It would look something like this:

S3Client.fromEnvironment {
    endpointProvider = object : S3EndpointProvider {
        override suspend fun resolveEndpoint(params: S3EndpointParameters): Endpoint = Endpoint("https://endpoint.example")
    }
}
yibo-long commented 1 week ago

@0marperez thanks for the suggestion,I tried that it still running with endpoint discoverer:

TimestreamWriteClient {
        region = awsRegion
        timestreamEndpoint?.let { endpoint ->
            endpointProvider = TimestreamWriteEndpointProvider {
                Endpoint(endpoint)
            }
        }
    }

As far as I can see the code won't skip discovery:

image

which will always try calling describeEndpoints regardless of TimestreamWriteEndpointProvider:

image

With that said, I think it will be great either providing the similar logic with endpointDiscoveryEnabled (which is mentioned to work in https://docs.aws.amazon.com/sdkref/latest/guide/feature-endpoint-discovery.html, however I couldn't find a way to set it in kotlin clients), or allowing overriding TimestreamWriteEndpointDiscoverer, which is currently a final class.

0marperez commented 1 week ago

@yibo-long I am able to override TimestreamWriteEndpointDiscoverer using this code:

TimestreamWriteClient.fromEnvironment {
    region = "..."
    endpointProvider = object : TimestreamWriteEndpointProvider {
        override suspend fun resolveEndpoint(params: TimestreamWriteEndpointParameters): Endpoint = Endpoint("https://endpoint.example")
    }
}

It's a bit different to what you have currently. In regards to disabling endpoint discovery, I see the issue you're having and I'll be working on correcting the documentation in the meantime and working on seeing how we can add the functionality

0marperez commented 3 days ago

Hi @yibo-long just checking in, did the code above help you become unblocked?

yibo-long commented 3 days ago

Hi @0marperez, unfortunately no.

As you can see from my screenshot of the generated SDK code, val discoveredHost = cache.get(cacheKey) { discoverHost(client) } is run before val originalEndpoint = delegate.resolve(request), and then delegate.resolve calls endpointProvider.resolveEndpoint(params). discoverHost will still throw error for java.lang.IllegalArgumentException: http://localhost:4566 is not a valid inet host

0marperez commented 1 day ago

Quick update, we documented the bugs causing this issue and created internal issues to keep track of them (SDK-KT-354 and SDK-KT-359)