Skip to content

Introduce application containers #7162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

KyleAure
Copy link
Contributor

@KyleAure KyleAure commented Jun 7, 2023

This is a draft PR to further explore the development of application platform containers.
Related Issue #7098

@KyleAure KyleAure force-pushed the 7098-application-container branch from dd5dd51 to 4f226f9 Compare June 7, 2023 15:07
Copy link
Member

@eddumelendez eddumelendez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the quick draft, @KyleAure ! I'm really excited about this. I've left some comments and suggestions.

@KyleAure KyleAure marked this pull request as ready for review June 13, 2023 14:13
@KyleAure KyleAure requested a review from a team as a code owner June 13, 2023 14:13
@KyleAure KyleAure changed the title WIP blueprint for application containers Introduce application containers Jun 13, 2023
Copy link
Member

@eddumelendez eddumelendez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, @KyleAure ! I have left some comments. Please, also run ./gradlew spotlessApply and let's take into account the Java version baseline for Testcontainers is 8.

* @param archives - An archive created using shrinkwrap and test runtime
* @return self
*/
public ApplicationServerContainer withArchvies(@NonNull Archive<?>... archives) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public ApplicationServerContainer withArchvies(@NonNull Archive<?>... archives) {
public ApplicationServerContainer withArchives(@NonNull Archive<?>... archives) {

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, we would like to be more open about how the archive is provided. WDYT about receiving File... archives instead?

AFAIK we can do something like this.

WebArchive archive = ShrinkWrap.create(WebArchive.class,"jakarta-tc.war")
        .addClass(App.class)
        .addPackages(true,
                "dev.wittek.tc.jakarta.book",
                "dev.wittek.tc.jakarta.dice",
                "dev.wittek.tc.jakarta.time",
                "dev.wittek.tc.jakarta.turing")
        .addAsResource("META-INF/persistence.xml")
        .addAsResource("META-INF/init.sql")
        .addAsResource("turing.csv")
        .addAsLibraries(deps);
archive.as(ZipExporter.class).exportTo(Paths.get("target/jakarta-tc.war").toFile(), true);

This way we can support those using ShrinkWrap and those who doesn't

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed spelling error.

I think having native ShrinkWrap support would really help with ease of use.
If users are looking to replace Arquillian with TestContainers then those users will never have had to export their archives to the host system since Arquillian takes care of that behind the scenes.

Without this kind of support I'd say it isn't even worth creating the ApplicationContainer and users could just use a GenericContainer. ShrinkWrap is the value add here.

This way we can support those using ShrinkWrap and those who do not

This is achievable with the pre-existing method:

public ApplicationServerContainer withArchives(@NonNull MountableFile... archives)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

* @param httpPort - The HTTP port used by the Application platform
* @return self
*/
public ApplicationServerContainer withHttpPort(int httpPort) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please elaborate more on the use cases for this?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the port a user would use to connect to their application.

Similar to the database containers we want to be able to construct a connection URL for testing getApplicationURL().

It is common for application containers to be configured with a different port than the default.

Example:

private static ApplicationServerContainer liberty = new LibertyServerContainer(libertyImage)
        .withArchives(createDeployment())
        .withAppContextRoot("test/app/service/")
        .withHttpPort(80);

@Test
public void testURL() {
  String actual = liberty.getApplicationURL();
  String expected = "http://localhost:35286/test/app/service" //where 35286 maps to port 80
  assertThat(actual).isEqualTo(expected);
}

You could argue that we could just rely on firstMappedPort but for future iterations, I could see us adding in withHttpsPort and getSecureApplicationURL() which wouldn't be able to relay on that.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is common for application containers to be configured with a different port than the default.

even for testing? Just curious. I mean, it can be done but I was not expecting it in testing, at least not very often.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, if I am an application developer who wants to do integration testing I will have my test application server configured to the same specification as my production application server. Most applications do not run on the default port 9080.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move the method body to the configure method. The logic about replacePort, appendPort seems a little bit odd here. I think we don't need to overwrite setExposedPorts neither.

Tests in ApplicationServerContainerTest should call configure before getExposedPorts() in order to get the proper container configuration. Also, I'm not sure order is required for ports. LMK if I'm missing something

Please, also add tests when readinessPort is set.

@KyleAure KyleAure force-pushed the 7098-application-container branch from b957fd8 to 2a0359c Compare June 19, 2023 15:24
@KyleAure
Copy link
Contributor Author

KyleAure commented Jun 19, 2023

@eddumelendez Responded to your review comments and updated where necessary.

As for running the spotless gradle task, I keep getting the following error:

Could not determine the dependencies of task ':application-server-commons:spotlessCheck'.
> Could not create task ':application-server-commons:spotlessJavaCheck'.
   > Could not create task ':application-server-commons:spotlessJava'.
      > Running npm command 'npm install --no-audit --no-package-lock --no-fund --prefer-offline' failed with exit code: 1

I've tried uninstalling and reinstalling node multiple times and I honestly cannot get this gradle task working.
Honestly, I don't like that this task has a dependency on node/npm seems out of place in a java project.

** got this working on another system and pushed the spotless changes **

@@ -0,0 +1,56 @@
# Liberty Containers
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In order to display the docs we should add a section Application Servers just like we have it for databases

- Modules:
- Databases:

Comment on lines +3 to +7
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tests failed because of the Java version. Currently, Testcontainers uses Java 8 but we can perform tests with other version declaring the following in the build.gradle

test {
javaLauncher = javaToolchains.launcherFor {
languageVersion = JavaLanguageVersion.of(11)
}
}
compileTestJava {
javaCompiler = javaToolchains.compilerFor {
languageVersion = JavaLanguageVersion.of(11)
}
options.release.set(11)
}

Copy link
Member

@eddumelendez eddumelendez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@KyleAure few more comments

// Container defaults
static final int DEFAULT_HTTP_PORT = 9080;

static final int DEFAULT_HTTPS_PORT = 9443;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unused for now. So, it can be removed.


@GET
public String getAll() {
System.out.println("Calling getAll");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can remove the println calls

//This is unintuitive and should have a better solution.

// configureLiberty {
liberty.withEnv("DB_URL", mockDatabase.getNetworkAliases().get(0) + ":1080");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use the network alias instead to be explicit. Otherwise, the network alias mockDatabase declared in MockServerContainer is not being used.

Suggested change
liberty.withEnv("DB_URL", mockDatabase.getNetworkAliases().get(0) + ":1080");
liberty.withEnv("DB_URL", "mockDatabase:1080");

Comment on lines +50 to +52

//Note cannot use mockDatabase.getEndpoint() since it will return http://localhost:56254 when instead we need http://mockDatabase:1080
//This is unintuitive and should have a better solution.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using container networks, services can call each other through the network alias and the direct port. That's how it is done in docker-compose. I agree that in order to know the ports right now can be challenging but I think javadocs around it can help. That's something to tackle around all Testcontainers modules.

Suggested change
//Note cannot use mockDatabase.getEndpoint() since it will return http://localhost:56254 when instead we need http://mockDatabase:1080
//This is unintuitive and should have a better solution.

Comment on lines +61 to +63
public ApplicationServerContainer(@NonNull final Future<String> image) {
super(image);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not being used. It can be removed.

private Duration httpWaitTimeout = Duration.ofSeconds(60);

// Expected path for Microprofile platforms to query for readiness
static final String MP_HEALTH_READINESS_PATH = "/health/ready";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is not being used.

* @return self
*/
public ApplicationServerContainer withAppContextRoot(@NonNull String appContextRoot) {
if (Objects.nonNull(this.appContextRoot) && this.appContextRoot == this.readinessPath) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (Objects.nonNull(this.appContextRoot) && this.appContextRoot == this.readinessPath) {
if (Objects.nonNull(this.appContextRoot) && this.appContextRoot.equals( this.readinessPath)) {

if (path.matches(".*\\.jar|.*\\.war|.*\\.ear|.*\\.rar")) {
return path.substring(path.lastIndexOf("/"));
}
throw new IllegalArgumentException("File did not contain an application archive");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
throw new IllegalArgumentException("File did not contain an application archive");
throw new IllegalArgumentException("File did not contain a valid application archive");

* @param httpPort - The HTTP port used by the Application platform
* @return self
*/
public ApplicationServerContainer withHttpPort(int httpPort) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to move the method body to the configure method. The logic about replacePort, appendPort seems a little bit odd here. I think we don't need to overwrite setExposedPorts neither.

Tests in ApplicationServerContainerTest should call configure before getExposedPorts() in order to get the proper container configuration. Also, I'm not sure order is required for ports. LMK if I'm missing something

Please, also add tests when readinessPort is set.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants