javadockertestcontainersopen-libertymicroprofile

ContainerLaunchException: Cannot start Testcontainer OpenLiberty Server in Integration Tests using Spock


I have wrote some Integration Tests for my OpenLiberty with MicroProfile application. In order for the tests to work I must first execute the libertyDev command. So I thought it was a good idea to use Testcontainers in which I will try to create an OpenLiberty server container and load it with the proper configuration files. Following this scope my tests are as follows.

@Testcontainers
class TrackingInventoryIntegrationSpec extends Specification {

    private static final String LIBERTY_CONFIG_BASE_PATH = 'src/main/liberty/config'
    private static final String CONTAINER_CONFIG_BASE_PATH = 'config'

    @Shared
    GenericContainer openLiberty = new GenericContainer(DockerImageName.parse("icr.io/appcafe/open-liberty:full-java17-openj9-ubi"))
            .withExposedPorts(9080, 9443)
            .withCopyFileToContainer(MountableFile.forHostPath("build/libs/tracking-inventory-0.0.1-SNAPSHOT.war"), "${CONTAINER_CONFIG_BASE_PATH}/apps/tracking-inventory-0.0.1-SNAPSHOT.war")
            .withCopyFileToContainer(MountableFile.forHostPath("${LIBERTY_CONFIG_BASE_PATH}/server.xml"), "${CONTAINER_CONFIG_BASE_PATH}/server.xml")
            .withCopyFileToContainer(MountableFile.forHostPath("${LIBERTY_CONFIG_BASE_PATH}/bootstrap.properties"), "${CONTAINER_CONFIG_BASE_PATH}/bootstrap.properties")
            .withCopyFileToContainer(MountableFile.forHostPath("${LIBERTY_CONFIG_BASE_PATH}/GeneratedSSLInclude.xml"), "${CONTAINER_CONFIG_BASE_PATH}/GeneratedSSLInclude.xml")
            .withCopyFileToContainer(MountableFile.forHostPath("${LIBERTY_CONFIG_BASE_PATH}/users.xml"), "${CONTAINER_CONFIG_BASE_PATH}/users.xml")
            .waitingFor(Wait.forLogMessage(".*CWWKZ0001I: Application .* started in .* seconds.*", 1)).withStartupTimeout(Duration.ofMinutes(2))
            .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("openLiberty")))

    @Shared
    Jsonb jsonb

    def requestBody

    @Shared
    HttpClient client

    @Shared
    String appUrl

    def setup() {
        openLiberty.start()
        appUrl = "http://${openLiberty.getHost()}:${openLiberty.getMappedPort(9080)}/inventory"

        client = HttpClient.newHttpClient()
        jsonb = JsonbBuilder.create()
        requestBody = TestItemProvider.generateRandomItem()
    }

    def 'Successful create item and persist it into JSON, HTML and CSV'() {
        when: 'the call is succeeded'
        def response = doPost(jsonb.toJson(requestBody))

        then: 'empty response body means successful request'
        response.status == Response.Status.CREATED.statusCode
    }

    def 'Failed to create the item'() {
        given: 'an item with null name'
        def requestBody = TestItemProvider.createItemWithNullName()

        when: 'calling the application with this item'
        def response = doPost(jsonb.toJson(requestBody))

        then: 'a response with error message is returned'
        response.status != Response.Status.CREATED.statusCode
        response.readEntity(ErrorResponse.class).errors.size() > 0
    }

    def 'Successfully get an item'() {
        given: 'an item is already created'
        doPost(jsonb.toJson(requestBody))

        when: 'the call is made to the get api'
        def response = doGet()

        then: 'the response contains an OK status'
        response.status == Response.Status.OK.statusCode

        and: 'the response body contains the correct information'
        def returnedItemMap = response.readEntity(List.class)[0]

        returnedItemMap.name == requestBody.name
        returnedItemMap.serialNumber == requestBody.serialNumber
        returnedItemMap.value == requestBody.value
    }

    def 'Successfully delete an item'() {
        given: 'an item is already created'
        doPost(jsonb.toJson(requestBody))

        when: 'the call is made to the delete api'
        def response = doDelete(requestBody.serialNumber)

        then: 'the response contains an OK status'
        response.status == Response.Status.NO_CONTENT.statusCode
    }

    def doPost(Object requestPayload) {
        Client client = ClientBuilder.newClient()
        String targetUrl = "$appUrl/tracking-inventory/inventory"
        return client.target(targetUrl)
                .request(MediaType.APPLICATION_JSON)
                .post(Entity.json(requestPayload))
    }

    def doGet() {
        Client client = ClientBuilder.newClient()
        String targetUrl = "$appUrl/tracking-inventory/inventory"
        return client.target(targetUrl)
                .request(MediaType.APPLICATION_JSON)
                .get()
    }
}

Here I am mounting the war and my server.xml file as well as various configuration files. The WAR file gets placed into build/libs folder and I have created a task in my build.gradle file for this procedure which is the tests.dependsOn war, to properly generate it before tests execution.

My problem is that upon running the first test, I am getting the next error:

org.testcontainers.containers.ContainerLaunchException: Container startup failed for image open-liberty:23.0.0.12-full-java17-openj9
    at app//org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:359)
    at app//org.testcontainers.containers.GenericContainer.start(GenericContainer.java:330)
    at org.testcontainers.spock.TestcontainersMethodInterceptor.startContainers_closure3(TestcontainersMethodInterceptor.groovy:83)
    at app//groovy.lang.Closure.call(Closure.java:433)
    at app//groovy.lang.Closure.call(Closure.java:422)
    at app//org.testcontainers.spock.TestcontainersMethodInterceptor.startContainers(TestcontainersMethodInterceptor.groovy:80)
    at app//org.testcontainers.spock.TestcontainersMethodInterceptor.interceptSetupSpecMethod(TestcontainersMethodInterceptor.groovy:25)
    at app//org.spockframework.runtime.extension.AbstractMethodInterceptor.intercept(AbstractMethodInterceptor.java:36)
    at app//org.spockframework.runtime.extension.MethodInvocation.proceed(MethodInvocation.java:101)
    at app//org.spockframework.runtime.model.MethodInfo.invoke(MethodInfo.java:156)
    at java.base@17.0.9/java.util.ArrayList.forEach(ArrayList.java:1511)
Caused by: org.rnorth.ducttape.RetryCountExceededException: Retry limit hit with exception
    at app//org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:88)
    at app//org.testcontainers.containers.GenericContainer.doStart(GenericContainer.java:344)
    ... 10 more
Caused by: org.testcontainers.containers.ContainerLaunchException: Could not create/start container
    at app//org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:563)
    at app//org.testcontainers.containers.GenericContainer.lambda$doStart$0(GenericContainer.java:354)
    at app//org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:81)
    ... 11 more
Caused by: org.testcontainers.containers.ContainerLaunchException: Timed out waiting for URL to be accessible (http://localhost:55240/ should return HTTP [200])
    at app//org.testcontainers.containers.wait.strategy.HttpWaitStrategy.waitUntilReady(HttpWaitStrategy.java:320)
    at app//org.testcontainers.containers.wait.strategy.AbstractWaitStrategy.waitUntilReady(AbstractWaitStrategy.java:52)
    at app//org.testcontainers.containers.GenericContainer.waitUntilContainerStarted(GenericContainer.java:909)
    at app//org.testcontainers.containers.GenericContainer.tryStart(GenericContainer.java:500)
    ... 13 more
Caused by: org.rnorth.ducttape.TimeoutException: Timeout waiting for result with exception
    at app//org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess(Unreliables.java:54)
    at app//org.testcontainers.containers.wait.strategy.HttpWaitStrategy.waitUntilReady(HttpWaitStrategy.java:252)
    ... 16 more
Caused by: java.lang.RuntimeException: java.net.SocketException: Unexpected end of file from server
    at org.testcontainers.containers.wait.strategy.HttpWaitStrategy.lambda$null$6(HttpWaitStrategy.java:312)
    at org.rnorth.ducttape.ratelimits.RateLimiter.doWhenReady(RateLimiter.java:27)
    at org.testcontainers.containers.wait.strategy.HttpWaitStrategy.lambda$waitUntilReady$7(HttpWaitStrategy.java:257)
    at org.rnorth.ducttape.unreliables.Unreliables.lambda$retryUntilSuccess$0(Unreliables.java:43)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
    at java.base/java.lang.Thread.run(Thread.java:857)
Caused by: java.net.SocketException: Unexpected end of file from server
    at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:954)
    at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:761)
    at java.base/sun.net.www.http.HttpClient.parseHTTPHeader(HttpClient.java:951)
    at java.base/sun.net.www.http.HttpClient.parseHTTP(HttpClient.java:761)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream0(HttpURLConnection.java:1688)
    at java.base/sun.net.www.protocol.http.HttpURLConnection.getInputStream(HttpURLConnection.java:1589)
    at java.base/java.net.HttpURLConnection.getResponseCode(HttpURLConnection.java:529)
    at org.testcontainers.containers.wait.strategy.HttpWaitStrategy.lambda$null$6(HttpWaitStrategy.java:276)
    ... 7 more

The container logs are the following:

WARNING: Unknown module: jdk.management.agent specified to --add-exports
WARNING: Unknown module: jdk.attach specified to --add-exports
Launching defaultServer (Open Liberty 23.0.0.12/wlp-1.0.84.cl231220231127-1901) on Eclipse OpenJ9 VM, version 17.0.10+7 (en_US)
[AUDIT   ] CWWKE0001I: The server defaultServer has been launched.
[AUDIT   ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml
[AUDIT   ] CWWKG0093A: Processing configuration drop-ins resource: /opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/open-default-port.xml
[AUDIT   ] CWWKG0028A: Processing included configuration resource: /opt/ol/wlp/usr/servers/defaultServer/users.xml
[AUDIT   ] CWWKG0028A: Processing included configuration resource: /opt/ol/wlp/usr/servers/defaultServer/GeneratedSSLInclude.xml
[AUDIT   ] CWWKG0102I: Found conflicting settings for defaultKeyStore instance of keyStore configuration.
  Property password has conflicting values:
    Secure value is set in file:/opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/keystore.xml.
    Secure value is set in file:/opt/ol/wlp/usr/servers/defaultServer/GeneratedSSLInclude.xml.
  Property password will be set to the value defined in file:/opt/ol/wlp/usr/servers/defaultServer/GeneratedSSLInclude.xml.

[AUDIT   ] CWWKG0102I: Found conflicting settings for defaultHttpEndpoint instance of httpEndpoint configuration.
  Property host has conflicting values:
    Value * is set in file:/opt/ol/wlp/usr/servers/defaultServer/configDropins/defaults/open-default-port.xml.
    Value localhost is set in file:/opt/ol/wlp/usr/servers/defaultServer/server.xml.
  Property host will be set to localhost.

[AUDIT   ] CWWKZ0058I: Monitoring dropins for applications.
[AUDIT   ] CWWKS4104A: LTPA keys created in 0.206 seconds. LTPA key file: /opt/ol/wlp/output/defaultServer/resources/security/ltpa.keys
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/jwt/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/openapi/platform/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/openapi/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/IBMJMXConnectorREST/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/metrics/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/health/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/openapi/ui/
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/ibm/api/
[AUDIT   ] CWPKI0803A: SSL certificate created in 1.513 seconds. SSL key file: /opt/ol/wlp/output/defaultServer/resources/security/key.p12
[AUDIT   ] CWWKI0001I: The CORBA name server is now available at corbaloc:iiop:localhost:2809/NameService.
[AUDIT   ] CWWKT0016I: Web application available (default_host): http://localhost:9080/
[AUDIT   ] CWWKZ0001I: Application tracking-inventory-0.0.1-SNAPSHOT started in 2.275 seconds.
[AUDIT   ] CWWKF1037I: The server added the [appAuthentication-2.0, appAuthorization-2.0, appClientSupport-2.0, appSecurity-4.0, batch-2.0, beanValidation-3.0, cdi-3.0, concurrent-2.0, connectors-2.0, connectorsInboundSecurity-2.0, enterpriseBeans-4.0, enterpriseBeansHome-4.0, enterpriseBeansLite-4.0, enterpriseBeansPersistentTimer-4.0, enterpriseBeansRemote-4.0, expressionLanguage-4.0, faces-3.0, jakartaee-9.1, json-1.0, jsonb-2.0, jsonp-2.0, jwt-1.0, localConnector-1.0, mail-2.0, managedBeans-2.0, mdb-4.0, messaging-3.0, messagingClient-3.0, messagingSecurity-3.0, messagingServer-3.0, microProfile-5.0, monitor-1.0, mpConfig-3.0, mpFaultTolerance-4.0, mpHealth-4.0, mpJwt-2.0, mpMetrics-4.0, mpOpenAPI-3.0, mpOpenTracing-3.0, mpRestClient-3.0, pages-3.0, persistence-3.0, persistenceContainer-3.0, restConnector-2.0, restfulWS-3.0, restfulWSClient-3.0, servlet-5.0, transportSecurity-1.0, webProfile-9.1, websocket-2.0, xmlBinding-3.0, xmlWS-3.0] features to the existing feature set.
[AUDIT   ] CWWKF0012I: The server installed the following features: [appAuthentication-2.0, appAuthorization-2.0, appClientSupport-2.0, appSecurity-4.0, batch-2.0, beanValidation-3.0, cdi-3.0, concurrent-2.0, connectors-2.0, connectorsInboundSecurity-2.0, distributedMap-1.0, enterpriseBeans-4.0, enterpriseBeansHome-4.0, enterpriseBeansLite-4.0, enterpriseBeansPersistentTimer-4.0, enterpriseBeansRemote-4.0, expressionLanguage-4.0, faces-3.0, jakartaee-9.1, jdbc-4.2, jndi-1.0, json-1.0, jsonb-2.0, jsonp-2.0, jwt-1.0, localConnector-1.0, mail-2.0, managedBeans-2.0, mdb-4.0, messaging-3.0, messagingClient-3.0, messagingSecurity-3.0, messagingServer-3.0, microProfile-5.0, monitor-1.0, mpConfig-3.0, mpFaultTolerance-4.0, mpHealth-4.0, mpJwt-2.0, mpMetrics-4.0, mpOpenAPI-3.0, mpOpenTracing-3.0, mpRestClient-3.0, pages-3.0, persistence-3.0, persistenceContainer-3.0, restConnector-2.0, restfulWS-3.0, restfulWSClient-3.0, servlet-5.0, ssl-1.0, transportSecurity-1.0, webProfile-9.1, websocket-2.0, xmlBinding-3.0, xmlWS-3.0].
[AUDIT   ] CWWKF0013I: The server removed the following features: [appClientSupport-1.0, appSecurity-2.0, appSecurity-3.0, batch-1.0, beanValidation-2.0, cdi-2.0, concurrent-1.0, ejb-3.2, ejbHome-3.2, ejbLite-3.2, ejbPersistentTimer-3.2, ejbRemote-3.2, el-3.0, j2eeManagement-1.1, jacc-1.5, jaspic-1.1, javaMail-1.6, javaee-8.0, jaxb-2.2, jaxrs-2.1, jaxrsClient-2.1, jaxws-2.2, jca-1.7, jcaInboundSecurity-1.0, jms-2.0, jpa-2.2, jpaContainer-2.2, jsf-2.3, jsonb-1.0, jsonp-1.1, jsp-2.3, managedBeans-1.0, mdb-3.2, servlet-4.0, wasJmsClient-2.0, wasJmsSecurity-1.0, wasJmsServer-1.0, webProfile-8.0, websocket-1.1].
[AUDIT   ] CWWKF0011I: The defaultServer server is ready to run a smarter planet. The defaultServer server started in 6.601 seconds.

I tried to run the image outside my tests using the docker run --rm -p 9080:9080 open-liberty:23.0.0.12-full-java17-openj9 command and then check the http://localhost:9080 and it is working as expected. Why am I losing connection with the server? What am I missing?


Solution

  • I managed to solve my issue by configuring the <httpEndpoint> element in my server.xml file and specifically the host parameter as host="*". Beforehand I had it set to host="localhost" and this was limiting the communication to my container. I am providing the server.xml for future reference:

    <?xml version="1.0" encoding="UTF-8"?>
    <server description="new server">
    
        <!-- Enable features -->
        <featureManager>
            <feature>jakartaee-9.1</feature>
            <feature>microProfile-5.0</feature>
            <feature>localConnector-1.0</feature>
        </featureManager>
    
        <include location="users.xml" optional="true"/>
    
        <!-- SSL Config -->
        <ssl id="defaultSSLConfig" trustDefaultCerts="true"/>
        <include location="GeneratedSSLInclude.xml"/>
    
        <!-- App Config -->
        <httpEndpoint
                host="*"
                httpPort="${default.http.port}"
                httpsPort="${default.https.port}"
                id="defaultHttpEndpoint"/>
        <applicationManager autoExpand="true"/>
        <webApplication
                location="tracking-inventory-0.0.1-SNAPSHOT.war"
                context-root="/">
        </webApplication>
    </server>