diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy deleted file mode 100644 index 0b1d15baab6b..000000000000 --- a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/groovy/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1 - -import io.lettuce.core.RedisClient -import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceSyncClientTest -import io.opentelemetry.instrumentation.test.AgentTestTrait - -class LettuceSyncClientTest extends AbstractLettuceSyncClientTest implements AgentTestTrait { - @Override - RedisClient createClient(String uri) { - return RedisClient.create(uri) - } -} diff --git a/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java new file mode 100644 index 000000000000..3cc637e967a4 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/javaagent/src/test/java/io/opentelemetry/javaagent/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java @@ -0,0 +1,27 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.instrumentation.lettuce.v5_1; + +import io.lettuce.core.RedisClient; +import io.opentelemetry.instrumentation.lettuce.v5_1.AbstractLettuceSyncClientTest; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LettuceSyncClientTest extends AbstractLettuceSyncClientTest { + @RegisterExtension + static InstrumentationExtension agentTesting = AgentInstrumentationExtension.create(); + + @Override + public InstrumentationExtension getInstrumentationExtension() { + return agentTesting; + } + + @Override + protected RedisClient createClient(String uri) { + return RedisClient.create(uri); + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy deleted file mode 100644 index 76c536376387..000000000000 --- a/instrumentation/lettuce/lettuce-5.1/library/src/test/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.groovy +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.lettuce.v5_1 - -import io.lettuce.core.RedisClient -import io.lettuce.core.resource.ClientResources -import io.opentelemetry.instrumentation.test.LibraryTestTrait - -class LettuceSyncClientTest extends AbstractLettuceSyncClientTest implements LibraryTestTrait { - @Override - RedisClient createClient(String uri) { - return RedisClient.create( - ClientResources.builder() - .tracing(LettuceTelemetry.create(getOpenTelemetry()).newTracing()) - .build(), - uri) - } -} diff --git a/instrumentation/lettuce/lettuce-5.1/library/src/test/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java b/instrumentation/lettuce/lettuce-5.1/library/src/test/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java new file mode 100644 index 000000000000..5a790332bb6e --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/library/src/test/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceSyncClientTest.java @@ -0,0 +1,34 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.resource.ClientResources; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.LibraryInstrumentationExtension; +import org.junit.jupiter.api.extension.RegisterExtension; + +class LettuceSyncClientTest extends AbstractLettuceSyncClientTest { + + @RegisterExtension + static InstrumentationExtension testing = LibraryInstrumentationExtension.create(); + + @Override + public InstrumentationExtension getInstrumentationExtension() { + return testing; + } + + @Override + protected RedisClient createClient(String uri) { + return RedisClient.create( + ClientResources.builder() + .tracing( + LettuceTelemetry.create(getInstrumentationExtension().getOpenTelemetry()) + .newTracing()) + .build(), + uri); + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy deleted file mode 100644 index 2911cab9cee3..000000000000 --- a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.groovy +++ /dev/null @@ -1,490 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.lettuce.v5_1 - -import io.lettuce.core.RedisClient -import io.lettuce.core.RedisConnectionException -import io.lettuce.core.RedisException -import io.lettuce.core.ScriptOutputType -import io.lettuce.core.api.StatefulConnection -import io.lettuce.core.api.sync.RedisCommands -import io.opentelemetry.instrumentation.test.InstrumentationSpecification -import io.opentelemetry.instrumentation.test.utils.PortUtils -import io.opentelemetry.semconv.SemanticAttributes -import org.testcontainers.containers.GenericContainer -import spock.lang.Shared - -import static io.opentelemetry.api.trace.SpanKind.CLIENT -import static io.opentelemetry.api.trace.StatusCode.ERROR -import static java.nio.charset.StandardCharsets.UTF_8 - -abstract class AbstractLettuceSyncClientTest extends InstrumentationSpecification { - public static final int DB_INDEX = 0 - - private static GenericContainer redisServer = new GenericContainer<>("redis:6.2.3-alpine").withExposedPorts(6379) - - abstract RedisClient createClient(String uri) - - @Shared - String expectedHostAttributeValue - @Shared - int port - @Shared - String dbUriNonExistent - @Shared - String embeddedDbLocalhostUri - - @Shared - Map testHashMap = [ - firstname: "John", - lastname : "Doe", - age : "53" - ] - - RedisClient redisClient - StatefulConnection connection - RedisCommands syncCommands - - def setup() { - redisServer.start() - - port = redisServer.getMappedPort(6379) - String host = redisServer.getHost() - String dbAddr = host + ":" + port + "/" + DB_INDEX - String embeddedDbUri = "redis://" + dbAddr - embeddedDbLocalhostUri = "redis://localhost:" + port + "/" + DB_INDEX - expectedHostAttributeValue = host == "127.0.0.1" ? null : host - - int incorrectPort = PortUtils.findOpenPort() - String dbAddrNonExistent = host + ":" + incorrectPort + "/" + DB_INDEX - dbUriNonExistent = "redis://" + dbAddrNonExistent - - redisClient = createClient(embeddedDbUri) - redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) - - connection = redisClient.connect() - syncCommands = connection.sync() - - syncCommands.set("TESTKEY", "TESTVAL") - syncCommands.hmset("TESTHM", testHashMap) - - // 2 sets - ignoreTracesAndClear(2) - } - - def cleanup() { - connection.close() - redisClient.shutdown() - redisServer.stop() - } - - def "connect"() { - when: - StatefulConnection connection = redisClient.connect() - - then: - // Lettuce tracing does not trace connect - assertTraces(0) {} - - cleanup: - connection.close() - } - - def "connect exception"() { - setup: - RedisClient testConnectionClient = createClient(dbUriNonExistent) - testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) - - when: - testConnectionClient.connect() - - then: - thrown RedisConnectionException - // Lettuce tracing does not trace connect - assertTraces(0) {} - - cleanup: - testConnectionClient.shutdown() - } - - def "set command"() { - setup: - String res = syncCommands.set("TESTSETKEY", "TESTSETVAL") - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "set command localhost"() { - setup: - RedisClient testConnectionClient = createClient(embeddedDbLocalhostUri) - testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS) - StatefulConnection connection = testConnectionClient.connect() - String res = connection.sync().set("TESTSETKEY", "TESTSETVAL") - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "SET TESTSETKEY ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - - cleanup: - connection.close() - testConnectionClient.shutdown() - } - - def "get command"() { - setup: - String res = syncCommands.get("TESTKEY") - - expect: - res == "TESTVAL" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "GET TESTKEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "get non existent key command"() { - setup: - String res = syncCommands.get("NON_EXISTENT_KEY") - - expect: - res == null - assertTraces(1) { - trace(0, 1) { - span(0) { - name "GET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "GET NON_EXISTENT_KEY" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "command with no arguments"() { - setup: - def keyRetrieved = syncCommands.randomkey() - - expect: - keyRetrieved != null - assertTraces(1) { - trace(0, 1) { - span(0) { - name "RANDOMKEY" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_STATEMENT" "RANDOMKEY" - "$SemanticAttributes.DB_SYSTEM" "redis" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "list command"() { - setup: - long res = syncCommands.lpush("TESTLIST", "TESTLIST ELEMENT") - - expect: - res == 1 - assertTraces(1) { - trace(0, 1) { - span(0) { - name "LPUSH" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "LPUSH TESTLIST ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "hash set command"() { - setup: - def res = syncCommands.hmset("user", testHashMap) - - expect: - res == "OK" - assertTraces(1) { - trace(0, 1) { - span(0) { - name "HMSET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "HMSET user firstname ? lastname ? age ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "hash getall command"() { - setup: - Map res = syncCommands.hgetall("TESTHM") - - expect: - res == testHashMap - assertTraces(1) { - trace(0, 1) { - span(0) { - name "HGETALL" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "HGETALL TESTHM" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "eval command"() { - given: - def script = "redis.call('lpush', KEYS[1], ARGV[1], ARGV[2]); return redis.call('llen', KEYS[1])" - - when: - def result = syncCommands.eval(script, ScriptOutputType.INTEGER, ["TESTLIST"] as String[], "abc", "def") - - then: - result == 2 - - def b64Script = Base64.encoder.encodeToString(script.getBytes(UTF_8)) - assertTraces(1) { - trace(0, 1) { - span(0) { - name "EVAL" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "EVAL $b64Script 1 TESTLIST ? ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "mset command"() { - when: - def res = syncCommands.mset([ - "key1": "value1", - "key2": "value2" - ]) - - then: - res == "OK" - - assertTraces(1) { - trace(0, 1) { - span(0) { - name "MSET" - kind CLIENT - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "MSET key1 ? key2 ?" - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - - def "debug segfault command (returns void) with no argument produces no span"() { - setup: - syncCommands.debugSegfault() - - expect: - assertTraces(1) { - trace(0, 1) { - span(0) { - name "DEBUG" - kind CLIENT - // Disconnect not an actual error even though an exception is recorded. - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "DEBUG SEGFAULT" - } - if (!Boolean.getBoolean("testLatestDeps")) { - // these are no longer recorded since Lettuce 6.1.6 - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - } - } - } - } - } - - def "shutdown command (returns void) produces no span"() { - setup: - syncCommands.shutdown(false) - - expect: - assertTraces(1) { - trace(0, 1) { - span(0) { - name "SHUTDOWN" - kind CLIENT - if (Boolean.getBoolean("testLatestDeps")) { - // Seems to only be treated as an error with Lettuce 6+ - status ERROR - } - attributes { - "$SemanticAttributes.NET_SOCK_PEER_ADDR" "127.0.0.1" - "$SemanticAttributes.NET_SOCK_PEER_NAME" expectedHostAttributeValue - "$SemanticAttributes.NET_SOCK_PEER_PORT" port - "$SemanticAttributes.DB_SYSTEM" "redis" - "$SemanticAttributes.DB_STATEMENT" "SHUTDOWN NOSAVE" - if (!Boolean.getBoolean("testLatestDeps")) { - // Lettuce adds this tag before 6.0 - // TODO(anuraaga): Filter this out? - "error" "Connection disconnected" - } - } - event(0) { - eventName "redis.encode.start" - } - event(1) { - eventName "redis.encode.end" - } - if (Boolean.getBoolean("testLatestDeps")) { - errorEvent(RedisException, "Connection disconnected", 2) - } - } - } - } - } -} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy b/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy deleted file mode 100644 index 25fd109eab30..000000000000 --- a/instrumentation/lettuce/lettuce-5.1/testing/src/main/groovy/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.groovy +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.instrumentation.lettuce.v5_1 - -import groovy.transform.PackageScope -import io.lettuce.core.ClientOptions - -@PackageScope -final class LettuceTestUtil { - - static final ClientOptions CLIENT_OPTIONS - - static { - def options = ClientOptions.builder() - // Disable autoreconnect so we do not get stray traces popping up on server shutdown - .autoReconnect(false) - if (Boolean.getBoolean("testLatestDeps")) { - // Force RESP2 on 6+ for consistency in tests - options - .pingBeforeActivateConnection(false) - .protocolVersion(Class.forName("io.lettuce.core.protocol.ProtocolVersion").getField("RESP2").get(null)) - } - CLIENT_OPTIONS = options.build() - } - - private LettuceTestUtil() {} -} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceClientTest.java b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceClientTest.java new file mode 100644 index 000000000000..e72501753546 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceClientTest.java @@ -0,0 +1,81 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1; + +import io.lettuce.core.RedisClient; +import io.lettuce.core.api.StatefulRedisConnection; +import io.opentelemetry.instrumentation.testing.internal.AutoCleanupExtension; +import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension; +import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +abstract class AbstractLettuceClientTest { + protected static final Logger logger = LoggerFactory.getLogger(AbstractLettuceClientTest.class); + + @RegisterExtension + static InstrumentationExtension agentTesting = AgentInstrumentationExtension.create(); + + public InstrumentationExtension getInstrumentationExtension() { + return agentTesting; + } + + @RegisterExtension static final AutoCleanupExtension cleanup = AutoCleanupExtension.create(); + + protected static final int DB_INDEX = 0; + + protected static GenericContainer redisServer = + new GenericContainer<>("redis:6.2.3-alpine") + .withExposedPorts(6379) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .waitingFor(Wait.forLogMessage(".*Ready to accept connections.*", 1)); + + protected static RedisClient redisClient; + + protected static StatefulRedisConnection connection; + + protected abstract RedisClient createClient(String uri); + + protected static String host; + protected static int port; + protected static String embeddedDbUri; + + protected static ContainerConnection newContainerConnection() { + GenericContainer server = + new GenericContainer<>("redis:6.2.3-alpine") + .withExposedPorts(6379) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .waitingFor(Wait.forLogMessage(".*Ready to accept connections.*", 1)); + server.start(); + cleanup.deferCleanup(server::stop); + + long serverPort = server.getMappedPort(6379); + + RedisClient client = RedisClient.create("redis://" + host + ":" + serverPort + "/" + DB_INDEX); + client.setOptions(LettuceTestUtil.CLIENT_OPTIONS); + cleanup.deferCleanup(client::shutdown); + + StatefulRedisConnection statefulConnection = client.connect(); + cleanup.deferCleanup(statefulConnection); + return new ContainerConnection(statefulConnection, serverPort); + } + + protected static class ContainerConnection { + public final StatefulRedisConnection connection; + public final long port; + + private ContainerConnection(StatefulRedisConnection connection, long port) { + this.connection = connection; + this.port = port; + } + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java new file mode 100644 index 000000000000..821a0dfed366 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/AbstractLettuceSyncClientTest.java @@ -0,0 +1,478 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.equalTo; +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.catchThrowable; + +import com.google.common.collect.ImmutableMap; +import io.lettuce.core.RedisClient; +import io.lettuce.core.RedisConnectionException; +import io.lettuce.core.RedisException; +import io.lettuce.core.ScriptOutputType; +import io.lettuce.core.api.StatefulRedisConnection; +import io.lettuce.core.api.sync.RedisCommands; +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.trace.SpanKind; +import io.opentelemetry.instrumentation.test.utils.PortUtils; +import io.opentelemetry.sdk.trace.data.StatusData; +import io.opentelemetry.semconv.SemanticAttributes; +import java.util.Base64; +import java.util.Map; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +@SuppressWarnings("deprecation") // until old http semconv are dropped in 2.0 +public abstract class AbstractLettuceSyncClientTest extends AbstractLettuceClientTest { + + private static String dbUriNonExistent; + private static String embeddedDbLocalhostUri; + private static String expectedHostAttributeValue; + + private static final ImmutableMap testHashMap = + ImmutableMap.of( + "firstname", "John", + "lastname", "Doe", + "age", "53"); + + private static RedisCommands syncCommands; + + @BeforeAll + void setUp() { + redisServer.start(); + + host = redisServer.getHost(); + port = redisServer.getMappedPort(6379); + embeddedDbUri = "redis://" + host + ":" + port + "/" + DB_INDEX; + embeddedDbLocalhostUri = "redis://localhost:" + port + "/" + DB_INDEX; + expectedHostAttributeValue = "127.0.0.1".equals(host) ? null : host; + + int incorrectPort = PortUtils.findOpenPort(); + dbUriNonExistent = "redis://" + host + ":" + incorrectPort + "/" + DB_INDEX; + + redisClient = createClient(embeddedDbUri); + redisClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS); + + connection = redisClient.connect(); + syncCommands = connection.sync(); + + syncCommands.set("TESTKEY", "TESTVAL"); + syncCommands.hmset("TESTHM", testHashMap); + + // 2 sets + getInstrumentationExtension().waitForTraces(2); + getInstrumentationExtension().clearData(); + } + + @AfterAll + static void cleanUp() { + connection.close(); + redisClient.shutdown(); + redisServer.stop(); + } + + @Test + void testConnect() { + StatefulRedisConnection testConnection = redisClient.connect(); + cleanup.deferCleanup(testConnection); + + // Lettuce tracing does not trace connect + assertThat(getInstrumentationExtension().spans()).isEmpty(); + } + + @Test + void testConnectException() { + RedisClient testConnectionClient = RedisClient.create(dbUriNonExistent); + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS); + cleanup.deferCleanup(testConnectionClient::shutdown); + + Throwable thrown = catchThrowable(testConnectionClient::connect); + + assertThat(thrown).isInstanceOf(RedisConnectionException.class); + + // Lettuce tracing does not trace connect + assertThat(getInstrumentationExtension().spans()).isEmpty(); + } + + @Test + void testSetCommand() { + String res = syncCommands.set("TESTSETKEY", "TESTSETVAL"); + assertThat(res).isEqualTo("OK"); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "SET TESTSETKEY ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testSetCommandLocalhost() { + RedisClient testConnectionClient = createClient(embeddedDbLocalhostUri); + testConnectionClient.setOptions(LettuceTestUtil.CLIENT_OPTIONS); + cleanup.deferCleanup(testConnectionClient::shutdown); + + StatefulRedisConnection testConnection = testConnectionClient.connect(); + cleanup.deferCleanup(testConnection); + + String res = testConnection.sync().set("TESTSETKEY", "TESTSETVAL"); + assertThat(res).isEqualTo("OK"); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "SET TESTSETKEY ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testGetCommand() { + String res = syncCommands.get("TESTKEY"); + assertThat(res).isEqualTo("TESTVAL"); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "GET TESTKEY")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testGetNonExistentKeyCommand() { + String res = syncCommands.get("NON_EXISTENT_KEY"); + assertThat(res).isNull(); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("GET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "GET NON_EXISTENT_KEY")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testCommandWithNoArguments() { + String res = syncCommands.randomkey(); + assertThat(res).isNotNull(); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("RANDOMKEY") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "RANDOMKEY")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testListCommand() { + // Needs its own container or inconsistent command count + ContainerConnection containerConnection = newContainerConnection(); + RedisCommands commands = containerConnection.connection.sync(); + + long res = commands.lpush("TESTLIST", "TESTLIST ELEMENT"); + assertThat(res).isEqualTo(1); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("LPUSH") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo( + SemanticAttributes.NET_SOCK_PEER_PORT, + containerConnection.port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "LPUSH TESTLIST ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testHashSetCommand() { + String res = syncCommands.hmset("user", testHashMap); + assertThat(res).isEqualTo("OK"); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("HMSET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo( + SemanticAttributes.DB_STATEMENT, + "HMSET user firstname ? lastname ? age ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testHashGetallCommand() { + Map res = syncCommands.hgetall("TESTHM"); + assertThat(res).isEqualTo(testHashMap); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("HGETALL") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "HGETALL TESTHM")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testEvalCommand() { + String script = + "redis.call('lpush', KEYS[1], ARGV[1], ARGV[2]); return redis.call('llen', KEYS[1])"; + + Long result = + syncCommands.eval( + script, ScriptOutputType.INTEGER, new String[] {"TESTLIST"}, "abc", "def"); + assertThat(result).isEqualTo(2); + + String b64Script = Base64.getEncoder().encodeToString(script.getBytes(UTF_8)); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("EVAL") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo( + SemanticAttributes.DB_STATEMENT, + "EVAL " + b64Script + " 1 TESTLIST ? ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testMsetCommand() { + String result = syncCommands.mset(ImmutableMap.of("key1", "value1", "key2", "value2")); + + assertThat(result).isEqualTo("OK"); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("MSET") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo(SemanticAttributes.NET_SOCK_PEER_PORT, port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "MSET key1 ? key2 ?")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end")))); + } + + @Test + void testDebugSegfaultCommandWithNoArgumentProducesNoSpan() { + // Test causes redis to crash therefore it needs its own container + ContainerConnection containerConnection = newContainerConnection(); + RedisCommands commands = containerConnection.connection.sync(); + + commands.debugSegfault(); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> { + if (Boolean.getBoolean("testLatestDeps")) { + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("DEBUG") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo( + SemanticAttributes.NET_SOCK_PEER_PORT, + containerConnection.port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "DEBUG SEGFAULT"))); + } else { + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("DEBUG") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo( + SemanticAttributes.NET_SOCK_PEER_PORT, + containerConnection.port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "DEBUG SEGFAULT")) + // these are no longer recorded since Lettuce 6.1.6 + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end"))); + } + }); + } + + @Test + void testShutdownCommandProducesNoSpan() { + // Test causes redis to crash therefore it needs its own container + ContainerConnection containerConnection = newContainerConnection(); + RedisCommands commands = containerConnection.connection.sync(); + + commands.shutdown(false); + + getInstrumentationExtension() + .waitAndAssertTraces( + trace -> { + if (Boolean.getBoolean("testLatestDeps")) { + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SHUTDOWN") + .hasKind(SpanKind.CLIENT) + // Seems to only be treated as an error with Lettuce 6+ + .hasStatus(StatusData.error()) + .hasException(new RedisException("Connection disconnected")) + .hasAttributesSatisfyingExactly( + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo( + SemanticAttributes.NET_SOCK_PEER_PORT, + containerConnection.port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "SHUTDOWN NOSAVE"))); + } else { + trace.hasSpansSatisfyingExactly( + span -> + span.hasName("SHUTDOWN") + .hasKind(SpanKind.CLIENT) + .hasAttributesSatisfyingExactly( + equalTo(AttributeKey.stringKey("error"), "Connection disconnected"), + equalTo(SemanticAttributes.NET_SOCK_PEER_ADDR, "127.0.0.1"), + equalTo( + SemanticAttributes.NET_SOCK_PEER_NAME, + expectedHostAttributeValue), + equalTo( + SemanticAttributes.NET_SOCK_PEER_PORT, + containerConnection.port), + equalTo(SemanticAttributes.DB_SYSTEM, "redis"), + equalTo(SemanticAttributes.DB_STATEMENT, "SHUTDOWN NOSAVE")) + .hasEventsSatisfyingExactly( + event -> event.hasName("redis.encode.start"), + event -> event.hasName("redis.encode.end"))); + } + }); + } +} diff --git a/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.java b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.java new file mode 100644 index 000000000000..a200e9b87c06 --- /dev/null +++ b/instrumentation/lettuce/lettuce-5.1/testing/src/main/java/io/opentelemetry/instrumentation/lettuce/v5_1/LettuceTestUtil.java @@ -0,0 +1,26 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.instrumentation.lettuce.v5_1; + +import io.lettuce.core.ClientOptions; + +final class LettuceTestUtil { + static final ClientOptions CLIENT_OPTIONS; + + static { + ClientOptions.Builder options = + ClientOptions.builder() + // Disable autoreconnect so we do not get stray traces popping up on server shutdown + .autoReconnect(false); + if (Boolean.getBoolean("testLatestDeps")) { + // Force RESP2 on 6+ for consistency in tests + options.pingBeforeActivateConnection(false); + } + CLIENT_OPTIONS = options.build(); + } + + private LettuceTestUtil() {} +}