From 17a062aade818f3a50c45d7966cceeb4f00c30e4 Mon Sep 17 00:00:00 2001 From: yiyuanh Date: Tue, 22 Oct 2024 14:25:03 -0700 Subject: [PATCH 1/4] set up cfn primary id in contract tests --- .../test/awssdk/base/AwsSdkBaseTest.java | 167 +++++++++++++++++- .../test/utils/AppSignalsConstants.java | 2 + .../utils/SemanticConventionsConstants.java | 1 + 3 files changed, 169 insertions(+), 1 deletion(-) 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..fd3f8b9a41 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 @@ -258,6 +258,7 @@ private void assertSpanClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -276,6 +277,7 @@ private void assertSpanClientAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -293,6 +295,7 @@ private void assertSpanProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -310,6 +313,7 @@ private void assertSpanProducerAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -358,6 +362,7 @@ private void assertSpanAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -381,6 +386,7 @@ private void assertSpanAttributes( method, type, identifier, + cloudformationIdentifier, awsSpanKind); for (var assertion : extraAssertions) { assertThat(spanAttributes).satisfiesOnlyOnce(assertion); @@ -396,6 +402,7 @@ private void assertAwsAttributes( String operation, String type, String identifier, + String clouformationIdentifier, String spanKind) { var assertions = @@ -411,6 +418,9 @@ private void assertAwsAttributes( 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 +445,7 @@ protected void assertMetricClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -446,6 +457,7 @@ protected void assertMetricClientAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -458,6 +470,7 @@ protected void assertMetricProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -469,6 +482,7 @@ protected void assertMetricProducerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -481,6 +495,7 @@ protected void assertMetricConsumerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -492,6 +507,7 @@ protected void assertMetricConsumerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -505,6 +521,7 @@ protected void assertMetricAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertThat(resourceScopeMetrics) .anySatisfy( @@ -524,6 +541,7 @@ protected void assertMetricAttributes( method, type, identifier, + cloudformationIdentifier, spanKind); if (expectedSum != null) { double actualSum = dataPoint.getSum(); @@ -554,6 +572,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 +584,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, "create-bucket.s3.localstack", 4566, "http://create-bucket.s3.localstack:4566", @@ -579,6 +599,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -589,6 +610,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -599,6 +621,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); } @@ -617,6 +640,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 +652,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, "put-object.s3.localstack", 4566, "http://put-object.s3.localstack:4566", @@ -642,6 +667,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -652,6 +678,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -662,6 +689,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -679,6 +707,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 +719,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, "get-object.s3.localstack", 4566, "http://get-object.s3.localstack:4566", @@ -704,6 +734,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -714,6 +745,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -724,6 +756,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -741,6 +774,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 +786,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, "error-bucket.s3.test", 8080, "http://error-bucket.s3.test:8080", @@ -766,6 +801,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -776,6 +812,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -786,6 +823,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); } @@ -803,6 +841,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 +853,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, "fault-bucket.s3.test", 8080, "http://fault-bucket.s3.test:8080", @@ -828,6 +868,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -838,6 +879,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -848,6 +890,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -873,6 +916,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 +928,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -898,6 +943,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -908,6 +954,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -918,6 +965,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); } @@ -935,6 +983,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 +995,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -960,6 +1010,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -970,6 +1021,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -980,6 +1032,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -997,6 +1050,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 +1062,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1022,6 +1077,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1032,6 +1088,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1042,6 +1099,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1065,6 +1123,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 +1135,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1090,6 +1150,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -1100,6 +1161,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1110,6 +1172,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1127,6 +1190,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 +1202,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1152,6 +1217,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1162,6 +1228,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1172,6 +1239,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1190,6 +1258,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 +1270,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1216,6 +1286,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1226,6 +1297,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1236,6 +1308,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1258,6 +1331,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 +1356,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricConsumerAttributes( metrics, @@ -1292,6 +1367,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1310,6 +1386,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 +1398,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1336,6 +1414,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1346,6 +1425,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1356,6 +1436,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1374,6 +1455,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 +1467,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1400,6 +1483,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1410,6 +1494,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); assertMetricProducerAttributes( metrics, @@ -1420,6 +1505,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1437,6 +1523,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 +1535,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1462,6 +1550,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1472,6 +1561,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1482,6 +1572,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1499,6 +1590,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 +1602,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1525,6 +1618,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1535,6 +1629,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1545,6 +1640,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1562,6 +1658,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 +1670,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1587,6 +1685,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1597,6 +1696,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1607,6 +1707,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1625,6 +1726,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 +1737,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1651,6 +1754,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1661,6 +1765,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1671,6 +1776,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1688,6 +1794,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 +1805,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1712,6 +1820,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1722,6 +1831,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1732,6 +1842,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1749,6 +1860,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 +1871,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1775,6 +1888,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1785,6 +1899,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1795,6 +1910,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1812,6 +1928,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 +1939,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1843,6 +1961,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1853,6 +1972,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1863,6 +1983,7 @@ protected void doTestBedrockRuntimeAi21Jamba() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1880,6 +2001,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 +2012,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1914,6 +2037,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1924,6 +2048,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1934,6 +2059,7 @@ protected void doTestBedrockRuntimeAmazonTitan() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1952,6 +2078,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 +2090,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1987,6 +2115,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1997,6 +2126,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2007,6 +2137,7 @@ protected void doTestBedrockRuntimeAnthropicClaude() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2025,6 +2156,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 +2168,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2059,6 +2192,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2069,6 +2203,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2079,6 +2214,7 @@ protected void doTestBedrockRuntimeCohereCommandR() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2097,6 +2233,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 +2245,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2130,6 +2268,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2140,6 +2279,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2150,6 +2290,7 @@ protected void doTestBedrockRuntimeMetaLlama() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2168,6 +2309,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 +2321,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2202,6 +2345,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2212,6 +2356,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2222,6 +2367,7 @@ protected void doTestBedrockRuntimeMistral() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2239,6 +2385,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 +2397,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 +2417,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2275,6 +2428,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2285,6 +2439,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2302,6 +2457,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 +2468,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2326,6 +2483,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2336,6 +2494,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2346,6 +2505,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2364,6 +2524,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 +2535,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2390,6 +2552,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2400,6 +2563,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2410,6 +2574,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 0.0); } } 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..8a330c322d 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"; From dc311098834274536654b6a347b58e3cc16d06f4 Mon Sep 17 00:00:00 2001 From: yiyuanh Date: Mon, 25 Nov 2024 09:55:52 -0800 Subject: [PATCH 2/4] set up secrets manager contract tests --- .../test/awssdk/base/AwsSdkBaseTest.java | 225 +++++++++++++++++- .../test/awssdk/v1/AwsSdkV1Test.java | 25 ++ .../test/awssdk/v2/AwsSdkV2Test.java | 27 ++- .../utils/SemanticConventionsConstants.java | 1 + .../aws-sdk/aws-sdk-v1/build.gradle.kts | 1 + .../main/java/com/amazon/sampleapp/App.java | 84 +++++++ .../aws-sdk/aws-sdk-v2/build.gradle.kts | 1 + .../main/java/com/amazon/sampleapp/App.java | 91 +++++++ 8 files changed, 449 insertions(+), 6 deletions(-) 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 fd3f8b9a41..f6c80623b9 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 @@ -23,6 +23,7 @@ import java.util.List; import java.util.Map; import java.util.Set; + import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -42,7 +43,8 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { LocalStackContainer.Service.S3, LocalStackContainer.Service.DYNAMODB, LocalStackContainer.Service.SQS, - LocalStackContainer.Service.KINESIS) + LocalStackContainer.Service.KINESIS, + LocalStackContainer.Service.SECRETSMANAGER) .withEnv("DEFAULT_REGION", "us-west-2") .withNetwork(network) .withEnv("LOCALSTACK_HOST", "127.0.0.1") @@ -102,6 +104,8 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeSpanNamePrefix(); + protected abstract String getSecretsManagerSpanNamePrefix(); + protected abstract String getS3RpcServiceName(); protected abstract String getDynamoDbRpcServiceName(); @@ -118,6 +122,8 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeRpcServiceName(); + protected abstract String getSecretsManagerRpcServiceName(); + private String getS3ServiceName() { return "AWS::S3"; } @@ -150,6 +156,10 @@ private String getBedrockRuntimeServiceName() { return "AWS::BedrockRuntime"; } + private String getSecretsManagerServiceName() { + return "AWS::SecretsManager"; + } + private String s3SpanName(String operation) { return String.format("%s.%s", getS3SpanNamePrefix(), operation); } @@ -182,10 +192,23 @@ private String bedrockAgentRuntimeSpanName(String operation) { return String.format("%s.%s", getBedrockAgentRuntimeSpanNamePrefix(), operation); } + private String secretsManagerSpanName(String operation) { + return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), 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); + } }; } @@ -413,7 +436,7 @@ 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( @@ -2577,4 +2600,198 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { 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); + } } 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..7c70f43d28 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,11 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "AWSBedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "AWSSecretsManager"; + } + protected String getS3RpcServiceName() { return "Amazon S3"; } @@ -90,6 +95,11 @@ protected String getSqsRpcServiceName() { return "AmazonSQS"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "AWSSecretsManager"; + } + protected String getKinesisRpcServiceName() { return "AmazonKinesis"; } @@ -260,4 +270,19 @@ void testBedrockAgentRuntimeAgentId() { void testBedrockAgentRuntimeKnowledgeBaseId() { doTestBedrockAgentRuntimeKnowledgeBaseId(); } + + @Test + void testSecretsManagerDescribeSecret() throws Exception { + doTestSecretsManagerDescribeSecret(); + } + + @Test + void testSecretsManagerError() throws Exception { + doTestSecretsManagerError(); + } + + @Test + void testSecretsManagerFault() throws Exception { + doTestSecretsManagerFault(); + } } 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..7bde91bc74 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,11 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "SecretsManager"; + } + @Override protected String getS3RpcServiceName() { return "S3"; @@ -114,6 +119,11 @@ protected String getBedrockAgentRuntimeRpcServiceName() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "SecretsManager"; + } + @Test void testS3CreateBucket() throws Exception { doTestS3CreateBucket(); @@ -259,10 +269,23 @@ 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(); + } } 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 8a330c322d..b5a49c1de6 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 @@ -70,6 +70,7 @@ 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"; // 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..b01879e9d5 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,7 @@ 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-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..f6f626cc8b 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 @@ -52,6 +52,11 @@ 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.sqs.AmazonSQSClient; import com.amazonaws.services.sqs.model.CreateQueueRequest; import com.amazonaws.services.sqs.model.ReceiveMessageRequest; @@ -125,6 +130,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupS3(); setupSqs(); setupKinesis(); + setupSecretsManager(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. @@ -518,6 +524,84 @@ 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 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..e4aa0fb9eb 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,7 @@ 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: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..95d0442730 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 @@ -68,6 +68,11 @@ 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.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.CreateQueueRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; @@ -121,6 +126,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupS3(); setupSqs(); setupKinesis(); + setupSecretsManager(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); @@ -532,6 +538,91 @@ 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 setupBedrock() { // Localstack does not support Bedrock related services. // We point all Bedrock related request endpoints to the local app, From 3ede02e290968a2796e2ab3e6ab533e424a22640 Mon Sep 17 00:00:00 2001 From: yiyuanh Date: Mon, 25 Nov 2024 14:39:23 -0800 Subject: [PATCH 3/4] set up step functions contract tests --- .../test/awssdk/base/AwsSdkBaseTest.java | 295 +++++++++++++++++- .../test/awssdk/v1/AwsSdkV1Test.java | 30 ++ .../test/awssdk/v2/AwsSdkV2Test.java | 30 ++ .../utils/SemanticConventionsConstants.java | 2 + .../aws-sdk/aws-sdk-v1/build.gradle.kts | 2 + .../main/java/com/amazon/sampleapp/App.java | 184 +++++++++++ .../aws-sdk/aws-sdk-v2/build.gradle.kts | 2 + .../main/java/com/amazon/sampleapp/App.java | 191 ++++++++++++ 8 files changed, 735 insertions(+), 1 deletion(-) 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 f6c80623b9..9e04736f5e 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 @@ -44,7 +44,9 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { LocalStackContainer.Service.DYNAMODB, LocalStackContainer.Service.SQS, LocalStackContainer.Service.KINESIS, - LocalStackContainer.Service.SECRETSMANAGER) + LocalStackContainer.Service.SECRETSMANAGER, + LocalStackContainer.Service.IAM, + LocalStackContainer.Service.STEPFUNCTIONS) .withEnv("DEFAULT_REGION", "us-west-2") .withNetwork(network) .withEnv("LOCALSTACK_HOST", "127.0.0.1") @@ -106,6 +108,8 @@ protected String getApplicationWaitPattern() { protected abstract String getSecretsManagerSpanNamePrefix(); + protected abstract String getStepFunctionsSpanNamePrefix(); + protected abstract String getS3RpcServiceName(); protected abstract String getDynamoDbRpcServiceName(); @@ -124,6 +128,8 @@ protected String getApplicationWaitPattern() { protected abstract String getSecretsManagerRpcServiceName(); + protected abstract String getStepFunctionsRpcServiceName(); + private String getS3ServiceName() { return "AWS::S3"; } @@ -160,6 +166,8 @@ private String getSecretsManagerServiceName() { return "AWS::SecretsManager"; } + private String getStepFunctionsServiceName() { return "AWS::StepFunctions"; } + private String s3SpanName(String operation) { return String.format("%s.%s", getS3SpanNamePrefix(), operation); } @@ -196,6 +204,10 @@ private String secretsManagerSpanName(String operation) { return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), operation); } + private String stepFunctionsSpanName(String operation) { + return String.format("%s.%s", getStepFunctionsSpanNamePrefix(), operation); + } + protected ThrowingConsumer assertAttribute(String key, String value) { return (attribute) -> { var actualKey = attribute.getKey(); @@ -399,6 +411,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( @@ -2794,4 +2807,284 @@ protected void doTestSecretsManagerFault() throws Exception { 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); + } } 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 7c70f43d28..52f87456f2 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 @@ -81,6 +81,11 @@ protected String getSecretsManagerSpanNamePrefix() { return "AWSSecretsManager"; } + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "AWSStepFunctions"; + } + protected String getS3RpcServiceName() { return "Amazon S3"; } @@ -100,6 +105,11 @@ protected String getSecretsManagerRpcServiceName() { return "AWSSecretsManager"; } + @Override + protected String getStepFunctionsRpcServiceName() { + return "AWSStepFunctions"; + } + protected String getKinesisRpcServiceName() { return "AmazonKinesis"; } @@ -285,4 +295,24 @@ void testSecretsManagerError() throws Exception { 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(); + } } 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 7bde91bc74..bd4ab25f92 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 @@ -80,6 +80,11 @@ protected String getSecretsManagerSpanNamePrefix() { return "SecretsManager"; } + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "Sfn"; + } + @Override protected String getS3RpcServiceName() { return "S3"; @@ -124,6 +129,11 @@ protected String getSecretsManagerRpcServiceName() { return "SecretsManager"; } + @Override + protected String getStepFunctionsRpcServiceName() { + return "Sfn"; + } + @Test void testS3CreateBucket() throws Exception { doTestS3CreateBucket(); @@ -288,4 +298,24 @@ void testSecretsManagerError() throws Exception { 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(); + } } 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 b5a49c1de6..8764e2b2a0 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 @@ -71,6 +71,8 @@ public class SemanticConventionsConstants { 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"; // 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 b01879e9d5..bf4cdf27da 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 @@ -34,6 +34,8 @@ dependencies { 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-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 f6f626cc8b..38a98fb28f 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,8 @@ 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.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; @@ -57,6 +59,9 @@ 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.stepfunctions.AWSStepFunctionsClient; +import com.amazonaws.services.stepfunctions.model.*; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; import com.amazonaws.services.sqs.AmazonSQSClient; import com.amazonaws.services.sqs.model.CreateQueueRequest; import com.amazonaws.services.sqs.model.ReceiveMessageRequest; @@ -131,6 +136,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupSqs(); setupKinesis(); setupSecretsManager(); + setupStepFunctions(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. @@ -602,6 +608,184 @@ private static void setupSecretsManager() { }); } + 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 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 e4aa0fb9eb..a286d07ae3 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 @@ -34,6 +34,8 @@ dependencies { 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: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 95d0442730..a196e977ad 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; @@ -73,6 +76,8 @@ 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.*; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.CreateQueueRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; @@ -127,6 +132,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupSqs(); setupKinesis(); setupSecretsManager(); + setupSfn(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); @@ -623,6 +629,191 @@ private static void setupSecretsManager() { }); } + 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 setupBedrock() { // Localstack does not support Bedrock related services. // We point all Bedrock related request endpoints to the local app, From a35013301c1af00a34d8a560de1730dddf2c0287 Mon Sep 17 00:00:00 2001 From: yiyuanh Date: Mon, 25 Nov 2024 21:14:00 -0800 Subject: [PATCH 4/4] set up sns contract tests --- .../test/awssdk/base/AwsSdkBaseTest.java | 189 +++++++++++++++++- .../test/awssdk/v1/AwsSdkV1Test.java | 25 +++ .../test/awssdk/v2/AwsSdkV2Test.java | 25 +++ .../utils/SemanticConventionsConstants.java | 1 + .../aws-sdk/aws-sdk-v1/build.gradle.kts | 1 + .../main/java/com/amazon/sampleapp/App.java | 171 +++++++++++++--- .../aws-sdk/aws-sdk-v2/build.gradle.kts | 1 + .../main/java/com/amazon/sampleapp/App.java | 115 ++++++++++- 8 files changed, 493 insertions(+), 35 deletions(-) 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 9e04736f5e..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 @@ -23,7 +23,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -46,7 +45,8 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { LocalStackContainer.Service.KINESIS, LocalStackContainer.Service.SECRETSMANAGER, LocalStackContainer.Service.IAM, - LocalStackContainer.Service.STEPFUNCTIONS) + LocalStackContainer.Service.STEPFUNCTIONS, + LocalStackContainer.Service.SNS) .withEnv("DEFAULT_REGION", "us-west-2") .withNetwork(network) .withEnv("LOCALSTACK_HOST", "127.0.0.1") @@ -110,6 +110,8 @@ protected String getApplicationWaitPattern() { protected abstract String getStepFunctionsSpanNamePrefix(); + protected abstract String getSnsSpanNamePrefix(); + protected abstract String getS3RpcServiceName(); protected abstract String getDynamoDbRpcServiceName(); @@ -128,6 +130,8 @@ protected String getApplicationWaitPattern() { protected abstract String getSecretsManagerRpcServiceName(); + protected abstract String getSnsRpcServiceName(); + protected abstract String getStepFunctionsRpcServiceName(); private String getS3ServiceName() { @@ -166,7 +170,13 @@ private String getSecretsManagerServiceName() { return "AWS::SecretsManager"; } - private String getStepFunctionsServiceName() { return "AWS::StepFunctions"; } + private String getStepFunctionsServiceName() { + return "AWS::StepFunctions"; + } + + protected String getSnsServiceName() { + return "AWS::SNS"; + } private String s3SpanName(String operation) { return String.format("%s.%s", getS3SpanNamePrefix(), operation); @@ -208,6 +218,10 @@ 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) -> { var actualKey = attribute.getKey(); @@ -3087,4 +3101,173 @@ protected void doTestStepFunctionsFault() throws Exception { 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 52f87456f2..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 @@ -86,6 +86,11 @@ protected String getStepFunctionsSpanNamePrefix() { return "AWSStepFunctions"; } + @Override + protected String getSnsSpanNamePrefix() { + return "SNS"; + } + protected String getS3RpcServiceName() { return "Amazon S3"; } @@ -110,6 +115,11 @@ protected String getStepFunctionsRpcServiceName() { return "AWSStepFunctions"; } + @Override + protected String getSnsRpcServiceName() { + return "AmazonSNS"; + } + protected String getKinesisRpcServiceName() { return "AmazonKinesis"; } @@ -315,4 +325,19 @@ void testStepFunctionsError() throws Exception { 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 bd4ab25f92..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 @@ -85,6 +85,11 @@ protected String getStepFunctionsSpanNamePrefix() { return "Sfn"; } + @Override + protected String getSnsSpanNamePrefix() { + return "Sns"; + } + @Override protected String getS3RpcServiceName() { return "S3"; @@ -134,6 +139,11 @@ protected String getStepFunctionsRpcServiceName() { return "Sfn"; } + @Override + protected String getSnsRpcServiceName() { + return "Sns"; + } + @Test void testS3CreateBucket() throws Exception { doTestS3CreateBucket(); @@ -318,4 +328,19 @@ void testStepFunctionsError() throws Exception { 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/SemanticConventionsConstants.java b/appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java index 8764e2b2a0..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 @@ -73,6 +73,7 @@ public class SemanticConventionsConstants { 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 bf4cdf27da..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 @@ -36,6 +36,7 @@ dependencies { 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 38a98fb28f..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,7 @@ 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; @@ -59,13 +60,25 @@ 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.stepfunctions.AWSStepFunctionsClient; -import com.amazonaws.services.stepfunctions.model.*; -import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; +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; @@ -137,6 +150,7 @@ public static void main(String[] args) throws IOException, InterruptedException setupKinesis(); setupSecretsManager(); setupStepFunctions(); + setupSns(); setupBedrock(); // Add this log line so that we only start testing after all routes are configured. @@ -570,22 +584,27 @@ private static void setupSecretsManager() { }); get( - "/secretsmanager/error", + "/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(); + 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 ""; + 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( @@ -595,16 +614,21 @@ private static void setupSecretsManager() { var faultClient = AWSSecretsManagerClient.builder() .withCredentials(CREDENTIALS_PROVIDER) - .withEndpointConfiguration(new EndpointConfiguration("http://fault.test:8080", Regions.US_WEST_2.getName())) + .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"); + 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""; + return ""; }); } @@ -728,7 +752,8 @@ private static void setupStepFunctions() { get( "/sfn/describestatemachine/:name", (req, res) -> { - var describeRequest = new DescribeStateMachineRequest().withStateMachineArn(finalExistingStateMachineArn); + var describeRequest = + new DescribeStateMachineRequest().withStateMachineArn(finalExistingStateMachineArn); stepFunctionsClient.describeStateMachine(describeRequest); return ""; }); @@ -736,7 +761,8 @@ private static void setupStepFunctions() { get( "/sfn/describeactivity/:name", (req, res) -> { - var describeRequest = new DescribeActivityRequest().withActivityArn(finalExistingActivityArn); + var describeRequest = + new DescribeActivityRequest().withActivityArn(finalExistingActivityArn); stepFunctionsClient.describeActivity(describeRequest); return ""; }); @@ -749,13 +775,15 @@ private static void setupStepFunctions() { AWSStepFunctionsClient.builder() .withCredentials(CREDENTIALS_PROVIDER) .withEndpointConfiguration( - new EndpointConfiguration("http://error.test:8080", Regions.US_WEST_2.getName())) + 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"); + .withActivityArn( + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"); errorClient.describeActivity(describeRequest); } catch (Exception e) { logger.error("Error describing activity", e); @@ -771,13 +799,15 @@ private static void setupStepFunctions() { AWSStepFunctionsClient.builder() .withCredentials(CREDENTIALS_PROVIDER) .withEndpointConfiguration( - new EndpointConfiguration("http://fault.test:8080", Regions.US_WEST_2.getName())) + 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"); + .withActivityArn( + "arn:aws:states:us-west-2:000000000000:activity:fault-activity"); faultClient.describeActivity(describeRequest); } catch (Exception e) { logger.error("Error describing activity", e); @@ -786,6 +816,95 @@ private static void setupStepFunctions() { }); } + 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 a286d07ae3..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 @@ -36,6 +36,7 @@ dependencies { 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 a196e977ad..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 @@ -77,7 +77,20 @@ 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.*; +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; @@ -134,6 +147,7 @@ public static void main(String[] args) throws IOException, InterruptedException 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"); @@ -597,7 +611,8 @@ private static void setupSecretsManager() { try { var describeRequest = DescribeSecretRequest.builder() - .secretId("arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret-id") + .secretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:nonexistent-secret-id") .build(); errorClient.describeSecret(describeRequest); } catch (Exception e) { @@ -619,7 +634,8 @@ private static void setupSecretsManager() { try { var describeRequest = DescribeSecretRequest.builder() - .secretId("arn:aws:secretsmanager:us-west-2:000000000000:secret:fault-secret-id") + .secretId( + "arn:aws:secretsmanager:us-west-2:000000000000:secret:fault-secret-id") .build(); faultClient.describeSecret(describeRequest); } catch (Exception e) { @@ -718,8 +734,7 @@ private static void setupSfn() { .definition(stateMachineDefinition) .type(StateMachineType.STANDARD) .build(); - existingStateMachineArn = - sfnClient.createStateMachine(sfnRequest).stateMachineArn(); + existingStateMachineArn = sfnClient.createStateMachine(sfnRequest).stateMachineArn(); } var activityName = "test-activity"; @@ -782,7 +797,8 @@ private static void setupSfn() { try { var describeRequest = DescribeActivityRequest.builder() - .activityArn("arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity") + .activityArn( + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity") .build(); errorClient.describeActivity(describeRequest); } catch (Exception e) { @@ -814,6 +830,93 @@ private static void setupSfn() { }); } + 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,