View Javadoc
1   /**
2    * This file Copyright (c) 2017 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.integrationtests.docker;
35  
36  import static com.github.dockerjava.api.model.ExposedPort.tcp;
37  import static com.github.dockerjava.api.model.Ports.Binding.bindPort;
38  
39  import info.magnolia.integrationtests.IntegrationTestSettings;
40  
41  import java.nio.file.Paths;
42  import java.time.Duration;
43  import java.time.temporal.ChronoUnit;
44  import java.util.function.UnaryOperator;
45  import java.util.stream.IntStream;
46  import java.util.stream.Stream;
47  
48  import org.apache.commons.lang3.StringUtils;
49  import org.slf4j.Logger;
50  import org.slf4j.LoggerFactory;
51  import org.testcontainers.DockerClientFactory;
52  import org.testcontainers.containers.BindMode;
53  import org.testcontainers.containers.GenericContainer;
54  import org.testcontainers.containers.wait.LogMessageWaitStrategy;
55  
56  import com.github.dockerjava.api.DockerClient;
57  import com.github.dockerjava.api.command.CreateNetworkResponse;
58  import com.github.dockerjava.api.command.RemoveContainerCmd;
59  import com.github.dockerjava.api.model.Container;
60  import com.github.dockerjava.api.model.PortBinding;
61  
62  /**
63   * Programmatically sets up Docker environment for the integration tests. Spins up
64   * an application server container (Tomcat by default), a Selenium Hub and several
65   * workers, all in a dedicated Docker network.
66   *
67   * While this might not be a most powerful/efficient setup for integration tests, it
68   * is simple and does not require much Docker knowledge from the user.
69   */
70  public class IntegrationTestEnvironment {
71  
72      private static final Logger log = LoggerFactory.getLogger(IntegrationTestEnvironment.class);
73  
74      private final IntegrationTestSettings testSettings;
75  
76      private final DockerClient docker = DockerClientFactory.instance().client();
77  
78      public IntegrationTestEnvironment(IntegrationTestSettings testSettings) {
79          this.testSettings = testSettings;
80      }
81  
82      public IntegrationTestEnvironment() {
83          this(IntegrationTestSettings.access());
84      }
85  
86      public void initialiseTestEnvironment() {
87          if (!testSettings.testEnvironmentSetupRequired()) {
88              return;
89          }
90  
91          log.info("Preparing environment for the Selenium UI tests");
92  
93          // [re-]start the test network
94          ensureTestNetworkIsUp();
95          // [re-]start Selenium Hub
96          launchSeleniumHub();
97          // [re-]launch tomcat
98          launchApplicationServer();
99  
100         log.info("Done preparing environment for the Selenium UI tests");
101     }
102 
103     private void ensureTestNetworkIsUp() {
104         boolean netWorkCreationRequired = docker.listNetworksCmd().withNameFilter(testSettings.testNetworkName()).exec().isEmpty();
105 
106         if (netWorkCreationRequired) {
107             log.info("Creating Docker network [{}]", testSettings.testNetworkName());
108 
109             final CreateNetworkResponse networkCreationResult =
110                     docker.createNetworkCmd()
111                             .withName(testSettings.testNetworkName())
112                             .exec();
113 
114             log.info("Successfully created Docker network [{}] with id [{}]", testSettings.testNetworkName(), networkCreationResult.getId());
115         } else {
116             log.info("[{}] network exists, will not try to create it");
117         }
118     }
119 
120     private void launchSeleniumHub() {
121         log.info("Starting Selenium Hub container");
122         recreateContainer(
123                 testSettings.seleniumHubContainerName(),
124                 "selenium/hub:3.7.1",
125                 hub -> hub
126                         .withCreateContainerCmdModifier(cmd ->
127                                 cmd.withPortBindings(new PortBinding(
128                                         bindPort(testSettings.seleniumHubPort()),
129                                         tcp(testSettings.seleniumHubPort()))))
130                         .withStartupTimeout(Duration.of(3, ChronoUnit.SECONDS))
131                         .withLogConsumer(outputFrame -> log.info(outputFrame.getUtf8String()))
132                         .withCreateContainerCmdModifier(cmd -> cmd.withNetworkMode(testSettings.testNetworkName())));
133 
134 
135         log.info("Starting {} Selenium Node containers", testSettings.concurrentTestCases());
136         IntStream.range(0, testSettings.concurrentTestCases()).mapToObj(index ->
137                 new SeleniumGridNodeContainer()
138                         .withDebugMode(testSettings.debugMode())
139                         .withTestNetwork(testSettings.testNetworkName())
140                         .withSeleniumHubHost(testSettings.seleniumHubContainerName())
141                         .withSeleniumHubPort(testSettings.seleniumHubPort())
142                         .withUploadDirectory(testSettings.uploadDirectory())
143                         .withTargetBrowser(testSettings.seleniumBrowser()))
144                 .forEach(GenericContainer::start);
145     }
146 
147     private void launchApplicationServer() {
148         log.info("Starting application server");
149         recreateContainer(
150                 "server",
151                 "amd64/tomcat:8.5-jre8",
152                 tomcat -> tomcat
153                         .withCreateContainerCmdModifier(cmd -> cmd.withPortBindings(new PortBinding(bindPort(8599), tcp(8080))))
154                         .withCreateContainerCmdModifier(cmd -> cmd.withNetworkMode(testSettings.testNetworkName()))
155                         .withEnv("JAVA_OPTS", "-Xmx2048m -Dmagnolia.update.auto=true")
156                         .waitingFor(new LogMessageWaitStrategy()
157                                 .withRegEx("^.*Catalina\\.start Server startup in [0-9]+ ms\n$")
158                                 .withStartupTimeout(Duration.of(testSettings.startupTimeout(), ChronoUnit.MINUTES)))
159                         .withLogConsumer(outputFrame -> log.info(outputFrame.getUtf8String().replaceFirst("\n$", "")))
160                         .withFileSystemBind(
161                                 Paths.get(testSettings.authorWarLocation()).toAbsolutePath().toString(),
162                                 String.format("/usr/local/tomcat/webapps/%s.war", StringUtils.removeEnd(testSettings.authorContextPath(), "/")),
163                                 BindMode.READ_WRITE)
164                         .withFileSystemBind(
165                                 Paths.get(testSettings.publicWarLocation()).toAbsolutePath().toString(),
166                                 String.format("/usr/local/tomcat/webapps/%s.war", StringUtils.removeEnd(testSettings.publicContextPath(), "/")),
167                                 BindMode.READ_WRITE));
168     }
169 
170     private void recreateContainer(String name, String image, UnaryOperator<GenericContainer<?>> containerConfigurer) {
171         docker.listContainersCmd()
172                 .withShowAll(true)
173                 .exec().stream()
174                 .filter(container -> Stream.of(container.getNames()).anyMatch(containerName -> containerName.equals(String.format("/%s", name))))
175                 .map(Container::getId)
176                 .findFirst()
177                 .map(containerId -> docker.removeContainerCmd(containerId).withForce(true))
178                 .ifPresent(RemoveContainerCmd::exec);
179 
180         containerConfigurer.apply(new GenericContainer<>(image).withCreateContainerCmdModifier(cmd -> cmd.withName(name))).start();
181     }
182 }