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?
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>