From ff95105f33efdb513a6df7391e1e128a187e05e4 Mon Sep 17 00:00:00 2001 From: a10zn8 Date: Wed, 12 Jul 2023 17:56:17 +0400 Subject: [PATCH] simplified zipkin with ssl --- .gitignore | 8 + build.gradle | 2 +- gradle/libs.versions.toml | 4 +- ...Configuration.kt => TraceConfiguration.kt} | 6 +- .../config/spans/ClientSpansInterceptor.kt | 62 ------ .../config/spans/ProviderSpanHandler.kt | 96 ++++----- .../config/spans/ServerSpansInterceptor.kt | 44 ----- .../dshackle/config/spans/SpanConfig.kt | 69 ------- .../config/spans/ZipkinSSLCustomizer.kt | 90 +++++++++ .../dshackle/config/spans/export.kt | 34 ---- src/main/resources/application.yml | 13 +- .../config/spans/CollectSpanConfigTest.kt | 138 ------------- .../config/spans/ProviderSpanHandlerTest.kt | 185 +++++++----------- 13 files changed, 216 insertions(+), 535 deletions(-) rename src/main/kotlin/io/emeraldpay/dshackle/config/context/{TraceGrpcConfiguration.kt => TraceConfiguration.kt} (79%) delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/spans/ClientSpansInterceptor.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/spans/ServerSpansInterceptor.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/spans/SpanConfig.kt create mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/spans/ZipkinSSLCustomizer.kt delete mode 100644 src/main/kotlin/io/emeraldpay/dshackle/config/spans/export.kt delete mode 100644 src/test/kotlin/io/emeraldpay/dshackle/config/spans/CollectSpanConfigTest.kt diff --git a/.gitignore b/.gitignore index 003d28ea..565a9829 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,11 @@ testsetup/ # nix env /dshackle-cli/node_modules/ + +# temp files +/test* +/test/ +Test*.kt + +# http-client config +http-client.env.json diff --git a/build.gradle b/build.gradle index 84e4b19c..8402dfe8 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ dependencies { implementation libs.bundles.jackson implementation libs.bundles.apache.commons - implementation libs.bouncycastle + implementation libs.bundles.bouncycastle implementation libs.caffeine implementation libs.javax.annotations diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 7016d61c..446d239d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -24,7 +24,8 @@ logstash-encoder = "net.logstash.logback:logstash-logback-encoder:7.2" janino = "org.codehaus.janino:janino:3.1.9" -bouncycastle = "org.bouncycastle:bcprov-jdk15on:1.61" +bouncycastle-prov = "org.bouncycastle:bcprov-jdk15on:1.61" +bouncycastle-pkix = 'org.bouncycastle:bcpkix-jdk15on:1.61' caffeine = "com.github.ben-manes.caffeine:caffeine:2.8.5" @@ -135,6 +136,7 @@ reactor = ["reactor-core", "reactor-netty", "reactor-extra", "reactor-kotlin"] spring-framework = ["spring-boot-starter", "spring-security-core", "spring-security-web", "spring-security-config"] testcontainers = ["testcontainers", "testcontainers-ganache"] junit = ["junit-jupiter", "assertj"] +bouncycastle = ["bouncycastle-pkix", "bouncycastle-prov"] [plugins] diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceGrpcConfiguration.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceConfiguration.kt similarity index 79% rename from src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceGrpcConfiguration.kt rename to src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceConfiguration.kt index 2aff6ed7..5c06db89 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceGrpcConfiguration.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/context/TraceConfiguration.kt @@ -2,12 +2,13 @@ package io.emeraldpay.dshackle.config.context import brave.grpc.GrpcTracing import brave.rpc.RpcTracing +import brave.sampler.Sampler import io.grpc.ServerInterceptor import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @Configuration -open class TraceGrpcConfiguration { +open class TraceConfiguration { @Bean open fun grpcTracing(rpcTracing: RpcTracing): GrpcTracing = GrpcTracing.create(rpcTracing) @@ -15,4 +16,7 @@ open class TraceGrpcConfiguration { @Bean open fun grpcServerBraveInterceptor(grpcTracing: GrpcTracing): ServerInterceptor = grpcTracing.newServerInterceptor() + + @Bean + open fun defaultSampler(): Sampler = Sampler.ALWAYS_SAMPLE } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ClientSpansInterceptor.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ClientSpansInterceptor.kt deleted file mode 100644 index b1da5809..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ClientSpansInterceptor.kt +++ /dev/null @@ -1,62 +0,0 @@ -package io.emeraldpay.dshackle.config.spans - -import brave.Tracer -import brave.handler.MutableSpan -import brave.handler.SpanHandler -import com.fasterxml.jackson.databind.ObjectMapper -import com.fasterxml.jackson.module.kotlin.readValue -import io.grpc.CallOptions -import io.grpc.Channel -import io.grpc.ClientCall -import io.grpc.ClientInterceptor -import io.grpc.ForwardingClientCall.SimpleForwardingClientCall -import io.grpc.ForwardingClientCallListener -import io.grpc.Metadata -import io.grpc.Metadata.ASCII_STRING_MARSHALLER -import io.grpc.MethodDescriptor -import org.springframework.beans.factory.annotation.Qualifier - -class ClientSpansInterceptor( - private val zipkinSpanHandler: SpanHandler, - private val tracer: Tracer, - @Qualifier("spanMapper") - private val spanMapper: ObjectMapper -) : ClientInterceptor { - - override fun interceptCall( - method: MethodDescriptor, - callOptions: CallOptions, - next: Channel - ): ClientCall { - return if (method.fullMethodName == "emerald.Blockchain/NativeCall") { - SpanClientCall(next.newCall(method, callOptions)) - } else { - next.newCall(method, callOptions) - } - } - - private inner class SpanClientCall( - delegate: ClientCall? - ) : SimpleForwardingClientCall(delegate) { - override fun start(responseListener: Listener, headers: Metadata) { - val spanResponseListener = SpanClientCallListener(responseListener) - super.start(spanResponseListener, headers) - } - } - - private inner class SpanClientCallListener( - delegate: ClientCall.Listener, - ) : ForwardingClientCallListener.SimpleForwardingClientCallListener(delegate) { - override fun onHeaders(headers: Metadata) { - headers[Metadata.Key.of(SPAN_HEADER, ASCII_STRING_MARSHALLER)] - ?.takeIf { it.isNotBlank() } - ?.let { - val spansFromProvider = spanMapper.readValue>(it) - spansFromProvider.forEach { span -> - zipkinSpanHandler.end(tracer.currentSpan().context(), span, SpanHandler.Cause.FINISHED) - } - } - super.onHeaders(headers) - } - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandler.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandler.kt index 58936392..9cd0dea0 100644 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandler.kt +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandler.kt @@ -3,78 +3,48 @@ package io.emeraldpay.dshackle.config.spans import brave.handler.MutableSpan import brave.handler.SpanHandler import brave.propagation.TraceContext -import com.fasterxml.jackson.databind.ObjectMapper -import com.github.benmanes.caffeine.cache.Caffeine -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.cloud.sleuth.Span -import java.time.Duration +import org.springframework.beans.factory.annotation.Value +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty +import org.springframework.stereotype.Component +import java.util.concurrent.TimeUnit +@Component +@ConditionalOnProperty(value = ["spring.zipkin.enabled"], havingValue = "true") class ProviderSpanHandler( - @Qualifier("spanMapper") - private val spanMapper: ObjectMapper, - private val spanExportableList: List + @Value("\${spans.collect.collect-only-errors:false}") + private val onlyErrors: Boolean? = false, + @Value("\${spans.collect.long-span-threshold}") + private val longSpanThreshold: Long? = null ) : SpanHandler() { - private val spans = Caffeine - .newBuilder() - .expireAfterWrite(Duration.ofMinutes(5)) - .build>() - override fun end(context: TraceContext, span: MutableSpan, cause: Cause): Boolean { - if (span.traceId().length > 20 && span.parentId() != null) { - val spanList = spans.asMap().computeIfAbsent(span.parentId()) { mutableListOf() } - spanList.add(span) + override fun end(context: TraceContext?, span: MutableSpan, cause: Cause?): Boolean { + val duration = TimeUnit.MILLISECONDS.convert(span.finishTimestamp() - span.startTimestamp(), TimeUnit.MICROSECONDS) + + val isError = span.tags().containsKey("error") + + // If onlyErrors is true and there is an error, return true. + if (onlyErrors == true && isError) { + return true } - return super.end(context, span, cause) - } - fun getErrorSpans(spanId: String, currentSpan: Span): String { - val spansInfo = SpansInfo() + // If onlyErrors is true, there is no error, and the span is long, return true. + if (onlyErrors == true && !isError && longSpanThreshold != null && duration >= longSpanThreshold) { + return true + } - enrichErrorSpans(spanId, spansInfo) - currentSpan.end() - currentSpan.context().parentId()?.let { - spans.getIfPresent(it)?.let { mutableSpans -> - if (mutableSpans.isNotEmpty()) { - processSpanInfo(mutableSpans[0], spansInfo) - } + // If onlyErrors is false, check for the time threshold condition. + if (onlyErrors == false) { + // If longSpanThreshold is null, return true. + if (longSpanThreshold == null) { + return true + } + // If longSpanThreshold is set, only return true if the duration is >= time threshold. + else if (duration >= longSpanThreshold) { + return true } } - spansInfo.spans - .map { it.parentId() } - .forEach { - if (it != null) { - spans.invalidate(it) - } - } - - return if (spansInfo.exportable) { - spanMapper.writeValueAsString(spansInfo.spans) - } else { - "" - } + // Return false if none of the above conditions are met. + return false } - - private fun enrichErrorSpans(spanId: String, spansInfo: SpansInfo) { - val currentSpans: List? = spans.getIfPresent(spanId) - - currentSpans?.forEach { - processSpanInfo(it, spansInfo) - if (spanId != it.id()) { - enrichErrorSpans(it.id(), spansInfo) - } - } - } - - private fun processSpanInfo(span: MutableSpan, spansInfo: SpansInfo) { - spansInfo.spans.add(span) - if (spanExportableList.any { it.isExportable(span) }) { - spansInfo.exportable = true - } - } - - private data class SpansInfo( - var exportable: Boolean = false, - val spans: MutableList = mutableListOf() - ) } diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ServerSpansInterceptor.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ServerSpansInterceptor.kt deleted file mode 100644 index f47fca39..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ServerSpansInterceptor.kt +++ /dev/null @@ -1,44 +0,0 @@ -package io.emeraldpay.dshackle.config.spans - -import io.grpc.ForwardingServerCall.SimpleForwardingServerCall -import io.grpc.Metadata -import io.grpc.ServerCall -import io.grpc.ServerCallHandler -import io.grpc.ServerInterceptor -import org.springframework.cloud.sleuth.Tracer - -class ServerSpansInterceptor( - private val tracer: Tracer, - private val providerSpanHandler: ProviderSpanHandler -) : ServerInterceptor { - override fun interceptCall( - call: ServerCall, - headers: Metadata, - next: ServerCallHandler - ): ServerCall.Listener { - val serverCall = if (call.methodDescriptor.fullMethodName == "emerald.Blockchain/NativeCall") { - SpanServerCall(call) - } else { - call - } - - return next.startCall(serverCall, headers) - } - - private inner class SpanServerCall( - private val call: ServerCall - ) : SimpleForwardingServerCall(call) { - override fun sendHeaders(headers: Metadata) { - tracer.currentSpan()?.let { - val parentId = it.context().parentId() - if (parentId != null) { - val spans = providerSpanHandler.getErrorSpans(it.context().spanId(), it) - if (spans.isNotBlank()) { - headers.put(Metadata.Key.of(SPAN_HEADER, Metadata.ASCII_STRING_MARSHALLER), spans) - } - } - call.sendHeaders(headers) - } - } - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/SpanConfig.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/SpanConfig.kt deleted file mode 100644 index c24be4d5..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/SpanConfig.kt +++ /dev/null @@ -1,69 +0,0 @@ -package io.emeraldpay.dshackle.config.spans - -import brave.Tracer -import brave.handler.SpanHandler -import com.fasterxml.jackson.annotation.JsonAutoDetect -import com.fasterxml.jackson.annotation.JsonInclude -import com.fasterxml.jackson.databind.DeserializationFeature -import com.fasterxml.jackson.databind.ObjectMapper -import io.grpc.ClientInterceptor -import io.grpc.ServerInterceptor -import org.springframework.beans.factory.annotation.Qualifier -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty -import org.springframework.context.annotation.Bean -import org.springframework.context.annotation.Configuration - -const val SPAN_HEADER = "spans" - -@Configuration -@ConditionalOnProperty(value = ["spans.collect.enabled"], havingValue = "true") -open class SpanConfig { - - @Bean - open fun spanMapper(): ObjectMapper = - ObjectMapper() - .apply { - setSerializationInclusion(JsonInclude.Include.NON_DEFAULT) - configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - setVisibility( - this.serializationConfig.defaultVisibilityChecker - .withFieldVisibility(JsonAutoDetect.Visibility.ANY) - .withGetterVisibility(JsonAutoDetect.Visibility.NONE) - .withSetterVisibility(JsonAutoDetect.Visibility.NONE) - .withCreatorVisibility(JsonAutoDetect.Visibility.NONE) - ) - } - - @Configuration - @ConditionalOnBean(SpanConfig::class) - @ConditionalOnProperty(value = ["spans.collect.main.enabled"], havingValue = "true") - open class MainSpanConfig { - @Bean - open fun clientSpansInterceptor( - zipkinSpanHandler: SpanHandler, - tracer: Tracer, - @Qualifier("spanMapper") - spanMapper: ObjectMapper - ): ClientInterceptor = ClientSpansInterceptor(zipkinSpanHandler, tracer, spanMapper) - } - - @Configuration - @ConditionalOnBean(SpanConfig::class) - @ConditionalOnProperty(value = ["spans.collect.provider.enabled"], havingValue = "true") - open class ProviderSpanConfig { - - @Bean - open fun errorSpanHandler( - @Qualifier("spanMapper") - spanMapper: ObjectMapper, - spanExportableList: List - ): ProviderSpanHandler = ProviderSpanHandler(spanMapper, spanExportableList) - - @Bean - open fun serverSpansInterceptor( - tracer: org.springframework.cloud.sleuth.Tracer, - providerSpanHandler: ProviderSpanHandler - ): ServerInterceptor = ServerSpansInterceptor(tracer, providerSpanHandler) - } -} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ZipkinSSLCustomizer.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ZipkinSSLCustomizer.kt new file mode 100644 index 00000000..a3678998 --- /dev/null +++ b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/ZipkinSSLCustomizer.kt @@ -0,0 +1,90 @@ +package io.emeraldpay.dshackle.config.spans + +import io.emeraldpay.dshackle.config.MainConfig +import org.apache.http.conn.ssl.SSLConnectionSocketFactory +import org.apache.http.impl.client.HttpClients +import org.bouncycastle.openssl.PEMParser +import org.springframework.cloud.sleuth.zipkin2.ZipkinRestTemplateCustomizer +import org.springframework.http.HttpRequest +import org.springframework.http.client.ClientHttpRequestExecution +import org.springframework.http.client.ClientHttpRequestInterceptor +import org.springframework.http.client.ClientHttpResponse +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory +import org.springframework.stereotype.Component +import org.springframework.web.client.RestTemplate +import java.io.ByteArrayOutputStream +import java.io.StringReader +import java.nio.file.Files +import java.nio.file.Paths +import java.security.KeyFactory +import java.security.KeyStore +import java.security.SecureRandom +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.security.spec.PKCS8EncodedKeySpec +import java.util.zip.GZIPOutputStream +import javax.net.ssl.KeyManagerFactory +import javax.net.ssl.SSLContext + +@Component +class ZipkinSSLCustomizer(private val mainConfig: MainConfig) : ZipkinRestTemplateCustomizer { + override fun customizeTemplate(restTemplate: RestTemplate): RestTemplate { + return if (mainConfig.tls?.enabled == true) { + setupSSL(mainConfig.tls!!.certificate!!, mainConfig.tls!!.key!!) + } else { + restTemplate + }.apply { + interceptors.add(0, GZipInterceptor()) + } + } + + private fun setupSSL(certPath: String, privateKeyPath: String): RestTemplate { + // Load the certificate + val certificateReader = StringReader(Files.readString(Paths.get(certPath))) + val pemObject = PEMParser(certificateReader).readPemObject() + val certificate = CertificateFactory.getInstance("X.509").generateCertificate(pemObject.content.inputStream()) as X509Certificate + // Load the private key + val privateKeyReader = StringReader(Files.readString(Paths.get(privateKeyPath))) + val pemKeyPair = PEMParser(privateKeyReader).readPemObject() + val privKeySpec = PKCS8EncodedKeySpec(pemKeyPair.content) + val privateKey = KeyFactory.getInstance("RSA").generatePrivate(privKeySpec) + + // Create the key store + val keyStore = KeyStore.getInstance(KeyStore.getDefaultType()).apply { + load(null, null) + setCertificateEntry("certificate", certificate) + setKeyEntry("private-key", privateKey, null, arrayOf(certificate)) + } + + // Create the SSL context + val keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()).apply { + init(keyStore, null) + } + val sslContext = SSLContext.getInstance("TLS").apply { + init(keyManagerFactory.keyManagers, null, SecureRandom()) + } + + // Create the HTTP client + val socketFactory = SSLConnectionSocketFactory(sslContext) + val httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build() + + // Create the request factory + val requestFactory = HttpComponentsClientHttpRequestFactory(httpClient) + + // Create the RestTemplate + return RestTemplate(requestFactory) + } + + private class GZipInterceptor : ClientHttpRequestInterceptor { + override fun intercept( + request: HttpRequest, + body: ByteArray, + execution: ClientHttpRequestExecution + ): ClientHttpResponse { + request.headers.add("Content-Encoding", "gzip") + val gzipped = ByteArrayOutputStream() + GZIPOutputStream(gzipped).use { compressor -> compressor.write(body) } + return execution.execute(request, gzipped.toByteArray()) + } + } +} diff --git a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/export.kt b/src/main/kotlin/io/emeraldpay/dshackle/config/spans/export.kt deleted file mode 100644 index b270aed9..00000000 --- a/src/main/kotlin/io/emeraldpay/dshackle/config/spans/export.kt +++ /dev/null @@ -1,34 +0,0 @@ -package io.emeraldpay.dshackle.config.spans - -import brave.handler.MutableSpan -import io.emeraldpay.dshackle.commons.SPAN_ERROR -import io.emeraldpay.dshackle.commons.SPAN_NO_RESPONSE_MESSAGE -import org.springframework.beans.factory.annotation.Value -import org.springframework.stereotype.Component -import java.util.concurrent.TimeUnit - -interface SpanExportable { - fun isExportable(span: MutableSpan): Boolean -} - -@Component -class ErrorSpanExportable : SpanExportable { - override fun isExportable(span: MutableSpan): Boolean = span.tags().containsKey(SPAN_ERROR) -} - -@Component -class NoResponseSpanExportable : SpanExportable { - override fun isExportable(span: MutableSpan): Boolean = span.tags().containsKey(SPAN_NO_RESPONSE_MESSAGE) -} - -@Component -class LongResponseSpanExportable( - @Value("\${spans.collect.provider.long-span-threshold}") - private val longSpanThreshold: Long? = null -) : SpanExportable { - - override fun isExportable(span: MutableSpan): Boolean { - val duration = span.finishTimestamp() - span.startTimestamp() - return TimeUnit.SECONDS.convert(duration, TimeUnit.MICROSECONDS) >= longSpanThreshold!! - } -} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 21a811a2..b5768dc9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -3,8 +3,8 @@ spring: max-metadata-size: ${MAX_METADATA_SIZE:16384} name: ${DRPC_APP_NAME:dshackle} zipkin: - enabled: ${ZIPKIN_ENABLE:false} - base-url: ${ZIPKIN_URL:http://localhost:9411/} + enabled: ${ENABLE_COLLECT_SPANS:false} + base-url: ${ZIPKIN_URL:https://trace.drpc.dev/} sleuth: span-filter: additional-span-name-patterns-to-ignore: @@ -16,12 +16,9 @@ spring: - ".+SubscribeStatus" - ".+SubscribeNodeStatus" - ".+Describe" + - ".+ServerReflectionInfo" spans: collect: - enabled: ${ENABLE_COLLECT_SPANS:false} - provider: - enabled: ${ENABLE_PROVIDER_COLLECT_SPANS:true} - long-span-threshold: ${LONG_SPAN_THRESHOLD:1} - main: - enabled: ${ENABLE_MAIN_COLLECT_SPANS:false} \ No newline at end of file + long-span-threshold: ${LONG_SPAN_THRESHOLD:1000} + collect-only-errors: ${ONLY_ERROR_SPANS:true} \ No newline at end of file diff --git a/src/test/kotlin/io/emeraldpay/dshackle/config/spans/CollectSpanConfigTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/config/spans/CollectSpanConfigTest.kt deleted file mode 100644 index f0fca008..00000000 --- a/src/test/kotlin/io/emeraldpay/dshackle/config/spans/CollectSpanConfigTest.kt +++ /dev/null @@ -1,138 +0,0 @@ -package io.emeraldpay.dshackle.config.spans - -import brave.handler.SpanHandler -import org.junit.jupiter.api.Assertions.assertThrows -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertDoesNotThrow -import org.junit.jupiter.api.extension.ExtendWith -import org.mockito.Mockito.mock -import org.springframework.beans.factory.NoSuchBeanDefinitionException -import org.springframework.beans.factory.annotation.Autowired -import org.springframework.boot.test.context.TestConfiguration -import org.springframework.cloud.sleuth.Tracer -import org.springframework.context.ApplicationContext -import org.springframework.context.annotation.Bean -import org.springframework.test.context.ContextConfiguration -import org.springframework.test.context.TestPropertySource -import org.springframework.test.context.junit.jupiter.SpringExtension - -class CollectSpanConfigTest { - - @ContextConfiguration( - classes = [SpanConfig::class], - ) - @ExtendWith(SpringExtension::class) - @TestPropertySource( - properties = [ - "spans.collect.enabled=false" - ] - ) - class SpanConfigTest { - @Autowired - private lateinit var appCtx: ApplicationContext - - @Test - fun testSpanConfig() { - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(SpanConfig::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ProviderSpanHandler::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ServerSpansInterceptor::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ClientSpansInterceptor::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean("spanMapper") - } - } - } - - @ContextConfiguration( - classes = [SpanConfig::class, SpanProviderConfigTest.Config::class], - ) - @ExtendWith(SpringExtension::class) - @TestPropertySource( - properties = [ - "spans.collect.enabled=true", - "spans.collect.provider.enabled=true", - "spans.collect.main.enabled=false" - ] - ) - class SpanProviderConfigTest { - @Autowired - private lateinit var appCtx: ApplicationContext - - @Test - fun testSpanProviderConfig() { - assertDoesNotThrow { - appCtx.getBean(SpanConfig::class.java) - } - assertDoesNotThrow { - appCtx.getBean(ProviderSpanHandler::class.java) - } - assertDoesNotThrow { - appCtx.getBean(ServerSpansInterceptor::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ClientSpansInterceptor::class.java) - } - assertDoesNotThrow { - appCtx.getBean("spanMapper") - } - } - - @TestConfiguration - open class Config { - @Bean - open fun tracer(): Tracer = mock(Tracer::class.java) - } - } - - @ContextConfiguration( - classes = [SpanConfig::class, SpanMainConfigTest.Config::class], - ) - @ExtendWith(SpringExtension::class) - @TestPropertySource( - properties = [ - "spans.collect.enabled=true", - "spans.collect.provider.enabled=false", - "spans.collect.main.enabled=true" - ] - ) - class SpanMainConfigTest { - @Autowired - private lateinit var appCtx: ApplicationContext - - @Test - fun testSpanMainConfig() { - assertDoesNotThrow { - appCtx.getBean(SpanConfig::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ProviderSpanHandler::class.java) - } - assertThrows(NoSuchBeanDefinitionException::class.java) { - appCtx.getBean(ServerSpansInterceptor::class.java) - } - assertDoesNotThrow { - appCtx.getBean(ClientSpansInterceptor::class.java) - } - assertDoesNotThrow { - appCtx.getBean("spanMapper") - } - } - - @TestConfiguration - open class Config { - @Bean - open fun tracer(): brave.Tracer = mock(brave.Tracer::class.java) - - @Bean - open fun zipkinSpanHandler(): SpanHandler = mock(SpanHandler::class.java) - } - } -} diff --git a/src/test/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandlerTest.kt b/src/test/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandlerTest.kt index e521b53e..14e3c024 100644 --- a/src/test/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandlerTest.kt +++ b/src/test/kotlin/io/emeraldpay/dshackle/config/spans/ProviderSpanHandlerTest.kt @@ -1,128 +1,85 @@ package io.emeraldpay.dshackle.config.spans import brave.handler.MutableSpan -import brave.handler.SpanHandler -import brave.propagation.TraceContext -import com.fasterxml.jackson.module.kotlin.readValue -import io.emeraldpay.dshackle.commons.SPAN_ERROR -import io.emeraldpay.dshackle.commons.SPAN_NO_RESPONSE_MESSAGE import org.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments import org.junit.jupiter.params.provider.MethodSource -import org.mockito.Mockito -import org.mockito.Mockito.`when` -import org.springframework.cloud.sleuth.Span -import org.springframework.cloud.sleuth.brave.bridge.BraveTraceContext +import java.util.stream.Stream class ProviderSpanHandlerTest { - private val mapper = SpanConfig().spanMapper() - private val spanExportableList = listOf( - ErrorSpanExportable(), NoResponseSpanExportable(), LongResponseSpanExportable(10) - ) - private val ctx = TraceContext.newBuilder() - .traceId(1223324) - .spanId(234235) - .build() - - @Test - fun `span with length of traceId less than 20 is not collected`() { - val spanId = "f7e83f2b69ec684d" - val currentSpan = Mockito.mock(Span::class.java) - val handler = spanHandler() - - `when`(currentSpan.context()).thenReturn(BraveTraceContext(ctx)) - - handler.end(ctx, span("f7e83f2b69ec684d", spanId), SpanHandler.Cause.FINISHED) - - val result = handler.getErrorSpans(spanId, currentSpan) - assertEquals("", result) - } - - @Test - fun `span without parenId is not collected`() { - val spanId = "f7e83f2b69ec684d" - val currentSpan = Mockito.mock(Span::class.java) - val handler = spanHandler() - - `when`(currentSpan.context()).thenReturn(BraveTraceContext(ctx)) - - handler.end(ctx, span("6666632728347823749827349723985", spanId), SpanHandler.Cause.FINISHED) - - val result = handler.getErrorSpans(spanId, currentSpan) - assertEquals("", result) - } - - @ParameterizedTest - @MethodSource("spans") - fun `span with length of traceId greater than 20 and with parentId is collected`(span: MutableSpan) { - val currentSpan = Mockito.mock(Span::class.java) - val handler = spanHandler() - - `when`(currentSpan.context()).thenReturn(BraveTraceContext(ctx)) - - handler.end(ctx, span, SpanHandler.Cause.FINISHED) - - val result = handler.getErrorSpans("f7e83f2b69ec682d", currentSpan) - val collectedSpans = mapper.readValue>(result) - assertTrue(collectedSpans.size == 1) - assertEquals(span, collectedSpans[0]) - } - - @Test - fun `span without error tag is not collected`() { - val spanId = "f7e83f2b69ec684d" - val currentSpan = Mockito.mock(Span::class.java) - val handler = spanHandler() - val span = span("6666632728347823749827349723985", spanId) - .apply { - parentId("f7e83f2b69ec682d") - removeTag(SPAN_ERROR) - } - - `when`(currentSpan.context()).thenReturn(BraveTraceContext(ctx)) - - handler.end(ctx, span, SpanHandler.Cause.FINISHED) - - val result = handler.getErrorSpans("f7e83f2b69ec682d", currentSpan) - assertEquals("", result) - } - - private fun span(traceId: String, spanId: String) = MutableSpan() - .apply { - traceId(traceId) - id(spanId) - tag(SPAN_ERROR, "true") - } companion object { @JvmStatic - fun spans() = listOf( - MutableSpan() - .apply { - traceId("6666632728347823749827349723985") - id("f7e83f2b69ec682d") - tag(SPAN_ERROR, "true") - parentId("f7e83f2b69ec682d") - }, - MutableSpan() - .apply { - traceId("6666632728347823749827349723985") - id("f7e83f2b69ec111d") - parentId("f7e83f2b69ec682d") - tag(SPAN_NO_RESPONSE_MESSAGE, "noResp") - }, - MutableSpan() - .apply { - traceId("6666632728347823749827349723985") - id("f7e83f2b69ec111d") - parentId("f7e83f2b69ec682d") - startTimestamp(3034272) - finishTimestamp(15034272) - } - ) + fun data(): Stream { + return Stream.of( + Arguments.of( + false, null, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + }, + true + ), + Arguments.of( + false, 1000L, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(999999) + }, + false + ), + Arguments.of( + false, 1000L, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + }, + true + ), + Arguments.of( + true, null, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + }, + false + ), + Arguments.of( + true, null, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + tag("error", "true") + }, + true + ), + Arguments.of( + false, null, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + tag("error", "true") + }, + true + ), + Arguments.of( + true, 1000L, + MutableSpan().apply { + startTimestamp(0) + finishTimestamp(1000000) + }, + true + ), + ) + } } - private fun spanHandler() = ProviderSpanHandler(mapper, spanExportableList) + @ParameterizedTest + @MethodSource("data") + fun end(onlyErrors: Boolean?, timeThreshold: Long?, span: MutableSpan, expected: Boolean) { + val handler = ProviderSpanHandler(onlyErrors, timeThreshold) + val res = handler.end(null, span, null) + assertEquals(expected, res) + } }