spring-boottestcontainersspring-boot-testcontainers

SpringBootTest fails to start SQS container on CI server but not locally


I have the following ControllerIntegration test base class

@TestInstance(Lifecycle.PER_CLASS)
@AutoConfigureMockMvc
@EnableConfigurationProperties
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
@SpringBootTest(classes = MyServicesApplication.class, webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public abstract class AbstractControllerIntegrationTest extends AbstractPostgresJupiterTest implements SqsIntegrationTestSupport {

    static {
        localstack.start();
    }

... helper methods
}

The SqsIntegrationTestSupport looks like below. Note, I have overloaded some of the properties just because it's unclear to me which ones I really need - e.g., both credentials.access-key and credentials.sts.access-key, but given this works fine locally, I don't think that's an issue.

public interface SqsIntegrationTestSupport {
  String QUEUE_NAME = "my_queue";

  @Container
  LocalStackContainer localstack =
      new LocalStackContainer(DockerImageName.parse("localstack/localstack:3"))
          .withServices(SQS)
          .withEnv("HOSTNAME_EXTERNAL", "localhost");

  @BeforeAll
  static void beforeAll() throws IOException, InterruptedException {
    // create the queue(s)
    localstack.execInContainer("awslocal", "sqs", "create-queue", "--queue-name", QUEUE_NAME);
  }

  @DynamicPropertySource
  static void overrideConfiguration(final DynamicPropertyRegistry registry) {
    registry.add("sqs.queue.name", () -> QUEUE_NAME);
    registry.add("spring.cloud.aws.region.static", () -> localstack.getRegion());
    registry.add("spring.cloud.aws.credentials.access-key", () -> localstack.getAccessKey());
    registry.add("spring.cloud.aws.credentials.secret-key", () -> localstack.getSecretKey());
    registry.add("spring.cloud.aws.sqs.endpoint", () -> localstack.getEndpointOverride(SQS).toString());
    registry.add("spring.cloud.aws.endpoint", () -> localstack.getEndpointOverride(SQS).toString());
    registry.add("spring.cloud.aws.credentials.sts.access-key", () -> localstack.getAccessKey());
    registry.add("spring.cloud.aws.credentials.sts.secret-key", () -> localstack.getSecretKey());
  }
}

I'm pretty sure it's not relevant, as other tests that use only the AbstractPostgresJupiterTest class are fine, but here are the annotations for that one

@ContextConfiguration(
        initializers = AbstractPostgresJupiterTest.class
)
@TestExecutionListeners(
        value = {CleanDatabaseTestExecutionListener.class},
        mergeMode = TestExecutionListeners.MergeMode.MERGE_WITH_DEFAULTS // Retains default TestExecutionListeners
)
@Import(value = {MyBatisConfig.class})
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
public class AbstractPostgresJupiterTest implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    @Container
    static final PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:16.0");
... postgres stuff
}

When I run any test that uses the AbstractControllerIntegrationTest base class locally using mvn clean test, it runs fine with the following output:

00:05:47.753 [main] INFO  c.a.f.c.MyControllerIntegrationTest - Starting MyControllerIntegrationTest using Java 21.0.5 with PID 2019 (started by runner in /my-directory)
00:05:47.753 [main] INFO  c.a.f.c.MyControllerIntegrationTest - No active profile set, falling back to 1 default profile: "default"
00:05:48.798 [main] INFO  o.s.d.r.c.RepositoryConfigurationDelegate - Bootstrapping Spring Data JDBC repositories in DEFAULT mode.
00:05:48.841 [main] INFO  o.s.d.r.c.RepositoryConfigurationDelegate - Finished Spring Data repository scanning in 39 ms. Found 0 JDBC repository interfaces.
00:05:49.506 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat initialized with port 0 (http)
00:05:49.518 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Initializing ProtocolHandler ["http-nio-auto-1"]
00:05:49.521 [main] INFO  o.a.catalina.core.StandardService - Starting service [Tomcat]
00:05:49.521 [main] INFO  o.a.catalina.core.StandardEngine - Starting Servlet engine: [Apache Tomcat/10.1.31]
00:05:49.590 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring embedded WebApplicationContext
00:05:49.590 [main] INFO  o.s.b.w.s.c.ServletWebServerApplicationContext - Root WebApplicationContext: initialization completed in 1835 ms
00:05:49.835 [main] DEBUG c.a.c.RequestLoggingFilterConfig$1 - Filter 'logFilter' configured for use
00:05:49.888 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Starting...
00:05:49.925 [main] INFO  com.zaxxer.hikari.pool.HikariPool - HikariPool-3 - Added connection org.postgresql.jdbc.PgConnection@40f49d72
00:05:49.925 [main] INFO  com.zaxxer.hikari.HikariDataSource - HikariPool-3 - Start completed.
00:05:49.925 [main] INFO  c.a.MyServicesApplication - Running with dataSource: jdbc:postgresql://localhost:32769/test
00:05:50.008 [main] INFO  c.a.MyServicesApplication - jdbiWrapper generated DataSourceProperties DataSourceProperties[host=localhost, port=32769, db=test, username=test, ***
00:05:50.362 [main] INFO  c.a.u.m.MessageHandlerClient - Message Handler URL: http://null:null
00:05:51.958 [main] WARN  o.s.b.a.m.MustacheAutoConfiguration - Cannot find template location: classpath:/templates/ (please add some templates, check your Mustache configuration, or set spring.mustache.check-template-location=false)
00:05:52.093 [main] WARN  i.a.c.a.c.CredentialsProviderAutoConfiguration - Skipping creating `StsCredentialsProvider`. `software.amazon.awssdk:sts` is on the classpath, but neither `spring.cloud.aws.credentials.sts` properties are configured nor `AWS_WEB_IDENTITY_TOKEN_FILE` or the javaproperty `aws.webIdentityTokenFile` is set
00:05:52.715 [main] DEBUG c.a.c.RequestLoggingFilterConfig$1 - Filter 'logFilter' configured for use
00:05:52.717 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring TestDispatcherServlet ''
00:05:52.717 [main] INFO  o.s.t.w.s.TestDispatcherServlet - Initializing Servlet ''
00:05:52.729 [main] INFO  o.s.b.a.e.web.EndpointLinksResolver - Exposing 1 endpoint beneath base path '/actuator'
00:05:52.745 [main] INFO  o.s.t.w.s.TestDispatcherServlet - Completed initialization in 28 ms
00:05:52.848 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Starting ProtocolHandler ["http-nio-auto-1"]
00:05:52.861 [main] INFO  o.a.c.c.C.[Tomcat].[localhost].[/] - Initializing Spring DispatcherServlet 'dispatcherServlet'
00:05:52.861 [main] INFO  o.s.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
00:05:52.863 [main] INFO  o.s.web.servlet.DispatcherServlet - Completed initialization in 2 ms
00:05:52.864 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port 36753 (http) with context path '/'
16:18:18.688 [lifecycle-thread-1] INFO  i.a.c.s.l.AbstractMessageListenerContainer - Container io.awspring.cloud.sqs.sqsListenerEndpointContainer#0 started
16:18:18.704 [main] INFO  c.a.f.c.MyControllerIntegrationTest - Started MyControllerIntegrationTest in 9.996 seconds (process running for 17.165)

However, in my CI environment, it fails, probably on instantiating the SQS container, but I can't tell why. The output is identical to above until after the Tomcat started message, and then I see:

00:05:52.864 [main] INFO  o.s.b.w.e.tomcat.TomcatWebServer - Tomcat started on port 36753 (http) with context path '/'
00:05:53.016 [main] INFO  o.a.coyote.http11.Http11NioProtocol - Stopping ProtocolHandler ["http-nio-auto-1-36753"]
00:05:53.038 [main] WARN  o.s.b.w.s.c.AnnotationConfigServletWebServerApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'io.awspring.cloud.messaging.internalEndpointRegistryBeanName'

There is an absolutely massive CONDITIONS EVALUATION REPORT after the failure, but I would not begin to know how to make use of it as it contains reports on a ton of things that aren't even in my project. Nothing obviously to do with SQS or AWS afaict.

How can I narrow down the issue?

Thank you!


Solution

  • I had the same scenario, Localstack with Testcontainers, the difference being using Kotlin with Gradle instead of Java with Maven. Everything was running smoothly on local but having the exact same issue on Github Actions.

    2024-12-23T01:50:37.096Z  WARN 2117 --- [    Test worker] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
    2024-12-23T01:50:37.580Z  INFO 2117 --- [    Test worker] o.s.b.a.e.web.EndpointLinksResolver      : Exposing 1 endpoint beneath base path '/actuator'
    2024-12-23T01:50:37.655Z  WARN 2117 --- [    Test worker] o.s.w.c.s.GenericWebApplicationContext   : Exception encountered during context initialization - cancelling refresh attempt: org.springframework.context.ApplicationContextException: Failed to start bean 'io.awspring.cloud.messaging.internalEndpointRegistryBeanName'
    

    I had to, firstly, add some properties to show more logs, as it was just giving me that line.

    testLogging {
        showStandardStreams = true
        exceptionFormat = TestExceptionFormat.FULL
    }
    

    After that, the reason for exception started to show :

    Caused by: software.amazon.awssdk.core.exception.SdkClientException: Unable to load credentials from any of the providers in the chain AwsCredentialsProviderChain(credentialsProviders=[SystemPropertyCredentialsProvider(), EnvironmentVariableCredentialsProvider(), WebIdentityTokenCredentialsProvider(), ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])), ContainerCredentialsProvider(), InstanceProfileCredentialsProvider()]) : [SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., EnvironmentVariableCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId)., WebIdentityTokenCredentialsProvider(): Either the environment variable AWS_WEB_IDENTITY_TOKEN_FILE or the javaproperty aws.webIdentityTokenFile must be set., ProfileCredentialsProvider(profileName=default, profileFile=ProfileFile(sections=[])): Profile file contained no credentials for profile 'default': ProfileFile(sections=[]), ContainerCredentialsProvider(): Cannot fetch credentials from container - neither AWS_CONTAINER_CREDENTIALS_FULL_URI or AWS_CONTAINER_CREDENTIALS_RELATIVE_URI environment variables are set., InstanceProfileCredentialsProvider(): Failed to load credentials from IMDS.]
    

    The error pointed to AWS credentials issue, but the test was working locally with no problem. I tested a bunch of things, made sure that both region and credentials (access key and secret key) were present on application start but in the end, the only thing that worked was adding on the test resources application.properties the following lines (which are the actual localstack credentials):

    spring.cloud.aws.region.static=us-east-1
    spring.cloud.aws.credentials.access-key=test
    spring.cloud.aws.credentials.secret-key=test
    

    Not sure if this could be also your problem, but give it a try.