diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java index 0f8652fb70..278aa4011d 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java @@ -42,7 +42,11 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { LocalStackContainer.Service.S3, LocalStackContainer.Service.DYNAMODB, LocalStackContainer.Service.SQS, - LocalStackContainer.Service.KINESIS) + LocalStackContainer.Service.KINESIS, + LocalStackContainer.Service.SECRETSMANAGER, + LocalStackContainer.Service.IAM, + LocalStackContainer.Service.STEPFUNCTIONS, + LocalStackContainer.Service.SNS) .withEnv("DEFAULT_REGION", "us-west-2") .withNetwork(network) .withEnv("LOCALSTACK_HOST", "127.0.0.1") @@ -102,6 +106,12 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeSpanNamePrefix(); + protected abstract String getSecretsManagerSpanNamePrefix(); + + protected abstract String getStepFunctionsSpanNamePrefix(); + + protected abstract String getSnsSpanNamePrefix(); + protected abstract String getS3RpcServiceName(); protected abstract String getDynamoDbRpcServiceName(); @@ -118,6 +128,12 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeRpcServiceName(); + protected abstract String getSecretsManagerRpcServiceName(); + + protected abstract String getSnsRpcServiceName(); + + protected abstract String getStepFunctionsRpcServiceName(); + private String getS3ServiceName() { return "AWS::S3"; } @@ -150,6 +166,18 @@ private String getBedrockRuntimeServiceName() { return "AWS::BedrockRuntime"; } + private String getSecretsManagerServiceName() { + return "AWS::SecretsManager"; + } + + private String getStepFunctionsServiceName() { + return "AWS::StepFunctions"; + } + + protected String getSnsServiceName() { + return "AWS::SNS"; + } + private String s3SpanName(String operation) { return String.format("%s.%s", getS3SpanNamePrefix(), operation); } @@ -182,10 +210,31 @@ private String bedrockAgentRuntimeSpanName(String operation) { return String.format("%s.%s", getBedrockAgentRuntimeSpanNamePrefix(), operation); } + private String secretsManagerSpanName(String operation) { + return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), operation); + } + + private String stepFunctionsSpanName(String operation) { + return String.format("%s.%s", getStepFunctionsSpanNamePrefix(), operation); + } + + private String snsSpanName(String operation) { + return String.format("%s.%s", getSnsSpanNamePrefix(), operation); + } + protected ThrowingConsumer assertAttribute(String key, String value) { return (attribute) -> { - assertThat(attribute.getKey()).isEqualTo(key); - assertThat(attribute.getValue().getStringValue()).isEqualTo(value); + var actualKey = attribute.getKey(); + var actualValue = attribute.getValue().getStringValue(); + + assertThat(actualKey).isEqualTo(key); + + // We only want to Regex Pattern Match on the Secret Id and Secret Arn + if (actualValue.contains("secret-id")) { + assertThat(actualValue).matches(value); + } else { + assertThat(actualValue).isEqualTo(value); + } }; } @@ -258,6 +307,7 @@ private void assertSpanClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -276,6 +326,7 @@ private void assertSpanClientAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -293,6 +344,7 @@ private void assertSpanProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -310,6 +362,7 @@ private void assertSpanProducerAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -358,6 +411,7 @@ private void assertSpanAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -371,6 +425,7 @@ private void assertSpanAttributes( var spanAttributes = span.getAttributesList(); assertThat(span.getKind()).isEqualTo(spanKind); assertThat(span.getName()).isEqualTo(spanName); + assertSemanticConventionsAttributes( spanAttributes, rpcService, method, peerName, peerPort, url, statusCode); assertAwsAttributes( @@ -381,6 +436,7 @@ private void assertSpanAttributes( method, type, identifier, + cloudformationIdentifier, awsSpanKind); for (var assertion : extraAssertions) { assertThat(spanAttributes).satisfiesOnlyOnce(assertion); @@ -396,6 +452,7 @@ private void assertAwsAttributes( String operation, String type, String identifier, + String clouformationIdentifier, String spanKind) { var assertions = @@ -406,11 +463,14 @@ private void assertAwsAttributes( .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_REMOTE_OPERATION, operation)) .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_REMOTE_SERVICE, service)) .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_SPAN_KIND, spanKind)); - if (type != null && identifier != null) { + if (type != null && identifier != null && clouformationIdentifier != null) { assertions.satisfiesOnlyOnce( assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_TYPE, type)); assertions.satisfiesOnlyOnce( assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_IDENTIFIER, identifier)); + assertions.satisfiesOnlyOnce( + assertAttribute( + AppSignalsConstants.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, clouformationIdentifier)); } } @@ -435,6 +495,7 @@ protected void assertMetricClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -446,6 +507,7 @@ protected void assertMetricClientAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -458,6 +520,7 @@ protected void assertMetricProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -469,6 +532,7 @@ protected void assertMetricProducerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -481,6 +545,7 @@ protected void assertMetricConsumerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -492,6 +557,7 @@ protected void assertMetricConsumerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -505,6 +571,7 @@ protected void assertMetricAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertThat(resourceScopeMetrics) .anySatisfy( @@ -524,6 +591,7 @@ protected void assertMetricAttributes( method, type, identifier, + cloudformationIdentifier, spanKind); if (expectedSum != null) { double actualSum = dataPoint.getSum(); @@ -554,6 +622,7 @@ protected void doTestS3CreateBucket() throws Exception { var localOperation = "GET /s3/createbucket/:bucketname"; var type = "AWS::S3::Bucket"; var identifier = "create-bucket"; + var cloudformationIdentifier = "create-bucket"; assertSpanClientAttributes( traces, @@ -565,6 +634,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, "create-bucket.s3.localstack", 4566, "http://create-bucket.s3.localstack:4566", @@ -579,6 +649,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -589,6 +660,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -599,6 +671,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); } @@ -617,6 +690,7 @@ protected void doTestS3CreateObject() throws Exception { var localOperation = "GET /s3/createobject/:bucketname/:objectname"; var type = "AWS::S3::Bucket"; var identifier = "put-object"; + var cloudformationIdentifier = "put-object"; assertSpanClientAttributes( traces, @@ -628,6 +702,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, "put-object.s3.localstack", 4566, "http://put-object.s3.localstack:4566", @@ -642,6 +717,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -652,6 +728,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -662,6 +739,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -679,6 +757,7 @@ protected void doTestS3GetObject() throws Exception { var localOperation = "GET /s3/getobject/:bucketName/:objectname"; var type = "AWS::S3::Bucket"; var identifier = "get-object"; + var cloudformationIdentifier = "get-object"; assertSpanClientAttributes( traces, @@ -690,6 +769,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, "get-object.s3.localstack", 4566, "http://get-object.s3.localstack:4566", @@ -704,6 +784,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -714,6 +795,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -724,6 +806,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -741,6 +824,7 @@ protected void doTestS3Error() { var localOperation = "GET /s3/error"; var type = "AWS::S3::Bucket"; var identifier = "error-bucket"; + var cloudformationIdentifier = "error-bucket"; assertSpanClientAttributes( traces, @@ -752,6 +836,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, "error-bucket.s3.test", 8080, "http://error-bucket.s3.test:8080", @@ -766,6 +851,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -776,6 +862,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -786,6 +873,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); } @@ -803,6 +891,7 @@ protected void doTestS3Fault() { var localOperation = "GET /s3/fault"; var type = "AWS::S3::Bucket"; var identifier = "fault-bucket"; + var cloudformationIdentifier = "fault-bucket"; assertSpanClientAttributes( traces, @@ -814,6 +903,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, "fault-bucket.s3.test", 8080, "http://fault-bucket.s3.test:8080", @@ -828,6 +918,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -838,6 +929,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -848,6 +940,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -873,6 +966,7 @@ protected void doTestDynamoDbCreateTable() { var localOperation = "GET /ddb/createtable/:tablename"; var type = "AWS::DynamoDB::Table"; var identifier = "some-table"; + var cloudformationIdentifier = "some-table"; assertSpanClientAttributes( traces, @@ -884,6 +978,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -898,6 +993,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -908,6 +1004,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -918,6 +1015,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); } @@ -935,6 +1033,7 @@ protected void doTestDynamoDbPutItem() { var localOperation = "GET /ddb/putitem/:tablename/:partitionkey"; var type = "AWS::DynamoDB::Table"; var identifier = "putitem-table"; + var cloudformationIdentifier = "putitem-table"; assertSpanClientAttributes( traces, @@ -946,6 +1045,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -960,6 +1060,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -970,6 +1071,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -980,6 +1082,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -997,6 +1100,7 @@ protected void doTestDynamoDbError() throws Exception { var localOperation = "GET /ddb/error"; var type = "AWS::DynamoDB::Table"; var identifier = "nonexistanttable"; + var cloudformationIdentifier = "nonexistanttable"; assertSpanClientAttributes( traces, @@ -1008,6 +1112,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1022,6 +1127,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1032,6 +1138,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1042,6 +1149,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1065,6 +1173,7 @@ protected void doTestDynamoDbFault() throws Exception { var localOperation = "GET /ddb/fault"; var type = "AWS::DynamoDB::Table"; var identifier = "nonexistanttable"; + var cloudformationIdentifier = "nonexistanttable"; assertSpanClientAttributes( traces, @@ -1076,6 +1185,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1090,6 +1200,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -1100,6 +1211,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1110,6 +1222,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1127,6 +1240,7 @@ protected void doTestSQSCreateQueue() throws Exception { var localOperation = "GET /sqs/createqueue/:queuename"; var type = "AWS::SQS::Queue"; var identifier = "some-queue"; + var cloudformationIdentifier = "some-queue"; assertSpanClientAttributes( traces, @@ -1138,6 +1252,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1152,6 +1267,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1162,6 +1278,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1172,6 +1289,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1190,6 +1308,7 @@ protected void doTestSQSSendMessage() throws Exception { // SendMessage does not capture aws.queue.name String type = null; String identifier = null; + String cloudformationIdentifier = null; assertSpanProducerAttributes( traces, @@ -1201,6 +1320,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1216,6 +1336,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1226,6 +1347,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1236,6 +1358,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1258,6 +1381,7 @@ protected void doTestSQSReceiveMessage() throws Exception { // ReceiveMessage does not capture aws.queue.name String type = null; String identifier = null; + String cloudformationIdentifier = null; // Consumer traces for SQS behave like a Server span (they create the local aws service // attributes), but have RPC attributes like a client span. assertSpanConsumerAttributes( @@ -1282,6 +1406,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricConsumerAttributes( metrics, @@ -1292,6 +1417,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1310,6 +1436,7 @@ protected void doTestSQSError() throws Exception { // SendMessage does not capture aws.queue.name String type = null; String identifier = null; + String cloudformationIdentifier = null; assertSpanProducerAttributes( traces, @@ -1321,6 +1448,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1336,6 +1464,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1346,6 +1475,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1356,6 +1486,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1374,6 +1505,7 @@ protected void doTestSQSFault() throws Exception { // SendMessage does not capture aws.queue.name String type = null; String identifier = null; + String cloudformationIdentifier = null; assertSpanProducerAttributes( traces, @@ -1385,6 +1517,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1400,6 +1533,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1410,6 +1544,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); assertMetricProducerAttributes( metrics, @@ -1420,6 +1555,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1437,6 +1573,7 @@ protected void doTestKinesisPutRecord() throws Exception { var localOperation = "GET /kinesis/putrecord/:streamname"; var type = "AWS::Kinesis::Stream"; var identifier = "my-stream"; + var cloudformationIdentifier = "my-stream"; assertSpanClientAttributes( traces, @@ -1448,6 +1585,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1462,6 +1600,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1472,6 +1611,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1482,6 +1622,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1499,6 +1640,7 @@ protected void doTestKinesisError() throws Exception { var localOperation = "GET /kinesis/error"; var type = "AWS::Kinesis::Stream"; var identifier = "nonexistantstream"; + var cloudformationIdentifier = "nonexistantstream"; assertSpanClientAttributes( traces, @@ -1510,6 +1652,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1525,6 +1668,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1535,6 +1679,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1545,6 +1690,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1562,6 +1708,7 @@ protected void doTestKinesisFault() throws Exception { var localOperation = "GET /kinesis/fault"; var type = "AWS::Kinesis::Stream"; var identifier = "faultstream"; + var cloudformationIdentifier = "faultstream"; assertSpanClientAttributes( traces, @@ -1573,6 +1720,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1587,6 +1735,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1597,6 +1746,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1607,6 +1757,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1625,6 +1776,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { var localOperation = "GET /bedrockagent/getknowledgeBase/:knowledgeBaseId"; String type = "AWS::Bedrock::KnowledgeBase"; String identifier = "knowledge-base-id"; + String cloudformationIdentifier = "knowledge-base-id"; assertSpanClientAttributes( traces, bedrockAgentSpanName("GetKnowledgeBase"), @@ -1635,6 +1787,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1651,6 +1804,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1661,6 +1815,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1671,6 +1826,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1688,6 +1844,7 @@ protected void doTestBedrockAgentAgentId() { var localOperation = "GET /bedrockagent/getagent/:agentId"; String type = "AWS::Bedrock::Agent"; String identifier = "test-agent-id"; + String cloudformationIdentifier = "test-agent-id"; assertSpanClientAttributes( traces, bedrockAgentSpanName("GetAgent"), @@ -1698,6 +1855,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1712,6 +1870,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1722,6 +1881,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1732,6 +1892,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1749,6 +1910,7 @@ protected void doTestBedrockAgentDataSourceId() { var localOperation = "GET /bedrockagent/get-data-source"; String type = "AWS::Bedrock::DataSource"; String identifier = "nonExistDatasourceId"; + String cloudformationIdentifier = "nonExistDatasourceId"; assertSpanClientAttributes( traces, bedrockAgentSpanName("GetDataSource"), @@ -1759,6 +1921,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1775,6 +1938,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1785,6 +1949,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1795,6 +1960,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1812,6 +1978,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { var localOperation = "GET /bedrockruntime/invokeModel/ai21Jamba"; String type = "AWS::Bedrock::Model"; String identifier = "ai21.jamba-1-5-mini-v1:0"; + String cloudformationIdentifier = "ai21.jamba-1-5-mini-v1:0"; assertSpanClientAttributes( traces, bedrockRuntimeSpanName("InvokeModel"), @@ -1822,6 +1989,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1843,6 +2011,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1853,6 +2022,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1863,6 +2033,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1880,6 +2051,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { var localOperation = "GET /bedrockruntime/invokeModel/amazonTitan"; String type = "AWS::Bedrock::Model"; String identifier = "amazon.titan-text-premier-v1:0"; + String cloudformationIdentifier = "amazon.titan-text-premier-v1:0"; assertSpanClientAttributes( traces, bedrockRuntimeSpanName("InvokeModel"), @@ -1890,6 +2062,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1914,6 +2087,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1924,6 +2098,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1934,6 +2109,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1952,6 +2128,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { var localOperation = "GET /bedrockruntime/invokeModel/anthropicClaude"; String type = "AWS::Bedrock::Model"; String identifier = "anthropic.claude-3-haiku-20240307-v1:0"; + String cloudformationIdentifier = "anthropic.claude-3-haiku-20240307-v1:0"; assertSpanClientAttributes( traces, @@ -1963,6 +2140,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1987,6 +2165,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1997,6 +2176,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2007,6 +2187,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2025,6 +2206,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { var localOperation = "GET /bedrockruntime/invokeModel/cohereCommandR"; String type = "AWS::Bedrock::Model"; String identifier = "cohere.command-r-v1:0"; + String cloudformationIdentifier = "cohere.command-r-v1:0"; assertSpanClientAttributes( traces, @@ -2036,6 +2218,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2059,6 +2242,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2069,6 +2253,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2079,6 +2264,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2097,6 +2283,7 @@ protected void doTestBedrockRuntimeMetaLlama() { var localOperation = "GET /bedrockruntime/invokeModel/metaLlama"; String type = "AWS::Bedrock::Model"; String identifier = "meta.llama3-70b-instruct-v1:0"; + String cloudformationIdentifier = "meta.llama3-70b-instruct-v1:0"; assertSpanClientAttributes( traces, @@ -2108,6 +2295,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2130,6 +2318,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2140,6 +2329,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2150,6 +2340,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2168,6 +2359,7 @@ protected void doTestBedrockRuntimeMistral() { var localOperation = "GET /bedrockruntime/invokeModel/mistralAi"; String type = "AWS::Bedrock::Model"; String identifier = "mistral.mistral-large-2402-v1:0"; + String cloudformationIdentifier = "mistral.mistral-large-2402-v1:0"; assertSpanClientAttributes( traces, @@ -2179,6 +2371,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2202,6 +2395,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2212,6 +2406,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2222,6 +2417,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2239,6 +2435,8 @@ protected void doTestBedrockGuardrailId() { var localOperation = "GET /bedrock/getguardrail"; String type = "AWS::Bedrock::Guardrail"; String identifier = "test-bedrock-guardrail"; + String cloudformationIdentifier = + "arn:aws:bedrock:us-east-1:000000000000:guardrail/test-bedrock-guardrail"; assertSpanClientAttributes( traces, bedrockSpanName("GetGuardrail"), @@ -2249,13 +2447,17 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", 200, List.of( assertAttribute( - SemanticConventionsConstants.AWS_GUARDRAIL_ID, "test-bedrock-guardrail"))); + SemanticConventionsConstants.AWS_GUARDRAIL_ID, "test-bedrock-guardrail"), + assertAttribute( + SemanticConventionsConstants.AWS_GUARDRAIL_ARN, + "arn:aws:bedrock:us-east-1:000000000000:guardrail/test-bedrock-guardrail"))); assertMetricClientAttributes( metrics, AppSignalsConstants.LATENCY_METRIC, @@ -2265,6 +2467,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2275,6 +2478,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2285,6 +2489,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2302,6 +2507,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { var localOperation = "GET /bedrockagentruntime/getmemory/:agentId"; String type = "AWS::Bedrock::Agent"; String identifier = "test-agent-id"; + String cloudformationIdentifier = "test-agent-id"; assertSpanClientAttributes( traces, bedrockAgentRuntimeSpanName("GetAgentMemory"), @@ -2312,6 +2518,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2326,6 +2533,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2336,6 +2544,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2346,6 +2555,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2364,6 +2574,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { var localOperation = "GET /bedrockagentruntime/retrieve/:knowledgeBaseId"; String type = "AWS::Bedrock::KnowledgeBase"; String identifier = "test-knowledge-base-id"; + String cloudformationIdentifier = "test-knowledge-base-id"; assertSpanClientAttributes( traces, bedrockAgentRuntimeSpanName("Retrieve"), @@ -2374,6 +2585,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2390,6 +2602,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2400,6 +2613,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2410,6 +2624,650 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestSecretsManagerDescribeSecret() throws Exception { + appClient.get("/secretsmanager/describesecret/test-secret-id").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/describesecret/:secretId"; + var type = "AWS::SecretsManager::Secret"; + var identifier = "test-secret-id-[A-Za-z0-9]{6}"; + var cloudformationIdentifier = + "arn:aws:secretsmanager:us-west-2:000000000000:secret:test-secret-id-[A-Za-z0-9]{6}"; + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_SECRET_ARN, + "arn:aws:secretsmanager:us-west-2:000000000000:secret:test-secret-id-[A-Za-z0-9]{6}"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestSecretsManagerError() throws Exception { + appClient.get("/secretsmanager/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/error"; + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + "error.test", + 8080, + "http://error.test:8080", + 400, + List.of()); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 1.0); + } + + protected void doTestSecretsManagerFault() throws Exception { + appClient.get("/secretsmanager/fault").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/fault"; + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + "fault.test", + 8080, + "http://fault.test:8080", + 500, + List.of()); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 1.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 0.0); + } + + protected void doTestStepFunctionsDescribeStateMachine() throws Exception { + appClient.get("/sfn/describestatemachine/test-state-machine").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/describestatemachine/:name"; + var type = "AWS::StepFunctions::StateMachine"; + var identifier = "test-state-machine"; + var cloudformationIdentifier = + "arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeStateMachine"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_STATE_MACHINE_ARN, + "arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestStepFunctionsDescribeActivity() throws Exception { + appClient.get("/sfn/describeactivity/test-activity").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/describeactivity/:name"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "test-activity"; + var cloudformationIdentifier = "arn:aws:states:us-west-2:000000000000:activity:test-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:test-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestStepFunctionsError() throws Exception { + appClient.get("/sfn/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/error"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "nonexistent-activity"; + var cloudformationIdentifier = + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "error.test", + 8080, + "http://error.test:8080", + 400, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 1.0); + } + + protected void doTestStepFunctionsFault() throws Exception { + appClient.get("/sfn/fault").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/fault"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "fault-activity"; + var cloudformationIdentifier = "arn:aws:states:us-west-2:000000000000:activity:fault-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "fault.test", + 8080, + "http://fault.test:8080", + 500, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:fault-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 1.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestSnsGetTopicAttributes() throws Exception { + appClient.get("/sns/gettopicattributes/test-topic").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sns/gettopicattributes/:topicId"; + var type = "AWS::SNS::Topic"; + var identifier = "test-topic"; + var cloudformationIdentifier = "arn:aws:sns:us-west-2:000000000000:test-topic"; + + assertSpanClientAttributes( + traces, + snsSpanName("GetTopicAttributes"), + getSnsRpcServiceName(), + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_TOPIC_ARN, + "arn:aws:sns:us-west-2:000000000000:test-topic"))); + } + + protected void doTestSnsError() throws Exception { + appClient.get("/sns/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sns/error"; + assertSpanClientAttributes( + traces, + snsSpanName("GetTopicAttributes"), + getSnsRpcServiceName(), + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + "error.test", + 8080, + "http://error.test:8080", + 400, + List.of()); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + 5000.0); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + 0.0); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + 1.0); + } + + protected void doTestSnsFault() throws Exception { + appClient.get("/sns/fault").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sns/fault"; + assertSpanClientAttributes( + traces, + snsSpanName("GetTopicAttributes"), + getSnsRpcServiceName(), + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + "fault.test", + 8080, + "http://fault.test:8080", + 500, + List.of()); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + 5000.0); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, + 1.0); + + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSnsServiceName(), + "GetTopicAttributes", + null, + null, + null, 0.0); } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java index fa5a586c5d..5a53b83a1e 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java @@ -76,6 +76,21 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "AWSBedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "AWSSecretsManager"; + } + + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "AWSStepFunctions"; + } + + @Override + protected String getSnsSpanNamePrefix() { + return "SNS"; + } + protected String getS3RpcServiceName() { return "Amazon S3"; } @@ -90,6 +105,21 @@ protected String getSqsRpcServiceName() { return "AmazonSQS"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "AWSSecretsManager"; + } + + @Override + protected String getStepFunctionsRpcServiceName() { + return "AWSStepFunctions"; + } + + @Override + protected String getSnsRpcServiceName() { + return "AmazonSNS"; + } + protected String getKinesisRpcServiceName() { return "AmazonKinesis"; } @@ -260,4 +290,54 @@ void testBedrockAgentRuntimeAgentId() { void testBedrockAgentRuntimeKnowledgeBaseId() { doTestBedrockAgentRuntimeKnowledgeBaseId(); } + + @Test + void testSecretsManagerDescribeSecret() throws Exception { + doTestSecretsManagerDescribeSecret(); + } + + @Test + void testSecretsManagerError() throws Exception { + doTestSecretsManagerError(); + } + + @Test + void testSecretsManagerFault() throws Exception { + doTestSecretsManagerFault(); + } + + @Test + void testStepFunctionsDescribeStateMachine() throws Exception { + doTestStepFunctionsDescribeStateMachine(); + } + + @Test + void testStepFunctionsDescribeActivity() throws Exception { + doTestStepFunctionsDescribeActivity(); + } + + @Test + void testStepFunctionsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testStepFunctionsFault() throws Exception { + doTestStepFunctionsFault(); + } + + @Test + void testSnsGetTopicAttributes() throws Exception { + doTestSnsGetTopicAttributes(); + } + + @Test + void testSnsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testSnsFault() throws Exception { + doTestStepFunctionsFault(); + } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java index 46c6b7e425..c1259ca6cc 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java @@ -75,6 +75,21 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "SecretsManager"; + } + + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "Sfn"; + } + + @Override + protected String getSnsSpanNamePrefix() { + return "Sns"; + } + @Override protected String getS3RpcServiceName() { return "S3"; @@ -114,6 +129,21 @@ protected String getBedrockAgentRuntimeRpcServiceName() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "SecretsManager"; + } + + @Override + protected String getStepFunctionsRpcServiceName() { + return "Sfn"; + } + + @Override + protected String getSnsRpcServiceName() { + return "Sns"; + } + @Test void testS3CreateBucket() throws Exception { doTestS3CreateBucket(); @@ -259,10 +289,58 @@ void testBedrockAgentRuntimeAgentId() { doTestBedrockAgentRuntimeAgentId(); } - // TODO: Enable testBedrockAgentRuntimeKnowledgeBaseId test after KnowledgeBaseId is supported in - // OTEL BedrockAgentRuntime instrumentation @Test void testBedrockAgentRuntimeKnowledgeBaseId() { doTestBedrockAgentRuntimeKnowledgeBaseId(); } + + @Test + void testSecretsManagerDescribeSecret() throws Exception { + doTestSecretsManagerDescribeSecret(); + } + + @Test + void testSecretsManagerError() throws Exception { + doTestSecretsManagerError(); + } + + @Test + void testSecretsManagerFault() throws Exception { + doTestSecretsManagerFault(); + } + + @Test + void testStepFunctionsDescribeStateMachine() throws Exception { + doTestStepFunctionsDescribeStateMachine(); + } + + @Test + void testStepFunctionsDescribeActivity() throws Exception { + doTestStepFunctionsDescribeActivity(); + } + + @Test + void testStepFunctionsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testStepFunctionsFault() throws Exception { + doTestStepFunctionsFault(); + } + + @Test + void testSnsGetTopicAttributes() throws Exception { + doTestSnsGetTopicAttributes(); + } + + @Test + void testSnsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testSnsFault() throws Exception { + doTestStepFunctionsFault(); + } } diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java index 675b69032b..0ff11305c2 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/AppSignalsConstants.java @@ -31,6 +31,8 @@ public class AppSignalsConstants { public static final String AWS_REMOTE_OPERATION = "aws.remote.operation"; public static final String AWS_REMOTE_RESOURCE_TYPE = "aws.remote.resource.type"; public static final String AWS_REMOTE_RESOURCE_IDENTIFIER = "aws.remote.resource.identifier"; + public static final String AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER = + "aws.remote.resource.cfn.primary.identifier"; public static final String AWS_SPAN_KIND = "aws.span.kind"; public static final String AWS_REMOTE_DB_USER = "aws.remote.db.user"; diff --git a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java index f0cac6da46..51077ea6a1 100644 --- a/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java +++ b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java @@ -62,6 +62,7 @@ public class SemanticConventionsConstants { public static final String AWS_DATA_SOURCE_ID = "aws.bedrock.data_source.id"; public static final String AWS_AGENT_ID = "aws.bedrock.agent.id"; public static final String AWS_GUARDRAIL_ID = "aws.bedrock.guardrail.id"; + public static final String AWS_GUARDRAIL_ARN = "aws.bedrock.guardrail.arn"; public static final String GEN_AI_REQUEST_MODEL = "gen_ai.request.model"; public static final String GEN_AI_REQUEST_MAX_TOKENS = "gen_ai.request.max_tokens"; public static final String GEN_AI_REQUEST_TEMPERATURE = "gen_ai.request.temperature"; @@ -69,6 +70,10 @@ public class SemanticConventionsConstants { public static final String GEN_AI_RESPONSE_FINISH_REASONS = "gen_ai.response.finish_reasons"; public static final String GEN_AI_USAGE_INPUT_TOKENS = "gen_ai.usage.input_tokens"; public static final String GEN_AI_USAGE_OUTPUT_TOKENS = "gen_ai.usage.output_tokens"; + public static final String AWS_SECRET_ARN = "aws.secretsmanager.secret.arn"; + public static final String AWS_STATE_MACHINE_ARN = "aws.stepfunctions.state_machine.arn"; + public static final String AWS_ACTIVITY_ARN = "aws.stepfunctions.activity.arn"; + public static final String AWS_TOPIC_ARN = "aws.sns.topic.arn"; // kafka public static final String MESSAGING_CLIENT_ID = "messaging.client_id"; diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v1/build.gradle.kts b/appsignals-tests/images/aws-sdk/aws-sdk-v1/build.gradle.kts index 6ee3f0cae1..77fad5427c 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v1/build.gradle.kts +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v1/build.gradle.kts @@ -33,6 +33,10 @@ dependencies { implementation("com.amazonaws:aws-java-sdk-dynamodb") implementation("com.amazonaws:aws-java-sdk-sqs") implementation("com.amazonaws:aws-java-sdk-kinesis") + implementation("com.amazonaws:aws-java-sdk-secretsmanager") + implementation("com.amazonaws:aws-java-sdk-iam") + implementation("com.amazonaws:aws-java-sdk-stepfunctions") + implementation("com.amazonaws:aws-java-sdk-sns") implementation("com.amazonaws:aws-java-sdk-bedrock") implementation("com.amazonaws:aws-java-sdk-bedrockagent") implementation("com.amazonaws:aws-java-sdk-bedrockruntime") diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java b/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java index ad5f7a73b4..6b39559b0d 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java @@ -44,6 +44,9 @@ import com.amazonaws.services.dynamodbv2.model.KeyType; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; +import com.amazonaws.services.identitymanagement.model.CreateRoleRequest; +import com.amazonaws.services.identitymanagement.model.PutRolePolicyRequest; import com.amazonaws.services.kinesis.AmazonKinesisClient; import com.amazonaws.services.kinesis.model.CreateStreamRequest; import com.amazonaws.services.kinesis.model.PutRecordRequest; @@ -52,10 +55,30 @@ import com.amazonaws.services.s3.model.GetObjectRequest; import com.amazonaws.services.s3.model.PutObjectRequest; import com.amazonaws.services.s3.model.Region; +import com.amazonaws.services.secretsmanager.AWSSecretsManagerClient; +import com.amazonaws.services.secretsmanager.model.CreateSecretRequest; +import com.amazonaws.services.secretsmanager.model.DescribeSecretRequest; +import com.amazonaws.services.secretsmanager.model.ListSecretsRequest; +import com.amazonaws.services.secretsmanager.model.SecretListEntry; +import com.amazonaws.services.sns.AmazonSNSClient; +import com.amazonaws.services.sns.model.CreateTopicRequest; +import com.amazonaws.services.sns.model.GetTopicAttributesRequest; +import com.amazonaws.services.sns.model.ListTopicsRequest; +import com.amazonaws.services.sns.model.Topic; import com.amazonaws.services.sqs.AmazonSQSClient; import com.amazonaws.services.sqs.model.CreateQueueRequest; import com.amazonaws.services.sqs.model.ReceiveMessageRequest; import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.stepfunctions.AWSStepFunctionsClient; +import com.amazonaws.services.stepfunctions.model.ActivityListItem; +import com.amazonaws.services.stepfunctions.model.CreateActivityRequest; +import com.amazonaws.services.stepfunctions.model.CreateStateMachineRequest; +import com.amazonaws.services.stepfunctions.model.DescribeActivityRequest; +import com.amazonaws.services.stepfunctions.model.DescribeStateMachineRequest; +import com.amazonaws.services.stepfunctions.model.ListActivitiesRequest; +import com.amazonaws.services.stepfunctions.model.ListStateMachinesRequest; +import com.amazonaws.services.stepfunctions.model.StateMachineListItem; +import com.amazonaws.services.stepfunctions.model.StateMachineType; import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; import java.io.IOException; @@ -125,6 +148,9 @@ public static void main(String[] args) throws IOException, InterruptedException setupS3(); setupSqs(); setupKinesis(); + setupSecretsManager(); + setupStepFunctions(); + setupSns(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. @@ -518,6 +544,367 @@ private static void setupS3() { }); } + private static void setupSecretsManager() { + var secretsManagerClient = + AWSSecretsManagerClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + var secretName = "test-secret-id"; + String existingSecretArn = null; + try { + var listRequest = new ListSecretsRequest(); + var listResponse = secretsManagerClient.listSecrets(listRequest); + existingSecretArn = + listResponse.getSecretList().stream() + .filter(secret -> secret.getName().contains(secretName)) + .findFirst() + .map(SecretListEntry::getARN) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing secrets", e); + } + + if (existingSecretArn != null) { + logger.debug("Secret already exists, skipping creation"); + } else { + logger.info("Secret not found, creating new one"); + var createSecretRequest = new CreateSecretRequest().withName(secretName); + var createSecretResponse = secretsManagerClient.createSecret(createSecretRequest); + existingSecretArn = createSecretResponse.getARN(); + } + + String finalExistingSecretArn = existingSecretArn; + get( + "/secretsmanager/describesecret/:secretId", + (req, res) -> { + var describeRequest = new DescribeSecretRequest().withSecretId(finalExistingSecretArn); + secretsManagerClient.describeSecret(describeRequest); + return ""; + }); + + get( + "/secretsmanager/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + AWSSecretsManagerClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "http://error.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var describeRequest = + new DescribeSecretRequest() + .withSecretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret-id"); + errorClient.describeSecret(describeRequest); + } catch (Exception e) { + logger.debug("Error describing secret", e); + } + return ""; + }); + + get( + "/secretsmanager/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + AWSSecretsManagerClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "http://fault.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var describeRequest = + new DescribeSecretRequest() + .withSecretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:fault-secret-id"); + faultClient.describeSecret(describeRequest); + } catch (Exception e) { + logger.debug("Error describing secret", e); + } + return ""; + }); + } + + private static void setupStepFunctions() { + var stepFunctionsClient = + AWSStepFunctionsClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + var iamClient = + AmazonIdentityManagementClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + + var sfnName = "test-state-machine"; + String existingStateMachineArn = null; + try { + var listRequest = new ListStateMachinesRequest(); + var listResponse = stepFunctionsClient.listStateMachines(listRequest); + existingStateMachineArn = + listResponse.getStateMachines().stream() + .filter(machine -> machine.getName().equals(sfnName)) + .findFirst() + .map(StateMachineListItem::getStateMachineArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing state machines", e); + } + + if (existingStateMachineArn != null) { + logger.debug("State machine already exists, skipping creation"); + } else { + logger.debug("State machine not found, creating new one"); + String trustPolicy = + "{" + + "\"Version\": \"2012-10-17\"," + + "\"Statement\": [" + + " {" + + " \"Effect\": \"Allow\"," + + " \"Principal\": {" + + " \"Service\": \"states.amazonaws.com\"" + + " }," + + " \"Action\": \"sts:AssumeRole\"" + + " }" + + "]}"; + var roleRequest = + new CreateRoleRequest() + .withRoleName(sfnName + "-role") + .withAssumeRolePolicyDocument(trustPolicy); + var roleArn = iamClient.createRole(roleRequest).getRole().getArn(); + String policyDocument = + "{" + + "\"Version\": \"2012-10-17\"," + + "\"Statement\": [" + + " {" + + " \"Effect\": \"Allow\"," + + " \"Action\": [" + + " \"lambda:InvokeFunction\"" + + " ]," + + " \"Resource\": [" + + " \"*\"" + + " ]" + + " }" + + "]}"; + var policyRequest = + new PutRolePolicyRequest() + .withRoleName(sfnName + "-role") + .withPolicyName(sfnName + "-policy") + .withPolicyDocument(policyDocument); + iamClient.putRolePolicy(policyRequest); + String stateMachineDefinition = + "{" + + " \"Comment\": \"A Hello World example of the Amazon States Language using a Pass state\"," + + " \"StartAt\": \"HelloWorld\"," + + " \"States\": {" + + " \"HelloWorld\": {" + + " \"Type\": \"Pass\"," + + " \"Result\": \"Hello World!\"," + + " \"End\": true" + + " }" + + " }" + + "}"; + var sfnRequest = + new CreateStateMachineRequest() + .withName(sfnName) + .withRoleArn(roleArn) + .withDefinition(stateMachineDefinition) + .withType(StateMachineType.STANDARD); + var createResponse = stepFunctionsClient.createStateMachine(sfnRequest); + existingStateMachineArn = createResponse.getStateMachineArn(); + } + + var activityName = "test-activity"; + String existingActivityArn = null; + try { + var listRequest = new ListActivitiesRequest(); + var listResponse = stepFunctionsClient.listActivities(listRequest); + existingActivityArn = + listResponse.getActivities().stream() + .filter(activity -> activity.getName().equals(activityName)) + .findFirst() + .map(ActivityListItem::getActivityArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing activities", e); + } + + if (existingActivityArn != null) { + logger.debug("Activity already exists, skipping creation"); + } else { + logger.debug("Activity does not exist, creating new one"); + var createRequest = new CreateActivityRequest().withName(activityName); + var createResponse = stepFunctionsClient.createActivity(createRequest); + existingActivityArn = createResponse.getActivityArn(); + } + + String finalExistingStateMachineArn = existingStateMachineArn; + String finalExistingActivityArn = existingActivityArn; + + get( + "/sfn/describestatemachine/:name", + (req, res) -> { + var describeRequest = + new DescribeStateMachineRequest().withStateMachineArn(finalExistingStateMachineArn); + stepFunctionsClient.describeStateMachine(describeRequest); + return ""; + }); + + get( + "/sfn/describeactivity/:name", + (req, res) -> { + var describeRequest = + new DescribeActivityRequest().withActivityArn(finalExistingActivityArn); + stepFunctionsClient.describeActivity(describeRequest); + return ""; + }); + + get( + "/sfn/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + AWSStepFunctionsClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "http://error.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var describeRequest = + new DescribeActivityRequest() + .withActivityArn( + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"); + errorClient.describeActivity(describeRequest); + } catch (Exception e) { + logger.error("Error describing activity", e); + } + return ""; + }); + + get( + "/sfn/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + AWSStepFunctionsClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "http://fault.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var describeRequest = + new DescribeActivityRequest() + .withActivityArn( + "arn:aws:states:us-west-2:000000000000:activity:fault-activity"); + faultClient.describeActivity(describeRequest); + } catch (Exception e) { + logger.error("Error describing activity", e); + } + return ""; + }); + } + + private static void setupSns() { + var snsClient = + AmazonSNSClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + + var topicName = "test-topic"; + String existingTopicArn = null; + + try { + var listTopicsRequest = new ListTopicsRequest(); + var listTopicsResult = snsClient.listTopics(listTopicsRequest); + existingTopicArn = + listTopicsResult.getTopics().stream() + .filter(topic -> topic.getTopicArn().contains(topicName)) + .findFirst() + .map(Topic::getTopicArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing topics", e); + } + + if (existingTopicArn != null) { + logger.debug("Topic already exists, skipping creation"); + } else { + logger.debug("Topic does not exist, creating new one"); + var createTopicRequest = new CreateTopicRequest().withName(topicName); + var createTopicResult = snsClient.createTopic(createTopicRequest); + existingTopicArn = createTopicResult.getTopicArn(); + } + + String finalExistingTopicArn = existingTopicArn; + get( + "/sns/gettopicattributes/:topicId", + (req, res) -> { + var getTopicAttributesRequest = + new GetTopicAttributesRequest().withTopicArn(finalExistingTopicArn); + snsClient.getTopicAttributes(getTopicAttributesRequest); + return ""; + }); + + get( + "/sns/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + AmazonSNSClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "https://error.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var getTopicAttributesRequest = + new GetTopicAttributesRequest() + .withTopicArn("arn:aws:sns:us-west-2:000000000000:nonexistent-topic"); + errorClient.getTopicAttributes(getTopicAttributesRequest); + } catch (Exception e) { + logger.error("Error describing topic", e); + } + return ""; + }); + + get( + "/sns/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + AmazonSNSClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration( + new EndpointConfiguration( + "http://fault.test:8080", Regions.US_WEST_2.getName())) + .build(); + + try { + var getTopicAttributesRequest = + new GetTopicAttributesRequest() + .withTopicArn("arn:aws:sns:us-west-2:000000000000:fault-topic"); + faultClient.getTopicAttributes(getTopicAttributesRequest); + } catch (Exception e) { + logger.error("Error describing topic", e); + } + return ""; + }); + } + private static void setupBedrock() { // Localstack does not support Bedrock related services. // We point all Bedrock related request endpoints to the local app, diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v2/build.gradle.kts b/appsignals-tests/images/aws-sdk/aws-sdk-v2/build.gradle.kts index e50f70772f..0ba17450d6 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v2/build.gradle.kts +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v2/build.gradle.kts @@ -33,6 +33,10 @@ dependencies { implementation("software.amazon.awssdk:dynamodb") implementation("software.amazon.awssdk:sqs") implementation("software.amazon.awssdk:kinesis") + implementation("software.amazon.awssdk:secretsmanager") + implementation("software.amazon.awssdk:iam") + implementation("software.amazon.awssdk:sfn") + implementation("software.amazon.awssdk:sns") implementation("software.amazon.awssdk:bedrock") implementation("software.amazon.awssdk:bedrockagent") implementation("software.amazon.awssdk:bedrockruntime") diff --git a/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java b/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java index 2982135fd4..c96b762dfd 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java @@ -60,6 +60,9 @@ import software.amazon.awssdk.services.dynamodb.model.KeyType; import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput; import software.amazon.awssdk.services.dynamodb.model.PutItemRequest; +import software.amazon.awssdk.services.iam.IamClient; +import software.amazon.awssdk.services.iam.model.CreateRoleRequest; +import software.amazon.awssdk.services.iam.model.PutRolePolicyRequest; import software.amazon.awssdk.services.kinesis.KinesisClient; import software.amazon.awssdk.services.kinesis.model.CreateStreamRequest; import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest; @@ -68,6 +71,26 @@ import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; +import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest; +import software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest; +import software.amazon.awssdk.services.secretsmanager.model.SecretListEntry; +import software.amazon.awssdk.services.sfn.SfnClient; +import software.amazon.awssdk.services.sfn.model.ActivityListItem; +import software.amazon.awssdk.services.sfn.model.CreateActivityRequest; +import software.amazon.awssdk.services.sfn.model.CreateStateMachineRequest; +import software.amazon.awssdk.services.sfn.model.DescribeActivityRequest; +import software.amazon.awssdk.services.sfn.model.DescribeStateMachineRequest; +import software.amazon.awssdk.services.sfn.model.ListActivitiesRequest; +import software.amazon.awssdk.services.sfn.model.ListStateMachinesRequest; +import software.amazon.awssdk.services.sfn.model.StateMachineListItem; +import software.amazon.awssdk.services.sfn.model.StateMachineType; +import software.amazon.awssdk.services.sns.SnsClient; +import software.amazon.awssdk.services.sns.model.CreateTopicRequest; +import software.amazon.awssdk.services.sns.model.GetTopicAttributesRequest; +import software.amazon.awssdk.services.sns.model.ListTopicsRequest; +import software.amazon.awssdk.services.sns.model.Topic; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.CreateQueueRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; @@ -121,7 +144,10 @@ public static void main(String[] args) throws IOException, InterruptedException setupS3(); setupSqs(); setupKinesis(); + setupSecretsManager(); + setupSfn(); setupBedrock(); + setupSns(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); logger.info("All routes initialized"); @@ -532,6 +558,365 @@ private static void setupS3() { }); } + private static void setupSecretsManager() { + var secretsManagerClient = + SecretsManagerClient.builder() + .endpointOverride(endpoint) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + var secretName = "test-secret-id"; + String existingSecretArn = null; + try { + var listRequest = ListSecretsRequest.builder().build(); + var listResponse = secretsManagerClient.listSecrets(listRequest); + existingSecretArn = + listResponse.secretList().stream() + .filter(secret -> secret.name().contains(secretName)) + .findFirst() + .map(SecretListEntry::arn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing secrets", e); + } + + if (existingSecretArn != null) { + logger.debug("Secret already exists, skipping creation"); + } else { + logger.debug("Secret not found, creating a new one"); + var createSecretRequest = CreateSecretRequest.builder().name(secretName).build(); + var createSecretResponse = secretsManagerClient.createSecret(createSecretRequest); + existingSecretArn = createSecretResponse.arn(); + } + + String finalExistingSecretArn = existingSecretArn; + get( + "/secretsmanager/describesecret/:secretId", + (req, res) -> { + var describeRequest = + DescribeSecretRequest.builder().secretId(finalExistingSecretArn).build(); + secretsManagerClient.describeSecret(describeRequest); + return ""; + }); + + get( + "/secretsmanager/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + SecretsManagerClient.builder() + .endpointOverride(URI.create("http://error.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var describeRequest = + DescribeSecretRequest.builder() + .secretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret-id") + .build(); + errorClient.describeSecret(describeRequest); + } catch (Exception e) { + logger.error("Error describing secret", e); + } + return ""; + }); + + get( + "/secretsmanager/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + SecretsManagerClient.builder() + .endpointOverride(URI.create("http://fault.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var describeRequest = + DescribeSecretRequest.builder() + .secretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:fault-secret-id") + .build(); + faultClient.describeSecret(describeRequest); + } catch (Exception e) { + logger.error("Error describing secret", e); + } + return ""; + }); + } + + private static void setupSfn() { + var sfnClient = + SfnClient.builder() + .endpointOverride(endpoint) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + var iamClient = + IamClient.builder() + .endpointOverride(endpoint) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + var sfnName = "test-state-machine"; + String existingStateMachineArn = null; + try { + var listRequest = ListStateMachinesRequest.builder().build(); + var listResponse = sfnClient.listStateMachines(listRequest); + existingStateMachineArn = + listResponse.stateMachines().stream() + .filter(machine -> machine.name().equals(sfnName)) + .findFirst() + .map(StateMachineListItem::stateMachineArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing state machines", e); + } + + if (existingStateMachineArn != null) { + logger.debug("State machine already exists, skipping creation"); + } else { + logger.debug("State machine not found, creating a new one"); + String trustPolicy = + "{" + + "\"Version\": \"2012-10-17\"," + + "\"Statement\": [" + + " {" + + " \"Effect\": \"Allow\"," + + " \"Principal\": {" + + " \"Service\": \"states.amazonaws.com\"" + + " }," + + " \"Action\": \"sts:AssumeRole\"" + + " }" + + "]}"; + var roleRequest = + CreateRoleRequest.builder() + .roleName(sfnName + "-role") + .assumeRolePolicyDocument(trustPolicy) + .build(); + var roleArn = iamClient.createRole(roleRequest).role().arn(); + String policyDocument = + "{" + + "\"Version\": \"2012-10-17\"," + + "\"Statement\": [" + + " {" + + " \"Effect\": \"Allow\"," + + " \"Action\": [" + + " \"lambda:InvokeFunction\"" + + " ]," + + " \"Resource\": [" + + " \"*\"" + + " ]" + + " }" + + "]}"; + var policyRequest = + PutRolePolicyRequest.builder() + .roleName(sfnName + "-role") + .policyName(sfnName + "-policy") + .policyDocument(policyDocument) + .build(); + iamClient.putRolePolicy(policyRequest); + String stateMachineDefinition = + "{" + + " \"Comment\": \"A Hello World example of the Amazon States Language using a Pass state\"," + + " \"StartAt\": \"HelloWorld\"," + + " \"States\": {" + + " \"HelloWorld\": {" + + " \"Type\": \"Pass\"," + + " \"Result\": \"Hello World!\"," + + " \"End\": true" + + " }" + + " }" + + "}"; + var sfnRequest = + CreateStateMachineRequest.builder() + .name(sfnName) + .roleArn(roleArn) + .definition(stateMachineDefinition) + .type(StateMachineType.STANDARD) + .build(); + existingStateMachineArn = sfnClient.createStateMachine(sfnRequest).stateMachineArn(); + } + + var activityName = "test-activity"; + String existingActivityArn = null; + + try { + var listRequest = ListActivitiesRequest.builder().build(); + var listResponse = sfnClient.listActivities(listRequest); + existingActivityArn = + listResponse.activities().stream() + .filter(activity -> activity.name().equals(activityName)) + .findFirst() + .map(ActivityListItem::activityArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing activities", e); + } + + if (existingActivityArn != null) { + logger.debug("Activities already exists, skipping creation"); + } else { + logger.debug("Activities not found, creating a new one"); + var createRequest = CreateActivityRequest.builder().name(activityName).build(); + existingActivityArn = sfnClient.createActivity(createRequest).activityArn(); + } + + String finalExistingStateMachineArn = existingStateMachineArn; + String finalExistingActivityArn = existingActivityArn; + + get( + "/sfn/describestatemachine/:name", + (req, res) -> { + var describeRequest = + DescribeStateMachineRequest.builder() + .stateMachineArn(finalExistingStateMachineArn) + .build(); + sfnClient.describeStateMachine(describeRequest); + return ""; + }); + + get( + "/sfn/describeactivity/:name", + (req, res) -> { + var describeRequest = + DescribeActivityRequest.builder().activityArn(finalExistingActivityArn).build(); + sfnClient.describeActivity(describeRequest); + return ""; + }); + + get( + "/sfn/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + SfnClient.builder() + .endpointOverride(URI.create("http://error.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var describeRequest = + DescribeActivityRequest.builder() + .activityArn( + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity") + .build(); + errorClient.describeActivity(describeRequest); + } catch (Exception e) { + logger.error("Error describing activity", e); + } + return ""; + }); + + get( + "/sfn/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + SfnClient.builder() + .endpointOverride(URI.create("http://fault.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var describeRequest = + DescribeActivityRequest.builder() + .activityArn("arn:aws:states:us-west-2:000000000000:activity:fault-activity") + .build(); + faultClient.describeActivity(describeRequest); + } catch (Exception e) { + logger.error("Error describing activity", e); + } + return ""; + }); + } + + private static void setupSns() { + var snsClient = + SnsClient.builder() + .endpointOverride(endpoint) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + var topicName = "test-topic"; + String existingTopicArn = null; + + try { + var listRequest = ListTopicsRequest.builder().build(); + var listResponse = snsClient.listTopics(listRequest); + existingTopicArn = + listResponse.topics().stream() + .filter(topic -> topic.topicArn().contains(topicName)) + .findFirst() + .map(Topic::topicArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing topics", e); + } + + if (existingTopicArn != null) { + logger.debug("Topics already exists, skipping creation"); + } else { + logger.debug("Topics not found, creating a new one"); + var createTopicRequest = CreateTopicRequest.builder().name(topicName).build(); + var createTopicResponse = snsClient.createTopic(createTopicRequest); + existingTopicArn = createTopicResponse.topicArn(); + } + + String finalExistingTopicArn = existingTopicArn; + get( + "/sns/gettopicattributes/:topicId", + (req, res) -> { + var getTopicAttributesRequest = + GetTopicAttributesRequest.builder().topicArn(finalExistingTopicArn).build(); + snsClient.getTopicAttributes(getTopicAttributesRequest); + return ""; + }); + + get( + "/sns/error", + (req, res) -> { + setMainStatus(400); + var errorClient = + SnsClient.builder() + .endpointOverride(URI.create("http://error.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var getTopicAttributesRequest = + GetTopicAttributesRequest.builder() + .topicArn("arn:aws:sns:us-west-2:000000000000:nonexistent-topic") + .build(); + errorClient.getTopicAttributes(getTopicAttributesRequest); + } catch (Exception e) { + logger.error("Error describing topic", e); + } + return ""; + }); + + get( + "/sns/fault", + (req, res) -> { + setMainStatus(500); + var faultClient = + SnsClient.builder() + .endpointOverride(URI.create("http://fault.test:8080")) + .credentialsProvider(CREDENTIALS_PROVIDER) + .build(); + + try { + var getTopicAttributesRequest = + GetTopicAttributesRequest.builder() + .topicArn("arn:aws:sns:us-west-2:000000000000:fault-topic") + .build(); + faultClient.getTopicAttributes(getTopicAttributesRequest); + } catch (Exception e) { + logger.error("Error describing topic", e); + } + return ""; + }); + } + private static void setupBedrock() { // Localstack does not support Bedrock related services. // We point all Bedrock related request endpoints to the local app,