diff --git a/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java b/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java index 10f13f6ab152f..e28ab5b5c05d5 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/discovery/single/SingleNodeDiscoveryIT.java @@ -87,7 +87,8 @@ public Path nodeConfigPath(int nodeOrdinal) { 0, "other", Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); try { other.beforeTest(random()); @@ -137,7 +138,8 @@ public Path nodeConfigPath(int nodeOrdinal) { 0, "other", Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); try (var mockLog = MockLog.capture(JoinHelper.class)) { mockLog.addExpectation( diff --git a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/MultiClusterRepoAccessIT.java b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/MultiClusterRepoAccessIT.java index c1549c1f3d384..875052ce3998c 100644 --- a/server/src/internalClusterTest/java/org/elasticsearch/snapshots/MultiClusterRepoAccessIT.java +++ b/server/src/internalClusterTest/java/org/elasticsearch/snapshots/MultiClusterRepoAccessIT.java @@ -77,7 +77,8 @@ public Path nodeConfigPath(int nodeOrdinal) { InternalSettingsPlugin.class, getTestTransportPlugin() ), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); secondCluster.beforeTest(random()); } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java index 04e59e5476f3b..2ea51461fb528 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/EntitlementMetaTests.java @@ -12,7 +12,6 @@ import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; import org.elasticsearch.test.ESTestCase; -import org.elasticsearch.test.ESTestCase.WithEntitlementsOnTestCode; import java.io.IOException; import java.nio.file.Path; @@ -42,7 +41,7 @@ */ public class EntitlementMetaTests extends ESTestCase { public void testSelfTestPasses() { - assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests()); Elasticsearch.entitlementSelfTest(); } diff --git a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java index c126490922e48..a91a2b5d4aeca 100644 --- a/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java +++ b/server/src/test/java/org/elasticsearch/bootstrap/WithEntitlementsOnTestCodeMetaTests.java @@ -30,13 +30,13 @@ public class WithEntitlementsOnTestCodeMetaTests extends ESTestCase { * is called from server code. The self-test should pass as usual. */ public void testSelfTestPasses() { - assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests()); Elasticsearch.entitlementSelfTest(); } @SuppressForbidden(reason = "Testing that a forbidden API is disallowed") public void testForbiddenActionDenied() { - assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTest()); + assumeTrue("Not yet working in serverless", TestEntitlementBootstrap.isEnabledForTests()); assertThrows(NotEntitledException.class, () -> Path.of(".").toRealPath()); } } diff --git a/server/src/test/java/org/elasticsearch/indices/IndicesServiceCloseTests.java b/server/src/test/java/org/elasticsearch/indices/IndicesServiceCloseTests.java index a48ba63882734..78df6c9e88c65 100644 --- a/server/src/test/java/org/elasticsearch/indices/IndicesServiceCloseTests.java +++ b/server/src/test/java/org/elasticsearch/indices/IndicesServiceCloseTests.java @@ -77,7 +77,8 @@ private Node startNode() throws NodeValidationException { Node node = new MockNode( settings, Arrays.asList(getTestTransportPlugin(), MockHttpTransport.TestPlugin.class, InternalSettingsPlugin.class), - true + true, + () -> {} ); node.start(); return node; diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java index d9c10d0d251e5..2e9ffc7558280 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementBootstrap.java @@ -9,22 +9,18 @@ package org.elasticsearch.entitlement.bootstrap; -import org.apache.lucene.tests.mockfile.FilterPath; import org.elasticsearch.bootstrap.TestBuildInfo; import org.elasticsearch.bootstrap.TestBuildInfoParser; import org.elasticsearch.bootstrap.TestScopeResolver; import org.elasticsearch.common.Strings; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.core.Booleans; -import org.elasticsearch.core.Nullable; import org.elasticsearch.core.PathUtils; import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.entitlement.initialization.EntitlementInitialization; import org.elasticsearch.entitlement.runtime.policy.PathLookup; -import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir; import org.elasticsearch.entitlement.runtime.policy.Policy; +import org.elasticsearch.entitlement.runtime.policy.PolicyManager; import org.elasticsearch.entitlement.runtime.policy.PolicyParser; -import org.elasticsearch.entitlement.runtime.policy.TestPathLookup; import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager; import org.elasticsearch.logging.LogManager; import org.elasticsearch.logging.Logger; @@ -35,175 +31,52 @@ import java.net.URI; import java.net.URL; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.ArrayList; import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; import java.util.HashMap; -import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.BiFunction; -import java.util.function.Consumer; import static java.util.stream.Collectors.toCollection; import static java.util.stream.Collectors.toSet; -import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; -import static org.elasticsearch.env.Environment.PATH_DATA_SETTING; -import static org.elasticsearch.env.Environment.PATH_HOME_SETTING; -import static org.elasticsearch.env.Environment.PATH_REPO_SETTING; -import static org.elasticsearch.env.Environment.PATH_SHARED_DATA_SETTING; public class TestEntitlementBootstrap { - private static final Logger logger = LogManager.getLogger(TestEntitlementBootstrap.class); - private static Map> baseDirPaths = new ConcurrentHashMap<>(); - private static TestPolicyManager policyManager; + private static TestPathLookup TEST_PATH_LOOKUP; + private static TestPolicyManager POLICY_MANAGER; /** * Activates entitlement checking in tests. */ - public static void bootstrap(@Nullable Path tempDir) throws IOException { - if (isEnabledForTest() == false) { - return; - } - var previousTempDir = baseDirPaths.put(TEMP, zeroOrOne(tempDir)); - assert previousTempDir == null : "Test entitlement bootstrap called multiple times"; - TestPathLookup pathLookup = new TestPathLookup(baseDirPaths); - policyManager = createPolicyManager(pathLookup); - EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); - logger.debug("Loading entitlement agent"); - EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); - } - - public static void registerNodeBaseDirs(Settings settings, Path configPath) { - if (policyManager == null) { - return; - } - - Path homeDir = homeDir(settings); - Path configDir = configDir(configPath, homeDir); - Collection dataDirs = dataDirs(settings, homeDir); - Collection sharedDataDir = sharedDataDir(settings); - Collection repoDirs = repoDirs(settings); - logger.debug( - "Registering node dirs: config [{}], dataDirs [{}], sharedDataDir [{}], repoDirs [{}]", - configDir, - dataDirs, - sharedDataDir, - repoDirs - ); - baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.add(configDir))); - baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.addAll(dataDirs))); - baseDirPaths.compute(BaseDir.SHARED_DATA, baseDirModifier(paths -> paths.addAll(sharedDataDir))); - baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.addAll(repoDirs))); - policyManager.clearModuleEntitlementsCache(); - } - - public static void unregisterNodeBaseDirs(Settings settings, Path configPath) { - if (policyManager == null) { + public static void bootstrap(Path tempDir) throws IOException { + if (isEnabledForTests() == false) { return; } - - Path homeDir = homeDir(settings); - Path configDir = configDir(configPath, homeDir); - Collection dataDirs = dataDirs(settings, homeDir); - Collection sharedDataDir = sharedDataDir(settings); - Collection repoDirs = repoDirs(settings); - logger.debug( - "Unregistering node dirs: config [{}], dataDirs [{}], sharedDataDir [{}], repoDirs [{}]", - configDir, - dataDirs, - sharedDataDir, - repoDirs - ); - baseDirPaths.compute(BaseDir.CONFIG, baseDirModifier(paths -> paths.remove(configDir))); - baseDirPaths.compute(BaseDir.DATA, baseDirModifier(paths -> paths.removeAll(dataDirs))); - baseDirPaths.compute(BaseDir.SHARED_DATA, baseDirModifier(paths -> paths.removeAll(sharedDataDir))); - baseDirPaths.compute(BaseDir.SHARED_REPO, baseDirModifier(paths -> paths.removeAll(repoDirs))); - policyManager.clearModuleEntitlementsCache(); - } - - private static Path homeDir(Settings settings) { - return absolutePath(PATH_HOME_SETTING.get(settings)); - } - - private static Path configDir(Path configDir, Path homeDir) { - return configDir != null ? unwrapFilterPath(configDir) : homeDir.resolve("config"); - } - - private static Collection dataDirs(Settings settings, Path homeDir) { - List dataDirs = PATH_DATA_SETTING.get(settings); - return dataDirs.isEmpty() - ? List.of(homeDir.resolve("data")) - : dataDirs.stream().map(TestEntitlementBootstrap::absolutePath).toList(); + assert POLICY_MANAGER == null && TEST_PATH_LOOKUP == null : "Test entitlement bootstrap called multiple times"; + TEST_PATH_LOOKUP = new TestPathLookup(tempDir); + POLICY_MANAGER = createPolicyManager(TEST_PATH_LOOKUP); + loadAgent(POLICY_MANAGER, TEST_PATH_LOOKUP); } - private static Collection sharedDataDir(Settings settings) { - String sharedDataDir = PATH_SHARED_DATA_SETTING.get(settings); - return Strings.hasText(sharedDataDir) ? List.of(absolutePath(sharedDataDir)) : List.of(); - } - - private static Collection repoDirs(Settings settings) { - return PATH_REPO_SETTING.get(settings).stream().map(TestEntitlementBootstrap::absolutePath).toList(); - } - - private static BiFunction, Collection> baseDirModifier(Consumer> consumer) { - // always return a new unmodifiable copy - return (BaseDir baseDir, Collection paths) -> { - paths = paths == null ? new HashSet<>() : new HashSet<>(paths); - consumer.accept(paths); - return Collections.unmodifiableCollection(paths); - }; - } - - private static Path unwrapFilterPath(Path path) { - while (path instanceof FilterPath fPath) { - path = fPath.getDelegate(); - } - return path; - } - - @SuppressForbidden(reason = "must be resolved using the default file system, rather then the mocked test file system") - private static Path absolutePath(String path) { - return Paths.get(path).toAbsolutePath().normalize(); - } - - private static List zeroOrOne(T item) { - if (item == null) { - return List.of(); - } else { - return List.of(item); - } - } - - public static boolean isEnabledForTest() { + public static boolean isEnabledForTests() { return Booleans.parseBoolean(System.getProperty("es.entitlement.enableForTests", "false")); } - public static void setActive(boolean newValue) { - policyManager.setActive(newValue); - } - - public static void setTriviallyAllowingTestCode(boolean newValue) { - policyManager.setTriviallyAllowingTestCode(newValue); + static TestPolicyManager testPolicyManager() { + return POLICY_MANAGER; } - public static void setEntitledTestPackages(String[] entitledTestPackages) { - policyManager.setEntitledTestPackages(entitledTestPackages); + static TestPathLookup testPathLookup() { + return TEST_PATH_LOOKUP; } - public static void resetAfterTest() { - // reset all base dirs except TEMP, which is initialized just once statically - baseDirPaths.keySet().retainAll(List.of(TEMP)); - if (policyManager != null) { - policyManager.resetAfterTest(); - } + private static void loadAgent(PolicyManager policyManager, PathLookup pathLookup) { + logger.debug("Loading entitlement agent"); + EntitlementInitialization.initializeArgs = new EntitlementInitialization.InitializeArgs(pathLookup, Set.of(), policyManager); + EntitlementBootstrap.loadAgent(EntitlementBootstrap.findAgentJar(), EntitlementInitialization.class.getName()); } private static TestPolicyManager createPolicyManager(PathLookup pathLookup) throws IOException { @@ -224,7 +97,7 @@ private static TestPolicyManager createPolicyManager(PathLookup pathLookup) thro String separator = System.getProperty("path.separator"); - // In productions, plugins would have access to their respective bundle directories, + // In production, plugins would have access to their respective bundle directories, // and so they'd be able to read from their jars. In testing, we approximate this // by considering the entire classpath to be "source paths" of all plugins. This // also has the effect of granting read access to everything on the test-only classpath, diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementsRule.java b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementsRule.java new file mode 100644 index 0000000000000..1c057de296e9b --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestEntitlementsRule.java @@ -0,0 +1,193 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.bootstrap; + +import org.apache.lucene.tests.mockfile.FilterPath; +import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.core.SuppressForbidden; +import org.elasticsearch.entitlement.runtime.policy.PathLookup; +import org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir; +import org.elasticsearch.entitlement.runtime.policy.TestPolicyManager; +import org.elasticsearch.logging.LogManager; +import org.elasticsearch.logging.Logger; +import org.elasticsearch.test.ESTestCase; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; + +import java.io.Closeable; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Consumer; + +import static org.elasticsearch.env.Environment.PATH_DATA_SETTING; +import static org.elasticsearch.env.Environment.PATH_HOME_SETTING; +import static org.elasticsearch.env.Environment.PATH_REPO_SETTING; +import static org.elasticsearch.env.Environment.PATH_SHARED_DATA_SETTING; + +public class TestEntitlementsRule implements TestRule { + private static final Logger logger = LogManager.getLogger(TestEntitlementsRule.class); + + private static final AtomicBoolean active = new AtomicBoolean(false); + private final TestPolicyManager policyManager; + private final TestPathLookup pathLookup; + + public TestEntitlementsRule() { + policyManager = TestEntitlementBootstrap.testPolicyManager(); + pathLookup = TestEntitlementBootstrap.testPathLookup(); + assert (policyManager == null) == (pathLookup == null); + } + + @Override + public Statement apply(Statement base, Description description) { + assert description.isSuite() : "must be used as ClassRule"; + + // class / suite level + boolean withoutEntitlements = description.getAnnotation(ESTestCase.WithoutEntitlements.class) != null; + boolean withEntitlementsOnTestCode = description.getAnnotation(ESTestCase.WithEntitlementsOnTestCode.class) != null; + var entitledPackages = description.getAnnotation(ESTestCase.EntitledTestPackages.class); + + if (policyManager != null) { + return new Statement() { + @Override + public void evaluate() throws Throwable { + if (active.compareAndSet(false, true)) { + try { + pathLookup.reset(); + policyManager.setActive(false == withoutEntitlements); + policyManager.setTriviallyAllowingTestCode(false == withEntitlementsOnTestCode); + if (entitledPackages != null) { + assert entitledPackages.value().length > 0 : "No test packages specified in @EntitledTestPackages"; + policyManager.setEntitledTestPackages(entitledPackages.value()); + } else { + policyManager.setEntitledTestPackages(); + } + policyManager.clearModuleEntitlementsCache(); + // evaluate the suite + base.evaluate(); + } finally { + pathLookup.reset(); + policyManager.resetAfterTest(); + active.set(false); + } + } else { + throw new AssertionError("TestPolicyManager doesn't support test isolation, test suits cannot be run in parallel"); + } + } + }; + } else if (withEntitlementsOnTestCode) { + throw new AssertionError( + "Cannot use @WithEntitlementsOnTestCode on tests that are not configured to use entitlements for testing" + ); + } else { + return base; + } + } + + /** + * Temporarily adds node paths based entitlements based on a node's {@code settings} and {@code configPath} + * until the returned handle is closed. + * @see PathLookup + */ + public Closeable addEntitledNodePaths(Settings settings, Path configPath) { + if (policyManager == null) { + return () -> {}; // noop if not running with entitlements + } + + var unwrappedConfigPath = configPath; + while (unwrappedConfigPath instanceof FilterPath fPath) { + unwrappedConfigPath = fPath.getDelegate(); + } + EntitledNodePaths entitledNodePaths = new EntitledNodePaths(settings, unwrappedConfigPath, this::removeEntitledNodePaths); + addEntitledNodePaths(entitledNodePaths); + return entitledNodePaths; + } + + /** + * Revoke all entitled node paths. + */ + public void revokeAllEntitledNodePaths() { + if (policyManager != null) { + pathLookup.reset(); + policyManager.clearModuleEntitlementsCache(); + } + } + + private record EntitledNodePaths(Settings settings, Path configPath, Consumer onClose) implements Closeable { + private Path homeDir() { + return absolutePath(PATH_HOME_SETTING.get(settings)); + } + + private Path configDir() { + return configPath != null ? configPath : homeDir().resolve("config"); + } + + private Path[] dataDirs() { + List dataDirs = PATH_DATA_SETTING.get(settings); + return dataDirs.isEmpty() + ? new Path[] { homeDir().resolve("data") } + : dataDirs.stream().map(EntitledNodePaths::absolutePath).toArray(Path[]::new); + } + + private Path[] sharedDataDir() { + String sharedDataDir = PATH_SHARED_DATA_SETTING.get(settings); + return Strings.hasText(sharedDataDir) ? new Path[] { absolutePath(sharedDataDir) } : new Path[0]; + } + + private Path[] repoDirs() { + return PATH_REPO_SETTING.get(settings).stream().map(EntitledNodePaths::absolutePath).toArray(Path[]::new); + } + + @SuppressForbidden(reason = "must be resolved using the default file system, rather then the mocked test file system") + private static Path absolutePath(String path) { + return Paths.get(path).toAbsolutePath().normalize(); + } + + @Override + public void close() { + // wipePendingDataDirectories in tests requires entitlement delegation to work as this uses server's FileSystemUtils. + // until ES-10920 is solved, node grants cannot be removed until the test suite completes unless explicitly removing all node + // grants using revokeNodeGrants where feasible. + // onClose.accept(this); + } + + @Override + public String toString() { + return Strings.format( + "EntitledNodePaths[configDir=%s, dataDirs=%s, sharedDataDir=%s, repoDirs=%s]", + configDir(), + dataDirs(), + sharedDataDir(), + repoDirs() + ); + } + } + + private void addEntitledNodePaths(EntitledNodePaths entitledNodePaths) { + logger.debug("Adding {}", entitledNodePaths); + pathLookup.add(BaseDir.CONFIG, entitledNodePaths.configDir()); + pathLookup.add(BaseDir.DATA, entitledNodePaths.dataDirs()); + pathLookup.add(BaseDir.SHARED_DATA, entitledNodePaths.sharedDataDir()); + pathLookup.add(BaseDir.SHARED_REPO, entitledNodePaths.repoDirs()); + policyManager.clearModuleEntitlementsCache(); + } + + private void removeEntitledNodePaths(EntitledNodePaths entitledNodePaths) { + logger.debug("Removing {}", entitledNodePaths); + pathLookup.remove(BaseDir.CONFIG, entitledNodePaths.configDir()); + pathLookup.remove(BaseDir.DATA, entitledNodePaths.dataDirs()); + pathLookup.remove(BaseDir.SHARED_DATA, entitledNodePaths.sharedDataDir()); + pathLookup.remove(BaseDir.SHARED_REPO, entitledNodePaths.repoDirs()); + policyManager.clearModuleEntitlementsCache(); + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestPathLookup.java new file mode 100644 index 0000000000000..1e491dec1d710 --- /dev/null +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/bootstrap/TestPathLookup.java @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the "Elastic License + * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side + * Public License v 1"; you may not use this file except in compliance with, at + * your election, the "Elastic License 2.0", the "GNU Affero General Public + * License v3.0 only", or the "Server Side Public License, v 1". + */ + +package org.elasticsearch.entitlement.bootstrap; + +import org.apache.lucene.tests.mockfile.FilterFileSystem; +import org.elasticsearch.entitlement.runtime.policy.PathLookup; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.stream.Stream; + +import static org.elasticsearch.entitlement.runtime.policy.PathLookup.BaseDir.TEMP; + +class TestPathLookup implements PathLookup { + private final Map> baseDirPaths; + + TestPathLookup(Path tempDir) { + baseDirPaths = new ConcurrentHashMap<>(); + baseDirPaths.put(TEMP, List.of(tempDir)); + } + + @Override + public Path pidFile() { + return null; + } + + @Override + public Stream getBaseDirPaths(BaseDir baseDir) { + return baseDirPaths.getOrDefault(baseDir, List.of()).stream(); + } + + @Override + public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { + return Stream.empty(); + } + + @Override + public boolean isPathOnDefaultFilesystem(Path path) { + var fileSystem = path.getFileSystem(); + if (fileSystem.getClass() != DEFAULT_FILESYSTEM_CLASS) { + while (fileSystem instanceof FilterFileSystem ffs) { + fileSystem = ffs.getDelegate(); + } + } + return fileSystem.getClass() == DEFAULT_FILESYSTEM_CLASS; + } + + void reset() { + baseDirPaths.keySet().retainAll(List.of(TEMP)); + } + + void add(BaseDir baseDir, Path... paths) { + baseDirPaths.compute(baseDir, baseDirModifier(Collection::add, paths)); + } + + void remove(BaseDir baseDir, Path... paths) { + baseDirPaths.compute(baseDir, baseDirModifier(Collection::remove, paths)); + } + + // This must allow for duplicate paths between nodes, the config dir for instance is shared across all nodes. + private static BiFunction, Collection> baseDirModifier( + BiConsumer, Path> operation, + Path... updates + ) { + // always return a new unmodifiable copy + return (BaseDir baseDir, Collection paths) -> { + paths = paths == null ? new ArrayList<>() : new ArrayList<>(paths); + for (Path update : updates) { + operation.accept(paths, update); + } + return Collections.unmodifiableCollection(paths); + }; + } +} diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java deleted file mode 100644 index 458d83590758c..0000000000000 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPathLookup.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the "Elastic License - * 2.0", the "GNU Affero General Public License v3.0 only", and the "Server Side - * Public License v 1"; you may not use this file except in compliance with, at - * your election, the "Elastic License 2.0", the "GNU Affero General Public - * License v3.0 only", or the "Server Side Public License, v 1". - */ - -package org.elasticsearch.entitlement.runtime.policy; - -import org.apache.lucene.tests.mockfile.FilterFileSystem; - -import java.nio.file.Path; -import java.util.Collection; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -public class TestPathLookup implements PathLookup { - final Map> baseDirPaths; - - public TestPathLookup(Map> baseDirPaths) { - this.baseDirPaths = baseDirPaths; - } - - @Override - public Path pidFile() { - return null; - } - - @Override - public Stream getBaseDirPaths(BaseDir baseDir) { - return baseDirPaths.getOrDefault(baseDir, List.of()).stream(); - } - - @Override - public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { - return Stream.empty(); - } - - @Override - public boolean isPathOnDefaultFilesystem(Path path) { - var fileSystem = path.getFileSystem(); - if (fileSystem.getClass() != DEFAULT_FILESYSTEM_CLASS) { - while (fileSystem instanceof FilterFileSystem ffs) { - fileSystem = ffs.getDelegate(); - } - } - return fileSystem.getClass() == DEFAULT_FILESYSTEM_CLASS; - } -} diff --git a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java index bd1e6a468de31..f7397c8f898ed 100644 --- a/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java +++ b/test/framework/src/main/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManager.java @@ -31,7 +31,7 @@ public class TestPolicyManager extends PolicyManager { boolean isActive; boolean isTriviallyAllowingTestCode; - String[] entitledTestPackages = TEST_FRAMEWORK_PACKAGE_PREFIXES; + String[] entitledTestPackages; /** * We don't have modules in tests, so we can't use the inherited map of entitlements per module. @@ -65,27 +65,30 @@ public void setTriviallyAllowingTestCode(boolean newValue) { } public void setEntitledTestPackages(String... entitledTestPackages) { + if (entitledTestPackages == null || entitledTestPackages.length == 0) { + this.entitledTestPackages = TEST_FRAMEWORK_PACKAGE_PREFIXES; // already validated and sorted + return; + } + assertNoRedundantPrefixes(TEST_FRAMEWORK_PACKAGE_PREFIXES, entitledTestPackages, false); if (entitledTestPackages.length > 1) { assertNoRedundantPrefixes(entitledTestPackages, entitledTestPackages, true); } - String[] packages = ArrayUtils.concat(this.entitledTestPackages, entitledTestPackages); + String[] packages = ArrayUtils.concat(TEST_FRAMEWORK_PACKAGE_PREFIXES, entitledTestPackages); Arrays.sort(packages); this.entitledTestPackages = packages; } - /** - * Called between tests so each test is not affected by prior tests - */ public final void resetAfterTest() { - clearModuleEntitlementsCache(); isActive = false; isTriviallyAllowingTestCode = true; + entitledTestPackages = TEST_FRAMEWORK_PACKAGE_PREFIXES; + clearModuleEntitlementsCache(); } /** * Clear cached module entitlements. - * This is required after updating entries in {@link TestPathLookup}. + * This is required after updating path entries. */ public final void clearModuleEntitlementsCache() { assert moduleEntitlementsMap.isEmpty() : "We're not supposed to be using moduleEntitlementsMap in tests"; diff --git a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java index 90ed355233556..ffcf02277c680 100644 --- a/test/framework/src/main/java/org/elasticsearch/node/MockNode.java +++ b/test/framework/src/main/java/org/elasticsearch/node/MockNode.java @@ -23,7 +23,7 @@ import org.elasticsearch.common.util.MockBigArrays; import org.elasticsearch.common.util.MockPageCacheRecycler; import org.elasticsearch.common.util.PageCacheRecycler; -import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; +import org.elasticsearch.core.IOUtils; import org.elasticsearch.env.Environment; import org.elasticsearch.http.HttpServerTransport; import org.elasticsearch.indices.ExecutorSelector; @@ -54,6 +54,7 @@ import org.elasticsearch.transport.TransportService; import org.elasticsearch.transport.TransportSettings; +import java.io.Closeable; import java.nio.file.Path; import java.util.Collection; import java.util.Collections; @@ -236,31 +237,37 @@ protected HttpServerTransport newHttpTransport(PluginsService pluginsService, Ne private final Collection> classpathPlugins; + // handle for temporarily entitled node paths for this node; these will be removed on close. + private final Closeable entitledNodePaths; + public MockNode(final Settings settings, final Collection> classpathPlugins) { - this(settings, classpathPlugins, true); + this(settings, classpathPlugins, true, () -> {}); } public MockNode( final Settings settings, final Collection> classpathPlugins, - final boolean forbidPrivateIndexSettings + final boolean forbidPrivateIndexSettings, + final Closeable entitledNodePaths ) { - this(settings, classpathPlugins, null, forbidPrivateIndexSettings); + this(settings, classpathPlugins, null, forbidPrivateIndexSettings, entitledNodePaths); } public MockNode( final Settings settings, final Collection> classpathPlugins, final Path configPath, - final boolean forbidPrivateIndexSettings + final boolean forbidPrivateIndexSettings, + final Closeable entitledNodePaths ) { - this(prepareEnvironment(settings, configPath), classpathPlugins, forbidPrivateIndexSettings); + this(prepareEnvironment(settings, configPath), classpathPlugins, forbidPrivateIndexSettings, entitledNodePaths); } private MockNode( final Environment environment, final Collection> classpathPlugins, - final boolean forbidPrivateIndexSettings + final boolean forbidPrivateIndexSettings, + final Closeable entitledNodePaths ) { super(NodeConstruction.prepareConstruction(environment, null, new MockServiceProvider() { @@ -271,10 +278,10 @@ PluginsService newPluginService(Environment environment, PluginsLoader pluginsLo }, forbidPrivateIndexSettings)); this.classpathPlugins = classpathPlugins; + this.entitledNodePaths = entitledNodePaths; } private static Environment prepareEnvironment(final Settings settings, final Path configPath) { - TestEntitlementBootstrap.registerNodeBaseDirs(settings, configPath); return InternalSettingsPreparer.prepareEnvironment( Settings.builder().put(TransportSettings.PORT.getKey(), ESTestCase.getPortRange()).put(settings).build(), Collections.emptyMap(), @@ -288,8 +295,7 @@ public synchronized boolean awaitClose(long timeout, TimeUnit timeUnit) throws I try { return super.awaitClose(timeout, timeUnit); } finally { - // wipePendingDataDirectories requires entitlement delegation to work due to this using FileSystemUtils ES-10920 - // TestEntitlementBootstrap.unregisterNodeBaseDirs(settings(), getEnvironment().configDir()); + IOUtils.closeWhileHandlingException(entitledNodePaths); } } diff --git a/test/framework/src/main/java/org/elasticsearch/test/AbstractMultiClustersTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/AbstractMultiClustersTestCase.java index ea82c9d21ab89..1319af74ff794 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/AbstractMultiClustersTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/AbstractMultiClustersTestCase.java @@ -126,7 +126,8 @@ public final void startClusters() throws Exception { 0, clusterName + "-", mockPlugins, - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); cluster.beforeTest(random()); clusters.put(clusterAlias, cluster); diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java index a5f27f7a6696a..88b608d5aed01 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESIntegTestCase.java @@ -526,6 +526,7 @@ private TestCluster buildAndPutCluster(Scope currentClusterScope, long seed) thr // close the previous one and create a new one if (testCluster != null) { IOUtils.closeWhileHandlingException(testCluster::close); + TEST_ENTITLEMENTS.revokeAllEntitledNodePaths(); } testCluster = buildTestCluster(currentClusterScope, seed); } @@ -2164,7 +2165,8 @@ protected TestCluster buildTestCluster(Scope scope, long seed) throws IOExceptio getClientWrapper(), forbidPrivateIndexSettings(), forceSingleDataPath(), - autoManageVotingExclusions() + autoManageVotingExclusions(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); } diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java index 4ba108d944d3d..59be33e3564ea 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESSingleNodeTestCase.java @@ -280,7 +280,7 @@ private Node newNode() { plugins.add(ConcurrentSearchTestPlugin.class); } plugins.add(MockScriptService.TestPlugin.class); - Node node = new MockNode(settings, plugins, forbidPrivateIndexSettings()); + Node node = new MockNode(settings, plugins, forbidPrivateIndexSettings(), TEST_ENTITLEMENTS.addEntitledNodePaths(settings, null)); try { node.start(); } catch (NodeValidationException e) { diff --git a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java index e4308a39124be..1f152a593a9ef 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java +++ b/test/framework/src/main/java/org/elasticsearch/test/ESTestCase.java @@ -102,7 +102,7 @@ import org.elasticsearch.core.SuppressForbidden; import org.elasticsearch.core.TimeValue; import org.elasticsearch.core.Tuple; -import org.elasticsearch.entitlement.bootstrap.TestEntitlementBootstrap; +import org.elasticsearch.entitlement.bootstrap.TestEntitlementsRule; import org.elasticsearch.env.Environment; import org.elasticsearch.env.NodeEnvironment; import org.elasticsearch.env.TestEnvironment; @@ -150,6 +150,7 @@ import org.junit.AfterClass; import org.junit.Before; import org.junit.BeforeClass; +import org.junit.ClassRule; import org.junit.Rule; import org.junit.internal.AssumptionViolatedException; import org.junit.rules.RuleChain; @@ -520,30 +521,8 @@ protected void afterIfSuccessful() throws Exception {} String[] value(); } - @BeforeClass - public static void setupEntitlementsForClass() { - boolean withoutEntitlements = getTestClass().isAnnotationPresent(WithoutEntitlements.class); - boolean withEntitlementsOnTestCode = getTestClass().isAnnotationPresent(WithEntitlementsOnTestCode.class); - EntitledTestPackages entitledPackages = getTestClass().getAnnotation(EntitledTestPackages.class); - - if (TestEntitlementBootstrap.isEnabledForTest()) { - TestEntitlementBootstrap.setActive(false == withoutEntitlements); - TestEntitlementBootstrap.setTriviallyAllowingTestCode(false == withEntitlementsOnTestCode); - if (entitledPackages != null) { - assert entitledPackages.value().length > 0 : "No test packages specified in @EntitledTestPackages"; - TestEntitlementBootstrap.setEntitledTestPackages(entitledPackages.value()); - } - } else if (withEntitlementsOnTestCode) { - throw new AssertionError( - "Cannot use @WithEntitlementsOnTestCode on tests that are not configured to use entitlements for testing" - ); - } - } - - @AfterClass - public static void resetEntitlements() { - TestEntitlementBootstrap.resetAfterTest(); - } + @ClassRule + public static final TestEntitlementsRule TEST_ENTITLEMENTS = new TestEntitlementsRule(); // setup mock filesystems for this test run. we change PathUtils // so that all accesses are plumbed thru any mock wrappers diff --git a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java index fd16b30850f29..5510c60252140 100644 --- a/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java +++ b/test/framework/src/main/java/org/elasticsearch/test/InternalTestCluster.java @@ -178,6 +178,14 @@ */ public final class InternalTestCluster extends TestCluster { + /** + * Temporarily adds node paths based entitlements based on a node's {@code settings} and {@code configPath} + * until the returned handle is closed. + */ + public interface EntitledNodePathsProvider { + Closeable addEntitledNodePaths(Settings settings, Path configPath); + } + private static final Logger logger = LogManager.getLogger(InternalTestCluster.class); private static final Predicate DATA_NODE_PREDICATE = new Predicate<>() { @@ -277,6 +285,8 @@ public String toString() { // index of node to bootstrap as master, or BOOTSTRAP_MASTER_NODE_INDEX_AUTO or BOOTSTRAP_MASTER_NODE_INDEX_DONE private int bootstrapMasterNodeIndex = BOOTSTRAP_MASTER_NODE_INDEX_AUTO; + private final EntitledNodePathsProvider entitledNodePathsProvider; + public InternalTestCluster( final long clusterSeed, final Path baseDir, @@ -289,7 +299,8 @@ public InternalTestCluster( final int numClientNodes, final String nodePrefix, final Collection> mockPlugins, - final Function clientWrapper + final Function clientWrapper, + EntitledNodePathsProvider entitledNodePathsProvider ) { this( clusterSeed, @@ -306,7 +317,8 @@ public InternalTestCluster( clientWrapper, true, false, - true + true, + entitledNodePathsProvider ); } @@ -325,7 +337,8 @@ public InternalTestCluster( final Function clientWrapper, final boolean forbidPrivateIndexSettings, final boolean forceSingleDataPath, - final boolean autoManageVotingExclusions + final boolean autoManageVotingExclusions, + final EntitledNodePathsProvider entitledNodePathsProvider ) { super(clusterSeed); this.autoManageMasterNodes = autoManageMasterNodes; @@ -334,6 +347,7 @@ public InternalTestCluster( this.baseDir = baseDir; this.clusterName = clusterName; this.autoManageVotingExclusions = autoManageVotingExclusions; + this.entitledNodePathsProvider = entitledNodePathsProvider; if (minNumDataNodes < 0 || maxNumDataNodes < 0) { throw new IllegalArgumentException("minimum and maximum number of data nodes must be >= 0"); } @@ -782,7 +796,14 @@ private synchronized NodeAndClient buildNode(int nodeId, Settings settings, bool // we clone this here since in the case of a node restart we might need it again secureSettings = ((MockSecureSettings) secureSettings).clone(); } - MockNode node = new MockNode(settings, plugins, nodeConfigurationSource.nodeConfigPath(nodeId), forbidPrivateIndexSettings); + Path configPath = nodeConfigurationSource.nodeConfigPath(nodeId); + MockNode node = new MockNode( + settings, + plugins, + configPath, + forbidPrivateIndexSettings, + entitledNodePathsProvider.addEntitledNodePaths(settings, configPath) + ); node.injector().getInstance(TransportService.class).addLifecycleListener(new LifecycleListener() { @Override public void afterStart() { @@ -1060,7 +1081,12 @@ private void recreateNode(final Settings newSettings, final Runnable onTransport .put(NodeEnvironment.NODE_ID_SEED_SETTING.getKey(), newIdSeed) .build(); Collection> plugins = node.getClasspathPlugins(); - node = new MockNode(finalSettings, plugins, forbidPrivateIndexSettings); + node = new MockNode( + finalSettings, + plugins, + forbidPrivateIndexSettings, + entitledNodePathsProvider.addEntitledNodePaths(finalSettings, null) + ); node.injector().getInstance(TransportService.class).addLifecycleListener(new LifecycleListener() { @Override public void afterStart() { diff --git a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java index c0f1aaa227cdb..35a7d29194700 100644 --- a/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java +++ b/test/framework/src/test/java/org/elasticsearch/entitlement/runtime/policy/TestPolicyManagerTests.java @@ -13,10 +13,12 @@ import org.elasticsearch.test.ESTestCase; import org.junit.Before; +import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; import static org.elasticsearch.entitlement.runtime.policy.PolicyManager.ComponentKind.PLUGIN; import static org.hamcrest.Matchers.both; @@ -34,20 +36,40 @@ public void setupPolicyManager() { List.of(), Map.of(), c -> new PolicyScope(PLUGIN, "example-plugin" + scopeCounter.incrementAndGet(), "org.example.module"), - new TestPathLookup(Map.of()), + new PathLookup() { + @Override + public Path pidFile() { + return null; + } + + @Override + public Stream getBaseDirPaths(BaseDir baseDir) { + return Stream.empty(); + } + + @Override + public Stream resolveSettingPaths(BaseDir baseDir, String settingName) { + return Stream.empty(); + } + + @Override + public boolean isPathOnDefaultFilesystem(Path path) { + return true; + } + }, List.of(), List.of() ); policyManager.setActive(true); } - public void testResetAfterTest() { + public void testClearModuleEntitlementsCache() { assertTrue(policyManager.classEntitlementsMap.isEmpty()); assertEquals("example-plugin1", policyManager.getEntitlements(getClass()).componentName()); assertEquals("example-plugin1", policyManager.getEntitlements(getClass()).componentName()); assertFalse(policyManager.classEntitlementsMap.isEmpty()); - policyManager.resetAfterTest(); + policyManager.clearModuleEntitlementsCache(); assertTrue(policyManager.classEntitlementsMap.isEmpty()); assertEquals("example-plugin2", policyManager.getEntitlements(getClass()).componentName()); diff --git a/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java b/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java index f5c358b3b3301..392ec929e39c3 100644 --- a/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java +++ b/test/framework/src/test/java/org/elasticsearch/test/test/InternalTestClusterTests.java @@ -85,7 +85,8 @@ public void testInitializiationIsConsistent() { numClientNodes, nodePrefix, Collections.emptyList(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); InternalTestCluster cluster1 = new InternalTestCluster( clusterSeed, @@ -99,7 +100,8 @@ public void testInitializiationIsConsistent() { numClientNodes, nodePrefix, Collections.emptyList(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); assertClusters(cluster0, cluster1, true); } @@ -198,7 +200,8 @@ public Path nodeConfigPath(int nodeOrdinal) { numClientNodes, nodePrefix, mockPlugins(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); cluster0.setBootstrapMasterNodeIndex(bootstrapMasterNodeIndex); @@ -214,7 +217,8 @@ public Path nodeConfigPath(int nodeOrdinal) { numClientNodes, nodePrefix, mockPlugins(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); cluster1.setBootstrapMasterNodeIndex(bootstrapMasterNodeIndex); @@ -280,7 +284,8 @@ public Path nodeConfigPath(int nodeOrdinal) { numClientNodes, nodePrefix, mockPlugins(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); try { cluster.beforeTest(random()); @@ -375,7 +380,8 @@ public Path nodeConfigPath(int nodeOrdinal) { 0, "", mockPlugins(), - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); cluster.beforeTest(random()); List roles = new ArrayList<>(); @@ -467,7 +473,8 @@ public Path nodeConfigPath(int nodeOrdinal) { 0, nodePrefix, plugins, - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); try { cluster.beforeTest(random()); diff --git a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java index 1b7875e4a36b4..7dc7fb523380c 100644 --- a/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java +++ b/x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/CcrIntegTestCase.java @@ -182,7 +182,8 @@ public final void startClusters() throws Exception { 0, "leader", mockPlugins, - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); leaderCluster.beforeTest(random()); leaderCluster.ensureAtLeastNumDataNodes(numberOfNodesPerCluster()); @@ -204,7 +205,8 @@ public final void startClusters() throws Exception { 0, "follower", mockPlugins, - Function.identity() + Function.identity(), + TEST_ENTITLEMENTS::addEntitledNodePaths ); clusterGroup = new ClusterGroup(leaderCluster, followerCluster);