package org.arquillian.cube.docker.impl.client.containerobject;
import static org.hamcrest.Matchers.hasItem;
import static org.hamcrest.Matchers.hasItems;
import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedExceptionAction;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import org.apache.commons.io.FileUtils;
import org.arquillian.cube.CubeController;
import org.arquillian.cube.CubeIp;
import org.arquillian.cube.HostPort;
import org.arquillian.cube.containerobject.Cube;
import org.arquillian.cube.containerobject.CubeDockerFile;
import org.arquillian.cube.containerobject.Environment;
import org.arquillian.cube.containerobject.Image;
import org.arquillian.cube.containerobject.Link;
import org.arquillian.cube.containerobject.Volume;
import org.arquillian.cube.docker.impl.client.config.CubeContainer;
import org.arquillian.cube.docker.impl.docker.DockerClientExecutor;
import org.arquillian.cube.docker.impl.model.DockerCube;
import org.arquillian.cube.impl.util.ReflectionUtil;
import org.arquillian.cube.spi.CubeRegistry;
import org.arquillian.cube.spi.metadata.HasPortBindings;
import org.arquillian.cube.spi.metadata.IsContainerObject;
import org.jboss.arquillian.test.spi.TestEnricher;
import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.GenericArchive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.descriptor.api.Descriptors;
import org.jboss.shrinkwrap.descriptor.api.docker.DockerDescriptor;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
public class DockerContainerObjectBuilderTest {
public static final String BASE_IMAGE = "tomee:8-jre-1.7.2-webprofile";
private CubeController cubeController;
private DockerClientExecutor dockerClientExecutor;
private CubeContainerObjectTestEnricher cubeContainerObjectTestEnricher;
private Collection<TestEnricher> enrichers;
private CubeRegistry cubeRegistry;
@Before
public void initMocks() {
cubeController = mock(CubeController.class);
dockerClientExecutor = mock(DockerClientExecutor.class);
cubeRegistry = mock(CubeRegistry.class);
cubeContainerObjectTestEnricher = mock(CubeContainerObjectTestEnricher.class);
doAnswer(DockerContainerObjectBuilderTest::objectContainerEnricherMockEnrich)
.when(cubeContainerObjectTestEnricher).enrich(any());
enrichers = Collections.singleton(cubeContainerObjectTestEnricher);
}
@Before
public void cleanupTestDirsBeforeEachTest() {
deleteTestDirectory();
}
@AfterClass
public static void cleanupTestDirsWhenDone() {
deleteTestDirectory();
}
@Test
public void shouldStartAContainerObjectDefinedUsingDockerfile() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
TestContainerObjectDefinedUsingDockerfile containerObject = new DockerContainerObjectBuilder<TestContainerObjectDefinedUsingDockerfile>(dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectDefinedUsingDockerfile.class)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
verify(cubeController, times(1)).create("containerDefinedUsingDockerfile");
verify(cubeController, times(1)).start("containerDefinedUsingDockerfile");
}
@Test
public void shouldStartAContainerObjectDefinedUsingDescriptor() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
TestContainerObjectDefinedUsingDescriptor containerObject = new DockerContainerObjectBuilder<TestContainerObjectDefinedUsingDescriptor>(
dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectDefinedUsingDescriptor.class)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
verify(cubeController, times(1)).create("containerDefinedUsingDescriptor");
verify(cubeController, times(1)).start("containerDefinedUsingDescriptor");
final File generatedDirectory = findGeneratedDirectory();
assertThat(generatedDirectory, is(notNullValue()));
assertThat(new File(generatedDirectory, "Dockerfile").exists(), is(true));
}
@Test
public void shouldLinkInnerContainersWithLink() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
TestContainerObjectWithAnnotatedLink containerObject = new DockerContainerObjectBuilder<TestContainerObjectWithAnnotatedLink>(
dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectWithAnnotatedLink.class)
.withEnrichers(enrichers)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
assertThat(containerObject.linkedContainerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
Collection<org.arquillian.cube.docker.impl.client.config.Link> links = cube.configuration().getLinks();
assertThat(links, is(notNullValue()));
assertThat(links.size(), is(1));
assertThat(links, hasItem(org.arquillian.cube.docker.impl.client.config.Link.valueOf("db:db")));
verify(cubeController, times(1)).create("containerWithAnnotatedLink");
verify(cubeController, times(1)).start("containerWithAnnotatedLink");
verify(cubeContainerObjectTestEnricher, times(1)).enrich(any(TestContainerObjectWithAnnotatedLink.class));
}
@Test
public void shouldLinkInnerContainersWithoutLink() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
TestContainerObjectWithNonAnnotatedLink containerObject = new DockerContainerObjectBuilder<TestContainerObjectWithNonAnnotatedLink>(dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectWithNonAnnotatedLink.class)
.withEnrichers(enrichers)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
assertThat(containerObject.linkedContainerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
Collection<org.arquillian.cube.docker.impl.client.config.Link> links = cube.configuration().getLinks();
assertThat(links, is(notNullValue()));
assertThat(links.size(), is(1));
assertThat(links, hasItem(org.arquillian.cube.docker.impl.client.config.Link.valueOf("inner:inner")));
verify(cubeController, times(1)).create("containerWithNonAnnotatedLink");
verify(cubeController, times(1)).start("containerWithNonAnnotatedLink");
verify(cubeContainerObjectTestEnricher, times(1)).enrich(any(TestContainerObjectWithAnnotatedLink.class));
}
@Test
public void shouldStartAContainerObjectDefinedUsingImage() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
TestContainerObjectDefinedUsingImage containerObject = new DockerContainerObjectBuilder<TestContainerObjectDefinedUsingImage>(dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectDefinedUsingImage.class)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
assertThat(cube.configuration().getImage().toImageRef(), is(BASE_IMAGE));
verify(cubeController, times(1)).create("containerDefinedUsingImage");
verify(cubeController, times(1)).start("containerDefinedUsingImage");
}
@Test
public void shouldStartAContainerObjectDefinedUsingImageAndEnvironmentVariables() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
CubeContainer ccconfig = new CubeContainer();
ccconfig.setEnv(Collections.singleton("e=f"));
CubeContainerObjectConfiguration ccoconfig = new CubeContainerObjectConfiguration(ccconfig);
TestContainerObjectDefinedUsingImageAndEnvironmentVariables containerObject = new DockerContainerObjectBuilder<TestContainerObjectDefinedUsingImageAndEnvironmentVariables>(dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectDefinedUsingImageAndEnvironmentVariables.class)
.withContainerObjectConfiguration(ccoconfig)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
assertThat(cube.configuration().getImage().toImageRef(), is(BASE_IMAGE));
assertThat(cube.configuration().getEnv(), hasItems("a=b", "c=d", "e=f"));
verify(cubeController, times(1)).create("containerWithEnvironmentVariables");
verify(cubeController, times(1)).start("containerWithEnvironmentVariables");
}
@Test
public void shouldStartAContainerObjectDefinedUsingImageAndVolumes() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
try {
CubeContainer ccconfig = new CubeContainer();
ccconfig.setBinds(Collections.singleton("/mypath3:/containerPath3:rw"));
CubeContainerObjectConfiguration ccoconfig = new CubeContainerObjectConfiguration(ccconfig);
TestContainerObjectDefinedUsingImageAndVolumes containerObject = new DockerContainerObjectBuilder<TestContainerObjectDefinedUsingImageAndVolumes>(dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectDefinedUsingImageAndVolumes.class)
.withContainerObjectConfiguration(ccoconfig)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
assertThat(cube.configuration().getImage().toImageRef(), is(BASE_IMAGE));
assertThat(cube.configuration().getBinds(), hasItems("/mypath:/containerPath:rw", "/mypath2:/containerPath2:rw", "/mypath3:/containerPath3:rw"));
verify(cubeController, times(1)).create("containerWithVolumes");
verify(cubeController, times(1)).start("containerWithVolumes");
}
@Test
public void shouldEnrichAContainerWithCubeIp() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
doAnswer(invocation -> {
// when started, initialize cube port bindings
initDockerCubeInternalIP(cubeRef.get(), "172.17.0.2");
return null;
}).when(cubeController).start("containerWithCubeIp");
try {
TestContainerObjectWithCubeIp containerObject = new DockerContainerObjectBuilder<TestContainerObjectWithCubeIp>(
dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectWithCubeIp.class)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
assertThat(containerObject.cubeIp, is("172.17.0.2"));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
assertThat(cube.hasMetadata(HasPortBindings.class), is(true));
verify(cubeController, times(1)).create("containerWithCubeIp");
verify(cubeController, times(1)).start("containerWithCubeIp");
}
@Test
public void shouldEnrichAContainerWithHostPort() {
final AtomicReference<DockerCube> cubeRef = new AtomicReference<>();
doAnswer(invocation -> {
// when started, initialize cube port bindings
initDockerCubeMappedPort(cubeRef.get(), "127.0.0.1", 8080, 8080);
return null;
}).when(cubeController).start("containerWithHostPort");
try {
TestContainerObjectWithHostPort containerObject = new DockerContainerObjectBuilder<TestContainerObjectWithHostPort>(
dockerClientExecutor, cubeController, cubeRegistry)
.withContainerObjectClass(TestContainerObjectWithHostPort.class)
.onCubeCreated(cubeRef::set)
.build();
assertThat(containerObject, is(notNullValue()));
assertThat(containerObject.port, is(8080));
} catch (IllegalAccessException|InvocationTargetException|IOException e) {
fail();
}
DockerCube cube = cubeRef.get();
assertThat(cube, is(notNullValue()));
assertThat(cube.hasMetadata(IsContainerObject.class), is(true));
assertThat(cube.getMetadata(IsContainerObject.class).getTestClass(), is(nullValue()));
assertThat(cube.hasMetadata(HasPortBindings.class), is(true));
verify(cubeController, times(1)).create("containerWithHostPort");
verify(cubeController, times(1)).start("containerWithHostPort");
}
//<editor-fold desc="container object classes used by test methods">
@Cube("containerDefinedUsingDockerfile")
@CubeDockerFile
public static class TestContainerObjectDefinedUsingDockerfile {
}
@Cube("containerDefinedUsingDescriptor")
public static class TestContainerObjectDefinedUsingDescriptor {
@CubeDockerFile
public static Archive<?> createDockerfile() {
String dockerDescriptor = Descriptors.create(DockerDescriptor.class)
.from(BASE_IMAGE)
.exportAsString();
return ShrinkWrap.create(GenericArchive.class)
.add(new StringAsset(dockerDescriptor), "Dockerfile");
}
}
@Cube("containerWithAnnotatedLink")
public static class TestContainerObjectWithAnnotatedLink {
@CubeDockerFile
public static Archive<?> createDockerfile() {
String dockerDescriptor = Descriptors.create(DockerDescriptor.class)
.from(BASE_IMAGE)
.exportAsString();
return ShrinkWrap.create(GenericArchive.class)
.add(new StringAsset(dockerDescriptor), "Dockerfile");
}
@Cube("inner")
@Link("db:db")
TestContainerObjectDefinedUsingDescriptor linkedContainerObject;
}
@Cube("containerWithNonAnnotatedLink")
public static class TestContainerObjectWithNonAnnotatedLink {
@CubeDockerFile
public static Archive<?> createDockerfile() {
String dockerDescriptor = Descriptors.create(DockerDescriptor.class)
.from(BASE_IMAGE)
.exportAsString();
return ShrinkWrap.create(GenericArchive.class)
.add(new StringAsset(dockerDescriptor), "Dockerfile");
}
@Cube("inner")
TestContainerObjectDefinedUsingDescriptor linkedContainerObject;
}
@Cube("containerDefinedUsingImage")
@Image(BASE_IMAGE)
public static class TestContainerObjectDefinedUsingImage {
}
@Cube("containerWithEnvironmentVariables")
@Image(BASE_IMAGE)
@Environment(key = "a", value = "b")
@Environment(key = "c", value = "d")
public static class TestContainerObjectDefinedUsingImageAndEnvironmentVariables {
}
@Cube("containerWithVolumes")
@Image(BASE_IMAGE)
@Volume(hostPath = "/mypath", containerPath = "/containerPath")
@Volume(hostPath = "/mypath2", containerPath = "/containerPath2")
public static class TestContainerObjectDefinedUsingImageAndVolumes {
}
@Cube("containerWithCubeIp")
@Image(BASE_IMAGE)
public static class TestContainerObjectWithCubeIp {
@CubeIp
String cubeIp;
}
@Cube(value = "containerWithHostPort", portBinding = "8080->8080/tcp")
@Image(BASE_IMAGE)
public static class TestContainerObjectWithHostPort {
@HostPort(8080)
int port;
}
//</editor-fold>
//<editor-fold desc="utility methods used by test methods">
private static void deleteTestDirectory() {
File tempDirectory = new File(System.getProperty("java.io.tmpdir"));
final File[] testsDirectories = tempDirectory.listFiles(DockerContainerObjectBuilderTest::testDirectoryFilter);
for (File testDirectory: testsDirectories) {
try {
FileUtils.deleteDirectory(testDirectory);
} catch (IOException e) {
// ignore
}
}
}
private static File findGeneratedDirectory() {
File tempDirectory = new File(System.getProperty("java.io.tmpdir"));
final File[] testsDirectories = tempDirectory.listFiles(DockerContainerObjectBuilderTest::testDirectoryFilter);
if (testsDirectories.length > 0) {
return testsDirectories[0];
} else {
return null;
}
}
private static boolean testDirectoryFilter(File dir, String name) {
return dir.isDirectory()
&& name.startsWith(DockerContainerObjectBuilder.TEMPORARY_FOLDER_PREFIX)
&& name.endsWith(DockerContainerObjectBuilder.TEMPORARY_FOLDER_SUFFIX);
}
private static Void objectContainerEnricherMockEnrich(InvocationOnMock invocation) throws Throwable {
// simulate ContainerObjectTestEnricher by setting every field annotated with @Cube with a new instance
Object containerObject = invocation.getArguments()[0];
ReflectionUtil.getFieldsWithAnnotation(containerObject.getClass(), Cube.class)
.stream().forEach(field -> {
try {
field.set(containerObject, field.getType().newInstance());
} catch (IllegalAccessException | InstantiationException e) {
throw new RuntimeException(e);
}
});
return null;
}
private static void initDockerCubeInternalIP(DockerCube dockerCube, String internalIP) {
PrivilegedExceptionAction<Void> action = () -> {
Field dockerCubePortBindingsField = DockerCube.class.getDeclaredField("portBindings");
dockerCubePortBindingsField.setAccessible(true);
Class<?> dockerCubePortBindingsClass = dockerCubePortBindingsField.getType();
Field dockerCubePortBindingsInternalIpField = dockerCubePortBindingsClass.getDeclaredField("internalIP");
dockerCubePortBindingsInternalIpField.setAccessible(true);
Object dockerCubePortBindings = dockerCubePortBindingsField.get(dockerCube);
dockerCubePortBindingsInternalIpField.set(dockerCubePortBindings, internalIP);
return null;
};
try {
AccessController.doPrivileged(action);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private static void initDockerCubeMappedPort(DockerCube dockerCube, String containerIP, int exposedPort, int boundPort) {
PrivilegedExceptionAction<Void> action = () -> {
Field dockerCubePortBindingsField = DockerCube.class.getDeclaredField("portBindings");
dockerCubePortBindingsField.setAccessible(true);
Class<?> dockerCubePortBindingsClass = dockerCubePortBindingsField.getType();
Field dockerCubePortBindingsMappedPortsField = dockerCubePortBindingsClass.getDeclaredField("mappedPorts");
dockerCubePortBindingsMappedPortsField.setAccessible(true);
Object dockerCubePortBindings = dockerCubePortBindingsField.get(dockerCube);
Map<Integer, HasPortBindings.PortAddress> mappedPorts = (Map<Integer, HasPortBindings.PortAddress>) dockerCubePortBindingsMappedPortsField.get(dockerCubePortBindings);
mappedPorts.put(exposedPort, new HasPortBindings.PortAddressImpl(containerIP, boundPort));
return null;
};
try {
AccessController.doPrivileged(action);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//</editor-fold>
}