2828import org .elasticsearch .cluster .metadata .Metadata ;
2929import org .elasticsearch .cluster .metadata .ProjectId ;
3030import org .elasticsearch .cluster .metadata .ProjectMetadata ;
31+ import org .elasticsearch .cluster .metadata .ReservedStateHandlerMetadata ;
32+ import org .elasticsearch .cluster .metadata .ReservedStateMetadata ;
3133import org .elasticsearch .cluster .project .ProjectResolver ;
3234import org .elasticsearch .cluster .project .ProjectStateRegistry ;
3335import org .elasticsearch .cluster .routing .GlobalRoutingTable ;
4749
4850import java .io .IOException ;
4951import java .util .Collection ;
52+ import java .util .HashMap ;
5053import java .util .Map ;
54+ import java .util .Objects ;
5155import java .util .Set ;
5256import java .util .function .BiPredicate ;
5357import java .util .function .Predicate ;
@@ -189,12 +193,12 @@ private static Map<String, Set<String>> getClusterFeatures(ClusterState clusterS
189193 }
190194
191195 private ClusterStateResponse buildResponse (final ClusterStateRequest request , final ClusterState rawState ) {
192- final ClusterState currentState = filterClusterState (rawState );
196+ final ClusterState filteredState = filterClusterState (rawState );
193197
194198 ThreadPool .assertCurrentThreadPool (ThreadPool .Names .MANAGEMENT ); // too heavy to construct & serialize cluster state without forking
195199
196200 if (request .blocks () == false ) {
197- final var blockException = currentState .blocks ().globalBlockedException (ClusterBlockLevel .METADATA_READ );
201+ final var blockException = filteredState .blocks ().globalBlockedException (ClusterBlockLevel .METADATA_READ );
198202 if (blockException != null ) {
199203 // There's a METADATA_READ block in place, but we aren't returning it to the caller, and yet the caller needs to know that
200204 // this block exists (e.g. it's the STATE_NOT_RECOVERED_BLOCK, so the rest of the state is known to be incomplete). Thus we
@@ -203,22 +207,22 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
203207 }
204208 }
205209
206- logger .trace ("Serving cluster state request using version {}" , currentState .version ());
207- ClusterState .Builder builder = ClusterState .builder (currentState .getClusterName ());
208- builder .version (currentState .version ());
209- builder .stateUUID (currentState .stateUUID ());
210+ logger .trace ("Serving cluster state request using version {}" , filteredState .version ());
211+ ClusterState .Builder builder = ClusterState .builder (filteredState .getClusterName ());
212+ builder .version (filteredState .version ());
213+ builder .stateUUID (filteredState .stateUUID ());
210214
211215 if (request .nodes ()) {
212- builder .nodes (currentState .nodes ());
213- builder .nodeIdsToCompatibilityVersions (getCompatibilityVersions (currentState ));
214- builder .nodeFeatures (getClusterFeatures (currentState ));
216+ builder .nodes (filteredState .nodes ());
217+ builder .nodeIdsToCompatibilityVersions (getCompatibilityVersions (filteredState ));
218+ builder .nodeFeatures (getClusterFeatures (filteredState ));
215219 }
216220 if (request .routingTable ()) {
217221 if (request .indices ().length > 0 ) {
218- final GlobalRoutingTable .Builder globalRoutingTableBuilder = GlobalRoutingTable .builder (currentState .globalRoutingTable ())
222+ final GlobalRoutingTable .Builder globalRoutingTableBuilder = GlobalRoutingTable .builder (filteredState .globalRoutingTable ())
219223 .clear ();
220- for (ProjectMetadata project : currentState .metadata ().projects ().values ()) {
221- RoutingTable projectRouting = currentState .routingTable (project .id ());
224+ for (ProjectMetadata project : filteredState .metadata ().projects ().values ()) {
225+ RoutingTable projectRouting = filteredState .routingTable (project .id ());
222226 RoutingTable .Builder routingTableBuilder = RoutingTable .builder ();
223227 String [] indices = indexNameExpressionResolver .concreteIndexNames (project , request );
224228 for (String filteredIndex : indices ) {
@@ -230,18 +234,18 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
230234 }
231235 builder .routingTable (globalRoutingTableBuilder .build ());
232236 } else {
233- builder .routingTable (currentState .globalRoutingTable ());
237+ builder .routingTable (filteredState .globalRoutingTable ());
234238 }
235239 } else {
236240 builder .routingTable (GlobalRoutingTable .builder ().build ());
237241 }
238242 if (request .blocks ()) {
239- builder .blocks (currentState .blocks ());
243+ builder .blocks (filteredState .blocks ());
240244 }
241245
242246 Metadata .Builder mdBuilder = Metadata .builder ();
243- mdBuilder .clusterUUID (currentState .metadata ().clusterUUID ());
244- mdBuilder .coordinationMetadata (currentState .coordinationMetadata ());
247+ mdBuilder .clusterUUID (filteredState .metadata ().clusterUUID ());
248+ mdBuilder .coordinationMetadata (filteredState .coordinationMetadata ());
245249
246250 if (request .metadata ()) {
247251 // filter out metadata that shouldn't be returned by the API
@@ -250,14 +254,30 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
250254 if (request .indices ().length > 0 ) {
251255 // if the request specified index names, then we don't want the whole metadata, just the version and projects (which will
252256 // be filtered (below) to only include the relevant indices)
253- mdBuilder .version (currentState .metadata ().version ());
257+ mdBuilder .version (filteredState .metadata ().version ());
254258 } else {
255259 // If there are no requested indices, then we want all the metadata, except for customs that aren't exposed via the API
256- mdBuilder = Metadata .builder (currentState .metadata ());
260+ mdBuilder = Metadata .builder (filteredState .metadata ());
257261 mdBuilder .removeCustomIf (notApi );
262+
263+ if (projectResolver .supportsMultipleProjects () && request .multiproject () == false ) {
264+ ProjectStateRegistry projectStateRegistry = ProjectStateRegistry .get (filteredState );
265+ if (projectStateRegistry .size () > 1 ) {
266+ throw new Metadata .MultiProjectPendingException (
267+ "There are multiple projects " + projectStateRegistry .knownProjects ()
268+ );
269+ }
270+ var reservedStateMetadata = new HashMap <>(filteredState .metadata ().reservedStateMetadata ());
271+ var singleProjectReservedStateMetadata = projectStateRegistry .reservedStateMetadata (projectResolver .getProjectId ());
272+ singleProjectReservedStateMetadata .forEach (
273+ (key , value ) -> reservedStateMetadata .merge (key , value , this ::mergeReservedStateMetadata )
274+ );
275+
276+ mdBuilder .put (reservedStateMetadata );
277+ }
258278 }
259279
260- for (ProjectMetadata project : currentState .metadata ().projects ().values ()) {
280+ for (ProjectMetadata project : filteredState .metadata ().projects ().values ()) {
261281 ProjectMetadata .Builder pBuilder ;
262282 if (request .indices ().length > 0 ) {
263283 // if the request specified index names, then only include the project-id and indices
@@ -289,7 +309,7 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
289309 mdBuilder .put (pBuilder );
290310 }
291311 } else {
292- for (ProjectId project : currentState .metadata ().projects ().keySet ()) {
312+ for (ProjectId project : filteredState .metadata ().projects ().keySet ()) {
293313 // Request doesn't want to retrieve metadata, so we just fill in empty projects
294314 // (because we can't have a truly empty Metadata)
295315 mdBuilder .put (ProjectMetadata .builder (project ));
@@ -298,14 +318,45 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
298318 builder .metadata (mdBuilder );
299319
300320 if (request .customs ()) {
301- for (Map .Entry <String , ClusterState .Custom > custom : currentState .customs ().entrySet ()) {
321+ for (Map .Entry <String , ClusterState .Custom > custom : filteredState .customs ().entrySet ()) {
302322 if (custom .getValue ().isPrivate () == false ) {
303323 builder .putCustom (custom .getKey (), custom .getValue ());
304324 }
305325 }
306326 }
307327
308- return new ClusterStateResponse (currentState .getClusterName (), builder .build (), false );
328+ return new ClusterStateResponse (filteredState .getClusterName (), builder .build (), false );
309329 }
310330
331+ private ReservedStateMetadata mergeReservedStateMetadata (
332+ ReservedStateMetadata clusterReservedMetadata ,
333+ ReservedStateMetadata projectReservedMetadata
334+ ) {
335+ if (Objects .equals (clusterReservedMetadata .version (), projectReservedMetadata .version ()) == false ) {
336+ logger .info (
337+ "Reserved state metadata version is different for Metadata ({}) and the requested project ({})" ,
338+ clusterReservedMetadata .version (),
339+ projectReservedMetadata .version ()
340+ );
341+ }
342+ ReservedStateMetadata .Builder builder = ReservedStateMetadata .builder (clusterReservedMetadata .namespace ())
343+ .version (Math .max (clusterReservedMetadata .version (), projectReservedMetadata .version ()));
344+
345+ for (ReservedStateHandlerMetadata handler : clusterReservedMetadata .handlers ().values ()) {
346+ builder .putHandler (handler );
347+ }
348+ for (Map .Entry <String , ReservedStateHandlerMetadata > handlerEntry : projectReservedMetadata .handlers ().entrySet ()) {
349+ assert clusterReservedMetadata .handlers ().containsKey (handlerEntry .getKey ()) == false
350+ : "Duplicate of handler: " + handlerEntry .getKey ();
351+ builder .putHandler (handlerEntry .getValue ());
352+ }
353+
354+ if (projectReservedMetadata .errorMetadata () != null ) {
355+ builder .errorMetadata (projectReservedMetadata .errorMetadata ());
356+ } else if (clusterReservedMetadata .errorMetadata () != null ) {
357+ builder .errorMetadata (clusterReservedMetadata .errorMetadata ());
358+ }
359+
360+ return builder .build ();
361+ }
311362}
0 commit comments