skip to Main Content

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.

package integration.gr.iag.dgtl.inventory

import gr.iag.dgtl.inventory.PropertyReader
import gr.iag.dgtl.inventory.TestItemProvider
import gr.iag.dgtl.inventory.dto.ErrorResponse
import jakarta.json.bind.Jsonb
import jakarta.json.bind.JsonbBuilder
import jakarta.ws.rs.client.Client
import jakarta.ws.rs.client.ClientBuilder
import jakarta.ws.rs.client.Entity
import jakarta.ws.rs.core.MediaType
import jakarta.ws.rs.core.Response
import org.slf4j.LoggerFactory
import org.testcontainers.containers.GenericContainer
import org.testcontainers.containers.output.Slf4jLogConsumer
import org.testcontainers.containers.wait.strategy.Wait
import org.testcontainers.spock.Testcontainers
import org.testcontainers.utility.DockerImageName
import org.testcontainers.utility.MountableFile
import spock.lang.Shared
import spock.lang.Specification

import java.net.http.HttpClient
import java.net.http.HttpRequest
import java.net.http.HttpResponse

@Testcontainers
class TrackingInventoryIntegrationSpec extends Specification {

    @Shared
    GenericContainer openLiberty = new GenericContainer(DockerImageName.parse("open-liberty:23.0.0.12-full-java17-openj9"))
            .withExposedPorts(9080)
            .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/tracking-inventory-0.0.1-SNAPSHOT.war"), "/opt/ol/wlp/usr/servers/defaultServer/apps/tracking-inventory-0.0.1-SNAPSHOT.war")
            .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/server.xml"), "/opt/ol/wlp/usr/servers/defaultServer/server.xml")
            .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/bootstrap.properties"), "/opt/ol/wlp/usr/servers/defaultServer/bootstrap.properties")
            .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/GeneratedSSLInclude.xml"), "/opt/ol/wlp/usr/servers/defaultServer/GeneratedSSLInclude.xml")
            .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/users.xml"), "/opt/ol/wlp/usr/servers/defaultServer/users.xml")
            .waitingFor(Wait.forHttp("/").forStatusCode(200))
            .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("openLiberty")))

    @Shared
    Jsonb jsonb

    def requestBody

    @Shared
    HttpClient client

    def appUrl

    def setup() {
        openLiberty.start()
        int actualPort = openLiberty.getMappedPort(9080)
        String host = openLiberty.getHost()
        appUrl = "http://$host:$actualPort"

        final String allLogs = openLiberty.getLogs()
        println("Container Logs: n$allLogs")

        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. I know that the WAR file gets placed into build/libs folder and I will try to write a task in my build.gradle file like 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 [email protected]/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?

2

Answers


  1. May I ask how you run the test? Run it inside the Liberty dev mode (you mentioned libertyDev) by hitting the enter command, or run it outside, e.g. gradle test, without a running Liberty.

    If inside dev mode, I doubt that there is port conflict between the Liberty started by dev mode and the Liberty started by the testcontainers. From your description, I guess both use 9080. For this issue, you may see this guide how to support running testcontainers test inside and outside dev mode (although the guide uses Maven).

    Login or Signup to reply.
  2. My code in Java:

        private static GenericContainer<?> openLiberty = new GenericContainer<>(DockerImageName.parse("open-liberty:23.0.0.12-full-java17-openj9"))
                .withExposedPorts(9080)
                .withCopyFileToContainer(MountableFile.forHostPath("./target/guide-cdi-intro.war"), "/opt/ol/wlp/usr/servers/defaultServer/apps/guide-cdi-intro.war")
                .withCopyFileToContainer(MountableFile.forHostPath("./src/main/liberty/config/server.xml"), "/opt/ol/wlp/usr/servers/defaultServer/server.xml")
                //.waitingFor(Wait.forHttp("/").forStatusCode(200))
                .withLogConsumer(new Slf4jLogConsumer(LoggerFactory.getLogger("openLiberty")));
    
        @BeforeAll
        public static void setup() throws Exception {
            openLiberty.start();
            openLiberty.waitingFor(Wait.forLogMessage("^.*CWWKF0011I.*$", 1));
            int actualPort = openLiberty.getMappedPort(9080);
            String host = openLiberty.getHost();
            appUrl = "http://" + host + ":" + actualPort;
            final String allLogs = openLiberty.getLogs();
            System.out.println("*** Container Logs: n" + allLogs);
        }
         
        @AfterAll
        public static void tearDown() {
            openLiberty.stop();
        }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search