Skip to content
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

Add TestContainerResourceManager for third party resources. #27642

Merged
merged 1 commit into from
Jul 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 37 additions & 0 deletions it/testcontainers/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* License); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an AS IS BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins { id 'org.apache.beam.module' }
applyJavaNature(
automaticModuleName: 'org.apache.beam.it.testcontainers',
enableSpotbugs: false,
validateShadowJar: false,
shadowClosure: {},
)

description = "Apache Beam :: IT :: Testcontainers"
ext.summary = "Integration test utilities for Testcontainers."

dependencies {
implementation project(path: ":it:common", configuration: "shadow")
implementation library.java.testcontainers_base

testImplementation library.java.truth
testImplementation library.java.mockito_core
testRuntimeOnly library.java.slf4j_simple
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.it.testcontainers;

import java.util.concurrent.Callable;
import javax.annotation.Nullable;
import org.apache.beam.it.common.ResourceManager;
import org.apache.beam.it.common.TestProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testcontainers.containers.GenericContainer;

/**
* Abstract class for managing TestContainers resources in integration tests.
*
* <p>Resource managers that extend this class will use TestContainers as a backend to spin up
* resources.
*
* <p>Optionally, a static resource can be specified by calling the useStaticContainer() method in
* the {@link Builder} class. A static resource is a pre-configured database or other resource that
* is ready to be connected to by the resource manager. This could be a pre-existing TestContainer
* that has not been closed, a local database instance, a remote VM, or any other source that can be
* connected to. If a static container is used, the host and port must also be configured using the
* Builder's setHost() and setPort() methods, respectively.
*/
public abstract class TestContainerResourceManager<T extends GenericContainer<?>>
implements ResourceManager {
private static final Logger LOG = LoggerFactory.getLogger(TestContainerResourceManager.class);

protected static final String HOST_IP = TestProperties.hostIp();
private final T container;
private final boolean usingStaticContainer;
private final String host;
protected int port;

protected <B extends Builder<?>> TestContainerResourceManager(T container, B builder) {
this(container, builder, null);
}

protected <B extends Builder<?>> TestContainerResourceManager(
T container, B builder, @Nullable Callable<Void> setup) {
this.container = container;
this.usingStaticContainer = builder.useStaticContainer;
this.host = builder.host == null ? HOST_IP : builder.host;
this.port = builder.port;

if (setup != null) {
try {
setup.call();
} catch (Exception e) {
throw new TestContainerResourceManagerException("Error running setup function.", e);
}
}

if (!usingStaticContainer) {
container.start();
} else if (builder.host == null || builder.port < 0) {
throw new TestContainerResourceManagerException(
"This manager was configured to use a static resource, but the host and port were not properly set.");
}
}

protected String getDockerImageName() {
return container.getDockerImageName();
}

/**
* Returns the host that the resource is running on. If a host was specified in the builder, then
* this method will return that value. Otherwise, the host will default to localhost.
*
* @return the host that the resource is running on
*/
public String getHost() {
return host;
}

/**
* Returns the port that the resource is actually mapped to on the host. When using a non-static
* resource, the TestContainers framework will map the resource port to a random port on the host.
* This method will return the mapped port that the resource traffic is routed through.
*
* <p>Note: When using a static container, the port that was specified in the builder will be
* returned regardless of what is passed into this method as a parameter.
*
* @param mappedPort the port the resource was configured to listen on
* @return the mapped port on the host
*/
protected int getPort(int mappedPort) {
if (port < 0) {
return container.getMappedPort(mappedPort);
}
return port;
}

/**
* Deletes all created resources (VM's, etc.) and stops the container, making the manager object
* unusable.
*
* @throws TestContainerResourceManagerException if there is an error deleting the TestContainers
* resources.
*/
@Override
public void cleanupAll() throws TestContainerResourceManagerException {
if (usingStaticContainer) {
LOG.info("This manager was configured to use a static resource that will not be cleaned up.");
return;
}

LOG.info("Attempting to cleanup TestContainers manager.");
try {
container.close();
LOG.info("TestContainers manager successfully cleaned up.");
} catch (Exception e) {
throw new TestContainerResourceManagerException("Failed to close TestContainer resources", e);
}
}

/** Builder for {@link TestContainerResourceManager}. */
public abstract static class Builder<T extends TestContainerResourceManager<?>> {

public String testId;
public String containerImageName;
public String containerImageTag;
public @Nullable String host;
public int port;
public boolean useStaticContainer;

public Builder(String testId, String containerImageName, String containerImageTag) {
this.testId = testId;
this.containerImageName = containerImageName;
this.containerImageTag = containerImageTag;
this.host = null;
this.port = -1;
}

/**
* Sets the name of the test container image. The tag is typically the version of the image.
*
* @param containerName The name of the container image.
* @return this builder object with the image name set.
*/
public Builder<T> setContainerImageName(String containerName) {
this.containerImageName = containerName;
return this;
}

/**
* Sets the tag for the test container. The tag is typically the version of the image.
*
* @param containerTag The tag to use for the container.
* @return this builder object with the tag set.
*/
public Builder<T> setContainerImageTag(String containerTag) {
this.containerImageTag = containerTag;
return this;
}

/**
* Sets the host of the resource that the resource manager will connect to. This will default to
* localhost if not set.
*
* @param containerHost the resource host address.
* @return this builder object with the host set.
*/
public Builder<T> setHost(String containerHost) {
this.host = containerHost;
return this;
}

/**
* Sets the port that the resource is hosted on.
*
* @param port the port the resource is hosted on.
* @return this builder object with the port set.
*/
public Builder<T> setPort(int port) {
this.port = port;
return this;
}

/**
* Configures the resource manager to use a static resource instead of creating a new
* TestContainer instance of the resource. This can be another TestContainer or any other VM or
* server hosting the resource.
*
* <p>Note: When this option is enabled, the setPort() and setHost() methods must also be called
* to configure the static resource address.
*
* @return this builder object with the useStaticContainer option enabled.
*/
public Builder<T> useStaticContainer() {
this.useStaticContainer = true;
return this;
}

/**
* Builds and returns a Resource Manager that extends TestContainerResourceManager.
*
* @return an instance of TestContainerResourceManager
*/
public abstract T build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.it.testcontainers;

/** Custom exception for {@link TestContainerResourceManager} implementations. */
public class TestContainerResourceManagerException extends RuntimeException {

public TestContainerResourceManagerException(String errorMessage) {
super(errorMessage);
}

public TestContainerResourceManagerException(String errorMessage, Exception cause) {
super(errorMessage, cause);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.beam.it.testcontainers;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/** Annotation that marks the test that requires TestContainers. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface TestContainersIntegrationTest {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

/** Package for managing TestContainers resources within integration tests. */
package org.apache.beam.it.testcontainers;
Loading
Loading