Skip to content

Commit 15ae5d0

Browse files
committed
feat: set up contract tests for step functions
1 parent 6c79f55 commit 15ae5d0

File tree

8 files changed

+229
-1
lines changed

8 files changed

+229
-1
lines changed

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/base/AwsSdkBaseTest.java

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,8 @@ public abstract class AwsSdkBaseTest extends ContractTestBase {
4646
LocalStackContainer.Service.DYNAMODB,
4747
LocalStackContainer.Service.SQS,
4848
LocalStackContainer.Service.KINESIS,
49-
LocalStackContainer.Service.SECRETSMANAGER)
49+
LocalStackContainer.Service.SECRETSMANAGER,
50+
LocalStackContainer.Service.STEPFUNCTIONS)
5051
.withEnv("DEFAULT_REGION", "us-west-2")
5152
.withNetwork(network)
5253
.withEnv("LOCALSTACK_HOST", "127.0.0.1")
@@ -108,6 +109,8 @@ protected String getApplicationWaitPattern() {
108109

109110
protected abstract String getSecretsManagerSpanNamePrefix();
110111

112+
protected abstract String getStepFunctionsSpanNamePrefix();
113+
111114
protected abstract String getS3RpcServiceName();
112115

113116
protected abstract String getDynamoDbRpcServiceName();
@@ -126,6 +129,8 @@ protected String getApplicationWaitPattern() {
126129

127130
protected abstract String getSecretsManagerRpcServiceName();
128131

132+
protected abstract String getStepFunctionsRpcServiceName();
133+
129134
private String getS3ServiceName() {
130135
return "AWS::S3";
131136
}
@@ -160,6 +165,8 @@ private String getBedrockRuntimeServiceName() {
160165

161166
private String getSecretsManagerServiceName() { return "AWS::SecretsManager"; }
162167

168+
private String getStepFunctionsServiceName() { return "AWS::StepFunctions"; }
169+
163170
private String s3SpanName(String operation) {
164171
return String.format("%s.%s", getS3SpanNamePrefix(), operation);
165172
}
@@ -196,6 +203,10 @@ private String secretsManagerSpanName(String operation) {
196203
return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), operation);
197204
}
198205

206+
private String stepFunctionsSpanName(String operation) {
207+
return String.format("%s.%s", getStepFunctionsSpanNamePrefix(), operation);
208+
}
209+
199210
private boolean isValidRegex(String pattern) {
200211
try {
201212
Pattern.compile(pattern);
@@ -2292,4 +2303,41 @@ protected void doTestSecretsManagerCreateSecret() throws Exception {
22922303
cloudformationIdentifier,
22932304
0.0);
22942305
}
2306+
2307+
protected void doTestStepFunctionsCreateStateMachine() throws Exception {
2308+
appClient.get("/sfn/createstatemachine/test-state-machine").aggregate().join();
2309+
var traces = mockCollectorClient.getTraces();
2310+
var metrics = mockCollectorClient.getMetrics(
2311+
Set.of(
2312+
AppSignalsConstants.ERROR_METRIC,
2313+
AppSignalsConstants.FAULT_METRIC,
2314+
AppSignalsConstants.LATENCY_METRIC));
2315+
2316+
var localService = getApplicationOtelServiceName();
2317+
var localOperation = "GET /sfn/createstatemachine/:name";
2318+
var type = "AWS::StepFunctions::StateMachine";
2319+
var identifier = "test-state-machine";
2320+
var cloudformationIdentifier = "arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine";
2321+
2322+
assertSpanClientAttributes(
2323+
traces,
2324+
stepFunctionsSpanName("CreateStateMachine"),
2325+
getStepFunctionsRpcServiceName(),
2326+
localService,
2327+
localOperation,
2328+
getStepFunctionsServiceName(),
2329+
"CreateStateMachine",
2330+
type,
2331+
identifier,
2332+
cloudformationIdentifier,
2333+
"localstack",
2334+
4566,
2335+
"http://localstack:4566",
2336+
200,
2337+
List.of(
2338+
assertAttribute(
2339+
SemanticConventionsConstants.AWS_STATE_MACHINE_ARN,
2340+
"arn:aws:states:us-west-2:000000000000:stateMachine:test-state-machine")
2341+
));
2342+
}
22952343
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v1/AwsSdkV1Test.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -248,4 +248,7 @@ void testBedrockAgentRuntimeKnowledgeBaseId() {
248248

249249
@Test
250250
void testSecretsManagerCreateSecret() throws Exception { doTestSecretsManagerCreateSecret(); }
251+
252+
@Test
253+
void testStepFunctionsCreateStateMachine() throws Exception { doTestStepFunctionsCreateStateMachine(); }
251254
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/awssdk/v2/AwsSdkV2Test.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,4 +249,7 @@ void testBedrockAgentRuntimeAgentId() {
249249

250250
@Test
251251
void testSecretsManagerCreateSecret() throws Exception { doTestSecretsManagerCreateSecret(); }
252+
253+
@Test
254+
void testStepFunctionsCreateStateMachine() throws Exception { doTestStepFunctionsCreateStateMachine(); }
252255
}

appsignals-tests/contract-tests/src/test/java/software/amazon/opentelemetry/appsignals/test/utils/SemanticConventionsConstants.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ public class SemanticConventionsConstants {
6565
public static final String AWS_GUARDRAIL_ARN = "aws.bedrock.guardrail.arn";
6666
public static final String GEN_AI_REQUEST_MODEL = "gen_ai.request.model";
6767
public static final String AWS_SECRET_ARN = "aws.secretsmanager.secret.arn";
68+
public static final String AWS_STATE_MACHINE_ARN = "aws.stepfunctions.state_machine.arn";
6869

6970
// kafka
7071
public static final String MESSAGING_CLIENT_ID = "messaging.client_id";

appsignals-tests/images/aws-sdk/aws-sdk-v1/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ dependencies {
3838
implementation("com.amazonaws:aws-java-sdk-bedrockruntime")
3939
implementation("com.amazonaws:aws-java-sdk-bedrockagentruntime")
4040
implementation("com.amazonaws:aws-java-sdk-secretsmanager")
41+
implementation("com.amazonaws:aws-java-sdk-iam")
42+
implementation("com.amazonaws:aws-java-sdk-stepfunctions")
4143
implementation("commons-logging:commons-logging")
4244
implementation("com.linecorp.armeria:armeria")
4345
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")

appsignals-tests/images/aws-sdk/aws-sdk-v1/src/main/java/com/amazon/sampleapp/App.java

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,10 @@
4444
import com.amazonaws.services.dynamodbv2.model.KeyType;
4545
import com.amazonaws.services.dynamodbv2.model.ProvisionedThroughput;
4646
import com.amazonaws.services.dynamodbv2.model.PutItemRequest;
47+
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClient;
48+
import com.amazonaws.services.identitymanagement.AmazonIdentityManagementClientBuilder;
49+
import com.amazonaws.services.identitymanagement.model.CreateRoleRequest;
50+
import com.amazonaws.services.identitymanagement.model.PutRolePolicyRequest;
4751
import com.amazonaws.services.kinesis.AmazonKinesisClient;
4852
import com.amazonaws.services.kinesis.model.CreateStreamRequest;
4953
import com.amazonaws.services.kinesis.model.PutRecordRequest;
@@ -66,6 +70,10 @@
6670
import java.nio.charset.StandardCharsets;
6771
import java.time.Duration;
6872
import java.util.Map;
73+
74+
import com.amazonaws.services.stepfunctions.AWSStepFunctionsClient;
75+
import com.amazonaws.services.stepfunctions.model.CreateStateMachineRequest;
76+
import com.amazonaws.services.stepfunctions.model.StateMachineType;
6977
import org.slf4j.Logger;
7078
import org.slf4j.LoggerFactory;
7179

@@ -648,4 +656,78 @@ private static void setupSecretsManager() {
648656
return "";
649657
});
650658
}
659+
660+
private static void setupStepFunctions() {
661+
var stepFunctionsClient = AWSStepFunctionsClient.builder().withCredentials(CREDENTIALS_PROVIDER).withEndpointConfiguration(endpointConfiguration).build();
662+
var iamClient = AmazonIdentityManagementClient.builder().withCredentials(CREDENTIALS_PROVIDER).withEndpointConfiguration(endpointConfiguration).build();
663+
664+
get(
665+
"/stepfunctions/createstatemachine:name",
666+
(req, res) -> {
667+
var name = req.params(":name");
668+
var roleRequest = new CreateRoleRequest().withRoleName(name + "-role");
669+
var roleArn = iamClient.createRole(roleRequest).getRole().getArn();
670+
String policyDocument = "{"
671+
+ "\"Version\": \"2012-10-17\","
672+
+ "\"Statement\": ["
673+
+ " {"
674+
+ " \"Effect\": \"Allow\","
675+
+ " \"Action\": ["
676+
+ " \"lambda:InvokeFunction\""
677+
+ " ],"
678+
+ " \"Resource\": ["
679+
+ " \"*\""
680+
+ " ]"
681+
+ " }"
682+
+ "]}";
683+
var policyRequest = new PutRolePolicyRequest()
684+
.withRoleName(name + "-role")
685+
.withPolicyName(name + "-policy")
686+
.withPolicyDocument(policyDocument);
687+
iamClient.putRolePolicy(policyRequest);
688+
// Simple state machine definition - a basic Hello World
689+
String stateMachineDefinition = "{"
690+
+ " \"Comment\": \"A Hello World example of the Amazon States Language using a Pass state\","
691+
+ " \"StartAt\": \"HelloWorld\","
692+
+ " \"States\": {"
693+
+ " \"HelloWorld\": {"
694+
+ " \"Type\": \"Pass\","
695+
+ " \"Result\": \"Hello World!\","
696+
+ " \"End\": true"
697+
+ " }"
698+
+ " }"
699+
+ "}";
700+
// Create state machine using the role
701+
var sfnRequest = new CreateStateMachineRequest()
702+
.withName(name)
703+
.withRoleArn(roleArn)
704+
.withDefinition(stateMachineDefinition)
705+
.withType(StateMachineType.STANDARD);
706+
stepFunctionsClient.createStateMachine(sfnRequest);
707+
return "";
708+
});
709+
710+
get("/sfn/error", (req, res) -> {
711+
setMainStatus(400);
712+
var errorClient = AWSStepFunctionsClient.builder()
713+
.withCredentials(CREDENTIALS_PROVIDER)
714+
.withEndpointConfiguration(
715+
new EndpointConfiguration(
716+
"http://error.test:8080",
717+
Regions.US_WEST_2.getName()))
718+
.build();
719+
720+
try {
721+
String invalidDefinition = "{\"Invalid\": \"Definition\"}";
722+
errorClient.createStateMachine(new CreateStateMachineRequest()
723+
.withName("error-state-machine")
724+
.withDefinition(invalidDefinition)
725+
.withRoleArn("arn:aws:iam::invalid:role/invalid"));
726+
} catch (Exception ex) {
727+
logger.info("Exception Caught in Sample App");
728+
ex.printStackTrace();
729+
}
730+
return "";
731+
});
732+
}
651733
}

appsignals-tests/images/aws-sdk/aws-sdk-v2/build.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ dependencies {
3838
implementation("software.amazon.awssdk:bedrockruntime")
3939
implementation("software.amazon.awssdk:bedrockagentruntime")
4040
implementation("software.amazon.awssdk:secretsmanager")
41+
implementation("software.amazon.awssdk:iam")
42+
implementation("software.amazon.awssdk:sfn")
4143
implementation("commons-logging:commons-logging")
4244
implementation("com.linecorp.armeria:armeria")
4345
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.3")

appsignals-tests/images/aws-sdk/aws-sdk-v2/src/main/java/com/amazon/sampleapp/App.java

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@
5757
import software.amazon.awssdk.services.dynamodb.model.KeyType;
5858
import software.amazon.awssdk.services.dynamodb.model.ProvisionedThroughput;
5959
import software.amazon.awssdk.services.dynamodb.model.PutItemRequest;
60+
import software.amazon.awssdk.services.iam.IamClient;
61+
import software.amazon.awssdk.services.iam.model.CreateRoleRequest;
62+
import software.amazon.awssdk.services.iam.model.PutRolePolicyRequest;
6063
import software.amazon.awssdk.services.kinesis.KinesisClient;
6164
import software.amazon.awssdk.services.kinesis.model.CreateStreamRequest;
6265
import software.amazon.awssdk.services.kinesis.model.DescribeStreamRequest;
@@ -69,6 +72,9 @@
6972
import software.amazon.awssdk.services.secretsmanager.model.CreateSecretRequest;
7073
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretRequest;
7174
import software.amazon.awssdk.services.secretsmanager.model.DescribeSecretResponse;
75+
import software.amazon.awssdk.services.sfn.SfnClient;
76+
import software.amazon.awssdk.services.sfn.model.CreateStateMachineRequest;
77+
import software.amazon.awssdk.services.sfn.model.StateMachineType;
7278
import software.amazon.awssdk.services.sqs.SqsClient;
7379
import software.amazon.awssdk.services.sqs.model.CreateQueueRequest;
7480
import software.amazon.awssdk.services.sqs.model.ReceiveMessageRequest;
@@ -676,4 +682,85 @@ private static void setupSecretsManager() {
676682
return "";
677683
});
678684
}
685+
686+
private static void setupStepFunctions() {
687+
var sfnClient = SfnClient.builder()
688+
.endpointOverride(endpoint)
689+
.credentialsProvider(CREDENTIALS_PROVIDER)
690+
.build();
691+
692+
var iamClient = IamClient.builder()
693+
.endpointOverride(endpoint)
694+
.credentialsProvider(CREDENTIALS_PROVIDER)
695+
.build();
696+
697+
get("/sfn/createstatemachine/:name", (req, res) -> {
698+
var name = req.params(":name");
699+
700+
// Create role for Step Functions
701+
String trustPolicy = "{"
702+
+ "\"Version\": \"2012-10-17\","
703+
+ "\"Statement\": ["
704+
+ " {"
705+
+ " \"Effect\": \"Allow\","
706+
+ " \"Principal\": {"
707+
+ " \"Service\": \"states.amazonaws.com\""
708+
+ " },"
709+
+ " \"Action\": \"sts:AssumeRole\""
710+
+ " }"
711+
+ "]}";
712+
713+
var roleRequest = CreateRoleRequest.builder()
714+
.roleName(name + "-role")
715+
.assumeRolePolicyDocument(trustPolicy)
716+
.build();
717+
var roleArn = iamClient.createRole(roleRequest).role().arn();
718+
719+
// Attach policy allowing Lambda invocation
720+
String policyDocument = "{"
721+
+ "\"Version\": \"2012-10-17\","
722+
+ "\"Statement\": ["
723+
+ " {"
724+
+ " \"Effect\": \"Allow\","
725+
+ " \"Action\": ["
726+
+ " \"lambda:InvokeFunction\""
727+
+ " ],"
728+
+ " \"Resource\": ["
729+
+ " \"*\""
730+
+ " ]"
731+
+ " }"
732+
+ "]}";
733+
734+
var policyRequest = PutRolePolicyRequest.builder()
735+
.roleName(name + "-role")
736+
.policyName(name + "-policy")
737+
.policyDocument(policyDocument)
738+
.build();
739+
iamClient.putRolePolicy(policyRequest);
740+
741+
// Simple state machine definition
742+
String stateMachineDefinition = "{"
743+
+ " \"Comment\": \"A Hello World example of the Amazon States Language using a Pass state\","
744+
+ " \"StartAt\": \"HelloWorld\","
745+
+ " \"States\": {"
746+
+ " \"HelloWorld\": {"
747+
+ " \"Type\": \"Pass\","
748+
+ " \"Result\": \"Hello World!\","
749+
+ " \"End\": true"
750+
+ " }"
751+
+ " }"
752+
+ "}";
753+
754+
// Create state machine
755+
var sfnRequest = CreateStateMachineRequest.builder()
756+
.name(name)
757+
.roleArn(roleArn)
758+
.definition(stateMachineDefinition)
759+
.type(StateMachineType.STANDARD)
760+
.build();
761+
762+
sfnClient.createStateMachine(sfnRequest);
763+
return "";
764+
});
765+
}
679766
}

0 commit comments

Comments
 (0)