Skip to content

Commit 158d7f5

Browse files
authored
Add TestEntitlementsRule with support for dynamic entitled node paths for testing (#132077) (#132636)
Use TestEntitlementsRule and closable entitled node paths for better isolation and management of entitlement related state during tests. However, wipePendingDataDirectories in tests requires entitlement delegation to work as this uses server's FileSystemUtils. Until ES-10920 is solved, entitled node paths cannot be removed unless the test suite completes or explicitly removing all entitled node paths in-between tests (if cluster scope is TEST). Relates to ES-12042 (cherry picked from commit 76dac08)
1 parent ab7d1bb commit 158d7f5

File tree

19 files changed

+421
-268
lines changed

19 files changed

+421
-268
lines changed

server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ public Path nodeConfigPath(int nodeOrdinal) {
8787
0,
8888
"other",
8989
Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class),
90-
Function.identity()
90+
Function.identity(),
91+
TEST_ENTITLEMENTS::addEntitledNodePaths
9192
);
9293
try {
9394
other.beforeTest(random());
@@ -137,7 +138,8 @@ public Path nodeConfigPath(int nodeOrdinal) {
137138
0,
138139
"other",
139140
Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class),
140-
Function.identity()
141+
Function.identity(),
142+
TEST_ENTITLEMENTS::addEntitledNodePaths
141143
);
142144
try (var mockLog = MockLog.capture(JoinHelper.class)) {
143145
mockLog.addExpectation(

server/src/internalClusterTest/java/org/elasticsearch/snapshots/MultiClusterRepoAccessIT.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ public Path nodeConfigPath(int nodeOrdinal) {
7777
InternalSettingsPlugin.class,
7878
getTestTransportPlugin()
7979
),
80-
Function.identity()
80+
Function.identity(),
81+
TEST_ENTITLEMENTS::addEntitledNodePaths
8182
);
8283
secondCluster.beforeTest(random());
8384
}

server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
import org.elasticsearch.core.SuppressForbidden;
1313
import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap;
1414
import org.elasticsearch.test.ESTestCase;
15-
import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode;
1615

1716
import java.io.IOException;
1817
import java.nio.file.Path;
@@ -42,7 +41,7 @@
4241
*/
4342
public class EntitlementMetaTests extends ESTestCase {
4443
public void testSelfTestPasses() {
45-
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest());
44+
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests());
4645
Elasticsearch.entitlementSelfTest();
4746
}
4847

server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,13 +30,13 @@ public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase {
3030
* is called from server code. The self-test should pass as usual.
3131
*/
3232
public void testSelfTestPasses() {
33-
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest());
33+
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests());
3434
Elasticsearch.entitlementSelfTest();
3535
}
3636

3737
@SuppressForbidden(reason = "Testing that a forbidden API is disallowed")
3838
public void testForbiddenActionDenied() {
39-
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest());
39+
assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests());
4040
assertThrows(NotEntitledException.class, () -> Path.of(".").toRealPath());
4141
}
4242
}

server/src/test/java/org/elasticsearch/indices/IndicesServiceCloseTests.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,8 @@ private Node startNode() throws NodeValidationException {
7777
Node node = new MockNode(
7878
settings,
7979
Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class, InternalSettingsPlugin.class),
80-
true
80+
true,
81+
() -> {}
8182
);
8283
node.start();
8384
return node;

test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java

Lines changed: 19 additions & 146 deletions
Original file line numberDiff line numberDiff line change
@@ -9,22 +9,18 @@
99

1010
package org.elasticsearch.entitlement.bootstrap;
1111

12-
import org.apache.lucene.tests.mockfile.FilterPath;
1312
import org.elasticsearch.bootstrap.TestBuildInfo;
1413
import org.elasticsearch.bootstrap.TestBuildInfoParser;
1514
import org.elasticsearch.bootstrap.TestScopeResolver;
1615
import org.elasticsearch.common.Strings;
17-
import org.elasticsearch.common.settings.Settings;
1816
import org.elasticsearch.core.Booleans;
19-
import org.elasticsearch.core.Nullable;
2017
import org.elasticsearch.core.PathUtils;
2118
import org.elasticsearch.core.SuppressForbidden;
2219
import org.elasticsearch.entitlement.initialization.EntitlementInitialization;
2320
import org.elasticsearch.entitlement.runtime.policy.PathLookup;
24-
import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir;
2521
import org.elasticsearch.entitlement.runtime.policy.Policy;
22+
import org.elasticsearch.entitlement.runtime.policy.PolicyManager;
2623
import org.elasticsearch.entitlement.runtime.policy.PolicyParser;
27-
import org.elasticsearch.entitlement.runtime.policy.TestPathLookup;
2824
import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager;
2925
import org.elasticsearch.logging.LogManager;
3026
import org.elasticsearch.logging.Logger;
@@ -35,175 +31,52 @@
3531
import java.net.URI;
3632
import java.net.URL;
3733
import java.nio.file.Path;
38-
import java.nio.file.Paths;
3934
import java.util.ArrayList;
4035
import java.util.Arrays;
41-
import java.util.Collection;
42-
import java.util.Collections;
4336
import java.util.HashMap;
44-
import java.util.HashSet;
4537
import java.util.List;
4638
import java.util.Map;
4739
import java.util.Set;
4840
import java.util.TreeSet;
49-
import java.util.concurrent.ConcurrentHashMap;
50-
import java.util.function.BiFunction;
51-
import java.util.function.Consumer;
5241

5342
import static java.util.stream.Collectors.toCollection;
5443
import static java.util.stream.Collectors.toSet;
55-
import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP;
56-
import static org.elasticsearch.env.Environment.PATH_DATA_SETTING;
57-
import static org.elasticsearch.env.Environment.PATH_HOME_SETTING;
58-
import static org.elasticsearch.env.Environment.PATH_REPO_SETTING;
59-
import static org.elasticsearch.env.Environment.PATH_SHARED_DATA_SETTING;
6044

6145
public class TestEntitlementBootstrap {
62-
6346
private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class);
6447

65-
private static Map<BaseDir, Collection<Path>> baseDirPaths = new ConcurrentHashMap<>();
66-
private static TestPolicyManager policyManager;
48+
private static TestPathLookup TEST_PATH_LOOKUP;
49+
private static TestPolicyManager POLICY_MANAGER;
6750

6851
/**
6952
* Activates entitlement checking in tests.
7053
*/
71-
public static void bootstrap(@Nullable Path tempDir) throws IOException {
72-
if (isEnabledForTest() == false) {
73-
return;
74-
}
75-
var previousTempDir = baseDirPaths.put(TEMP, zeroOrOne(tempDir));
76-
assert previousTempDir == null : "Test entitlement bootstrap called multiple times";
77-
TestPathLookup pathLookup = new TestPathLookup(baseDirPaths);
78-
policyManager = createPolicyManager(pathLookup);
79-
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager);
80-
logger.debug("Loading entitlement agent");
81-
EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName());
82-
}
83-
84-
public static void registerNodeBaseDirs(Settings settings, Path configPath) {
85-
if (policyManager == null) {
86-
return;
87-
}
88-
89-
Path homeDir = homeDir(settings);
90-
Path configDir = configDir(configPath, homeDir);
91-
Collection<Path> dataDirs = dataDirs(settings, homeDir);
92-
Collection<Path> sharedDataDir = sharedDataDir(settings);
93-
Collection<Path> repoDirs = repoDirs(settings);
94-
logger.debug(
95-
"Registering node dirs: config [{}], dataDirs [{}], sharedDataDir [{}], repoDirs [{}]",
96-
configDir,
97-
dataDirs,
98-
sharedDataDir,
99-
repoDirs
100-
);
101-
baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.add(configDir)));
102-
baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.addAll(dataDirs)));
103-
baseDirPaths.compute(BaseDir.SHARED_DATA, baseDirModifier(paths -> paths.addAll(sharedDataDir)));
104-
baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.addAll(repoDirs)));
105-
policyManager.clearModuleEntitlementsCache();
106-
}
107-
108-
public static void unregisterNodeBaseDirs(Settings settings, Path configPath) {
109-
if (policyManager == null) {
54+
public static void bootstrap(Path tempDir) throws IOException {
55+
if (isEnabledForTests() == false) {
11056
return;
11157
}
112-
113-
Path homeDir = homeDir(settings);
114-
Path configDir = configDir(configPath, homeDir);
115-
Collection<Path> dataDirs = dataDirs(settings, homeDir);
116-
Collection<Path> sharedDataDir = sharedDataDir(settings);
117-
Collection<Path> repoDirs = repoDirs(settings);
118-
logger.debug(
119-
"Unregistering node dirs: config [{}], dataDirs [{}], sharedDataDir [{}], repoDirs [{}]",
120-
configDir,
121-
dataDirs,
122-
sharedDataDir,
123-
repoDirs
124-
);
125-
baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.remove(configDir)));
126-
baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.removeAll(dataDirs)));
127-
baseDirPaths.compute(BaseDir.SHARED_DATA, baseDirModifier(paths -> paths.removeAll(sharedDataDir)));
128-
baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.removeAll(repoDirs)));
129-
policyManager.clearModuleEntitlementsCache();
130-
}
131-
132-
private static Path homeDir(Settings settings) {
133-
return absolutePath(PATH_HOME_SETTING.get(settings));
134-
}
135-
136-
private static Path configDir(Path configDir, Path homeDir) {
137-
return configDir != null ? unwrapFilterPath(configDir) : homeDir.resolve("config");
138-
}
139-
140-
private static Collection<Path> dataDirs(Settings settings, Path homeDir) {
141-
List<String> dataDirs = PATH_DATA_SETTING.get(settings);
142-
return dataDirs.isEmpty()
143-
? List.of(homeDir.resolve("data"))
144-
: dataDirs.stream().map(TestEntitlementBootstrap::absolutePath).toList();
58+
assert POLICY_MANAGER == null && TEST_PATH_LOOKUP == null : "Test entitlement bootstrap called multiple times";
59+
TEST_PATH_LOOKUP = new TestPathLookup(tempDir);
60+
POLICY_MANAGER = createPolicyManager(TEST_PATH_LOOKUP);
61+
loadAgent(POLICY_MANAGER, TEST_PATH_LOOKUP);
14562
}
14663

147-
private static Collection<Path> sharedDataDir(Settings settings) {
148-
String sharedDataDir = PATH_SHARED_DATA_SETTING.get(settings);
149-
return Strings.hasText(sharedDataDir) ? List.of(absolutePath(sharedDataDir)) : List.of();
150-
}
151-
152-
private static Collection<Path> repoDirs(Settings settings) {
153-
return PATH_REPO_SETTING.get(settings).stream().map(TestEntitlementBootstrap::absolutePath).toList();
154-
}
155-
156-
private static BiFunction<BaseDir, Collection<Path>, Collection<Path>> baseDirModifier(Consumer<Collection<Path>> consumer) {
157-
// always return a new unmodifiable copy
158-
return (BaseDir baseDir, Collection<Path> paths) -> {
159-
paths = paths == null ? new HashSet<>() : new HashSet<>(paths);
160-
consumer.accept(paths);
161-
return Collections.unmodifiableCollection(paths);
162-
};
163-
}
164-
165-
private static Path unwrapFilterPath(Path path) {
166-
while (path instanceof FilterPath fPath) {
167-
path = fPath.getDelegate();
168-
}
169-
return path;
170-
}
171-
172-
@SuppressForbidden(reason = "must be resolved using the default file system, rather then the mocked test file system")
173-
private static Path absolutePath(String path) {
174-
return Paths.get(path).toAbsolutePath().normalize();
175-
}
176-
177-
private static <T> List<T> zeroOrOne(T item) {
178-
if (item == null) {
179-
return List.of();
180-
} else {
181-
return List.of(item);
182-
}
183-
}
184-
185-
public static boolean isEnabledForTest() {
64+
public static boolean isEnabledForTests() {
18665
return Booleans.parseBoolean(System.getProperty("es.entitlement.enableForTests", "false"));
18766
}
18867

189-
public static void setActive(boolean newValue) {
190-
policyManager.setActive(newValue);
191-
}
192-
193-
public static void setTriviallyAllowingTestCode(boolean newValue) {
194-
policyManager.setTriviallyAllowingTestCode(newValue);
68+
static TestPolicyManager testPolicyManager() {
69+
return POLICY_MANAGER;
19570
}
19671

197-
public static void setEntitledTestPackages(String[] entitledTestPackages) {
198-
policyManager.setEntitledTestPackages(entitledTestPackages);
72+
static TestPathLookup testPathLookup() {
73+
return TEST_PATH_LOOKUP;
19974
}
20075

201-
public static void resetAfterTest() {
202-
// reset all base dirs except TEMP, which is initialized just once statically
203-
baseDirPaths.keySet().retainAll(List.of(TEMP));
204-
if (policyManager != null) {
205-
policyManager.resetAfterTest();
206-
}
76+
private static void loadAgent(PolicyManager policyManager, PathLookup pathLookup) {
77+
logger.debug("Loading entitlement agent");
78+
EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager);
79+
EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName());
20780
}
20881

20982
private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException {
@@ -224,7 +97,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro
22497

22598
String separator = System.getProperty("path.separator");
22699

227-
// In productions, plugins would have access to their respective bundle directories,
100+
// In production, plugins would have access to their respective bundle directories,
228101
// and so they'd be able to read from their jars. In testing, we approximate this
229102
// by considering the entire classpath to be "source paths" of all plugins. This
230103
// also has the effect of granting read access to everything on the test-only classpath,

0 commit comments

Comments
 (0)