diff --git a/libs/core/src/main/java/org/elasticsearch/core/FixForMultiProject.java b/libs/core/src/main/java/org/elasticsearch/core/FixForMultiProject.java index f3d7d78e8907..f89d87f5151c 100644 --- a/libs/core/src/main/java/org/elasticsearch/core/FixForMultiProject.java +++ b/libs/core/src/main/java/org/elasticsearch/core/FixForMultiProject.java @@ -23,4 +23,9 @@ import java.lang.annotation.Target; { ElementType.LOCAL_VARIABLE, ElementType.CONSTRUCTOR, ElementType.FIELD, ElementType.METHOD, ElementType.TYPE, ElementType.MODULE } ) public @interface FixForMultiProject { + + /** + * Some explanation on what and why for the future fix + */ + String description() default ""; } diff --git a/modules/multi-project/src/main/java/org/elasticsearch/multiproject/MultiProjectResolver.java b/modules/multi-project/src/main/java/org/elasticsearch/multiproject/MultiProjectResolver.java index 51502da7bbce..eeedbfc30a8a 100644 --- a/modules/multi-project/src/main/java/org/elasticsearch/multiproject/MultiProjectResolver.java +++ b/modules/multi-project/src/main/java/org/elasticsearch/multiproject/MultiProjectResolver.java @@ -37,14 +37,14 @@ public class MultiProjectResolver implements ProjectResolver { } @Override - public ProjectMetadata getProjectMetadata(Metadata metadata) { - var headerValue = getProjectIdFromThreadContext(); + public ProjectId getProjectId() { + final String headerValue = getProjectIdFromThreadContext(); // TODO: we temporarily fall back to the default project id when there is no project id present in the thread context. // This fallback should be converted into an exception once we merge to public/serverless. if (headerValue == null) { - return metadata.getProject(Metadata.DEFAULT_PROJECT_ID); + return Metadata.DEFAULT_PROJECT_ID; } - return findProject(metadata, headerValue); + return new ProjectId(headerValue); } @Override diff --git a/modules/multi-project/src/test/java/org/elasticsearch/multiproject/MultiProjectResolverTests.java b/modules/multi-project/src/test/java/org/elasticsearch/multiproject/MultiProjectResolverTests.java index b201d8216f0d..3ff6533c1712 100644 --- a/modules/multi-project/src/test/java/org/elasticsearch/multiproject/MultiProjectResolverTests.java +++ b/modules/multi-project/src/test/java/org/elasticsearch/multiproject/MultiProjectResolverTests.java @@ -78,7 +78,7 @@ public class MultiProjectResolverTests extends ESTestCase { var projects = createProjects(); var metadata = Metadata.builder().projectMetadata(projects).build(); threadPool.getThreadContext().putHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER, randomUUID()); - assertThrows(IllegalArgumentException.class, () -> resolver.getProjectMetadata(metadata)); + assertThrows(AssertionError.class, () -> resolver.getProjectMetadata(metadata)); } public void testGetAllProjectIds() { @@ -130,7 +130,7 @@ public class MultiProjectResolverTests extends ESTestCase { resolver.executeOnProject(projectId1, () -> { assertThat(resolver.getProjectMetadata(state).id(), equalTo(projectId1)); - assertThat(resolver.getProjectId(state), equalTo(projectId1)); + assertThat(resolver.getProjectId(), equalTo(projectId1)); assertThat(threadContext.getHeader(Task.X_OPAQUE_ID_HTTP_HEADER), equalTo(opaqueId)); assertThat(threadContext.getHeader(randomHeaderName), equalTo(randomHeaderValue)); @@ -152,7 +152,7 @@ public class MultiProjectResolverTests extends ESTestCase { // Can set a new project id, after the previous one has been cleared resolver.executeOnProject(projectId2, () -> { assertThat(resolver.getProjectMetadata(state).id(), equalTo(projectId2)); - assertThat(resolver.getProjectId(state), equalTo(projectId2)); + assertThat(resolver.getProjectId(), equalTo(projectId2)); assertThat(threadContext.getHeader(Task.X_OPAQUE_ID_HTTP_HEADER), equalTo(opaqueId)); assertThat(threadContext.getHeader(randomHeaderName), equalTo(randomHeaderValue)); diff --git a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java index 030e02c79d84..0662b25a7e3e 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/cluster/health/TransportClusterHealthAction.java @@ -103,7 +103,7 @@ public class TransportClusterHealthAction extends TransportMasterNodeReadAction< final CancellableTask cancellableTask = (CancellableTask) task; final int waitCount = getWaitCount(request); - final ProjectId projectId = projectResolver.getProjectId(state); + final ProjectId projectId = projectResolver.getProjectId(); if (request.waitForEvents() != null) { waitForEventsAndExecuteHealth( diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java index 973dd499d210..ef653b0d009a 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/create/TransportCreateIndexAction.java @@ -133,7 +133,8 @@ public class TransportCreateIndexAction extends TransportMasterNodeAction listener ) { - final var projectId = projectResolver.getProjectId(state); + final var projectId = projectResolver.getProjectId(); indexTemplateService.removeIndexTemplateV2(projectId, request.names(), request.masterNodeTimeout(), listener); } diff --git a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComposableIndexTemplateAction.java b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComposableIndexTemplateAction.java index 1c6eff57ae06..da045f0efa05 100644 --- a/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComposableIndexTemplateAction.java +++ b/server/src/main/java/org/elasticsearch/action/admin/indices/template/put/TransportPutComposableIndexTemplateAction.java @@ -97,7 +97,7 @@ public class TransportPutComposableIndexTemplateAction extends AcknowledgedTrans ) { verifyIfUsingReservedComponentTemplates(request, state); ComposableIndexTemplate indexTemplate = request.indexTemplate(); - ProjectId projectId = projectResolver.getProjectId(state); + ProjectId projectId = projectResolver.getProjectId(); indexTemplateService.putIndexTemplateV2( request.cause(), request.create(), diff --git a/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java b/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java index 2b92905df5d9..6e317376503e 100644 --- a/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java +++ b/server/src/main/java/org/elasticsearch/action/bulk/TransportAbstractBulkAction.java @@ -190,7 +190,7 @@ public abstract class TransportAbstractBulkAction extends HandledTransportAction throws IOException { boolean hasIndexRequestsWithPipelines = false; ClusterState state = clusterService.state(); - ProjectId projectId = projectResolver.getProjectId(state); + ProjectId projectId = projectResolver.getProjectId(); final Metadata metadata; Map componentTemplateSubstitutions = bulkRequest.getComponentTemplateSubstitutions(); Map indexTemplateSubstitutions = bulkRequest.getIndexTemplateSubstitutions(); diff --git a/server/src/main/java/org/elasticsearch/action/support/master/info/TransportClusterInfoAction.java b/server/src/main/java/org/elasticsearch/action/support/master/info/TransportClusterInfoAction.java index 9fcb5dbd7110..39f87a4c2e34 100644 --- a/server/src/main/java/org/elasticsearch/action/support/master/info/TransportClusterInfoAction.java +++ b/server/src/main/java/org/elasticsearch/action/support/master/info/TransportClusterInfoAction.java @@ -56,7 +56,7 @@ public abstract class TransportClusterInfoAction listener ) { - ProjectId projectId = projectResolver.getProjectId(state); + ProjectId projectId = projectResolver.getProjectId(); String[] concreteIndices = indexNameExpressionResolver.concreteIndexNames(state.metadata().getProject(projectId), request); doMasterOperation(task, request, concreteIndices, state, listener); } diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java index 8bac1b429eba..278c5bdcbf00 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/IndexNameExpressionResolver.java @@ -166,7 +166,7 @@ public class IndexNameExpressionResolver { * indices options in the context don't allow such a case; if a remote index is requested. */ public String[] concreteIndexNames(ClusterState state, IndicesOptions options, String... indexExpressions) { - return concreteIndexNames(state.metadata().getProject(projectResolver.getProjectId(state)), options, indexExpressions); + return concreteIndexNames(state.metadata().getProject(projectResolver.getProjectId()), options, indexExpressions); } /** @@ -195,7 +195,7 @@ public class IndexNameExpressionResolver { public String[] concreteIndexNames(ClusterState state, IndicesOptions options, boolean includeDataStreams, String... indexExpressions) { return concreteIndexNames( - state.metadata().getProject(projectResolver.getProjectId(state)), + state.metadata().getProject(projectResolver.getProjectId()), options, includeDataStreams, indexExpressions @@ -222,7 +222,7 @@ public class IndexNameExpressionResolver { } public String[] concreteIndexNames(ClusterState state, IndicesOptions options, IndicesRequest request) { - return concreteIndexNames(state.metadata().getProject(projectResolver.getProjectId(state)), options, request); + return concreteIndexNames(state.metadata().getProject(projectResolver.getProjectId()), options, request); } public String[] concreteIndexNames(ProjectMetadata project, IndicesOptions options, IndicesRequest request) { diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java index 9759bad53e5c..f4c3c64eb6be 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java @@ -457,6 +457,7 @@ public class Metadata implements Diffable, ChunkedToXContent { return projectMetadata.containsKey(projectId); } + @FixForMultiProject(description = "We should throw for null metadata for production") public ProjectMetadata getProject(ProjectId projectId) { ProjectMetadata metadata = projectMetadata.get(projectId); assert metadata != null : "Project " + projectId.id() + " not found in " + projectMetadata.keySet(); diff --git a/server/src/main/java/org/elasticsearch/cluster/project/DefaultProjectResolver.java b/server/src/main/java/org/elasticsearch/cluster/project/DefaultProjectResolver.java index 95bb025a6ab1..513caeb0d15a 100644 --- a/server/src/main/java/org/elasticsearch/cluster/project/DefaultProjectResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/project/DefaultProjectResolver.java @@ -9,27 +9,49 @@ package org.elasticsearch.cluster.project; +import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.core.FixForMultiProject; +import java.util.Collection; +import java.util.Map; + /** * This is the {@link ProjectResolver} implementation that stateful uses. * It mainly ensures that there's a single and implicit project existing at all times. */ public class DefaultProjectResolver implements ProjectResolver { + public static final DefaultProjectResolver INSTANCE = new DefaultProjectResolver(); @Override @FixForMultiProject public ProjectMetadata getProjectMetadata(Metadata metadata) { - // TODO-multi-project assert no specific project id is requested, and/or that a sole project exists in the cluster state - return metadata.getProject(); + assert assertSingleProject(metadata); + return ProjectResolver.super.getProjectMetadata(metadata); } @Override + public ProjectId getProjectId() { + // TODO: We can assert the cluster state really has such one project if we let DefaultProjectResolver reference a clusterService + return Metadata.DEFAULT_PROJECT_ID; + } + + @Override + public Collection getProjectIds(ClusterState clusterState) { + assert assertSingleProject(clusterState.metadata()); + return ProjectResolver.super.getProjectIds(clusterState); + } + + private boolean assertSingleProject(Metadata metadata) { + final Map projects = metadata.projects(); + assert projects.size() == 1 && projects.containsKey(getProjectId()) : "expect only default projects, but got " + projects.keySet(); + return true; + } + public void executeOnProject(ProjectId projectId, CheckedRunnable body) throws E { if (projectId.equals(Metadata.DEFAULT_PROJECT_ID)) { body.run(); diff --git a/server/src/main/java/org/elasticsearch/cluster/project/ProjectIdResolver.java b/server/src/main/java/org/elasticsearch/cluster/project/ProjectIdResolver.java new file mode 100644 index 000000000000..ca9f8af4c3cd --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/project/ProjectIdResolver.java @@ -0,0 +1,30 @@ +/* + * 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.cluster.project; + +import org.elasticsearch.cluster.metadata.ProjectId; + +/** + * This is a lightweight version of the {@link ProjectResolver}. It resolves for the {@link ProjectId} for the current + * request in the execution context. It intentionally does not take a {@link org.elasticsearch.cluster.ClusterState} + * so that it can be used in places where we currently do not want to expose {@link org.elasticsearch.cluster.ClusterState} + * such as REST handlers. + * NOTE this interface is also experimental since we have not fully settled on whether exposing it for REST handlers is + * a pattern to be followed. We should discuss it case-by-case by using it in more places until we settle on the issue. + */ +public interface ProjectIdResolver { + + /** + * Retrieve the project for the current request. + * + * @return The identifier of the current project. + */ + ProjectId getProjectId(); +} diff --git a/server/src/main/java/org/elasticsearch/cluster/project/ProjectResolver.java b/server/src/main/java/org/elasticsearch/cluster/project/ProjectResolver.java index cf8b2c207f4a..03c46e82e15a 100644 --- a/server/src/main/java/org/elasticsearch/cluster/project/ProjectResolver.java +++ b/server/src/main/java/org/elasticsearch/cluster/project/ProjectResolver.java @@ -17,42 +17,36 @@ import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.core.CheckedRunnable; import java.util.Collection; +import java.util.Objects; import java.util.Set; /** * This exposes methods for accessing project-scoped data from the global one. * The project in question is implied from the thread context. */ -public interface ProjectResolver { - ProjectMetadata getProjectMetadata(Metadata metadata); +public interface ProjectResolver extends ProjectIdResolver { + default ProjectMetadata getProjectMetadata(Metadata metadata) { + return metadata.getProject(getProjectId()); + } default ProjectMetadata getProjectMetadata(ClusterState clusterState) { return getProjectMetadata(clusterState.metadata()); } + // TODO: What happens if the context does not have a project? throw or return null? default ProjectState getProjectState(ClusterState clusterState) { - final ProjectId id = getProjectId(clusterState); + final ProjectId id = getProjectId(); final ProjectState projectState = clusterState.projectState(id); assert projectState != null : "Received null project state for [" + id + "] from " + clusterState; return projectState; } - // TODO multi-project: change this so it doesn't take in any parameters - /** - * @return The identifier of the current project. This will be the same value as - * {@link #getProjectMetadata(Metadata)}{@code .}{@link ProjectMetadata#id() id()}, but the resolver may implement it more efficiently - * and/or perform additional checks. - */ - default ProjectId getProjectId(ClusterState clusterState) { - return getProjectMetadata(clusterState).id(); - } - /** * Returns the identifiers of all projects on which this request should operate. * In practice, this will either be: *
    *
  • If the request is tied to a single project, then a collection with a single item that is the same as - * {@link #getProjectId(ClusterState)}
  • + * {@link #getProjectId()} if the project exists in the cluster state *
  • If the request is not tied to a single project and it is allowed to access all projects, * then a collection of all the project ids in the cluster
  • *
  • Otherwise an exception is thrown
  • @@ -61,14 +55,18 @@ public interface ProjectResolver { * @throws SecurityException if this request is required to provide a project id, but none was provided */ default Collection getProjectIds(ClusterState clusterState) { - return Set.of(this.getProjectId(clusterState)); + final ProjectId projectId = Objects.requireNonNull(getProjectId()); + if (clusterState.metadata().hasProject(projectId) == false) { + throw new IllegalArgumentException("Project [" + projectId + "] does not exist"); + } + return Set.of(getProjectId()); } /** * Execute a block in the context of a specific project. * * This method:
      - *
    1. Configures the execution (thread) context so that any calls to resolve a project (e.g. {@link #getProjectId(ClusterState)} + *
    2. Configures the execution (thread) context so that any calls to resolve a project (e.g. {@link #getProjectId()} * or {@link #getProjectMetadata(Metadata)}) will return the project specified by {@code projectId}.
    3. *
    4. Executes the {@link CheckedRunnable#run()} method on the supplied {@code body}
    5. *
    6. Restores the context to its original state
    7. diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/diskusage/TransportAnalyzeIndexDiskUsageActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/diskusage/TransportAnalyzeIndexDiskUsageActionTests.java index ddb2259c8147..2dad1307ccb9 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/diskusage/TransportAnalyzeIndexDiskUsageActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/diskusage/TransportAnalyzeIndexDiskUsageActionTests.java @@ -298,7 +298,7 @@ public class TransportAnalyzeIndexDiskUsageActionTests extends ESTestCase { new IndexNameExpressionResolver( new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ) { @Override public String[] concreteIndexNames(ProjectMetadata project, IndicesRequest request) { diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java index 61109ac26fd3..8b2276ee9ae2 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/get/GetIndexActionTests.java @@ -143,7 +143,7 @@ public class GetIndexActionTests extends ESSingleNodeTestCase { static class Resolver extends IndexNameExpressionResolver { Resolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/admin/indices/settings/get/GetSettingsActionTests.java b/server/src/test/java/org/elasticsearch/action/admin/indices/settings/get/GetSettingsActionTests.java index 407748cb08f3..eeeb55eb64bd 100644 --- a/server/src/test/java/org/elasticsearch/action/admin/indices/settings/get/GetSettingsActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/admin/indices/settings/get/GetSettingsActionTests.java @@ -157,7 +157,7 @@ public class GetSettingsActionTests extends ESTestCase { static class Resolver extends IndexNameExpressionResolver { Resolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java index 634ef1237950..ef53e9e0808e 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java @@ -159,7 +159,7 @@ public class TransportBulkActionIngestTests extends ESTestCase { TestIndexNameExpressionResolver.newInstance(), new IndexingPressure(SETTINGS), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + TestProjectResolvers.DEFAULT_PROJECT_ONLY, FailureStoreMetrics.NOOP ); } diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTests.java index 3d7628d7e27c..733da7a96516 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTests.java @@ -37,7 +37,7 @@ import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; -import org.elasticsearch.cluster.project.TestProjectResolvers; +import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.routing.GlobalRoutingTableTestHelper; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.cluster.service.ClusterService; @@ -45,6 +45,7 @@ import org.elasticsearch.common.io.stream.BytesStreamOutput; import org.elasticsearch.common.io.stream.StreamInput; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.EsRejectedExecutionException; +import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.features.FeatureService; import org.elasticsearch.index.Index; import org.elasticsearch.index.IndexNotFoundException; @@ -73,6 +74,7 @@ import java.util.SortedMap; import java.util.TreeMap; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import static org.elasticsearch.action.bulk.TransportBulkAction.prohibitCustomRoutingOnDataStream; import static org.elasticsearch.cluster.metadata.MetadataCreateDataStreamServiceTests.createDataStream; @@ -95,6 +97,7 @@ public class TransportBulkActionTests extends ESTestCase { private TestTransportBulkAction bulkAction; private FeatureService mockFeatureService; + private AtomicReference activeProjectId = new AtomicReference<>(); class TestTransportBulkAction extends TransportBulkAction { @@ -116,7 +119,17 @@ public class TransportBulkActionTests extends ESTestCase { new Resolver(), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + new ProjectResolver() { + @Override + public void executeOnProject(ProjectId projectId, CheckedRunnable body) throws E { + throw new UnsupportedOperationException(""); + } + + @Override + public ProjectId getProjectId() { + return activeProjectId.get(); + } + }, FailureStoreMetrics.NOOP ); } @@ -173,6 +186,7 @@ public class TransportBulkActionTests extends ESTestCase { transportService.acceptIncomingRequests(); mockFeatureService = mock(FeatureService.class); when(mockFeatureService.clusterHasFeature(any(), any())).thenReturn(true); + activeProjectId.set(Metadata.DEFAULT_PROJECT_ID); bulkAction = new TestTransportBulkAction(); } @@ -536,10 +550,11 @@ public class TransportBulkActionTests extends ESTestCase { // Construct a cluster state that contains the required data streams. // using a single, non-default project final ClusterState oldState = clusterService.state(); + final ProjectId projectId = new ProjectId(randomUUID()); final Metadata metadata = Metadata.builder(oldState.metadata()) .removeProject(Metadata.DEFAULT_PROJECT_ID) .put( - ProjectMetadata.builder(new ProjectId(randomUUID())) + ProjectMetadata.builder(projectId) .put(indexMetadata(".ds-data-stream-01")) .put(indexMetadata(".ds-failure-store-01")) .put(indexMetadata(".fs-failure-store-01")) @@ -574,6 +589,7 @@ public class TransportBulkActionTests extends ESTestCase { // And wait for it to be applied. latch.await(10L, TimeUnit.SECONDS); + activeProjectId.set(projectId); // Set the exceptions that the transport action should encounter. bulkAction.failIndexCreationException = new IndexNotFoundException("index"); bulkAction.failDataStreamRolloverException = new RuntimeException("data-stream-rollover-exception"); diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTookTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTookTests.java index a0d06989a04f..7f9afad854c0 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTookTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionTookTests.java @@ -221,7 +221,7 @@ public class TransportBulkActionTookTests extends ESTestCase { static class Resolver extends IndexNameExpressionResolver { Resolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override @@ -252,7 +252,7 @@ public class TransportBulkActionTookTests extends ESTestCase { indexNameExpressionResolver, new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + TestProjectResolvers.DEFAULT_PROJECT_ONLY, relativeTimeProvider, FailureStoreMetrics.NOOP ); diff --git a/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java b/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java index 34b2fc83c6bb..c422efed0f25 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportSimulateBulkActionTests.java @@ -92,7 +92,7 @@ public class TransportSimulateBulkActionTests extends ESTestCase { new ActionFilters(Set.of()), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + TestProjectResolvers.DEFAULT_PROJECT_ONLY, indicesService, NamedXContentRegistry.EMPTY, new IndexSettingProviders(Set.of()) diff --git a/server/src/test/java/org/elasticsearch/action/resync/TransportResyncReplicationActionTests.java b/server/src/test/java/org/elasticsearch/action/resync/TransportResyncReplicationActionTests.java index be5cbba0a4ec..b264eaa75bda 100644 --- a/server/src/test/java/org/elasticsearch/action/resync/TransportResyncReplicationActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/resync/TransportResyncReplicationActionTests.java @@ -176,7 +176,7 @@ public class TransportResyncReplicationActionTests extends ESTestCase { new ActionFilters(new HashSet<>()), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ); assertThat(action.globalBlockLevel(), nullValue()); diff --git a/server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java b/server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java index eb7822b042c7..8e86044a3367 100644 --- a/server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/support/broadcast/node/TransportBroadcastByNodeActionTests.java @@ -237,7 +237,7 @@ public class TransportBroadcastByNodeActionTests extends ESTestCase { static class MyResolver extends IndexNameExpressionResolver { MyResolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override diff --git a/server/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java b/server/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java index 7fe1dc6841f4..5cf950ab0dc4 100644 --- a/server/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/support/replication/TransportWriteActionTests.java @@ -431,7 +431,7 @@ public class TransportWriteActionTests extends ESTestCase { PrimaryActionExecution.RejectOnOverload, new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + TestProjectResolvers.DEFAULT_PROJECT_ONLY, ReplicaActionExecution.SubjectToCircuitBreaker ); this.withDocumentFailureOnPrimary = withDocumentFailureOnPrimary; @@ -461,7 +461,7 @@ public class TransportWriteActionTests extends ESTestCase { PrimaryActionExecution.RejectOnOverload, new IndexingPressure(settings), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly(), + TestProjectResolvers.DEFAULT_PROJECT_ONLY, ReplicaActionExecution.SubjectToCircuitBreaker ); this.withDocumentFailureOnPrimary = false; diff --git a/server/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java b/server/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java index a3551a62c11c..7ed0435d1fef 100644 --- a/server/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java +++ b/server/src/test/java/org/elasticsearch/action/support/single/instance/TransportInstanceSingleOperationActionTests.java @@ -144,7 +144,7 @@ public class TransportInstanceSingleOperationActionTests extends ESTestCase { static class MyResolver extends IndexNameExpressionResolver { MyResolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override diff --git a/server/src/test/java/org/elasticsearch/index/seqno/RetentionLeaseSyncActionTests.java b/server/src/test/java/org/elasticsearch/index/seqno/RetentionLeaseSyncActionTests.java index 238db194236b..93d56b45bd95 100644 --- a/server/src/test/java/org/elasticsearch/index/seqno/RetentionLeaseSyncActionTests.java +++ b/server/src/test/java/org/elasticsearch/index/seqno/RetentionLeaseSyncActionTests.java @@ -111,7 +111,7 @@ public class RetentionLeaseSyncActionTests extends ESTestCase { new ActionFilters(Collections.emptySet()), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ); final RetentionLeases retentionLeases = mock(RetentionLeases.class); final RetentionLeaseSyncAction.Request request = new RetentionLeaseSyncAction.Request(indexShard.shardId(), retentionLeases); @@ -149,7 +149,7 @@ public class RetentionLeaseSyncActionTests extends ESTestCase { new ActionFilters(Collections.emptySet()), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ); final RetentionLeases retentionLeases = mock(RetentionLeases.class); final RetentionLeaseSyncAction.Request request = new RetentionLeaseSyncAction.Request(indexShard.shardId(), retentionLeases); @@ -191,7 +191,7 @@ public class RetentionLeaseSyncActionTests extends ESTestCase { new ActionFilters(Collections.emptySet()), new IndexingPressure(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ); assertNull(action.indexBlockLevel()); diff --git a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java index f7c39d770a02..879e1e4173a1 100644 --- a/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java +++ b/server/src/test/java/org/elasticsearch/snapshots/SnapshotResiliencyTests.java @@ -105,7 +105,6 @@ import org.elasticsearch.cluster.node.DiscoveryNodeRole; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; import org.elasticsearch.cluster.project.DefaultProjectResolver; -import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.project.TestProjectResolvers; import org.elasticsearch.cluster.routing.BatchedRerouteService; import org.elasticsearch.cluster.routing.RerouteService; @@ -2168,7 +2167,6 @@ public class SnapshotResiliencyTests extends ESTestCase { null, emptySet() ); - ProjectResolver projectResolver = TestProjectResolvers.singleProjectOnly(); IndexNameExpressionResolver indexNameExpressionResolver = TestIndexNameExpressionResolver.newInstance(); bigArrays = new BigArrays(new PageCacheRecycler(settings), null, "test"); repositoriesService = new RepositoriesService( @@ -2360,7 +2358,7 @@ public class SnapshotResiliencyTests extends ESTestCase { actionFilters, new IndexingPressure(settings), EmptySystemIndices.INSTANCE, - projectResolver + TestProjectResolvers.DEFAULT_PROJECT_ONLY ) ), RetentionLeaseSyncer.EMPTY, @@ -2420,7 +2418,7 @@ public class SnapshotResiliencyTests extends ESTestCase { indexNameExpressionResolver, new IndexingPressure(settings), EmptySystemIndices.INSTANCE, - projectResolver, + TestProjectResolvers.DEFAULT_PROJECT_ONLY, FailureStoreMetrics.NOOP ) ); @@ -2436,7 +2434,7 @@ public class SnapshotResiliencyTests extends ESTestCase { actionFilters, indexingMemoryLimits, EmptySystemIndices.INSTANCE, - projectResolver, + TestProjectResolvers.DEFAULT_PROJECT_ONLY, DocumentParsingProvider.EMPTY_INSTANCE ); actions.put(TransportShardBulkAction.TYPE, transportShardBulkAction); @@ -2471,7 +2469,7 @@ public class SnapshotResiliencyTests extends ESTestCase { indexNameExpressionResolver, new RequestValidators<>(Collections.emptyList()), EmptySystemIndices.INSTANCE, - projectResolver + TestProjectResolvers.DEFAULT_PROJECT_ONLY ) ); actions.put( @@ -2499,7 +2497,7 @@ public class SnapshotResiliencyTests extends ESTestCase { searchPhaseController, clusterService, actionFilters, - projectResolver, + TestProjectResolvers.DEFAULT_PROJECT_ONLY, indexNameExpressionResolver, namedWriteableRegistry, EmptySystemIndices.INSTANCE.getExecutorSelector(), diff --git a/test/framework/src/main/java/org/elasticsearch/cluster/project/TestProjectResolvers.java b/test/framework/src/main/java/org/elasticsearch/cluster/project/TestProjectResolvers.java index a67bf555cff3..71fe131fd536 100644 --- a/test/framework/src/main/java/org/elasticsearch/cluster/project/TestProjectResolvers.java +++ b/test/framework/src/main/java/org/elasticsearch/cluster/project/TestProjectResolvers.java @@ -14,24 +14,28 @@ import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.common.util.concurrent.ThreadContext; -import org.elasticsearch.common.util.set.Sets; import org.elasticsearch.core.CheckedRunnable; import org.elasticsearch.tasks.Task; import java.util.Collection; import java.util.Objects; -import java.util.Set; /** * An implementation of {@link ProjectResolver} that handles multiple projects for testing purposes. Not usable in production */ public final class TestProjectResolvers { + public static final ProjectResolver DEFAULT_PROJECT_ONLY = singleProject(Metadata.DEFAULT_PROJECT_ID, true); + + /** + * @return a ProjectResolver that must only be used in a cluster context. It throws in single project related methods. + */ public static ProjectResolver allProjects() { return new ProjectResolver() { + @Override - public ProjectMetadata getProjectMetadata(Metadata metadata) { - return singleProjectMetadata(metadata); + public ProjectId getProjectId() { + throw new UnsupportedOperationException("This resolver can only be used to resolve multiple projects"); } @Override @@ -46,21 +50,31 @@ public final class TestProjectResolvers { }; } + /** + * This method returns a ProjectResolver that is unable to provide the project-id unless explicitly specified + * with the executeOnProject method. This is mostly useful in places where we just need a placeholder to satisfy + * the constructor signature. + */ public static ProjectResolver singleProjectOnly() { return new ProjectResolver() { private ProjectId enforceProjectId = null; @Override - public ProjectMetadata getProjectMetadata(Metadata metadata) { - final ProjectMetadata project = TestProjectResolvers.singleProjectMetadata(metadata); - if (enforceProjectId == null || enforceProjectId.equals(project.id())) { - return project; + public ProjectId getProjectId() { + if (enforceProjectId == null) { + throw new UnsupportedOperationException("Cannot get project-id before it is set"); } else { - throw new IllegalArgumentException("Expected project-id [" + enforceProjectId + "] but was [" + project.id() + "]"); + return enforceProjectId; } } + @Override + public Collection getProjectIds(ClusterState clusterState) { + checkSingleProject(clusterState.metadata()); + return ProjectResolver.super.getProjectIds(clusterState); + } + @Override public void executeOnProject(ProjectId projectId, CheckedRunnable body) throws E { if (enforceProjectId != null) { @@ -76,12 +90,37 @@ public final class TestProjectResolvers { }; } + /** + * This method returns a ProjectResolver that gives back the specified project-id when its getProjectId method is called. + * It also assumes it is the only project in the cluster state and throws if that is not the case. + */ public static ProjectResolver singleProject(ProjectId projectId) { + return singleProject(projectId, false); + } + + private static ProjectResolver singleProject(ProjectId projectId, boolean only) { Objects.requireNonNull(projectId); return new ProjectResolver() { + @Override public ProjectMetadata getProjectMetadata(Metadata metadata) { - return metadata.getProject(projectId); + if (only) { + checkSingleProject(metadata); + } + return ProjectResolver.super.getProjectMetadata(metadata); + } + + @Override + public ProjectId getProjectId() { + return projectId; + } + + @Override + public Collection getProjectIds(ClusterState clusterState) { + if (only) { + checkSingleProject(clusterState.metadata()); + } + return ProjectResolver.super.getProjectIds(clusterState); } @Override @@ -95,63 +134,25 @@ public final class TestProjectResolvers { }; } - public static ProjectResolver projects(Set allowedProjectIds) { - if (allowedProjectIds.isEmpty()) { - throw new IllegalArgumentException("Project Ids cannot be empty"); - } - return new ProjectResolver() { - @Override - public ProjectMetadata getProjectMetadata(Metadata metadata) { - final Set matchingProjects = getMatchingProjectIds(metadata); - switch (matchingProjects.size()) { - case 1: - return metadata.getProject(matchingProjects.iterator().next()); - case 0: - throw new IllegalStateException( - "No projects matching [" + allowedProjectIds + "] in [" + metadata.projects().keySet() + "]" - ); - default: - throw new IllegalStateException( - "Multiple projects (" - + matchingProjects - + ") match [" - + allowedProjectIds - + "] in [" - + metadata.projects().keySet() - + "]" - ); - } - } - - @Override - public Collection getProjectIds(ClusterState clusterState) { - return getMatchingProjectIds(clusterState.metadata()); - } - - private Set getMatchingProjectIds(Metadata metadata) { - return Sets.intersection(metadata.projects().keySet(), allowedProjectIds); - } - - @Override - public void executeOnProject(ProjectId projectId, CheckedRunnable body) throws E { - throw new UnsupportedOperationException("Cannot execute on a specific project when using a resolver with multiple ids"); - } - }; - } - public static ProjectResolver usingRequestHeader(ThreadContext threadContext) { return new ProjectResolver() { + @Override public ProjectMetadata getProjectMetadata(Metadata metadata) { - String headerValue = threadContext.getHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER); - var projectId = headerValue != null ? new ProjectId(headerValue) : Metadata.DEFAULT_PROJECT_ID; + final ProjectId projectId = getProjectId(); var project = metadata.projects().get(projectId); if (project == null) { - throw new IllegalArgumentException("Could not find project with id [" + headerValue + "]"); + throw new IllegalArgumentException("Could not find project with id [" + projectId.id() + "]"); } return project; } + @Override + public ProjectId getProjectId() { + String headerValue = threadContext.getHeader(Task.X_ELASTIC_PROJECT_ID_HTTP_HEADER); + return headerValue != null ? new ProjectId(headerValue) : Metadata.DEFAULT_PROJECT_ID; + } + @Override public void executeOnProject(ProjectId projectId, CheckedRunnable body) throws E { try (var ignore = threadContext.newStoredContext()) { @@ -167,9 +168,4 @@ public final class TestProjectResolvers { throw new IllegalStateException("Cluster has multiple projects: [" + metadata.projects().keySet() + "]"); } } - - private static ProjectMetadata singleProjectMetadata(Metadata metadata) { - checkSingleProject(metadata); - return metadata.projects().values().iterator().next(); - } } diff --git a/test/framework/src/main/java/org/elasticsearch/indices/TestIndexNameExpressionResolver.java b/test/framework/src/main/java/org/elasticsearch/indices/TestIndexNameExpressionResolver.java index a5d17b9ce20a..84ffed0df6b9 100644 --- a/test/framework/src/main/java/org/elasticsearch/indices/TestIndexNameExpressionResolver.java +++ b/test/framework/src/main/java/org/elasticsearch/indices/TestIndexNameExpressionResolver.java @@ -10,6 +10,7 @@ package org.elasticsearch.indices; import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; +import org.elasticsearch.cluster.project.ProjectResolver; import org.elasticsearch.cluster.project.TestProjectResolvers; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.util.concurrent.ThreadContext; @@ -30,10 +31,14 @@ public class TestIndexNameExpressionResolver { return new IndexNameExpressionResolver( new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, - TestProjectResolvers.singleProjectOnly() + TestProjectResolvers.DEFAULT_PROJECT_ONLY ); } + public static IndexNameExpressionResolver newInstance(ProjectResolver projectResolver) { + return new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, projectResolver); + } + /** * @return a new instance of a {@link IndexNameExpressionResolver} that has been created with the provided {@link ThreadContext} and * the default {@link SystemIndices} instance @@ -51,7 +56,7 @@ public class TestIndexNameExpressionResolver { * the provided {@link SystemIndices} instance */ public static IndexNameExpressionResolver newInstance(SystemIndices systemIndices) { - return new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY), systemIndices, TestProjectResolvers.singleProjectOnly()); + return new IndexNameExpressionResolver(new ThreadContext(Settings.EMPTY), systemIndices, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } /** diff --git a/test/framework/src/test/java/org/elasticsearch/cluster/project/TestProjectResolversTests.java b/test/framework/src/test/java/org/elasticsearch/cluster/project/TestProjectResolversTests.java index 92e254c72ddd..6c05508ee875 100644 --- a/test/framework/src/test/java/org/elasticsearch/cluster/project/TestProjectResolversTests.java +++ b/test/framework/src/test/java/org/elasticsearch/cluster/project/TestProjectResolversTests.java @@ -16,192 +16,88 @@ import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; import org.elasticsearch.test.ESTestCase; -import java.util.Collection; -import java.util.List; -import java.util.Set; - import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasSize; -import static org.hamcrest.Matchers.in; import static org.hamcrest.Matchers.notNullValue; public class TestProjectResolversTests extends ESTestCase { - public void testAllProjects_getProjectMetadata() { - { - ClusterState state = buildClusterState(1); - assertThat(state.metadata().projects().values(), hasSize(1)); + public void testAllProjects() { + final int numberOfProjects = randomIntBetween(1, 10); + ClusterState state = buildClusterState(numberOfProjects); + assertThat(state.metadata().projects().values(), hasSize(numberOfProjects)); - ProjectMetadata project = TestProjectResolvers.allProjects().getProjectMetadata(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().values())); - } - { - ClusterState state = buildClusterState(randomIntBetween(2, 10)); - assertThat(state.metadata().projects().values().size(), greaterThan(1)); - - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.allProjects().getProjectMetadata(state)); - } + expectThrows(UnsupportedOperationException.class, () -> TestProjectResolvers.allProjects().getProjectMetadata(state)); + expectThrows(UnsupportedOperationException.class, () -> TestProjectResolvers.allProjects().getProjectId()); + assertThat(TestProjectResolvers.allProjects().getProjectIds(state), equalTo(state.metadata().projects().keySet())); } - public void testAllProjects_getProjectId() { - { - ClusterState state = buildClusterState(1); - assertThat(state.metadata().projects().values(), hasSize(1)); + public void testSingleProject() { + final ProjectId projectId = new ProjectId(randomUUID()); + final ProjectResolver projectResolver = TestProjectResolvers.singleProject(projectId); + assertThat(projectResolver.getProjectId(), equalTo(projectId)); - ProjectId project = TestProjectResolvers.allProjects().getProjectId(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().keySet())); - } - { - ClusterState state = buildClusterState(randomIntBetween(2, 10)); - assertThat(state.metadata().projects().values().size(), greaterThan(1)); - - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.allProjects().getProjectId(state)); - } + ClusterState state = buildClusterState(projectId, randomIntBetween(0, 10)); + assertThat(projectResolver.getProjectMetadata(state), notNullValue()); } - public void testAllProjects_getProjectIds() { - for (int numberOfProjects : List.of(1, 3, 8)) { - ClusterState state = buildClusterState(numberOfProjects); - assertThat(state.metadata().projects().values(), hasSize(numberOfProjects)); + public void testSingleProjectOnly_getProjectIdAndMetadata() { + final ProjectId projectId = new ProjectId(randomUUID()); + final ClusterState state = buildClusterState(projectId); - Collection projects = TestProjectResolvers.allProjects().getProjectIds(state); - assertThat(projects, notNullValue()); - assertThat(projects, hasSize(numberOfProjects)); - assertThat(projects, equalTo(state.metadata().projects().keySet())); - } - } + final ProjectResolver projectResolver = TestProjectResolvers.singleProjectOnly(); + expectThrows(UnsupportedOperationException.class, projectResolver::getProjectId); + expectThrows(UnsupportedOperationException.class, () -> projectResolver.getProjectMetadata(state)); - public void testSingleProjectOnly_getProjectMetadata() { - { - ClusterState state = buildClusterState(1); - assertThat(state.metadata().projects().values(), hasSize(1)); - - ProjectMetadata project = TestProjectResolvers.singleProjectOnly().getProjectMetadata(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().values())); - } - { - ClusterState state = buildClusterState(randomIntBetween(2, 10)); - assertThat(state.metadata().projects().values().size(), greaterThan(1)); - - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.singleProjectOnly().getProjectMetadata(state)); - } - } - - public void testSingleProjectOnly_getProjectId() { - { - ClusterState state = buildClusterState(1); - assertThat(state.metadata().projects().values(), hasSize(1)); - - ProjectId project = TestProjectResolvers.singleProjectOnly().getProjectId(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().keySet())); - } - { - ClusterState state = buildClusterState(randomIntBetween(2, 10)); - assertThat(state.metadata().projects().values().size(), greaterThan(1)); - - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.singleProjectOnly().getProjectId(state)); - } + projectResolver.executeOnProject(projectId, () -> { + assertThat(projectResolver.getProjectId(), equalTo(projectId)); + assertThat(projectResolver.getProjectMetadata(state), equalTo(state.metadata().getProject(projectId))); + }); } public void testSingleProjectOnly_getProjectIds() { { - ClusterState state = buildClusterState(1); + final ProjectResolver projectResolver = TestProjectResolvers.singleProjectOnly(); + final ProjectId projectId = new ProjectId(randomUUID()); + ClusterState state = buildClusterState(projectId); assertThat(state.metadata().projects().values(), hasSize(1)); - Collection projects = TestProjectResolvers.singleProjectOnly().getProjectIds(state); - assertThat(projects, notNullValue()); - assertThat(projects, hasSize(1)); - assertThat(projects, equalTo(state.metadata().projects().keySet())); + expectThrows(UnsupportedOperationException.class, () -> projectResolver.getProjectIds(state)); + projectResolver.executeOnProject(projectId, () -> assertThat(projectResolver.getProjectIds(state), contains(projectId))); + projectResolver.executeOnProject(new ProjectId(randomUUID()), () -> { + expectThrows(IllegalArgumentException.class, () -> projectResolver.getProjectIds(state)); + }); } { - ClusterState state = buildClusterState(randomIntBetween(2, 10)); + final ProjectResolver projectResolver = TestProjectResolvers.singleProjectOnly(); + final ProjectId projectId = new ProjectId(randomUUID()); + ClusterState state = buildClusterState(projectId, randomIntBetween(1, 10)); assertThat(state.metadata().projects().values().size(), greaterThan(1)); - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.singleProjectOnly().getProjectIds(state)); + projectResolver.executeOnProject( + projectId, + () -> expectThrows(IllegalStateException.class, () -> projectResolver.getProjectIds(state)) + ); } } - public void testSpecifiedProjects_getProjectMetadata() { - for (int numberOfProjects : List.of(1, 3, 8)) { - ClusterState state = buildClusterState(numberOfProjects); - assertThat(state.metadata().projects().values(), hasSize(numberOfProjects)); - final Set allIds = state.metadata().projects().keySet(); - - final ProjectId id = randomFrom(allIds); - ProjectMetadata project = TestProjectResolvers.projects(Set.of(id)).getProjectMetadata(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().values())); - assertThat(project.id(), equalTo(id)); - - final ProjectId other = randomValueOtherThanMany(allIds::contains, () -> new ProjectId(randomUUID())); - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.projects(Set.of(other)).getProjectMetadata(state)); - - project = TestProjectResolvers.projects(Set.of(id, other)).getProjectMetadata(state); - assertThat(project, notNullValue()); - assertThat(project, in(state.metadata().projects().values())); - assertThat(project.id(), equalTo(id)); - - if (numberOfProjects > 1) { - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.projects(allIds).getProjectMetadata(state)); - } + private ClusterState buildClusterState(ProjectId... projectIds) { + Metadata.Builder metadata = Metadata.builder(); + for (var projectId : projectIds) { + metadata.put(ProjectMetadata.builder(projectId).build()); } + return ClusterState.builder(new ClusterName(randomAlphaOfLengthBetween(4, 8))).metadata(metadata).build(); } - public void testSpecifiedProjects_getProjectId() { - for (int numberOfProjects : List.of(1, 3, 8)) { - ClusterState state = buildClusterState(numberOfProjects); - assertThat(state.metadata().projects().values(), hasSize(numberOfProjects)); - final Set allIds = state.metadata().projects().keySet(); - - final ProjectId id = randomFrom(allIds); - ProjectId project = TestProjectResolvers.projects(Set.of(id)).getProjectId(state); - assertThat(project, notNullValue()); - assertThat(project, equalTo(id)); - - final ProjectId other = randomValueOtherThanMany(allIds::contains, () -> new ProjectId(randomUUID())); - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.projects(Set.of(other)).getProjectId(state)); - - project = TestProjectResolvers.projects(Set.of(id, other)).getProjectId(state); - assertThat(project, notNullValue()); - assertThat(project, equalTo(id)); - - if (numberOfProjects > 1) { - expectThrows(IllegalStateException.class, () -> TestProjectResolvers.projects(allIds).getProjectId(state)); - } - } - } - - public void testSpecifiedProjects_getProjectIds() { - for (int numberOfProjects : List.of(1, 3, 8)) { - ClusterState state = buildClusterState(numberOfProjects); - assertThat(state.metadata().projects().values(), hasSize(numberOfProjects)); - final Set allIds = state.metadata().projects().keySet(); - - Collection projects = TestProjectResolvers.projects(allIds).getProjectIds(state); - assertThat(projects, notNullValue()); - assertThat(projects, equalTo(allIds)); - - final ProjectId id = randomFrom(allIds); - projects = TestProjectResolvers.projects(Set.of(id)).getProjectIds(state); - assertThat(projects, notNullValue()); - assertThat(projects, hasSize(1)); - assertThat(projects, contains(id)); - - final ProjectId other = randomValueOtherThanMany(allIds::contains, () -> new ProjectId(randomUUID())); - assertThat(TestProjectResolvers.projects(Set.of(other)).getProjectIds(state), empty()); - - projects = TestProjectResolvers.projects(Set.of(id, other)).getProjectIds(state); - assertThat(projects, notNullValue()); - assertThat(projects, hasSize(1)); - assertThat(projects, contains(id)); + private ClusterState buildClusterState(ProjectId projectId, int numberOfExtraProjects) { + Metadata.Builder metadata = Metadata.builder(); + metadata.put(ProjectMetadata.builder(projectId).build()); + for (int i = 0; i < numberOfExtraProjects; i++) { + metadata.put(ProjectMetadata.builder(new ProjectId("p" + i + "_" + randomAlphaOfLength(8))).build()); } + return ClusterState.builder(new ClusterName(randomAlphaOfLengthBetween(4, 8))).metadata(metadata).build(); } private ClusterState buildClusterState(int numberOfProjects) { diff --git a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java index 0c351883b5d6..c4b57feb5a3f 100644 --- a/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java +++ b/x-pack/plugin/security/src/test/java/org/elasticsearch/xpack/security/authz/AuthorizationServiceTests.java @@ -89,6 +89,7 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.AliasMetadata; import org.elasticsearch.cluster.metadata.IndexAbstraction; import org.elasticsearch.cluster.metadata.IndexMetadata; +import org.elasticsearch.cluster.metadata.IndexNameExpressionResolver; import org.elasticsearch.cluster.metadata.Metadata; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; @@ -265,6 +266,7 @@ public class AuthorizationServiceTests extends ESTestCase { private boolean setFakeOriginatingAction = true; private SecurityContext securityContext; private ProjectResolver projectResolver; + private IndexNameExpressionResolver indexNameExpressionResolver; @SuppressWarnings("unchecked") @Before @@ -316,11 +318,8 @@ public class AuthorizationServiceTests extends ESTestCase { }).when(rolesStore).getRole(any(Subject.class), anyActionListener()); roleMap.put(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); operatorPrivilegesService = mock(OperatorPrivileges.OperatorPrivilegesService.class); - projectResolver = mock(ProjectResolver.class); - when(projectResolver.getProjectMetadata(any(ClusterState.class))).thenAnswer(inv -> { - ClusterState clusterState = inv.getArgument(0); - return clusterState.metadata().getProject(projectId); - }); + projectResolver = TestProjectResolvers.singleProject(projectId); + indexNameExpressionResolver = TestIndexNameExpressionResolver.newInstance(projectResolver); authorizationService = new AuthorizationService( settings, rolesStore, @@ -333,7 +332,7 @@ public class AuthorizationServiceTests extends ESTestCase { null, Collections.emptySet(), licenseState, - TestIndexNameExpressionResolver.newInstance(), + indexNameExpressionResolver, operatorPrivilegesService, RESTRICTED_INDICES, new AuthorizationDenialMessages.Default(), @@ -1768,11 +1767,11 @@ public class AuthorizationServiceTests extends ESTestCase { null, Collections.emptySet(), new XPackLicenseState(() -> 0), - TestIndexNameExpressionResolver.newInstance(), + indexNameExpressionResolver, operatorPrivilegesService, RESTRICTED_INDICES, new AuthorizationDenialMessages.Default(), - TestProjectResolvers.singleProjectOnly() + projectResolver ); RoleDescriptor role = new RoleDescriptor( @@ -1818,11 +1817,11 @@ public class AuthorizationServiceTests extends ESTestCase { null, Collections.emptySet(), new XPackLicenseState(() -> 0), - TestIndexNameExpressionResolver.newInstance(), + indexNameExpressionResolver, operatorPrivilegesService, RESTRICTED_INDICES, new AuthorizationDenialMessages.Default(), - TestProjectResolvers.singleProjectOnly() + projectResolver ); RoleDescriptor role = new RoleDescriptor( @@ -2427,8 +2426,6 @@ public class AuthorizationServiceTests extends ESTestCase { } public void testSuperusersCanExecuteReadOperationAgainstSecurityIndexWithWildcard() { - // TODO This test depends on IndexAbstractionResolver (via IndicesAndAliasesResolver), which only works with the default project - projectId = Metadata.DEFAULT_PROJECT_ID; final User superuser = new User("custom_admin", ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName()); final Authentication authentication = createAuthentication(superuser); roleMap.put(ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR.getName(), ReservedRolesStore.SUPERUSER_ROLE_DESCRIPTOR); @@ -3358,11 +3355,11 @@ public class AuthorizationServiceTests extends ESTestCase { engine, Collections.emptySet(), licenseState, - TestIndexNameExpressionResolver.newInstance(), + indexNameExpressionResolver, operatorPrivilegesService, RESTRICTED_INDICES, new AuthorizationDenialMessages.Default(), - TestProjectResolvers.singleProjectOnly() + projectResolver ); Subject subject = new Subject(new User("test", "a role"), mock(RealmRef.class)); @@ -3515,11 +3512,11 @@ public class AuthorizationServiceTests extends ESTestCase { engine, Collections.emptySet(), licenseState, - TestIndexNameExpressionResolver.newInstance(), + indexNameExpressionResolver, operatorPrivilegesService, RESTRICTED_INDICES, new AuthorizationDenialMessages.Default(), - TestProjectResolvers.singleProjectOnly() + projectResolver ); Authentication authentication; try (ThreadContext.StoredContext ignore = threadContext.stashContext()) { diff --git a/x-pack/plugin/transform/src/internalClusterTest/java/org/elasticsearch/xpack/transform/checkpoint/TransformGetCheckpointTests.java b/x-pack/plugin/transform/src/internalClusterTest/java/org/elasticsearch/xpack/transform/checkpoint/TransformGetCheckpointTests.java index 4e02d127e67a..a7f3d1f81218 100644 --- a/x-pack/plugin/transform/src/internalClusterTest/java/org/elasticsearch/xpack/transform/checkpoint/TransformGetCheckpointTests.java +++ b/x-pack/plugin/transform/src/internalClusterTest/java/org/elasticsearch/xpack/transform/checkpoint/TransformGetCheckpointTests.java @@ -272,7 +272,7 @@ public class TransformGetCheckpointTests extends ESSingleNodeTestCase { static class MockResolver extends IndexNameExpressionResolver { MockResolver() { - super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.singleProjectOnly()); + super(new ThreadContext(Settings.EMPTY), EmptySystemIndices.INSTANCE, TestProjectResolvers.DEFAULT_PROJECT_ONLY); } @Override