Browse Source

Make XPackUsage APIs project-aware (#130358)

Updates the APIs to be able to handle multiple projects. This commit
does not guarantee that every sub-API returns sensible results in a
multi-project context. Some APIs/features potentially require further
work to return sensible data for multiple projects.

Since this API is for internal use only in serverless, we can check at a
later time whether any features require additional work.
Niels Bauman 3 months ago
parent
commit
8bd4645eff
16 changed files with 122 additions and 61 deletions
  1. 8 5
      x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/CCRUsageTransportAction.java
  2. 10 7
      x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/CCRInfoTransportActionTests.java
  3. 6 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/DataStreamLifecycleUsageTransportAction.java
  4. 6 2
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/DataStreamUsageTransportAction.java
  5. 11 5
      x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/datatiers/DataTiersUsageTransportAction.java
  6. 6 7
      x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/datatiers/DataTiersUsageTransportActionTests.java
  7. 9 5
      x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleUsageTransportAction.java
  8. 20 7
      x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleInfoTransportActionTests.java
  9. 6 2
      x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/LogsDBUsageTransportAction.java
  10. 6 2
      x-pack/plugin/old-lucene-versions/src/main/java/org/elasticsearch/xpack/lucene/bwc/ArchiveUsageTransportAction.java
  11. 10 4
      x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/action/RollupUsageTransportAction.java
  12. 1 1
      x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/action/TransportPutRollupJobAction.java
  13. 8 1
      x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/action/RollupInfoTransportActionTests.java
  14. 6 2
      x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/SearchableSnapshotsUsageTransportAction.java
  15. 7 2
      x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SLMUsageTransportAction.java
  16. 2 7
      x-pack/qa/multi-project/xpack-rest-tests-with-multiple-projects/build.gradle

+ 8 - 5
x-pack/plugin/ccr/src/main/java/org/elasticsearch/xpack/ccr/CCRUsageTransportAction.java

@@ -10,7 +10,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.injection.guice.Inject;
@@ -32,6 +32,7 @@ public class CCRUsageTransportAction extends XPackUsageFeatureTransportAction {
 
     private final Settings settings;
     private final XPackLicenseState licenseState;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public CCRUsageTransportAction(
@@ -40,11 +41,13 @@ public class CCRUsageTransportAction extends XPackUsageFeatureTransportAction {
         ThreadPool threadPool,
         ActionFilters actionFilters,
         Settings settings,
-        XPackLicenseState licenseState
+        XPackLicenseState licenseState,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.CCR.name(), transportService, clusterService, threadPool, actionFilters);
         this.settings = settings;
         this.licenseState = licenseState;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -54,11 +57,11 @@ public class CCRUsageTransportAction extends XPackUsageFeatureTransportAction {
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        Metadata metadata = state.metadata();
+        final var project = projectResolver.getProjectMetadata(state);
 
         int numberOfFollowerIndices = 0;
         long lastFollowerIndexCreationDate = 0L;
-        for (IndexMetadata imd : metadata.getProject()) {
+        for (IndexMetadata imd : project) {
             if (imd.getCustomData("ccr") != null) {
                 numberOfFollowerIndices++;
                 if (lastFollowerIndexCreationDate < imd.getCreationDate()) {
@@ -66,7 +69,7 @@ public class CCRUsageTransportAction extends XPackUsageFeatureTransportAction {
                 }
             }
         }
-        AutoFollowMetadata autoFollowMetadata = metadata.getProject().custom(AutoFollowMetadata.TYPE);
+        AutoFollowMetadata autoFollowMetadata = project.custom(AutoFollowMetadata.TYPE);
         int numberOfAutoFollowPatterns = autoFollowMetadata != null ? autoFollowMetadata.getPatterns().size() : 0;
 
         Long lastFollowTimeInMillis;

+ 10 - 7
x-pack/plugin/ccr/src/test/java/org/elasticsearch/xpack/ccr/CCRInfoTransportActionTests.java

@@ -11,7 +11,8 @@ import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.metadata.ProjectMetadata;
+import org.elasticsearch.cluster.project.TestProjectResolvers;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.common.util.Maps;
@@ -92,7 +93,8 @@ public class CCRInfoTransportActionTests extends ESTestCase {
     }
 
     public void testUsageStats() throws Exception {
-        Metadata.Builder metadata = Metadata.builder();
+        final var projectId = randomProjectIdOrDefault();
+        ProjectMetadata.Builder project = ProjectMetadata.builder(projectId);
 
         int numFollowerIndices = randomIntBetween(0, 32);
         for (int i = 0; i < numFollowerIndices; i++) {
@@ -102,7 +104,7 @@ public class CCRInfoTransportActionTests extends ESTestCase {
                 .numberOfReplicas(0)
                 .creationDate(i)
                 .putCustom(Ccr.CCR_CUSTOM_METADATA_KEY, new HashMap<>());
-            metadata.put(followerIndex);
+            project.put(followerIndex);
         }
 
         // Add a regular index, to check that we do not take that one into account:
@@ -111,7 +113,7 @@ public class CCRInfoTransportActionTests extends ESTestCase {
             .numberOfShards(1)
             .numberOfReplicas(0)
             .creationDate(numFollowerIndices);
-        metadata.put(regularIndex);
+        project.put(regularIndex);
 
         int numAutoFollowPatterns = randomIntBetween(0, 32);
         Map<String, AutoFollowMetadata.AutoFollowPattern> patterns = Maps.newMapWithExpectedSize(numAutoFollowPatterns);
@@ -136,9 +138,9 @@ public class CCRInfoTransportActionTests extends ESTestCase {
             );
             patterns.put("pattern" + i, pattern);
         }
-        metadata.putCustom(AutoFollowMetadata.TYPE, new AutoFollowMetadata(patterns, Collections.emptyMap(), Collections.emptyMap()));
+        project.putCustom(AutoFollowMetadata.TYPE, new AutoFollowMetadata(patterns, Collections.emptyMap(), Collections.emptyMap()));
 
-        ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).metadata(metadata).build();
+        ClusterState clusterState = ClusterState.builder(new ClusterName("_name")).putProjectMetadata(project).build();
         Mockito.when(clusterService.state()).thenReturn(clusterState);
 
         ThreadPool threadPool = mock(ThreadPool.class);
@@ -149,7 +151,8 @@ public class CCRInfoTransportActionTests extends ESTestCase {
             threadPool,
             mock(ActionFilters.class),
             Settings.EMPTY,
-            licenseState
+            licenseState,
+            TestProjectResolvers.singleProject(projectId)
         );
         PlainActionFuture<XPackUsageFeatureResponse> future = new PlainActionFuture<>();
         usageAction.localClusterStateOperation(null, null, clusterState, future);

+ 6 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/DataStreamLifecycleUsageTransportAction.java

@@ -15,6 +15,7 @@ import org.elasticsearch.cluster.metadata.DataStream;
 import org.elasticsearch.cluster.metadata.DataStreamGlobalRetention;
 import org.elasticsearch.cluster.metadata.DataStreamGlobalRetentionSettings;
 import org.elasticsearch.cluster.metadata.DataStreamLifecycle;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.core.Tuple;
@@ -32,6 +33,7 @@ import java.util.Map;
 public class DataStreamLifecycleUsageTransportAction extends XPackUsageFeatureTransportAction {
 
     private final DataStreamGlobalRetentionSettings globalRetentionSettings;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public DataStreamLifecycleUsageTransportAction(
@@ -39,10 +41,12 @@ public class DataStreamLifecycleUsageTransportAction extends XPackUsageFeatureTr
         ClusterService clusterService,
         ThreadPool threadPool,
         ActionFilters actionFilters,
-        DataStreamGlobalRetentionSettings globalRetentionSettings
+        DataStreamGlobalRetentionSettings globalRetentionSettings,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.DATA_STREAM_LIFECYCLE.name(), transportService, clusterService, threadPool, actionFilters);
         this.globalRetentionSettings = globalRetentionSettings;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -52,7 +56,7 @@ public class DataStreamLifecycleUsageTransportAction extends XPackUsageFeatureTr
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        final Collection<DataStream> dataStreams = state.metadata().getProject().dataStreams().values();
+        final Collection<DataStream> dataStreams = projectResolver.getProjectMetadata(state).dataStreams().values();
         DataStreamLifecycleFeatureSetUsage.LifecycleStats lifecycleStats = calculateStats(
             dataStreams,
             clusterService.getClusterSettings().get(DataStreamLifecycle.CLUSTER_LIFECYCLE_DEFAULT_ROLLOVER_SETTING),

+ 6 - 2
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/action/DataStreamUsageTransportAction.java

@@ -15,6 +15,7 @@ import org.elasticsearch.cluster.metadata.DataStreamFailureStoreSettings;
 import org.elasticsearch.cluster.metadata.DataStreamGlobalRetention;
 import org.elasticsearch.cluster.metadata.DataStreamGlobalRetentionSettings;
 import org.elasticsearch.cluster.metadata.DataStreamLifecycle;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.core.TimeValue;
 import org.elasticsearch.core.Tuple;
@@ -33,6 +34,7 @@ public class DataStreamUsageTransportAction extends XPackUsageFeatureTransportAc
 
     private final DataStreamFailureStoreSettings dataStreamFailureStoreSettings;
     private final DataStreamGlobalRetentionSettings globalRetentionSettings;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public DataStreamUsageTransportAction(
@@ -41,11 +43,13 @@ public class DataStreamUsageTransportAction extends XPackUsageFeatureTransportAc
         ThreadPool threadPool,
         ActionFilters actionFilters,
         DataStreamFailureStoreSettings dataStreamFailureStoreSettings,
-        DataStreamGlobalRetentionSettings globalRetentionSettings
+        DataStreamGlobalRetentionSettings globalRetentionSettings,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.DATA_STREAMS.name(), transportService, clusterService, threadPool, actionFilters);
         this.dataStreamFailureStoreSettings = dataStreamFailureStoreSettings;
         this.globalRetentionSettings = globalRetentionSettings;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -55,7 +59,7 @@ public class DataStreamUsageTransportAction extends XPackUsageFeatureTransportAc
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        final Map<String, DataStream> dataStreams = state.metadata().getProject().dataStreams();
+        final Map<String, DataStream> dataStreams = projectResolver.getProjectMetadata(state).dataStreams();
         long backingIndicesCounter = 0;
         long failureStoreExplicitlyEnabledCounter = 0;
         long failureStoreEffectivelyEnabledCounter = 0;

+ 11 - 5
x-pack/plugin/core/src/main/java/org/elasticsearch/xpack/core/datatiers/DataTiersUsageTransportAction.java

@@ -13,9 +13,11 @@ import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.client.internal.Client;
 import org.elasticsearch.client.internal.ParentTaskAssigningClient;
 import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.ProjectState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.cluster.metadata.Metadata;
 import org.elasticsearch.cluster.node.DiscoveryNodeRole;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.routing.RoutingNode;
 import org.elasticsearch.cluster.routing.ShardRouting;
 import org.elasticsearch.cluster.routing.allocation.DataTier;
@@ -43,6 +45,7 @@ import java.util.stream.StreamSupport;
 public class DataTiersUsageTransportAction extends XPackUsageFeatureTransportAction {
 
     private final Client client;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public DataTiersUsageTransportAction(
@@ -50,10 +53,12 @@ public class DataTiersUsageTransportAction extends XPackUsageFeatureTransportAct
         ClusterService clusterService,
         ThreadPool threadPool,
         ActionFilters actionFilters,
-        Client client
+        Client client,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.DATA_TIERS.name(), transportService, clusterService, threadPool, actionFilters);
         this.client = client;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -69,11 +74,12 @@ public class DataTiersUsageTransportAction extends XPackUsageFeatureTransportAct
                 NodesDataTiersUsageTransportAction.TYPE,
                 new NodesDataTiersUsageTransportAction.NodesRequest(),
                 listener.delegateFailureAndWrap((delegate, response) -> {
+                    final var projectState = projectResolver.getProjectState(state);
                     // Generate tier specific stats for the nodes and indices
                     delegate.onResponse(
                         new XPackUsageFeatureResponse(
                             new DataTiersFeatureSetUsage(
-                                aggregateStats(response.getNodes(), getIndicesGroupedByTier(state, response.getNodes()))
+                                aggregateStats(response.getNodes(), getIndicesGroupedByTier(projectState, response.getNodes()))
                             )
                         )
                     );
@@ -82,16 +88,16 @@ public class DataTiersUsageTransportAction extends XPackUsageFeatureTransportAct
     }
 
     // Visible for testing
-    static Map<String, Set<String>> getIndicesGroupedByTier(ClusterState state, List<NodeDataTiersUsage> nodes) {
+    static Map<String, Set<String>> getIndicesGroupedByTier(ProjectState state, List<NodeDataTiersUsage> nodes) {
         Set<String> indices = nodes.stream()
-            .map(nodeResponse -> state.getRoutingNodes().node(nodeResponse.getNode().getId()))
+            .map(nodeResponse -> state.cluster().getRoutingNodes().node(nodeResponse.getNode().getId()))
             .filter(Objects::nonNull)
             .flatMap(node -> StreamSupport.stream(node.spliterator(), false))
             .map(ShardRouting::getIndexName)
             .collect(Collectors.toSet());
         Map<String, Set<String>> indicesByTierPreference = new HashMap<>();
         for (String indexName : indices) {
-            IndexMetadata indexMetadata = state.metadata().getProject().index(indexName);
+            IndexMetadata indexMetadata = state.metadata().index(indexName);
             // If the index was deleted in the meantime, skip
             if (indexMetadata == null) {
                 continue;

+ 6 - 7
x-pack/plugin/core/src/test/java/org/elasticsearch/xpack/core/datatiers/DataTiersUsageTransportActionTests.java

@@ -10,7 +10,7 @@ package org.elasticsearch.xpack.core.datatiers;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.metadata.ProjectMetadata;
 import org.elasticsearch.cluster.node.DiscoveryNode;
 import org.elasticsearch.cluster.node.DiscoveryNodeRole;
 import org.elasticsearch.cluster.node.DiscoveryNodes;
@@ -57,15 +57,14 @@ public class DataTiersUsageTransportActionTests extends ESTestCase {
         IndexMetadata nonTiered = indexMetadata("non-tier", 1, 0); // No tier
         IndexMetadata hotIndex3 = indexMetadata("hot-3", 1, 0, DataTier.DATA_HOT);
 
-        Metadata.Builder metadataBuilder = Metadata.builder()
+        ProjectMetadata.Builder projectBuilder = ProjectMetadata.builder(randomProjectIdOrDefault())
             .put(hotIndex1, false)
             .put(hotIndex2, false)
             .put(warmIndex1, false)
             .put(coldIndex1, false)
             .put(coldIndex2, false)
             .put(nonTiered, false)
-            .put(hotIndex3, false)
-            .generateClusterUuidIfNeeded();
+            .put(hotIndex3, false);
         RoutingTable.Builder routingTableBuilder = RoutingTable.builder();
         routingTableBuilder.add(getIndexRoutingTable(hotIndex1, dataNode));
         routingTableBuilder.add(getIndexRoutingTable(hotIndex2, dataNode));
@@ -76,11 +75,11 @@ public class DataTiersUsageTransportActionTests extends ESTestCase {
         routingTableBuilder.add(getIndexRoutingTable(nonTiered, dataNode));
         ClusterState clusterState = ClusterState.builder(new ClusterName("test"))
             .nodes(discoBuilder)
-            .metadata(metadataBuilder)
-            .routingTable(routingTableBuilder.build())
+            .putProjectMetadata(projectBuilder)
+            .putRoutingTable(projectBuilder.getId(), routingTableBuilder.build())
             .build();
         Map<String, Set<String>> result = DataTiersUsageTransportAction.getIndicesGroupedByTier(
-            clusterState,
+            clusterState.projectState(projectBuilder.getId()),
             List.of(new NodeDataTiersUsage(dataNode, Map.of(DataTier.DATA_WARM, createStats(5, 5, 0, 10))))
         );
         assertThat(result.keySet(), equalTo(Set.of(DataTier.DATA_HOT, DataTier.DATA_WARM, DataTier.DATA_COLD)));

+ 9 - 5
x-pack/plugin/ilm/src/main/java/org/elasticsearch/xpack/ilm/IndexLifecycleUsageTransportAction.java

@@ -10,7 +10,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.admin.indices.rollover.RolloverConditions;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
-import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.core.Tuple;
 import org.elasticsearch.injection.guice.Inject;
@@ -42,14 +42,18 @@ import static org.elasticsearch.xpack.core.ilm.TimeseriesLifecycleType.shouldInj
 
 public class IndexLifecycleUsageTransportAction extends XPackUsageFeatureTransportAction {
 
+    private final ProjectResolver projectResolver;
+
     @Inject
     public IndexLifecycleUsageTransportAction(
         TransportService transportService,
         ClusterService clusterService,
         ThreadPool threadPool,
-        ActionFilters actionFilters
+        ActionFilters actionFilters,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.INDEX_LIFECYCLE.name(), transportService, clusterService, threadPool, actionFilters);
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -59,12 +63,12 @@ public class IndexLifecycleUsageTransportAction extends XPackUsageFeatureTranspo
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        Metadata metadata = state.metadata();
-        IndexLifecycleMetadata lifecycleMetadata = metadata.getProject().custom(IndexLifecycleMetadata.TYPE);
+        final var project = projectResolver.getProjectMetadata(state);
+        IndexLifecycleMetadata lifecycleMetadata = project.custom(IndexLifecycleMetadata.TYPE);
         final IndexLifecycleFeatureSetUsage usage;
         if (lifecycleMetadata != null) {
             Map<String, Integer> policyUsage = new HashMap<>();
-            metadata.getProject().indices().values().forEach(value -> {
+            project.indices().values().forEach(value -> {
                 String policyName = value.getLifecyclePolicyName();
                 Integer indicesManaged = policyUsage.get(policyName);
                 if (indicesManaged == null) {

+ 20 - 7
x-pack/plugin/ilm/src/test/java/org/elasticsearch/xpack/ilm/IndexLifecycleInfoTransportActionTests.java

@@ -12,7 +12,9 @@ import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.cluster.ClusterName;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
-import org.elasticsearch.cluster.metadata.Metadata;
+import org.elasticsearch.cluster.metadata.ProjectId;
+import org.elasticsearch.cluster.metadata.ProjectMetadata;
+import org.elasticsearch.cluster.project.TestProjectResolvers;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.common.settings.Settings;
 import org.elasticsearch.index.IndexVersion;
@@ -87,12 +89,19 @@ public class IndexLifecycleInfoTransportActionTests extends ESTestCase {
         policies.add(policy3);
         PolicyStats policy3Stats = new PolicyStats(Map.of(), 1);
 
-        ClusterState clusterState = buildClusterState(policies, indexPolicies);
+        final var projectId = randomProjectIdOrDefault();
+        ClusterState clusterState = buildClusterState(projectId, policies, indexPolicies);
         Mockito.when(clusterService.state()).thenReturn(clusterState);
 
         ThreadPool threadPool = mock(ThreadPool.class);
         TransportService transportService = MockUtils.setupTransportServiceWithThreadpoolExecutor(threadPool);
-        var usageAction = new IndexLifecycleUsageTransportAction(transportService, null, threadPool, mock(ActionFilters.class));
+        var usageAction = new IndexLifecycleUsageTransportAction(
+            transportService,
+            null,
+            threadPool,
+            mock(ActionFilters.class),
+            TestProjectResolvers.singleProject(projectId)
+        );
         PlainActionFuture<XPackUsageFeatureResponse> future = new PlainActionFuture<>();
         usageAction.localClusterStateOperation(null, null, clusterState, future);
         IndexLifecycleFeatureSetUsage ilmUsage = (IndexLifecycleFeatureSetUsage) future.get().getUsage();
@@ -107,19 +116,23 @@ public class IndexLifecycleInfoTransportActionTests extends ESTestCase {
 
     }
 
-    private ClusterState buildClusterState(List<LifecyclePolicy> lifecyclePolicies, Map<String, String> indexPolicies) {
+    private ClusterState buildClusterState(
+        ProjectId projectId,
+        List<LifecyclePolicy> lifecyclePolicies,
+        Map<String, String> indexPolicies
+    ) {
         Map<String, LifecyclePolicyMetadata> lifecyclePolicyMetadatasMap = lifecyclePolicies.stream()
             .map(p -> new LifecyclePolicyMetadata(p, Map.of(), 1, 0L))
             .collect(Collectors.toMap(LifecyclePolicyMetadata::getName, Function.identity()));
         IndexLifecycleMetadata indexLifecycleMetadata = new IndexLifecycleMetadata(lifecyclePolicyMetadatasMap, OperationMode.RUNNING);
 
-        Metadata.Builder metadata = Metadata.builder().putCustom(IndexLifecycleMetadata.TYPE, indexLifecycleMetadata);
+        ProjectMetadata.Builder project = ProjectMetadata.builder(projectId).putCustom(IndexLifecycleMetadata.TYPE, indexLifecycleMetadata);
         indexPolicies.forEach((indexName, policyName) -> {
             Settings indexSettings = indexSettings(IndexVersion.current(), 1, 0).put(LifecycleSettings.LIFECYCLE_NAME, policyName).build();
             IndexMetadata.Builder indexMetadata = IndexMetadata.builder(indexName).settings(indexSettings);
-            metadata.put(indexMetadata);
+            project.put(indexMetadata);
         });
 
-        return ClusterState.builder(new ClusterName("my_cluster")).metadata(metadata).build();
+        return ClusterState.builder(new ClusterName("my_cluster")).putProjectMetadata(project).build();
     }
 }

+ 6 - 2
x-pack/plugin/logsdb/src/main/java/org/elasticsearch/xpack/logsdb/LogsDBUsageTransportAction.java

@@ -12,6 +12,7 @@ import org.elasticsearch.client.internal.Client;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
 import org.elasticsearch.cluster.node.DiscoveryNode;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.index.IndexMode;
 import org.elasticsearch.index.IndexSettings;
@@ -30,6 +31,7 @@ import org.elasticsearch.xpack.core.application.LogsDBFeatureSetUsage;
 public class LogsDBUsageTransportAction extends XPackUsageFeatureTransportAction {
     private final ClusterService clusterService;
     private final Client client;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public LogsDBUsageTransportAction(
@@ -37,11 +39,13 @@ public class LogsDBUsageTransportAction extends XPackUsageFeatureTransportAction
         ClusterService clusterService,
         ThreadPool threadPool,
         ActionFilters actionFilters,
-        Client client
+        Client client,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.LOGSDB.name(), transportService, clusterService, threadPool, actionFilters);
         this.clusterService = clusterService;
         this.client = client;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -53,7 +57,7 @@ public class LogsDBUsageTransportAction extends XPackUsageFeatureTransportAction
     ) {
         int numIndices = 0;
         int numIndicesWithSyntheticSources = 0;
-        for (IndexMetadata indexMetadata : state.metadata().getProject()) {
+        for (IndexMetadata indexMetadata : projectResolver.getProjectMetadata(state)) {
             if (indexMetadata.getIndexMode() == IndexMode.LOGSDB) {
                 numIndices++;
                 if (IndexSettings.INDEX_MAPPER_SOURCE_MODE_SETTING.get(indexMetadata.getSettings()) == SourceFieldMapper.Mode.SYNTHETIC) {

+ 6 - 2
x-pack/plugin/old-lucene-versions/src/main/java/org/elasticsearch/xpack/lucene/bwc/ArchiveUsageTransportAction.java

@@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.injection.guice.Inject;
 import org.elasticsearch.license.XPackLicenseState;
@@ -28,6 +29,7 @@ import static org.elasticsearch.xpack.lucene.bwc.OldLuceneVersions.ARCHIVE_FEATU
 public class ArchiveUsageTransportAction extends XPackUsageFeatureTransportAction {
 
     private final XPackLicenseState licenseState;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public ArchiveUsageTransportAction(
@@ -35,10 +37,12 @@ public class ArchiveUsageTransportAction extends XPackUsageFeatureTransportActio
         ClusterService clusterService,
         ThreadPool threadPool,
         ActionFilters actionFilters,
-        XPackLicenseState licenseState
+        XPackLicenseState licenseState,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.ARCHIVE.name(), transportService, clusterService, threadPool, actionFilters);
         this.licenseState = licenseState;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -49,7 +53,7 @@ public class ArchiveUsageTransportAction extends XPackUsageFeatureTransportActio
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
         int numArchiveIndices = 0;
-        for (IndexMetadata indexMetadata : state.metadata().getProject()) {
+        for (IndexMetadata indexMetadata : projectResolver.getProjectMetadata(state)) {
             if (indexMetadata.getCreationVersion().isLegacyIndexVersion()) {
                 numArchiveIndices++;
             }

+ 10 - 4
x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/action/RollupUsageTransportAction.java

@@ -9,6 +9,8 @@ package org.elasticsearch.xpack.rollup.action;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.metadata.ProjectMetadata;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.core.Predicates;
 import org.elasticsearch.injection.guice.Inject;
@@ -25,14 +27,18 @@ import org.elasticsearch.xpack.core.rollup.job.RollupJob;
 
 public class RollupUsageTransportAction extends XPackUsageFeatureTransportAction {
 
+    private final ProjectResolver projectResolver;
+
     @Inject
     public RollupUsageTransportAction(
         TransportService transportService,
         ClusterService clusterService,
         ThreadPool threadPool,
-        ActionFilters actionFilters
+        ActionFilters actionFilters,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.ROLLUP.name(), transportService, clusterService, threadPool, actionFilters);
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -42,14 +48,14 @@ public class RollupUsageTransportAction extends XPackUsageFeatureTransportAction
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        int numberOfRollupJobs = findNumberOfRollupJobs(state);
+        int numberOfRollupJobs = findNumberOfRollupJobs(projectResolver.getProjectMetadata(state));
         RollupFeatureSetUsage usage = new RollupFeatureSetUsage(numberOfRollupJobs);
         listener.onResponse(new XPackUsageFeatureResponse(usage));
     }
 
-    static int findNumberOfRollupJobs(ClusterState state) {
+    static int findNumberOfRollupJobs(ProjectMetadata project) {
         int numberOfRollupJobs = 0;
-        PersistentTasksCustomMetadata persistentTasks = state.metadata().getProject().custom(PersistentTasksCustomMetadata.TYPE);
+        PersistentTasksCustomMetadata persistentTasks = project.custom(PersistentTasksCustomMetadata.TYPE);
         if (persistentTasks != null) {
             numberOfRollupJobs = persistentTasks.findTasks(RollupJob.NAME, Predicates.always()).size();
         }

+ 1 - 1
x-pack/plugin/rollup/src/main/java/org/elasticsearch/xpack/rollup/action/TransportPutRollupJobAction.java

@@ -113,7 +113,7 @@ public class TransportPutRollupJobAction extends AcknowledgedTransportMasterNode
         XPackPlugin.checkReadyForXPackCustomMetadata(clusterState);
         checkForDeprecatedTZ(request);
 
-        int numberOfCurrentRollupJobs = RollupUsageTransportAction.findNumberOfRollupJobs(clusterState);
+        int numberOfCurrentRollupJobs = RollupUsageTransportAction.findNumberOfRollupJobs(clusterState.metadata().getProject());
         if (numberOfCurrentRollupJobs == 0) {
             try {
                 boolean hasRollupIndices = hasRollupIndices(clusterState.getMetadata());

+ 8 - 1
x-pack/plugin/rollup/src/test/java/org/elasticsearch/xpack/rollup/action/RollupInfoTransportActionTests.java

@@ -9,6 +9,7 @@ package org.elasticsearch.xpack.rollup.action;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.action.support.PlainActionFuture;
 import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.project.TestProjectResolvers;
 import org.elasticsearch.common.io.stream.BytesStreamOutput;
 import org.elasticsearch.test.ESTestCase;
 import org.elasticsearch.test.MockUtils;
@@ -41,7 +42,13 @@ public class RollupInfoTransportActionTests extends ESTestCase {
     public void testUsage() throws ExecutionException, InterruptedException, IOException {
         ThreadPool threadPool = mock(ThreadPool.class);
         TransportService transportService = MockUtils.setupTransportServiceWithThreadpoolExecutor(threadPool);
-        var usageAction = new RollupUsageTransportAction(transportService, null, threadPool, mock(ActionFilters.class));
+        var usageAction = new RollupUsageTransportAction(
+            transportService,
+            null,
+            threadPool,
+            mock(ActionFilters.class),
+            TestProjectResolvers.DEFAULT_PROJECT_ONLY
+        );
         PlainActionFuture<XPackUsageFeatureResponse> future = new PlainActionFuture<>();
         usageAction.localClusterStateOperation(null, null, ClusterState.EMPTY_STATE, future);
         RollupFeatureSetUsage rollupUsage = (RollupFeatureSetUsage) future.get().getUsage();

+ 6 - 2
x-pack/plugin/searchable-snapshots/src/main/java/org/elasticsearch/xpack/searchablesnapshots/action/SearchableSnapshotsUsageTransportAction.java

@@ -11,6 +11,7 @@ import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
 import org.elasticsearch.cluster.metadata.IndexMetadata;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.injection.guice.Inject;
 import org.elasticsearch.license.XPackLicenseState;
@@ -28,6 +29,7 @@ import static org.elasticsearch.xpack.core.searchablesnapshots.SearchableSnapsho
 public class SearchableSnapshotsUsageTransportAction extends XPackUsageFeatureTransportAction {
 
     private final XPackLicenseState licenseState;
+    private final ProjectResolver projectResolver;
 
     @Inject
     public SearchableSnapshotsUsageTransportAction(
@@ -35,10 +37,12 @@ public class SearchableSnapshotsUsageTransportAction extends XPackUsageFeatureTr
         ClusterService clusterService,
         ThreadPool threadPool,
         ActionFilters actionFilters,
-        XPackLicenseState licenseState
+        XPackLicenseState licenseState,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.SEARCHABLE_SNAPSHOTS.name(), transportService, clusterService, threadPool, actionFilters);
         this.licenseState = licenseState;
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -50,7 +54,7 @@ public class SearchableSnapshotsUsageTransportAction extends XPackUsageFeatureTr
     ) {
         int numFullCopySnapIndices = 0;
         int numSharedCacheSnapIndices = 0;
-        for (IndexMetadata indexMetadata : state.metadata().getProject()) {
+        for (IndexMetadata indexMetadata : projectResolver.getProjectMetadata(state)) {
             if (indexMetadata.isSearchableSnapshot()) {
                 if (indexMetadata.isPartialSearchableSnapshot()) {
                     numSharedCacheSnapIndices++;

+ 7 - 2
x-pack/plugin/slm/src/main/java/org/elasticsearch/xpack/slm/SLMUsageTransportAction.java

@@ -10,6 +10,7 @@ package org.elasticsearch.xpack.slm;
 import org.elasticsearch.action.ActionListener;
 import org.elasticsearch.action.support.ActionFilters;
 import org.elasticsearch.cluster.ClusterState;
+import org.elasticsearch.cluster.project.ProjectResolver;
 import org.elasticsearch.cluster.service.ClusterService;
 import org.elasticsearch.injection.guice.Inject;
 import org.elasticsearch.protocol.xpack.XPackUsageRequest;
@@ -24,14 +25,18 @@ import org.elasticsearch.xpack.core.slm.SnapshotLifecycleMetadata;
 
 public class SLMUsageTransportAction extends XPackUsageFeatureTransportAction {
 
+    private final ProjectResolver projectResolver;
+
     @Inject
     public SLMUsageTransportAction(
         TransportService transportService,
         ClusterService clusterService,
         ThreadPool threadPool,
-        ActionFilters actionFilters
+        ActionFilters actionFilters,
+        ProjectResolver projectResolver
     ) {
         super(XPackUsageFeatureAction.SNAPSHOT_LIFECYCLE.name(), transportService, clusterService, threadPool, actionFilters);
+        this.projectResolver = projectResolver;
     }
 
     @Override
@@ -41,7 +46,7 @@ public class SLMUsageTransportAction extends XPackUsageFeatureTransportAction {
         ClusterState state,
         ActionListener<XPackUsageFeatureResponse> listener
     ) {
-        final SnapshotLifecycleMetadata slmMeta = state.metadata().getProject().custom(SnapshotLifecycleMetadata.TYPE);
+        final SnapshotLifecycleMetadata slmMeta = projectResolver.getProjectMetadata(state).custom(SnapshotLifecycleMetadata.TYPE);
         final SLMFeatureSetUsage usage = new SLMFeatureSetUsage(slmMeta == null ? null : slmMeta.getStats());
         listener.onResponse(new XPackUsageFeatureResponse(usage));
     }

+ 2 - 7
x-pack/qa/multi-project/xpack-rest-tests-with-multiple-projects/build.gradle

@@ -30,14 +30,10 @@ tasks.named("yamlRestTest").configure {
     '^analytics/histogram/*',
     '^analytics/moving_percentiles/*',
     '^analytics/top_metrics/*',
-    '^analytics/usage/*',
     '^data_streams/10_data_stream_resolvability/*',
     '^deprecation/10_basic/*',
-    '^dlm/10_usage/*',
-    '^esql/60_usage/*',
-    '^health/10_usage/*',
+    '^health/10_usage/*', // The usage API is project-aware, this test just fails on the project-awareness of the SLM health indicator
     '^ilm/80_health/*',
-    '^logsdb/10_usage/*',
     '^migration/10_get_feature_upgrade_status/*',
     '^migration/20_post_feature_upgrade/*',
     '^ml/3rd_party_deployment/*',
@@ -110,7 +106,7 @@ tasks.named("yamlRestTest").configure {
     '^rollup/rollup_search/*',
     '^rollup/start_job/*',
     '^rollup/stop_job/*',
-    '^searchable_snapshots/10_usage/*',
+    '^searchable_snapshots/10_usage/*', // The usage API is project-aware, so this test can be unmuted once snapshot repositories are project-aware
     '^searchable_snapshots/20_synthetic_source/*',
     '^security/authz/14_cat_indices/*',
     '^security/authz/14_cat_indices/Test explicit request while multiple opened/*',
@@ -118,7 +114,6 @@ tasks.named("yamlRestTest").configure {
     '^security/settings/10_update_security_settings/*',
     '^snapshot/10_basic/*',
     '^snapshot/20_operator_privileges_disabled/*',
-    '^spatial/50_feature_usage/*',
     '^spatial/100_geo_grid_ingest/*',
     '^transform/preview_transforms/*',
     '^transform/transforms_cat_apis/*',