Skip to content

Commit 4454f05

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

File tree

8 files changed

+248
-1
lines changed

8 files changed

+248
-1
lines changed

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

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,9 @@ 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.IAM,
51+
LocalStackContainer.Service.STEPFUNCTIONS)
5052
.withEnv("DEFAULT_REGION", "us-west-2")
5153
.withNetwork(network)
5254
.withEnv("LOCALSTACK_HOST", "127.0.0.1")
@@ -108,6 +110,8 @@ protected String getApplicationWaitPattern() {
108110

109111
protected abstract String getSecretsManagerSpanNamePrefix();
110112

113+
protected abstract String getStepFunctionsSpanNamePrefix();
114+
111115
protected abstract String getS3RpcServiceName();
112116

113117
protected abstract String getDynamoDbRpcServiceName();
@@ -126,6 +130,8 @@ protected String getApplicationWaitPattern() {
126130

127131
protected abstract String getSecretsManagerRpcServiceName();
128132

133+
protected abstract String getStepFunctionsRpcServiceName();
134+
129135
private String getS3ServiceName() {
130136
return "AWS::S3";
131137
}
@@ -160,6 +166,8 @@ private String getBedrockRuntimeServiceName() {
160166

161167
private String getSecretsManagerServiceName() { return "AWS::SecretsManager"; }
162168

169+
private String getStepFunctionsServiceName() { return "AWS::StepFunctions"; }
170+
163171
private String s3SpanName(String operation) {
164172
return String.format("%s.%s", getS3SpanNamePrefix(), operation);
165173
}
@@ -196,6 +204,10 @@ private String secretsManagerSpanName(String operation) {
196204
return String.format("%s.%s", getSecretsManagerSpanNamePrefix(), operation);
197205
}
198206

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

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,11 @@ protected String getSecretsManagerSpanNamePrefix() {
8181
return "AWSSecretsManager";
8282
}
8383

84+
@Override
85+
protected String getStepFunctionsSpanNamePrefix() {
86+
return "AWSStepFunctions";
87+
}
88+
8489
protected String getS3RpcServiceName() {
8590
return "Amazon S3";
8691
}
@@ -100,6 +105,11 @@ protected String getSecretsManagerRpcServiceName() {
100105
return "AWSSecretsManager";
101106
}
102107

108+
@Override
109+
protected String getStepFunctionsRpcServiceName() {
110+
return "AWSStepFunctions";
111+
}
112+
103113
protected String getKinesisRpcServiceName() {
104114
return "AmazonKinesis";
105115
}
@@ -248,4 +258,7 @@ void testBedrockAgentRuntimeKnowledgeBaseId() {
248258

249259
@Test
250260
void testSecretsManagerCreateSecret() throws Exception { doTestSecretsManagerCreateSecret(); }
261+
262+
@Test
263+
void testStepFunctionsCreateStateMachine() throws Exception { doTestStepFunctionsCreateStateMachine(); }
251264
}

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ protected String getBedrockAgentRuntimeSpanNamePrefix() {
7878
@Override
7979
protected String getSecretsManagerSpanNamePrefix() { return "SecretsManager"; }
8080

81+
@Override
82+
protected String getStepFunctionsSpanNamePrefix() { return "Sfn"; }
83+
8184
@Override
8285
protected String getS3RpcServiceName() {
8386
return "S3";
@@ -120,6 +123,9 @@ protected String getBedrockAgentRuntimeRpcServiceName() {
120123
@Override
121124
protected String getSecretsManagerRpcServiceName() { return "SecretsManager"; }
122125

126+
@Override
127+
protected String getStepFunctionsRpcServiceName() { return "Sfn"; }
128+
123129
@Test
124130
void testS3CreateBucket() throws Exception {
125131
doTestS3CreateBucket();
@@ -249,4 +255,7 @@ void testBedrockAgentRuntimeAgentId() {
249255

250256
@Test
251257
void testSecretsManagerCreateSecret() throws Exception { doTestSecretsManagerCreateSecret(); }
258+
259+
@Test
260+
void testStepFunctionsCreateStateMachine() throws Exception { doTestStepFunctionsCreateStateMachine(); }
252261
}

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: 83 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

@@ -126,6 +134,7 @@ public static void main(String[] args) throws IOException, InterruptedException
126134
setupKinesis();
127135
setupBedrock();
128136
setupSecretsManager();
137+
setupStepFunctions();
129138

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

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: 88 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;
@@ -124,6 +130,7 @@ public static void main(String[] args) throws IOException, InterruptedException
124130
setupKinesis();
125131
setupBedrock();
126132
setupSecretsManager();
133+
setupStepFunctions();
127134
// Add this log line so that we only start testing after all routes are configured.
128135
awaitInitialization();
129136
logger.info("All routes initialized");
@@ -676,4 +683,85 @@ private static void setupSecretsManager() {
676683
return "";
677684
});
678685
}
686+
687+
private static void setupStepFunctions() {
688+
var sfnClient = SfnClient.builder()
689+
.endpointOverride(endpoint)
690+
.credentialsProvider(CREDENTIALS_PROVIDER)
691+
.build();
692+
693+
var iamClient = IamClient.builder()
694+
.endpointOverride(endpoint)
695+
.credentialsProvider(CREDENTIALS_PROVIDER)
696+
.build();
697+
698+
get("/sfn/createstatemachine/:name", (req, res) -> {
699+
var name = req.params(":name");
700+
701+
// Create role for Step Functions
702+
String trustPolicy = "{"
703+
+ "\"Version\": \"2012-10-17\","
704+
+ "\"Statement\": ["
705+
+ " {"
706+
+ " \"Effect\": \"Allow\","
707+
+ " \"Principal\": {"
708+
+ " \"Service\": \"states.amazonaws.com\""
709+
+ " },"
710+
+ " \"Action\": \"sts:AssumeRole\""
711+
+ " }"
712+
+ "]}";
713+
714+
var roleRequest = CreateRoleRequest.builder()
715+
.roleName(name + "-role")
716+
.assumeRolePolicyDocument(trustPolicy)
717+
.build();
718+
var roleArn = iamClient.createRole(roleRequest).role().arn();
719+
720+
// Attach policy allowing Lambda invocation
721+
String policyDocument = "{"
722+
+ "\"Version\": \"2012-10-17\","
723+
+ "\"Statement\": ["
724+
+ " {"
725+
+ " \"Effect\": \"Allow\","
726+
+ " \"Action\": ["
727+
+ " \"lambda:InvokeFunction\""
728+
+ " ],"
729+
+ " \"Resource\": ["
730+
+ " \"*\""
731+
+ " ]"
732+
+ " }"
733+
+ "]}";
734+
735+
var policyRequest = PutRolePolicyRequest.builder()
736+
.roleName(name + "-role")
737+
.policyName(name + "-policy")
738+
.policyDocument(policyDocument)
739+
.build();
740+
iamClient.putRolePolicy(policyRequest);
741+
742+
// Simple state machine definition
743+
String stateMachineDefinition = "{"
744+
+ " \"Comment\": \"A Hello World example of the Amazon States Language using a Pass state\","
745+
+ " \"StartAt\": \"HelloWorld\","
746+
+ " \"States\": {"
747+
+ " \"HelloWorld\": {"
748+
+ " \"Type\": \"Pass\","
749+
+ " \"Result\": \"Hello World!\","
750+
+ " \"End\": true"
751+
+ " }"
752+
+ " }"
753+
+ "}";
754+
755+
// Create state machine
756+
var sfnRequest = CreateStateMachineRequest.builder()
757+
.name(name)
758+
.roleArn(roleArn)
759+
.definition(stateMachineDefinition)
760+
.type(StateMachineType.STANDARD)
761+
.build();
762+
763+
sfnClient.createStateMachine(sfnRequest);
764+
return "";
765+
});
766+
}
679767
}

0 commit comments

Comments
 (0)