View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.maven.buildcache.its;
20  
21  import java.io.IOException;
22  import java.nio.charset.StandardCharsets;
23  import java.nio.file.Files;
24  import java.nio.file.Path;
25  import java.nio.file.Paths;
26  import java.util.Arrays;
27  import java.util.function.Consumer;
28  import java.util.function.Predicate;
29  import java.util.regex.Pattern;
30  import java.util.stream.Collectors;
31  import java.util.stream.Stream;
32  
33  import org.apache.maven.buildcache.its.junit.BeforeEach;
34  import org.apache.maven.buildcache.its.junit.Inject;
35  import org.apache.maven.buildcache.its.junit.IntegrationTest;
36  import org.apache.maven.buildcache.its.junit.IntegrationTestExtension;
37  import org.apache.maven.it.VerificationException;
38  import org.apache.maven.it.Verifier;
39  import org.jetbrains.annotations.NotNull;
40  import org.junit.jupiter.api.AfterEach;
41  import org.junit.jupiter.params.ParameterizedTest;
42  import org.junit.jupiter.params.provider.Arguments;
43  import org.junit.jupiter.params.provider.MethodSource;
44  import org.testcontainers.containers.GenericContainer;
45  import org.testcontainers.junit.jupiter.Container;
46  import org.testcontainers.junit.jupiter.Testcontainers;
47  import org.testcontainers.utility.DockerImageName;
48  
49  import static org.apache.maven.buildcache.xml.CacheConfigImpl.REMOTE_SERVER_ID_PROPERTY_NAME;
50  import static org.apache.maven.buildcache.xml.CacheConfigImpl.REMOTE_URL_PROPERTY_NAME;
51  import static org.junit.jupiter.api.Assertions.assertFalse;
52  import static org.junit.jupiter.api.Assertions.assertTrue;
53  
54  @IntegrationTest("src/test/projects/remote-cache-dav")
55  @Testcontainers(disabledWithoutDocker = true)
56  class RemoteCacheDavTest {
57  
58      private static final String DAV_DOCKER_IMAGE =
59              "xama/nginx-webdav@sha256:84171a7e67d7e98eeaa67de58e3ce141ec1d0ee9c37004e7096698c8379fd9cf";
60      private static final String DAV_USERNAME = "admin";
61      private static final String DAV_PASSWORD = "admin";
62      private static final String REPO_ID = "build-cache";
63      private static final String HTTP_TRANSPORT_PRIORITY =
64              "aether.priority.org.eclipse.aether.transport.http.HttpTransporterFactory";
65      private static final String WAGON_TRANSPORT_PRIORITY =
66              "aether.priority.org.eclipse.aether.transport.wagon.WagonTransporterFactory";
67      private static final String MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED = "maven.build.cache.remote.save.enabled";
68  
69      @Container
70      GenericContainer<?> dav;
71  
72      @Inject
73      Verifier verifier;
74  
75      Path basedir;
76      Path remoteCache;
77      Path localCache;
78      Path settings;
79      Path logDir;
80  
81      @BeforeEach
82      void setup() throws IOException {
83          basedir = Paths.get(verifier.getBasedir());
84          remoteCache = basedir.resolveSibling("cache-remote").toAbsolutePath().normalize();
85          localCache = basedir.resolveSibling("cache-local").toAbsolutePath().normalize();
86          settings = basedir.resolve("../settings.xml").toAbsolutePath().normalize();
87          logDir = basedir.getParent();
88  
89          Files.createDirectories(remoteCache);
90  
91          Files.write(
92                  settings,
93                  ("<settings>"
94                                  + "<servers><server>"
95                                  + "<id>" + REPO_ID + "</id>"
96                                  + "<username>" + DAV_USERNAME + "</username>"
97                                  + "<password>" + DAV_PASSWORD + "</password>"
98                                  + "</server></servers></settings>")
99                          .getBytes());
100 
101         dav = new GenericContainer<>(DockerImageName.parse(DAV_DOCKER_IMAGE))
102                 .withReuse(false)
103                 .withExposedPorts(80)
104                 .withEnv("WEBDAV_USERNAME", DAV_USERNAME)
105                 .withEnv("WEBDAV_PASSWORD", DAV_PASSWORD)
106                 .withFileSystemBind(remoteCache.toString(), "/var/webdav/public");
107     }
108 
109     @AfterEach
110     void cleanup() throws Exception {
111         dav.execInContainer("rm", "-rf", "/var/webdav");
112         cleanDirs(localCache);
113         dav.close();
114     }
115 
116     public static Stream<Arguments> transports() {
117         return Stream.of(Arguments.of("wagon"), Arguments.of("http"));
118     }
119 
120     @ParameterizedTest
121     @MethodSource("transports")
122     void doTestRemoteCache(String transport) throws VerificationException, IOException {
123         String url =
124                 ("wagon".equals(transport) ? "dav:" : "") + "http://localhost:" + dav.getFirstMappedPort() + "/mbce";
125         substitute(
126                 basedir.resolve(".mvn/maven-build-cache-config.xml"),
127                 "url",
128                 url,
129                 "id",
130                 REPO_ID,
131                 "location",
132                 localCache.toString());
133 
134         verifier.setAutoclean(false);
135 
136         cleanDirs(localCache, remoteCache.resolve("mbce"));
137         assertFalse(hasBuildInfoXml(localCache), () -> error(localCache, "local", false));
138         assertFalse(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", false));
139 
140         verifier.getCliOptions().clear();
141         verifier.addCliOption("--settings=" + settings);
142         verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
143         verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
144         verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=false");
145         verifier.setLogFileName("../log-1.txt");
146         verifier.executeGoals(Arrays.asList("clean", "install"));
147         verifier.verifyErrorFreeLog();
148 
149         assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
150         assertFalse(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", false));
151 
152         cleanDirs(localCache);
153 
154         verifier.getCliOptions().clear();
155         verifier.addCliOption("--settings=" + settings);
156         if (!"wagon".equals(transport)) {
157             verifier.setSystemProperty("aether.connector.http.supportWebDav", "true");
158         }
159         verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
160         verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
161         verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=true");
162         verifier.setLogFileName("../log-2.txt");
163         verifier.executeGoals(Arrays.asList("clean", "install"));
164         verifier.verifyErrorFreeLog();
165 
166         assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
167         assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
168 
169         cleanDirs(localCache);
170 
171         verifier.getCliOptions().clear();
172         verifier.addCliOption("--settings=" + settings);
173         if (!"wagon".equals(transport)) {
174             verifier.setSystemProperty("aether.connector.http.supportWebDav", "true");
175         }
176         verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
177         verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
178         verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=false");
179         verifier.setLogFileName("../log-3.txt");
180         verifier.executeGoals(Arrays.asList("clean", "install"));
181         verifier.verifyErrorFreeLog();
182 
183         assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
184         assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
185 
186         // replace url and server id with a bad one to be sure cli property is used
187         substitute(
188                 basedir.resolve(".mvn/maven-build-cache-config.xml"),
189                 "url",
190                 "http://foo.com",
191                 "id",
192                 "foo",
193                 "location",
194                 localCache.toString());
195 
196         cleanDirs(localCache);
197         try {
198             // depending on uid used for execution but can be different from the one using docker and so different file
199             // permissions..
200             dav.execInContainer("rm", "-rf", "/var/webdav/public/*");
201         } catch (InterruptedException e) {
202             throw new IOException("cannot delete remote cache");
203         }
204 
205         verifier.getCliOptions().clear();
206         verifier.addCliOption("--settings=" + settings);
207         verifier.addCliOption("-X");
208         verifier.addCliOption("-D" + HTTP_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "0" : "10"));
209         verifier.addCliOption("-D" + WAGON_TRANSPORT_PRIORITY + "=" + ("wagon".equals(transport) ? "10" : "0"));
210         verifier.addCliOption("-D" + MAVEN_BUILD_CACHE_REMOTE_SAVE_ENABLED + "=true");
211         verifier.setSystemProperty(REMOTE_URL_PROPERTY_NAME, url);
212         verifier.setSystemProperty(REMOTE_SERVER_ID_PROPERTY_NAME, REPO_ID);
213         verifier.setLogFileName("../log-4.txt");
214         verifier.executeGoals(Arrays.asList("clean", "install"));
215         verifier.verifyErrorFreeLog();
216 
217         assertTrue(hasBuildInfoXml(localCache), () -> error(localCache, "local", true));
218         assertTrue(hasBuildInfoXml(remoteCache), () -> error(remoteCache, "remote", true));
219     }
220 
221     private boolean hasBuildInfoXml(Path cache) throws IOException {
222         return Files.walk(cache).anyMatch(isBuildInfoXml());
223     }
224 
225     @NotNull
226     private Predicate<Path> isBuildInfoXml() {
227         return p -> p.getFileName().toString().equals("buildinfo.xml");
228     }
229 
230     private void cleanDirs(Path... paths) throws IOException {
231         for (Path path : paths) {
232             IntegrationTestExtension.deleteDir(path);
233             Files.createDirectories(path);
234             Runtime.getRuntime().exec("chmod go+rwx " + path);
235         }
236     }
237 
238     private static void substitute(Path path, String... strings) throws IOException {
239         String str = new String(Files.readAllBytes(path), StandardCharsets.UTF_8);
240         for (int i = 0; i < strings.length / 2; i++) {
241             str = str.replaceAll(Pattern.quote("${" + strings[i * 2] + "}"), strings[i * 2 + 1]);
242         }
243         Files.deleteIfExists(path);
244         Files.write(path, str.getBytes(StandardCharsets.UTF_8));
245     }
246 
247     private String error(Path directory, String cache, boolean shouldHave) {
248         StringBuilder sb =
249                 new StringBuilder("The " + cache + " cache should " + (shouldHave ? "" : "not ") + "contain a build\n");
250         try {
251             sb.append("Contents:\n");
252             Files.walk(directory).forEach(p -> sb.append("    ").append(p).append("\n"));
253 
254             for (Path log : Files.list(logDir)
255                     .filter(p -> p.getFileName().toString().matches("log.*\\.txt"))
256                     .collect(Collectors.toList())) {
257                 sb.append("Log file: ").append(log).append("\n");
258                 Files.lines(log).forEach(l -> sb.append("    ").append(l).append("\n"));
259             }
260 
261             sb.append("Container log:\n");
262             Stream.of(dav.getLogs().split("\n"))
263                     .forEach(l -> sb.append("    ").append(l).append("\n"));
264 
265             sb.append("Remote cache listing:\n");
266             ls(remoteCache, s -> sb.append("    ").append(s).append("\n"));
267         } catch (IOException e) {
268             sb.append("Error: ").append(e);
269         }
270         return sb.toString();
271     }
272 
273     private static void ls(Path currentDir, Consumer<String> out) throws IOException {
274         Files.walk(currentDir)
275                 .map(p -> new PathEntry(p, currentDir))
276                 .sorted()
277                 .map(PathEntry::longDisplay)
278                 .forEach(out);
279     }
280 }