Skip to content

Commit 410e573

Browse files
authored
Fix docker availability handling accross platforms (elastic#139199)
* Fix docker availability handling accross platforms - Invert logic in isExcludedOs to correctly apply exclusions only in CI. - Fail tests in CI if Docker is missing on supported OS instead of skipping. - Update assumption messages for clarity.
1 parent c41a6cf commit 410e573

File tree

4 files changed

+159
-107
lines changed

4 files changed

+159
-107
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3+
* or more contributor license agreements. Licensed under the "Elastic License
4+
* 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side
5+
* Public License v 1"; you may not use this file except in compliance with, at
6+
* your election, the "Elastic License 2.0", the "GNU Affero General Public
7+
* License v3.0 only", or the "Server Side Public License, v 1".
8+
*/
9+
10+
package org.elasticsearch.test.fixtures.testcontainers;
11+
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import org.testcontainers.DockerClientFactory;
15+
16+
import java.io.File;
17+
import java.io.IOException;
18+
import java.nio.file.Files;
19+
import java.nio.file.Path;
20+
import java.nio.file.Paths;
21+
import java.util.Collections;
22+
import java.util.HashMap;
23+
import java.util.List;
24+
import java.util.Map;
25+
import java.util.stream.Collectors;
26+
27+
public class DockerAvailability {
28+
29+
protected static final Logger LOGGER = LoggerFactory.getLogger(DockerAvailability.class);
30+
31+
private static final boolean EXCLUDED_OS = isExcludedOs();
32+
private static final boolean DOCKER_PROBING_SUCCESSFUL = isDockerAvailable();
33+
private static final boolean CI = Boolean.parseBoolean(System.getProperty("CI", "false"));
34+
private static final String DOCKER_ON_LINUX_EXCLUSIONS_FILE = ".ci/dockerOnLinuxExclusions";
35+
36+
static void assumeDockerIsAvailable() {
37+
org.junit.Assume.assumeFalse("The current OS is excluded from Docker-based tests", EXCLUDED_OS);
38+
if (CI && DOCKER_PROBING_SUCCESSFUL == false) {
39+
throw new AssertionError("Docker is expected to be available on this CI node but probing failed.");
40+
}
41+
org.junit.Assume.assumeTrue("Docker is not available", DOCKER_PROBING_SUCCESSFUL);
42+
}
43+
44+
/**
45+
* see <a href="https://github.com/elastic/elasticsearch/issues/102532">https://github.com/elastic/elasticsearch/issues/102532</a>
46+
* */
47+
public static boolean isDockerAvailable() {
48+
try {
49+
LOGGER.info("Probing docker environment...");
50+
DockerClientFactory.instance().client();
51+
LOGGER.info("Probing docker environment successful");
52+
return true;
53+
} catch (Throwable ex) {
54+
LOGGER.warn("Probing docker has failed; disabling test", ex);
55+
return false;
56+
}
57+
}
58+
59+
private static boolean isExcludedOs() {
60+
if (CI == false) {
61+
// we dont exclude OS outside of CI environment
62+
return false;
63+
}
64+
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
65+
return true;
66+
}
67+
final Path osRelease = Paths.get("/etc/os-release");
68+
if (Files.exists(osRelease)) {
69+
Map<String, String> values;
70+
71+
try {
72+
final List<String> osReleaseLines = Files.readAllLines(osRelease);
73+
values = parseOsRelease(osReleaseLines);
74+
} catch (IOException e) {
75+
throw new RuntimeException("Failed to read /etc/os-release", e);
76+
}
77+
78+
final String id = deriveId(values);
79+
final boolean excluded = getLinuxExclusionList().contains(id);
80+
81+
if (excluded) {
82+
LOGGER.warn("Linux OS id [{}] is present in the Docker exclude list. Tasks requiring Docker will be disabled.", id);
83+
}
84+
85+
return excluded;
86+
}
87+
88+
return false;
89+
}
90+
91+
static String deriveId(Map<String, String> values) {
92+
return values.get("ID") + "-" + values.get("VERSION_ID");
93+
}
94+
95+
// visible for testing
96+
static Map<String, String> parseOsRelease(final List<String> osReleaseLines) {
97+
final Map<String, String> values = new HashMap<>();
98+
99+
osReleaseLines.stream().map(String::trim).filter(line -> (line.isEmpty() || line.startsWith("#")) == false).forEach(line -> {
100+
final String[] parts = line.split("=", 2);
101+
final String key = parts[0];
102+
// remove optional leading and trailing quotes and whitespace
103+
final String value = parts[1].replaceAll("^['\"]?\\s*", "").replaceAll("\\s*['\"]?$", "");
104+
105+
values.put(key, value.toLowerCase());
106+
});
107+
108+
return values;
109+
}
110+
111+
private static List<String> getLinuxExclusionList() {
112+
File exclusionsFile = new File(System.getProperty("workspace.dir"), DOCKER_ON_LINUX_EXCLUSIONS_FILE);
113+
if (exclusionsFile.exists()) {
114+
try {
115+
return Files.readAllLines(exclusionsFile.toPath())
116+
.stream()
117+
.map(String::trim)
118+
.filter(line -> (line.isEmpty() || line.startsWith("#")) == false)
119+
.collect(Collectors.toList());
120+
} catch (IOException e) {
121+
throw new RuntimeException("Failed to read " + exclusionsFile.getAbsolutePath(), e);
122+
}
123+
} else {
124+
return Collections.emptyList();
125+
}
126+
}
127+
128+
}

test/fixtures/testcontainer-utils/src/main/java/org/elasticsearch/test/fixtures/testcontainers/DockerEnvironmentAwareTestContainer.java

Lines changed: 6 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,18 @@
1010
package org.elasticsearch.test.fixtures.testcontainers;
1111

1212
import org.elasticsearch.test.fixtures.CacheableTestFixture;
13-
import org.junit.Assume;
13+
import org.junit.AssumptionViolatedException;
1414
import org.junit.rules.TestRule;
1515
import org.junit.runner.Description;
1616
import org.junit.runners.model.Statement;
1717
import org.slf4j.Logger;
1818
import org.slf4j.LoggerFactory;
19-
import org.testcontainers.DockerClientFactory;
2019
import org.testcontainers.containers.GenericContainer;
2120
import org.testcontainers.containers.output.Slf4jLogConsumer;
2221

23-
import java.io.File;
24-
import java.io.IOException;
25-
import java.nio.file.Files;
26-
import java.nio.file.Path;
27-
import java.nio.file.Paths;
28-
import java.util.Collections;
29-
import java.util.HashMap;
30-
import java.util.List;
31-
import java.util.Map;
3222
import java.util.concurrent.Future;
33-
import java.util.stream.Collectors;
23+
24+
import static org.elasticsearch.test.fixtures.testcontainers.DockerAvailability.assumeDockerIsAvailable;
3425

3526
public class DockerEnvironmentAwareTestContainer extends GenericContainer<DockerEnvironmentAwareTestContainer>
3627
implements
@@ -39,27 +30,6 @@ public class DockerEnvironmentAwareTestContainer extends GenericContainer<Docker
3930

4031
protected static final Logger LOGGER = LoggerFactory.getLogger(DockerEnvironmentAwareTestContainer.class);
4132

42-
private static final String DOCKER_ON_LINUX_EXCLUSIONS_FILE = ".ci/dockerOnLinuxExclusions";
43-
44-
private static final boolean CI = Boolean.parseBoolean(System.getProperty("CI", "false"));
45-
private static final boolean EXCLUDED_OS = isExcludedOs();
46-
private static final boolean DOCKER_PROBING_SUCCESSFUL = isDockerAvailable();
47-
48-
/**
49-
* see <a href="https://github.com/elastic/elasticsearch/issues/102532">https://github.com/elastic/elasticsearch/issues/102532</a>
50-
* */
51-
private static boolean isDockerAvailable() {
52-
try {
53-
LOGGER.info("Probing docker environment...");
54-
DockerClientFactory.instance().client();
55-
LOGGER.info("Probing docker environment successful");
56-
return true;
57-
} catch (Throwable ex) {
58-
LOGGER.warn("Probing docker has failed; disabling test", ex);
59-
return false;
60-
}
61-
}
62-
6333
public DockerEnvironmentAwareTestContainer(Future<String> image) {
6434
super(image);
6535
}
@@ -72,20 +42,20 @@ public void evaluate() {
7242
try {
7343
start();
7444
statement.evaluate();
45+
} catch (AssumptionViolatedException e) {
46+
throw e;
7547
} catch (Throwable e) {
7648
throw new RuntimeException(e);
7749
} finally {
7850
stop();
7951
}
8052
}
8153
};
82-
8354
}
8455

8556
@Override
8657
public void start() {
87-
Assume.assumeFalse("Docker support excluded on OS", EXCLUDED_OS);
88-
Assume.assumeTrue("Docker probing succesful", DOCKER_PROBING_SUCCESSFUL);
58+
assumeDockerIsAvailable();
8959
withLogConsumer(new Slf4jLogConsumer(LOGGER));
9060
super.start();
9161
}
@@ -106,72 +76,4 @@ public void cache() {
10676
}
10777
}
10878

109-
static String deriveId(Map<String, String> values) {
110-
return values.get("ID") + "-" + values.get("VERSION_ID");
111-
}
112-
113-
private static boolean isExcludedOs() {
114-
if (CI) {
115-
// we dont exclude OS outside of CI environment
116-
return false;
117-
}
118-
if (System.getProperty("os.name").toLowerCase().startsWith("windows")) {
119-
return true;
120-
}
121-
final Path osRelease = Paths.get("/etc/os-release");
122-
if (Files.exists(osRelease)) {
123-
Map<String, String> values;
124-
125-
try {
126-
final List<String> osReleaseLines = Files.readAllLines(osRelease);
127-
values = parseOsRelease(osReleaseLines);
128-
} catch (IOException e) {
129-
throw new RuntimeException("Failed to read /etc/os-release", e);
130-
}
131-
132-
final String id = deriveId(values);
133-
final boolean excluded = getLinuxExclusionList().contains(id);
134-
135-
if (excluded) {
136-
LOGGER.warn("Linux OS id [{}] is present in the Docker exclude list. Tasks requiring Docker will be disabled.", id);
137-
}
138-
139-
return excluded;
140-
}
141-
142-
return false;
143-
}
144-
145-
private static List<String> getLinuxExclusionList() {
146-
File exclusionsFile = new File(System.getProperty("workspace.dir"), DOCKER_ON_LINUX_EXCLUSIONS_FILE);
147-
if (exclusionsFile.exists()) {
148-
try {
149-
return Files.readAllLines(exclusionsFile.toPath())
150-
.stream()
151-
.map(String::trim)
152-
.filter(line -> (line.isEmpty() || line.startsWith("#")) == false)
153-
.collect(Collectors.toList());
154-
} catch (IOException e) {
155-
throw new RuntimeException("Failed to read " + exclusionsFile.getAbsolutePath(), e);
156-
}
157-
} else {
158-
return Collections.emptyList();
159-
}
160-
}
161-
162-
// visible for testing
163-
static Map<String, String> parseOsRelease(final List<String> osReleaseLines) {
164-
final Map<String, String> values = new HashMap<>();
165-
166-
osReleaseLines.stream().map(String::trim).filter(line -> (line.isEmpty() || line.startsWith("#")) == false).forEach(line -> {
167-
final String[] parts = line.split("=", 2);
168-
final String key = parts[0];
169-
// remove optional leading and trailing quotes and whitespace
170-
final String value = parts[1].replaceAll("^['\"]?\\s*", "").replaceAll("\\s*['\"]?$", "");
171-
172-
values.put(key, value.toLowerCase());
173-
});
174-
175-
return values;
176-
}
17779
}

test/fixtures/testcontainer-utils/src/main/java/org/elasticsearch/test/fixtures/testcontainers/Junit4NetworkRule.java

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99

1010
package org.elasticsearch.test.fixtures.testcontainers;
1111

12+
import org.junit.AssumptionViolatedException;
1213
import org.junit.rules.TestRule;
1314
import org.junit.runner.Description;
1415
import org.junit.runners.model.Statement;
1516
import org.testcontainers.containers.Network;
1617

18+
import static org.elasticsearch.test.fixtures.testcontainers.DockerAvailability.assumeDockerIsAvailable;
19+
1720
public class Junit4NetworkRule implements TestRule {
1821

1922
private final Network network;
@@ -27,11 +30,23 @@ public Statement apply(Statement statement, Description description) {
2730
return new Statement() {
2831
@Override
2932
public void evaluate() throws Throwable {
30-
network.getId();
31-
statement.evaluate();
32-
network.close();
33+
try {
34+
assumeDockerIsAvailable();
35+
network.getId();
36+
statement.evaluate();
37+
network.close();
38+
} catch (AssumptionViolatedException e) {
39+
throw e;
40+
} finally {
41+
try {
42+
network.close();
43+
} catch (Throwable e) {
44+
45+
}
46+
}
3347
}
3448
};
49+
3550
}
3651

3752
public static Junit4NetworkRule from(Network network) {

test/fixtures/testcontainer-utils/src/test/java/org/elasticsearch/test/fixtures/testcontainers/Junit4NetworkRuleTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,19 +9,26 @@
99

1010
package org.elasticsearch.test.fixtures.testcontainers;
1111

12+
import org.junit.BeforeClass;
1213
import org.junit.Test;
1314
import org.junit.runner.Description;
1415
import org.junit.runners.model.Statement;
1516
import org.testcontainers.containers.Network;
1617

1718
import java.util.concurrent.atomic.AtomicBoolean;
1819

20+
import static org.elasticsearch.test.fixtures.testcontainers.DockerAvailability.assumeDockerIsAvailable;
1921
import static org.junit.Assert.assertEquals;
2022
import static org.junit.Assert.assertNotNull;
2123
import static org.junit.Assert.assertTrue;
2224

2325
public class Junit4NetworkRuleTests {
2426

27+
@BeforeClass
28+
public static void checkDockerAvailable() {
29+
assumeDockerIsAvailable();
30+
}
31+
2532
@Test
2633
public void testNetworkLifecycle() throws Throwable {
2734
// Track lifecycle events

0 commit comments

Comments
 (0)