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 99a1732834..5c7f48cc46 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,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.regex.Pattern; +import java.util.regex.PatternSyntaxException; import org.assertj.core.api.ThrowingConsumer; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -42,7 +44,10 @@ public abstract class AwsSdkBaseTest extends ContractTestBase { LocalStackContainer.Service.S3, LocalStackContainer.Service.DYNAMODB, LocalStackContainer.Service.SQS, - LocalStackContainer.Service.KINESIS) + LocalStackContainer.Service.KINESIS, + LocalStackContainer.Service.SECRETSMANAGER, + LocalStackContainer.Service.IAM, + LocalStackContainer.Service.STEPFUNCTIONS) .withEnv("DEFAULT_REGION", "us-west-2") .withNetwork(network) .withEnv("LOCALSTACK_HOST", "127.0.0.1") @@ -102,6 +107,10 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeSpanNamePrefix(); + protected abstract String getSecretsManagerSpanNamePrefix(); + + protected abstract String getStepFunctionsSpanNamePrefix(); + protected abstract String getS3RpcServiceName(); protected abstract String getDynamoDbRpcServiceName(); @@ -118,6 +127,10 @@ protected String getApplicationWaitPattern() { protected abstract String getBedrockAgentRuntimeRpcServiceName(); + protected abstract String getSecretsManagerRpcServiceName(); + + protected abstract String getStepFunctionsRpcServiceName(); + private String getS3ServiceName() { return "AWS::S3"; } @@ -150,6 +163,14 @@ private String getBedrockRuntimeServiceName() { return "AWS::BedrockRuntime"; } + private String getSecretsManagerServiceName() { + return "AWS::SecretsManager"; + } + + private String getStepFunctionsServiceName() { + return "AWS::StepFunctions"; + } + private String s3SpanName(String operation) { return String.format("%s.%s", getS3SpanNamePrefix(), operation); } @@ -182,10 +203,33 @@ private String bedrockAgentRuntimeSpanName(String operation) { return String.format("%s.%s", getBedrockAgentRuntimeSpanNamePrefix(), operation); } + private String secretsManagerSpanName(String operation) { + return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), operation); + } + + private String stepFunctionsSpanName(String operation) { + return String.format("%s.%s", getStepFunctionsSpanNamePrefix(), operation); + } + + private boolean isValidRegex(String pattern) { + try { + Pattern.compile(pattern); + return true; + } catch (PatternSyntaxException e) { + return false; + } + } + protected ThrowingConsumer assertAttribute(String key, String value) { return (attribute) -> { assertThat(attribute.getKey()).isEqualTo(key); - assertThat(attribute.getValue().getStringValue()).isEqualTo(value); + var actualValue = attribute.getValue().getStringValue(); + + if (isValidRegex(value)) { + assertThat(actualValue).matches(value); + } else { + assertThat(actualValue).isEqualTo(value); + } }; } @@ -258,6 +302,7 @@ private void assertSpanClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -276,6 +321,7 @@ private void assertSpanClientAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -293,6 +339,7 @@ private void assertSpanProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -310,6 +357,7 @@ private void assertSpanProducerAttributes( method, type, identifier, + cloudformationIdentifier, peerName, peerPort, url, @@ -358,6 +406,7 @@ private void assertSpanAttributes( String method, String type, String identifier, + String cloudformationIdentifier, String peerName, int peerPort, String url, @@ -381,6 +430,7 @@ private void assertSpanAttributes( method, type, identifier, + cloudformationIdentifier, awsSpanKind); for (var assertion : extraAssertions) { assertThat(spanAttributes).satisfiesOnlyOnce(assertion); @@ -396,6 +446,7 @@ private void assertAwsAttributes( String operation, String type, String identifier, + String clouformationIdentifier, String spanKind) { var assertions = @@ -406,11 +457,14 @@ private void assertAwsAttributes( .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_REMOTE_OPERATION, operation)) .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_REMOTE_SERVICE, service)) .satisfiesOnlyOnce(assertAttribute(AppSignalsConstants.AWS_SPAN_KIND, spanKind)); - if (type != null && identifier != null) { + if (type != null && identifier != null && clouformationIdentifier != null) { assertions.satisfiesOnlyOnce( assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_TYPE, type)); assertions.satisfiesOnlyOnce( assertAttribute(AppSignalsConstants.AWS_REMOTE_RESOURCE_IDENTIFIER, identifier)); + assertions.satisfiesOnlyOnce( + assertAttribute( + AppSignalsConstants.AWS_CLOUDFORMATION_PRIMARY_IDENTIFIER, clouformationIdentifier)); } } @@ -435,6 +489,7 @@ protected void assertMetricClientAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -446,6 +501,7 @@ protected void assertMetricClientAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -458,6 +514,7 @@ protected void assertMetricProducerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -469,6 +526,7 @@ protected void assertMetricProducerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -481,6 +539,7 @@ protected void assertMetricConsumerAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertMetricAttributes( resourceScopeMetrics, @@ -492,6 +551,7 @@ protected void assertMetricConsumerAttributes( method, type, identifier, + cloudformationIdentifier, expectedSum); } @@ -505,6 +565,7 @@ protected void assertMetricAttributes( String method, String type, String identifier, + String cloudformationIdentifier, Double expectedSum) { assertThat(resourceScopeMetrics) .anySatisfy( @@ -524,6 +585,7 @@ protected void assertMetricAttributes( method, type, identifier, + cloudformationIdentifier, spanKind); if (expectedSum != null) { double actualSum = dataPoint.getSum(); @@ -554,6 +616,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 +628,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, "create-bucket.s3.localstack", 4566, "http://create-bucket.s3.localstack:4566", @@ -579,6 +643,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -589,6 +654,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -599,6 +665,7 @@ protected void doTestS3CreateBucket() throws Exception { "CreateBucket", type, identifier, + cloudformationIdentifier, 0.0); } @@ -617,6 +684,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 +696,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, "put-object.s3.localstack", 4566, "http://put-object.s3.localstack:4566", @@ -642,6 +711,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -652,6 +722,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -662,6 +733,7 @@ protected void doTestS3CreateObject() throws Exception { "PutObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -679,6 +751,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 +763,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, "get-object.s3.localstack", 4566, "http://get-object.s3.localstack:4566", @@ -704,6 +778,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -714,6 +789,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -724,6 +800,7 @@ protected void doTestS3GetObject() throws Exception { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -741,6 +818,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 +830,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, "error-bucket.s3.test", 8080, "http://error-bucket.s3.test:8080", @@ -766,6 +845,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -776,6 +856,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -786,6 +867,7 @@ protected void doTestS3Error() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); } @@ -803,6 +885,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 +897,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, "fault-bucket.s3.test", 8080, "http://fault-bucket.s3.test:8080", @@ -828,6 +912,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -838,6 +923,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -848,6 +934,7 @@ protected void doTestS3Fault() { "GetObject", type, identifier, + cloudformationIdentifier, 0.0); } @@ -873,6 +960,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 +972,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -898,6 +987,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -908,6 +998,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -918,6 +1009,7 @@ protected void doTestDynamoDbCreateTable() { "CreateTable", type, identifier, + cloudformationIdentifier, 0.0); } @@ -935,6 +1027,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 +1039,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -960,6 +1054,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -970,6 +1065,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -980,6 +1076,7 @@ protected void doTestDynamoDbPutItem() { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -997,6 +1094,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 +1106,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1022,6 +1121,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1032,6 +1132,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1042,6 +1143,7 @@ protected void doTestDynamoDbError() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1065,6 +1167,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 +1179,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1090,6 +1194,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 20000.0); assertMetricClientAttributes( metrics, @@ -1100,6 +1205,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1110,6 +1216,7 @@ protected void doTestDynamoDbFault() throws Exception { "PutItem", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1127,6 +1234,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 +1246,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1152,6 +1261,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1162,6 +1272,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1172,6 +1283,7 @@ protected void doTestSQSCreateQueue() throws Exception { "CreateQueue", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1190,6 +1302,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 +1314,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1216,6 +1330,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1226,6 +1341,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1236,6 +1352,7 @@ protected void doTestSQSSendMessage() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1258,6 +1375,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 +1400,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricConsumerAttributes( metrics, @@ -1292,6 +1411,7 @@ protected void doTestSQSReceiveMessage() throws Exception { "ReceiveMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1310,6 +1430,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 +1442,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1336,6 +1458,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1346,6 +1469,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); assertMetricProducerAttributes( metrics, @@ -1356,6 +1480,7 @@ protected void doTestSQSError() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1374,6 +1499,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 +1511,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1400,6 +1527,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricProducerAttributes( metrics, @@ -1410,6 +1538,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 1.0); assertMetricProducerAttributes( metrics, @@ -1420,6 +1549,7 @@ protected void doTestSQSFault() throws Exception { "SendMessage", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1437,6 +1567,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 +1579,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "localstack", 4566, "http://localstack:4566", @@ -1462,6 +1594,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1472,6 +1605,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1482,6 +1616,7 @@ protected void doTestKinesisPutRecord() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1499,6 +1634,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 +1646,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "error.test", 8080, "http://error.test:8080", @@ -1525,6 +1662,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1535,6 +1673,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1545,6 +1684,7 @@ protected void doTestKinesisError() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); } @@ -1562,6 +1702,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 +1714,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, "fault.test", 8080, "http://fault.test:8080", @@ -1587,6 +1729,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1597,6 +1740,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 1.0); assertMetricClientAttributes( metrics, @@ -1607,6 +1751,7 @@ protected void doTestKinesisFault() throws Exception { "PutRecord", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1625,6 +1770,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 +1781,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1651,6 +1798,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1661,6 +1809,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1671,6 +1820,7 @@ protected void doTestBedrockAgentKnowledgeBaseId() { "GetKnowledgeBase", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1688,6 +1838,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 +1849,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1712,6 +1864,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1722,6 +1875,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1732,6 +1886,7 @@ protected void doTestBedrockAgentAgentId() { "GetAgent", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1749,6 +1904,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 +1915,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1775,6 +1932,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1785,6 +1943,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1795,6 +1954,7 @@ protected void doTestBedrockAgentDataSourceId() { "GetDataSource", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1812,6 +1972,7 @@ protected void doTestBedrockRuntimeModelId() { var localOperation = "GET /bedrockruntime/invokeModel"; String type = "AWS::Bedrock::Model"; String identifier = "anthropic.claude-v2"; + String cloudformationIdentifier = "anthropic.claude-v2"; assertSpanClientAttributes( traces, bedrockRuntimeSpanName("InvokeModel"), @@ -1822,6 +1983,7 @@ protected void doTestBedrockRuntimeModelId() { "InvokeModel", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1838,6 +2000,7 @@ protected void doTestBedrockRuntimeModelId() { "InvokeModel", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1848,6 +2011,7 @@ protected void doTestBedrockRuntimeModelId() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1858,6 +2022,7 @@ protected void doTestBedrockRuntimeModelId() { "InvokeModel", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1875,6 +2040,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"), @@ -1885,13 +2052,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, @@ -1901,6 +2072,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1911,6 +2083,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1921,6 +2094,7 @@ protected void doTestBedrockGuardrailId() { "GetGuardrail", type, identifier, + cloudformationIdentifier, 0.0); } @@ -1938,6 +2112,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"), @@ -1948,6 +2123,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -1962,6 +2138,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -1972,6 +2149,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -1982,6 +2160,7 @@ protected void doTestBedrockAgentRuntimeAgentId() { "GetAgentMemory", type, identifier, + cloudformationIdentifier, 0.0); } @@ -2000,6 +2179,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"), @@ -2010,6 +2190,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, "bedrock.test", 8080, "http://bedrock.test:8080", @@ -2026,6 +2207,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 5000.0); assertMetricClientAttributes( metrics, @@ -2036,6 +2218,7 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, 0.0); assertMetricClientAttributes( metrics, @@ -2046,6 +2229,487 @@ protected void doTestBedrockAgentRuntimeKnowledgeBaseId() { "Retrieve", type, identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestSecretsManagerDescribeSecret() throws Exception { + appClient.get("/secretsmanager/describesecret/test-secret-id").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/describesecret/:secretId"; + var type = "AWS::SecretsManager::Secret"; + var identifier = "test-secret-id-[A-Za-z0-9]{6}"; + var cloudformationIdentifier = + "arn:aws:secretsmanager:us-west-2:000000000000:secret:test-secret-id-[A-Za-z0-9]{6}"; + + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_SECRET_ARN, + "arn:aws:secretsmanager:us-west-2:000000000000:secret:test-secret-id-[A-Za-z0-9]{6}"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestSecretsManagerError() throws Exception { + appClient.get("/secretsmanager/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/error"; + + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + "error.test", + 8080, + "http://error.test:8080", + 400, + List.of()); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 1.0); + } + + protected void doTestSecretsManagerFault() throws Exception { + appClient.get("/secretsmanager/fault").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /secretsmanager/fault"; + + assertSpanClientAttributes( + traces, + secretsManagerSpanName("DescribeSecret"), + getSecretsManagerRpcServiceName(), + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + "fault.test", + 8080, + "http://fault.test:8080", + 500, + List.of()); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 1.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getSecretsManagerServiceName(), + "DescribeSecret", + null, + null, + null, + 0.0); + } + + protected void doTestStepFunctionsDescribeStateMachine() throws Exception { + appClient.get("/sfn/describestatemachine/test-state-machine").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/describestatemachine/:name"; + var type = "AWS::StepFunctions::StateMachine"; + var identifier = "test-state-machine"; + var cloudformationIdentifier = + "arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeStateMachine"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_STATE_MACHINE_ARN, + "arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeStateMachine", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestStepFunctionsDescribeActivity() throws Exception { + appClient.get("/sfn/describeactivity/test-activity").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/describeactivity/:name"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "test-activity"; + var cloudformationIdentifier = "arn:aws:states:us-west-2:000000000000:activity:test-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "localstack", + 4566, + "http://localstack:4566", + 200, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:test-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + } + + protected void doTestStepFunctionsError() throws Exception { + appClient.get("/sfn/error").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/error"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "nonexistent-activity"; + var cloudformationIdentifier = + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "error.test", + 8080, + "http://error.test:8080", + 400, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:nonexistent-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 0.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 1.0); + } + + protected void doTestStepFunctionsFault() throws Exception { + appClient.get("/sfn/fault").aggregate().join(); + var traces = mockCollectorClient.getTraces(); + var metrics = + mockCollectorClient.getMetrics( + Set.of( + AppSignalsConstants.ERROR_METRIC, + AppSignalsConstants.FAULT_METRIC, + AppSignalsConstants.LATENCY_METRIC)); + + var localService = getApplicationOtelServiceName(); + var localOperation = "GET /sfn/fault"; + var type = "AWS::StepFunctions::Activity"; + var identifier = "fault-activity"; + var cloudformationIdentifier = "arn:aws:states:us-west-2:000000000000:activity:fault-activity"; + + assertSpanClientAttributes( + traces, + stepFunctionsSpanName("DescribeActivity"), + getStepFunctionsRpcServiceName(), + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + "fault.test", + 8080, + "http://fault.test:8080", + 500, + List.of( + assertAttribute( + SemanticConventionsConstants.AWS_ACTIVITY_ARN, + "arn:aws:states:us-west-2:000000000000:activity:fault-activity"))); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.LATENCY_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 5000.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.FAULT_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, + 1.0); + assertMetricClientAttributes( + metrics, + AppSignalsConstants.ERROR_METRIC, + localService, + localOperation, + getStepFunctionsServiceName(), + "DescribeActivity", + type, + identifier, + cloudformationIdentifier, 0.0); } } 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 3cf5a8982e..f37549f966 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,16 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "AWSBedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "AWSSecretsManager"; + } + + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "AWSStepFunctions"; + } + protected String getS3RpcServiceName() { return "Amazon S3"; } @@ -90,6 +100,16 @@ protected String getSqsRpcServiceName() { return "AmazonSQS"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "AWSSecretsManager"; + } + + @Override + protected String getStepFunctionsRpcServiceName() { + return "AWSStepFunctions"; + } + protected String getKinesisRpcServiceName() { return "AmazonKinesis"; } @@ -235,4 +255,39 @@ void testBedrockAgentRuntimeAgentId() { void testBedrockAgentRuntimeKnowledgeBaseId() { doTestBedrockAgentRuntimeKnowledgeBaseId(); } + + @Test + void testSecretsManagerDescribeSecret() throws Exception { + doTestSecretsManagerDescribeSecret(); + } + + @Test + void testSecretsManagerError() throws Exception { + doTestSecretsManagerError(); + } + + @Test + void testSecretsManagerFault() throws Exception { + doTestSecretsManagerFault(); + } + + @Test + void testStepFunctionsDescribeStateMachine() throws Exception { + doTestStepFunctionsDescribeStateMachine(); + } + + @Test + void testStepFunctionsDescribeActivity() throws Exception { + doTestStepFunctionsDescribeActivity(); + } + + @Test + void testStepFunctionsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testStepFunctionsFault() throws Exception { + doTestStepFunctionsFault(); + } } 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 a76736daf3..b556b17277 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,16 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerSpanNamePrefix() { + return "SecretsManager"; + } + + @Override + protected String getStepFunctionsSpanNamePrefix() { + return "Sfn"; + } + @Override protected String getS3RpcServiceName() { return "S3"; @@ -114,6 +124,16 @@ protected String getBedrockAgentRuntimeRpcServiceName() { return "BedrockAgentRuntime"; } + @Override + protected String getSecretsManagerRpcServiceName() { + return "SecretsManager"; + } + + @Override + protected String getStepFunctionsRpcServiceName() { + return "Sfn"; + } + @Test void testS3CreateBucket() throws Exception { doTestS3CreateBucket(); @@ -240,4 +260,39 @@ void testBedrockAgentRuntimeAgentId() { // void testBedrockAgentRuntimeKnowledgeBaseId() { // doTestBedrockAgentRuntimeKnowledgeBaseId(); // } + + @Test + void testSecretsManagerDescribeSecret() throws Exception { + doTestSecretsManagerDescribeSecret(); + } + + @Test + void testSecretsManagerError() throws Exception { + doTestSecretsManagerError(); + } + + @Test + void testSecretsManagerFault() throws Exception { + doTestSecretsManagerFault(); + } + + @Test + void testStepFunctionsDescribeStateMachine() throws Exception { + doTestStepFunctionsDescribeStateMachine(); + } + + @Test + void testStepFunctionsDescribeActivity() throws Exception { + doTestStepFunctionsDescribeActivity(); + } + + @Test + void testStepFunctionsError() throws Exception { + doTestStepFunctionsError(); + } + + @Test + void testStepFunctionsFault() throws Exception { + doTestStepFunctionsFault(); + } } 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 3fe1644dd5..b024d667b7 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,7 +62,11 @@ 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 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 6ee3f0cae1..1bf1fc4fd0 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 @@ -37,6 +37,9 @@ dependencies { implementation("com.amazonaws:aws-java-sdk-bedrockagent") implementation("com.amazonaws:aws-java-sdk-bedrockruntime") implementation("com.amazonaws:aws-java-sdk-bedrockagentruntime") + implementation("com.amazonaws:aws-java-sdk-secretsmanager") + implementation("com.amazonaws:aws-java-sdk-iam") + implementation("com.amazonaws:aws-java-sdk-stepfunctions") implementation("commons-logging:commons-logging") implementation("com.linecorp.armeria:armeria") implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") 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 0a7498bf2a..70d77c927f 100644 --- a/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java +++ b/appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java @@ -44,6 +44,9 @@ import com.amazonaws.services.dynamodbv2.model.KeyType; import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput; import com.amazonaws.services.dynamodbv2.model.PutItemRequest; +import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient; +import com.amazonaws.services.identitymanagement.model.CreateRoleRequest; +import com.amazonaws.services.identitymanagement.model.PutRolePolicyRequest; import com.amazonaws.services.kinesis.AmazonKinesisClient; import com.amazonaws.services.kinesis.model.CreateStreamRequest; import com.amazonaws.services.kinesis.model.PutRecordRequest; @@ -52,10 +55,17 @@ 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; import com.amazonaws.services.sqs.model.SendMessageRequest; +import com.amazonaws.services.stepfunctions.AWSStepFunctionsClient; +import com.amazonaws.services.stepfunctions.model.*; import java.io.File; import java.io.IOException; import java.net.http.HttpClient; @@ -122,6 +132,8 @@ public static void main(String[] args) throws IOException, InterruptedException setupSqs(); setupKinesis(); setupBedrock(); + setupSecretsManager(); + setupStepFunctions(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); @@ -631,4 +643,281 @@ private static void setupBedrock() { return ""; }); } + + 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.info("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; + logger.info("Existing secret arn {}", finalExistingSecretArn); + get( + "/secretsmanager/describesecret/:secretId", + (req, res) -> { + var secretId = req.params(":secretId"); + 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.info("Error describing secret", e); + } + return ""; + }); + + get( + "/secretsmanager/fault", + (req, res) -> { + setMainStatus(500); + var errorClient = + 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"); + errorClient.describeSecret(describeRequest); + } catch (Exception e) { + logger.info("Error describing secret", e); + } + return ""; + }); + } + + private static void setupStepFunctions() { + var stepFunctionsClient = + AWSStepFunctionsClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + var iamClient = + AmazonIdentityManagementClient.builder() + .withCredentials(CREDENTIALS_PROVIDER) + .withEndpointConfiguration(endpointConfiguration) + .build(); + + var sfnName = "test-state-machine"; + String existingStateMachineArn = null; + try { + var listRequest = new ListStateMachinesRequest(); + var listResponse = stepFunctionsClient.listStateMachines(listRequest); + existingStateMachineArn = + listResponse.getStateMachines().stream() + .filter(machine -> machine.getName().equals(sfnName)) + .findFirst() + .map(StateMachineListItem::getStateMachineArn) + .orElse(null); + } catch (Exception e) { + logger.error("Error listing state machines", e); + } + + if (existingStateMachineArn != null) { + logger.info("State machine already exists, skipping creation"); + } else { + logger.info("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 = + 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); + // Simple state machine definition - a basic Hello World + 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" + + " }" + + " }" + + "}"; + // Create state machine using the role + 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; + + // List existing activities + 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.info("Activity already exists, skipping creation"); + } else { + logger.info("Activity not found, creating a new one"); + var createRequest = new CreateActivityRequest().withName(activityName); + var createResponse = stepFunctionsClient.createActivity(createRequest); + existingActivityArn = createResponse.getActivityArn(); + } + + String finalExistingStateMachineArn = existingStateMachineArn; + get( + "/sfn/describestatemachine/:name", + (req, res) -> { + var describeRequest = + new DescribeStateMachineRequest().withStateMachineArn(finalExistingStateMachineArn); + stepFunctionsClient.describeStateMachine(describeRequest); + return ""; + }); + + String finalExistingActivityArn = existingActivityArn; + 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.info("Error caught in Sample App", 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.info("Error caught in Sample App", e); + } + return ""; + }); + } } 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..76f5b6566e 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 @@ -37,6 +37,9 @@ dependencies { implementation("software.amazon.awssdk:bedrockagent") implementation("software.amazon.awssdk:bedrockruntime") implementation("software.amazon.awssdk:bedrockagentruntime") + implementation("software.amazon.awssdk:secretsmanager") + implementation("software.amazon.awssdk:iam") + implementation("software.amazon.awssdk:sfn") implementation("commons-logging:commons-logging") implementation("com.linecorp.armeria:armeria") implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3") 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 bc43402e9d..32727529c9 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 @@ -57,6 +57,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; @@ -65,6 +68,13 @@ import software.amazon.awssdk.services.s3.model.CreateBucketRequest; import software.amazon.awssdk.services.s3.model.GetObjectRequest; import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.secretsmanager.SecretsManagerClient; +import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest; +import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest; +import software.amazon.awssdk.services.secretsmanager.model.ListSecretsRequest; +import software.amazon.awssdk.services.secretsmanager.model.SecretListEntry; +import software.amazon.awssdk.services.sfn.SfnClient; +import software.amazon.awssdk.services.sfn.model.*; import software.amazon.awssdk.services.sqs.SqsClient; import software.amazon.awssdk.services.sqs.model.CreateQueueRequest; import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest; @@ -119,6 +129,8 @@ public static void main(String[] args) throws IOException, InterruptedException setupSqs(); setupKinesis(); setupBedrock(); + setupSecretsManager(); + setupStepFunctions(); // Add this log line so that we only start testing after all routes are configured. awaitInitialization(); logger.info("All routes initialized"); @@ -658,4 +670,278 @@ private static void setupBedrock() { return ""; }); } + + 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.info("Secret already exists, skipping creation"); + } else { + logger.info("Secret not found, creating a new one"); + var createSecretRequest = CreateSecretRequest.builder().name(secretName).build(); + var createResponse = secretsManagerClient.createSecret(createSecretRequest); + existingSecretArn = createResponse.arn(); + } + + String finalExistingSecretArn = existingSecretArn; + logger.info("Existing secret arn {}", finalExistingSecretArn); + get( + "/secretsmanager/describesecret/:secretId", + (req, res) -> { + var secretId = req.params(":secretId"); + 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.info("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.info("Fault caught in Sample App", e); + } + return ""; + }); + } + + private static void setupStepFunctions() { + 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.info("State machine already exists, skipping creation"); + } else { + logger.info("State machine not found, creating a new one"); + // Create role for Step Functions + 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(); + // Attach policy allowing Lambda invocation + 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); + // Simple state machine definition + 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" + + " }" + + " }" + + "}"; + // Create state machine + 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.info("Activity already exists, skipping creation"); + } else { + logger.info("Activity not found, creating a new one"); + var createRequest = CreateActivityRequest.builder().name(activityName).build(); + existingActivityArn = sfnClient.createActivity(createRequest).activityArn(); + } + + String finalExistingStateMachineArn = existingStateMachineArn; + get( + "/sfn/describestatemachine/:name", + (req, res) -> { + var describeRequest = + DescribeStateMachineRequest.builder() + .stateMachineArn(finalExistingStateMachineArn) + .build(); + sfnClient.describeStateMachine(describeRequest); + return ""; + }); + + String finalExistingActivityArn = existingActivityArn; + 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.info("Error caught in Sample App", 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.info("Fault caught in Sample App", e); + } + return ""; + }); + } }