diff --git a/server/src/main/java/org/elasticsearch/TransportVersions.java b/server/src/main/java/org/elasticsearch/TransportVersions.java index c23422728e027..98204a750b772 100644 --- a/server/src/main/java/org/elasticsearch/TransportVersions.java +++ b/server/src/main/java/org/elasticsearch/TransportVersions.java @@ -314,6 +314,7 @@ static TransportVersion def(int id) { public static final TransportVersion ML_INFERENCE_CUSTOM_SERVICE_INPUT_TYPE = def(9_105_0_00); public static final TransportVersion ML_INFERENCE_SAGEMAKER_ELASTIC = def(9_106_0_00); public static final TransportVersion SPARSE_VECTOR_FIELD_PRUNING_OPTIONS = def(9_107_0_00); + public static final TransportVersion CLUSTER_STATE_PROJECTS_SETTINGS = def(9_108_0_00); /* * STOP! READ THIS FIRST! No, really, diff --git a/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java b/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java index c4bc58b3d1831..5633bd8b89e1e 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java +++ b/server/src/main/java/org/elasticsearch/cluster/ClusterModule.java @@ -28,6 +28,7 @@ import org.elasticsearch.cluster.metadata.RepositoriesMetadata; import org.elasticsearch.cluster.metadata.StreamsMetadata; import org.elasticsearch.cluster.project.ProjectResolver; +import org.elasticsearch.cluster.project.ProjectStateRegistry; import org.elasticsearch.cluster.routing.DelayedAllocationService; import org.elasticsearch.cluster.routing.ShardRouting; import org.elasticsearch.cluster.routing.ShardRoutingRoleStrategy; @@ -292,6 +293,7 @@ public static List getNamedWriteables() { RegisteredPolicySnapshots::new, RegisteredPolicySnapshots.RegisteredSnapshotsDiff::new ); + registerClusterCustom(entries, ProjectStateRegistry.TYPE, ProjectStateRegistry::new, ProjectStateRegistry::readDiffFrom); // Secrets registerClusterCustom(entries, ClusterSecrets.TYPE, ClusterSecrets::new, ClusterSecrets::readDiffFrom); registerProjectCustom(entries, ProjectSecrets.TYPE, ProjectSecrets::new, ProjectSecrets::readDiffFrom); diff --git a/server/src/main/java/org/elasticsearch/cluster/ClusterState.java b/server/src/main/java/org/elasticsearch/cluster/ClusterState.java index f4b10b3a77c4c..d5356bd54b845 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ClusterState.java +++ b/server/src/main/java/org/elasticsearch/cluster/ClusterState.java @@ -1047,7 +1047,7 @@ public Builder(ClusterState state) { public Builder(ClusterName clusterName) { this.compatibilityVersions = new HashMap<>(); this.nodeFeatures = new HashMap<>(); - customs = ImmutableOpenMap.builder(); + this.customs = ImmutableOpenMap.builder(); this.clusterName = clusterName; } @@ -1330,7 +1330,6 @@ public void writeTo(StreamOutput out) throws IOException { } private static class ClusterStateDiff implements Diff { - private final long toVersion; private final String fromUuid; diff --git a/server/src/main/java/org/elasticsearch/cluster/ProjectState.java b/server/src/main/java/org/elasticsearch/cluster/ProjectState.java index 07dcae3ae575c..ab1c18f9504ed 100644 --- a/server/src/main/java/org/elasticsearch/cluster/ProjectState.java +++ b/server/src/main/java/org/elasticsearch/cluster/ProjectState.java @@ -12,8 +12,10 @@ import org.elasticsearch.cluster.block.ClusterBlocks; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.project.ProjectStateRegistry; import org.elasticsearch.cluster.routing.RoutingTable; import org.elasticsearch.common.Strings; +import org.elasticsearch.common.settings.Settings; import java.util.Objects; import java.util.function.Consumer; @@ -26,6 +28,7 @@ public final class ProjectState { private final ClusterState cluster; private final ProjectId project; private final ProjectMetadata projectMetadata; + private final Settings projectSettings; private final RoutingTable routingTable; ProjectState(ClusterState clusterState, ProjectId projectId) { @@ -34,6 +37,7 @@ public final class ProjectState { this.cluster = clusterState; this.project = projectId; this.projectMetadata = clusterState.metadata().getProject(projectId); + this.projectSettings = ProjectStateRegistry.getProjectSettings(projectId, clusterState); this.routingTable = clusterState.routingTable(projectId); } @@ -57,6 +61,10 @@ public ClusterBlocks blocks() { return cluster().blocks(); } + public Settings settings() { + return projectSettings; + } + @Override public boolean equals(Object obj) { if (obj == this) return true; 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 647839f435599..c42812b2385a1 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/Metadata.java @@ -985,13 +985,7 @@ private MetadataDiff(StreamInput in) throws IOException { RESERVED_DIFF_VALUE_READER ); - singleProject = new ProjectMetadata.ProjectMetadataDiff( - indices, - templates, - projectCustoms, - DiffableUtils.emptyDiff(), - Settings.EMPTY_DIFF - ); + singleProject = new ProjectMetadata.ProjectMetadataDiff(indices, templates, projectCustoms, DiffableUtils.emptyDiff()); multiProject = null; } else { fromNodeBeforeMultiProjectsSupport = false; diff --git a/server/src/main/java/org/elasticsearch/cluster/metadata/ProjectMetadata.java b/server/src/main/java/org/elasticsearch/cluster/metadata/ProjectMetadata.java index fddd27b7f1d01..4a10964b1be57 100644 --- a/server/src/main/java/org/elasticsearch/cluster/metadata/ProjectMetadata.java +++ b/server/src/main/java/org/elasticsearch/cluster/metadata/ProjectMetadata.java @@ -106,8 +106,6 @@ public class ProjectMetadata implements Iterable, Diffable indicesLookup; private final Map mappingsByHash; - private final Settings settings; - private final IndexVersion oldestIndexVersion; public static final ClusterBlock PROJECT_UNDER_DELETION_BLOCK = new ClusterBlock( @@ -138,7 +136,6 @@ private ProjectMetadata( String[] visibleClosedIndices, SortedMap indicesLookup, Map mappingsByHash, - Settings settings, IndexVersion oldestIndexVersion ) { this.id = id; @@ -157,7 +154,6 @@ private ProjectMetadata( this.visibleClosedIndices = visibleClosedIndices; this.indicesLookup = indicesLookup; this.mappingsByHash = mappingsByHash; - this.settings = settings; this.oldestIndexVersion = oldestIndexVersion; assert assertConsistent(); } @@ -239,7 +235,6 @@ public ProjectMetadata withLifecycleState(Index index, LifecycleExecutionState l visibleClosedIndices, indicesLookup, mappingsByHash, - settings, oldestIndexVersion ); } @@ -273,7 +268,6 @@ public ProjectMetadata withIndexSettingsUpdates(Map updates) { visibleClosedIndices, indicesLookup, mappingsByHash, - settings, oldestIndexVersion ); } @@ -308,7 +302,6 @@ public ProjectMetadata withAllocationAndTermUpdatesOnly(Map templates() { return templates; } - public Settings settings() { - return settings; - } - /** * Checks whether the provided index is a data stream. */ @@ -1123,9 +1111,6 @@ public boolean isIndexManagedByILM(IndexMetadata indexMetadata) { } static boolean isStateEquals(ProjectMetadata project1, ProjectMetadata project2) { - if (project1.settings().equals(project2.settings()) == false) { - return false; - } if (project1.templates().equals(project2.templates()) == false) { return false; } @@ -1159,7 +1144,6 @@ public static class Builder { private final ImmutableOpenMap.Builder templates; private final ImmutableOpenMap.Builder customs; private final ImmutableOpenMap.Builder reservedStateMetadata; - private Settings settings = Settings.EMPTY; private SortedMap previousIndicesLookup; @@ -1177,7 +1161,6 @@ public static class Builder { this.templates = ImmutableOpenMap.builder(projectMetadata.templates); this.customs = ImmutableOpenMap.builder(projectMetadata.customs); this.reservedStateMetadata = ImmutableOpenMap.builder(projectMetadata.reservedStateMetadata); - this.settings = projectMetadata.settings; this.previousIndicesLookup = projectMetadata.indicesLookup; this.mappingsByHash = new HashMap<>(projectMetadata.mappingsByHash); this.checkForUnusedMappings = false; @@ -1551,11 +1534,6 @@ public Builder removeReservedState(ReservedStateMetadata metadata) { return this; } - public Builder settings(Settings settings) { - this.settings = settings; - return this; - } - public Builder indexGraveyard(final IndexGraveyard indexGraveyard) { return putCustom(IndexGraveyard.TYPE, indexGraveyard); } @@ -1715,7 +1693,6 @@ public ProjectMetadata build(boolean skipNameCollisionChecks) { visibleClosedIndicesArray, indicesLookup, Collections.unmodifiableMap(mappingsByHash), - settings, IndexVersion.fromId(oldestIndexVersionId) ); } @@ -2143,7 +2120,7 @@ public static ProjectMetadata fromXContent(XContentParser parser) throws IOExcep } } case "settings" -> { - projectBuilder.settings(Settings.fromXContent(parser)); + Settings.fromXContent(parser); } default -> Metadata.Builder.parseCustomObject( parser, @@ -2190,11 +2167,6 @@ public Iterator toXContentChunked(ToXContent.Params p) { ), indices, customs, - multiProject ? Iterators.single((builder, params) -> { - builder.startObject("settings"); - settings.toXContent(builder, new ToXContent.MapParams(Collections.singletonMap("flat_settings", "true"))); - return builder.endObject(); - }) : Collections.emptyIterator(), multiProject ? ChunkedToXContentHelper.object("reserved_state", reservedStateMetadata().values().iterator()) : Collections.emptyIterator() @@ -2229,8 +2201,9 @@ public static ProjectMetadata readFrom(StreamInput in) throws IOException { builder.put(ReservedStateMetadata.readFrom(in)); } - if (in.getTransportVersion().onOrAfter(TransportVersions.PROJECT_METADATA_SETTINGS)) { - builder.settings(Settings.readSettingsFromStream(in)); + if (in.getTransportVersion() + .between(TransportVersions.PROJECT_METADATA_SETTINGS, TransportVersions.CLUSTER_STATE_PROJECTS_SETTINGS)) { + Settings.readSettingsFromStream(in); } return builder.build(); @@ -2264,8 +2237,9 @@ public void writeTo(StreamOutput out) throws IOException { VersionedNamedWriteable.writeVersionedWriteables(out, customs.values()); out.writeCollection(reservedStateMetadata.values()); - if (out.getTransportVersion().onOrAfter(TransportVersions.PROJECT_METADATA_SETTINGS)) { - settings.writeTo(out); + if (out.getTransportVersion() + .between(TransportVersions.PROJECT_METADATA_SETTINGS, TransportVersions.CLUSTER_STATE_PROJECTS_SETTINGS)) { + Settings.EMPTY.writeTo(out); } } @@ -2286,7 +2260,6 @@ static class ProjectMetadataDiff implements Diff { String, ReservedStateMetadata, ImmutableOpenMap> reservedStateMetadata; - private final Diff settingsDiff; private ProjectMetadataDiff(ProjectMetadata before, ProjectMetadata after) { if (before == after) { @@ -2294,7 +2267,6 @@ private ProjectMetadataDiff(ProjectMetadata before, ProjectMetadata after) { templates = DiffableUtils.emptyDiff(); customs = DiffableUtils.emptyDiff(); reservedStateMetadata = DiffableUtils.emptyDiff(); - settingsDiff = Settings.EMPTY_DIFF; } else { indices = DiffableUtils.diff(before.indices, after.indices, DiffableUtils.getStringKeySerializer()); templates = DiffableUtils.diff(before.templates, after.templates, DiffableUtils.getStringKeySerializer()); @@ -2309,7 +2281,6 @@ private ProjectMetadataDiff(ProjectMetadata before, ProjectMetadata after) { after.reservedStateMetadata, DiffableUtils.getStringKeySerializer() ); - settingsDiff = after.settings.diff(before.settings); } } @@ -2317,14 +2288,12 @@ private ProjectMetadataDiff(ProjectMetadata before, ProjectMetadata after) { DiffableUtils.MapDiff> indices, DiffableUtils.MapDiff> templates, DiffableUtils.MapDiff> customs, - DiffableUtils.MapDiff> reservedStateMetadata, - Diff settingsDiff + DiffableUtils.MapDiff> reservedStateMetadata ) { this.indices = indices; this.templates = templates; this.customs = customs; this.reservedStateMetadata = reservedStateMetadata; - this.settingsDiff = settingsDiff; } ProjectMetadataDiff(StreamInput in) throws IOException { @@ -2336,10 +2305,9 @@ private ProjectMetadataDiff(ProjectMetadata before, ProjectMetadata after) { DiffableUtils.getStringKeySerializer(), RESERVED_DIFF_VALUE_READER ); - if (in.getTransportVersion().onOrAfter(TransportVersions.PROJECT_METADATA_SETTINGS)) { - settingsDiff = Settings.readSettingsDiffFromStream(in); - } else { - settingsDiff = Settings.EMPTY_DIFF; + if (in.getTransportVersion() + .between(TransportVersions.PROJECT_METADATA_SETTINGS, TransportVersions.CLUSTER_STATE_PROJECTS_SETTINGS)) { + Settings.readSettingsDiffFromStream(in); } } @@ -2365,18 +2333,15 @@ public void writeTo(StreamOutput out) throws IOException { templates.writeTo(out); customs.writeTo(out); reservedStateMetadata.writeTo(out); - if (out.getTransportVersion().onOrAfter(TransportVersions.PROJECT_METADATA_SETTINGS)) { - settingsDiff.writeTo(out); + if (out.getTransportVersion() + .between(TransportVersions.PROJECT_METADATA_SETTINGS, TransportVersions.CLUSTER_STATE_PROJECTS_SETTINGS)) { + Settings.EMPTY_DIFF.writeTo(out); } } @Override public ProjectMetadata apply(ProjectMetadata part) { - if (indices.isEmpty() - && templates.isEmpty() - && customs.isEmpty() - && reservedStateMetadata.isEmpty() - && settingsDiff == Settings.EMPTY_DIFF) { + if (indices.isEmpty() && templates.isEmpty() && customs.isEmpty() && reservedStateMetadata.isEmpty()) { // nothing to do return part; } @@ -2391,7 +2356,6 @@ public ProjectMetadata apply(ProjectMetadata part) { && builder.dataStreamMetadata() == part.custom(DataStreamMetadata.TYPE, DataStreamMetadata.EMPTY)) { builder.previousIndicesLookup = part.indicesLookup; } - builder.settings = settingsDiff.apply(part.settings); return builder.build(true); } } diff --git a/server/src/main/java/org/elasticsearch/cluster/project/ProjectStateRegistry.java b/server/src/main/java/org/elasticsearch/cluster/project/ProjectStateRegistry.java new file mode 100644 index 0000000000000..1ae81b8daa0e9 --- /dev/null +++ b/server/src/main/java/org/elasticsearch/cluster/project/ProjectStateRegistry.java @@ -0,0 +1,137 @@ +/* + * 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.TransportVersion; +import org.elasticsearch.TransportVersions; +import org.elasticsearch.cluster.AbstractNamedDiffable; +import org.elasticsearch.cluster.ClusterState; +import org.elasticsearch.cluster.NamedDiff; +import org.elasticsearch.cluster.metadata.ProjectId; +import org.elasticsearch.common.collect.ImmutableOpenMap; +import org.elasticsearch.common.collect.Iterators; +import org.elasticsearch.common.io.stream.StreamInput; +import org.elasticsearch.common.io.stream.StreamOutput; +import org.elasticsearch.common.settings.Settings; +import org.elasticsearch.xcontent.ToXContent; + +import java.io.IOException; +import java.util.Collections; +import java.util.Iterator; +import java.util.Map; + +/** + * Represents a registry for managing and retrieving project-specific state in the cluster state. + */ +public class ProjectStateRegistry extends AbstractNamedDiffable implements ClusterState.Custom { + public static final String TYPE = "projects_registry"; + public static final ProjectStateRegistry EMPTY = new ProjectStateRegistry(Collections.emptyMap()); + + private final Map projectsSettings; + + public ProjectStateRegistry(StreamInput in) throws IOException { + projectsSettings = in.readMap(ProjectId::readFrom, Settings::readSettingsFromStream); + } + + private ProjectStateRegistry(Map projectsSettings) { + this.projectsSettings = projectsSettings; + } + + /** + * Retrieves the settings for a specific project based on its project ID from the specified cluster state without creating a new object. + * If you need a full state of the project rather than just its setting, please use {@link ClusterState#projectState(ProjectId)} + * + * @param projectId id of the project + * @param clusterState cluster state + * @return the settings for the specified project, or an empty settings object if no settings are found + */ + public static Settings getProjectSettings(ProjectId projectId, ClusterState clusterState) { + ProjectStateRegistry registry = clusterState.custom(TYPE, EMPTY); + return registry.projectsSettings.getOrDefault(projectId, Settings.EMPTY); + } + + @Override + public Iterator toXContentChunked(ToXContent.Params params) { + boolean multiProject = params.paramAsBoolean("multi-project", false); + if (multiProject == false) { + return Collections.emptyIterator(); + } + + return Iterators.concat( + Iterators.single((builder, p) -> builder.startArray("projects")), + Iterators.map(projectsSettings.entrySet().iterator(), entry -> (builder, p) -> { + builder.startObject(); + builder.field("id", entry.getKey()); + builder.startObject("settings"); + entry.getValue().toXContent(builder, new ToXContent.MapParams(Collections.singletonMap("flat_settings", "true"))); + builder.endObject(); + return builder.endObject(); + }), + Iterators.single((builder, p) -> builder.endArray()) + ); + } + + public static NamedDiff readDiffFrom(StreamInput in) throws IOException { + return readDiffFrom(ClusterState.Custom.class, TYPE, in); + } + + @Override + public String getWriteableName() { + return TYPE; + } + + @Override + public TransportVersion getMinimalSupportedVersion() { + return TransportVersions.CLUSTER_STATE_PROJECTS_SETTINGS; + } + + @Override + public void writeTo(StreamOutput out) throws IOException { + out.writeMap(projectsSettings); + } + + public int size() { + return projectsSettings.size(); + } + + public static Builder builder(ClusterState original) { + ProjectStateRegistry projectRegistry = original.custom(TYPE, EMPTY); + return builder(projectRegistry); + } + + public static Builder builder(ProjectStateRegistry projectRegistry) { + return new Builder(projectRegistry); + } + + public static Builder builder() { + return new Builder(); + } + + public static class Builder { + private final ImmutableOpenMap.Builder projectsSettings; + + private Builder() { + this.projectsSettings = ImmutableOpenMap.builder(); + } + + private Builder(ProjectStateRegistry original) { + this.projectsSettings = ImmutableOpenMap.builder(original.projectsSettings); + } + + public Builder putProjectSettings(ProjectId projectId, Settings settings) { + projectsSettings.put(projectId, settings); + return this; + } + + public ProjectStateRegistry build() { + return new ProjectStateRegistry(projectsSettings.build()); + } + } +} diff --git a/server/src/main/java/org/elasticsearch/common/settings/ProjectScopedSettings.java b/server/src/main/java/org/elasticsearch/common/settings/ProjectScopedSettings.java index d638349a3d345..fd74263c67a56 100644 --- a/server/src/main/java/org/elasticsearch/common/settings/ProjectScopedSettings.java +++ b/server/src/main/java/org/elasticsearch/common/settings/ProjectScopedSettings.java @@ -9,10 +9,18 @@ package org.elasticsearch.common.settings; +import org.elasticsearch.core.FixForMultiProject; + import java.util.Set; public class ProjectScopedSettings extends AbstractScopedSettings { public ProjectScopedSettings(Settings settings, Set> settingsSet) { super(settings, settingsSet, Setting.Property.ProjectScope); } + + @FixForMultiProject + @Override + public T get(Setting setting) { + throw new UnsupportedOperationException("Not implemented for project scoped settings"); + } } diff --git a/server/src/main/java/org/elasticsearch/reservedstate/service/ReservedProjectStateUpdateTask.java b/server/src/main/java/org/elasticsearch/reservedstate/service/ReservedProjectStateUpdateTask.java index 8dfe160a2aebc..a129e4b4b94be 100644 --- a/server/src/main/java/org/elasticsearch/reservedstate/service/ReservedProjectStateUpdateTask.java +++ b/server/src/main/java/org/elasticsearch/reservedstate/service/ReservedProjectStateUpdateTask.java @@ -14,6 +14,8 @@ import org.elasticsearch.cluster.ClusterState; import org.elasticsearch.cluster.metadata.ProjectId; import org.elasticsearch.cluster.metadata.ProjectMetadata; +import org.elasticsearch.cluster.project.ProjectStateRegistry; +import org.elasticsearch.common.settings.Settings; import org.elasticsearch.gateway.GatewayService; import org.elasticsearch.reservedstate.ReservedProjectStateHandler; import org.elasticsearch.reservedstate.TransformState; @@ -70,7 +72,15 @@ protected ClusterState execute(ClusterState currentState) { return currentState; } - ProjectMetadata updatedProject = result.v1().getMetadata().getProject(projectId); - return ClusterState.builder(currentState).putProjectMetadata(ProjectMetadata.builder(updatedProject).put(result.v2())).build(); + ClusterState updatedClusterState = result.v1(); + Settings updatedSettings = ProjectStateRegistry.getProjectSettings(projectId, updatedClusterState); + ProjectMetadata updatedProject = updatedClusterState.getMetadata().getProject(projectId); + return ClusterState.builder(currentState) + .putCustom( + ProjectStateRegistry.TYPE, + ProjectStateRegistry.builder(currentState).putProjectSettings(projectId, updatedSettings).build() + ) + .putProjectMetadata(ProjectMetadata.builder(updatedProject).put(result.v2())) + .build(); } } 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 e2c3591092d85..a8634d0a7dac5 100644 --- a/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java +++ b/server/src/test/java/org/elasticsearch/action/bulk/TransportBulkActionIngestTests.java @@ -38,6 +38,7 @@ import org.elasticsearch.cluster.metadata.Template; import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.project.ProjectStateRegistry; import org.elasticsearch.cluster.project.TestProjectResolvers; import org.elasticsearch.cluster.service.ClusterService; import org.elasticsearch.common.TriConsumer; @@ -350,6 +351,9 @@ public void setupAction() { when(state.getMetadata()).thenReturn(metadata); when(state.metadata()).thenReturn(metadata); when(state.blocks()).thenReturn(mock(ClusterBlocks.class)); + when(state.custom(eq(ProjectStateRegistry.TYPE), any())).thenReturn( + ProjectStateRegistry.builder().putProjectSettings(projectId, Settings.builder().build()).build() + ); when(clusterService.state()).thenReturn(state); doAnswer(invocation -> { ClusterChangedEvent event = mock(ClusterChangedEvent.class); diff --git a/server/src/test/java/org/elasticsearch/cluster/ClusterStateTests.java b/server/src/test/java/org/elasticsearch/cluster/ClusterStateTests.java index b38f44897a549..9d9a3c17e286d 100644 --- a/server/src/test/java/org/elasticsearch/cluster/ClusterStateTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/ClusterStateTests.java @@ -29,6 +29,7 @@ import org.elasticsearch.cluster.node.DiscoveryNode; import org.elasticsearch.cluster.node.DiscoveryNodeUtils; import org.elasticsearch.cluster.node.DiscoveryNodes; +import org.elasticsearch.cluster.project.ProjectStateRegistry; import org.elasticsearch.cluster.routing.GlobalRoutingTable; import org.elasticsearch.cluster.routing.GlobalRoutingTableTestHelper; import org.elasticsearch.cluster.routing.IndexRoutingTable; @@ -43,6 +44,7 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.collect.Iterators; +import org.elasticsearch.common.settings.Setting; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.TransportAddress; import org.elasticsearch.common.util.iterable.Iterables; @@ -92,6 +94,8 @@ import static org.hamcrest.Matchers.startsWith; public class ClusterStateTests extends ESTestCase { + private static final Setting PROJECT_SETTING = Setting.intSetting("project.setting", 0, Setting.Property.ProjectScope); + private static final Setting PROJECT_SETTING2 = Setting.intSetting("project.setting2", 0, Setting.Property.ProjectScope); public void testSupersedes() { final DiscoveryNode node1 = DiscoveryNodeUtils.builder("node1").roles(emptySet()).build(); @@ -270,7 +274,7 @@ public void testToXContentWithMultipleProjects() throws IOException { Map.ofEntries( Map.entry(Metadata.CONTEXT_MODE_PARAM, Metadata.CONTEXT_MODE_API), Map.entry("multi-project", "true"), - Map.entry("metric", "version,master_node,blocks,nodes,metadata,routing_table,customs") + Map.entry("metric", "version,master_node,blocks,nodes,metadata,routing_table,customs,projects_settings") // not routing_nodes because the order is not predictable ) ) @@ -512,7 +516,6 @@ public void testToXContentWithMultipleProjects() throws IOException { } }, "index-graveyard": { "tombstones": [] }, - "settings": {}, "reserved_state": {} }, { @@ -571,7 +574,6 @@ public void testToXContentWithMultipleProjects() throws IOException { } }, "index-graveyard": { "tombstones": [] }, - "settings": {}, "reserved_state": {} }, { @@ -579,7 +581,6 @@ public void testToXContentWithMultipleProjects() throws IOException { "templates": {}, "indices": {}, "index-graveyard": { "tombstones": [] }, - "settings": {}, "reserved_state": {} } ], @@ -800,6 +801,17 @@ public void testToXContentWithMultipleProjects() throws IOException { } } ] + }, + "projects_registry": { + "projects": [ + { + "id": "3LftaL7hgfXAsF60Gm6jcD", + "settings": { + "project.setting": "42", + "project.setting2": "43" + } + } + ] } } """, @@ -859,6 +871,8 @@ private static BytesReference sortRoutingTableXContent(String jsonContent) throw } private static ClusterState buildMultiProjectClusterState(DiscoveryNode... nodes) { + ProjectId projectId1 = ProjectId.fromId("3LftaL7hgfXAsF60Gm6jcD"); + ProjectId projectId2 = ProjectId.fromId("tb5W0bx765nDVIwqJPw92G"); final Metadata metadata = Metadata.builder() .clusterUUID("N8nJxElHSP23swO0bPLOcQ") .clusterUUIDCommitted(true) @@ -870,7 +884,7 @@ private static ClusterState buildMultiProjectClusterState(DiscoveryNode... nodes .build() ) .put( - ProjectMetadata.builder(ProjectId.fromId("3LftaL7hgfXAsF60Gm6jcD")) + ProjectMetadata.builder(projectId1) .put( IndexMetadata.builder("common-index") .settings( @@ -885,7 +899,7 @@ private static ClusterState buildMultiProjectClusterState(DiscoveryNode... nodes ) ) .put( - ProjectMetadata.builder(ProjectId.fromId("tb5W0bx765nDVIwqJPw92G")) + ProjectMetadata.builder(projectId2) .put( IndexMetadata.builder("common-index") .settings( @@ -906,11 +920,20 @@ private static ClusterState buildMultiProjectClusterState(DiscoveryNode... nodes .metadata(metadata) .nodes(discoveryNodes.build()) .routingTable(GlobalRoutingTableTestHelper.buildRoutingTable(metadata, RoutingTable.Builder::addAsNew)) + .putCustom( + ProjectStateRegistry.TYPE, + ProjectStateRegistry.builder() + .putProjectSettings( + projectId1, + Settings.builder().put(PROJECT_SETTING.getKey(), 42).put(PROJECT_SETTING2.getKey(), 43).build() + ) + .build() + ) .blocks( ClusterBlocks.builder() .addGlobalBlock(Metadata.CLUSTER_READ_ONLY_BLOCK) - .addIndexBlock(ProjectId.fromId("tb5W0bx765nDVIwqJPw92G"), "common-index", IndexMetadata.INDEX_METADATA_BLOCK) - .addIndexBlock(ProjectId.fromId("3LftaL7hgfXAsF60Gm6jcD"), "another-index", IndexMetadata.INDEX_READ_ONLY_BLOCK) + .addIndexBlock(projectId2, "common-index", IndexMetadata.INDEX_METADATA_BLOCK) + .addIndexBlock(projectId1, "another-index", IndexMetadata.INDEX_READ_ONLY_BLOCK) .addProjectGlobalBlock(ProjectId.fromId("WHyuJ0uqBYOPgHX9kYUXlZ"), ProjectMetadata.PROJECT_UNDER_DELETION_BLOCK) ) .build(); @@ -2202,6 +2225,8 @@ public static int expectedChunkCount(ToXContent.Params params, ClusterState clus chunkCount += 2 + snapshotDeletionsInProgress.getEntries().size(); } else if (custom instanceof SnapshotsInProgress snapshotsInProgress) { chunkCount += 2 + snapshotsInProgress.asStream().count(); + } else if (custom instanceof ProjectStateRegistry projectStateRegistry) { + chunkCount += 2 + projectStateRegistry.size(); } else { // could be anything, we have to just try it chunkCount += Iterables.size( diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/ProjectMetadataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/ProjectMetadataTests.java index 5fd534da2ddae..c39b5caeebce1 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/ProjectMetadataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/ProjectMetadataTests.java @@ -13,7 +13,6 @@ import org.elasticsearch.common.bytes.BytesArray; import org.elasticsearch.common.bytes.BytesReference; import org.elasticsearch.common.settings.Setting; -import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.xcontent.XContentHelper; import org.elasticsearch.index.IndexVersion; import org.elasticsearch.ingest.IngestMetadata; @@ -560,10 +559,7 @@ public void testToXContentMultiProject() throws IOException { }, "data_stream_aliases": {} }, - "settings": { - "project.setting.value": "43" - }, - "reserved_state": {} + "reserved_state": {} } """, IndexVersion.current(), @@ -623,7 +619,6 @@ private static ProjectMetadata prepareProjectMetadata() { builder.put(backingIndex1, false); builder.put(backingIndex2, false); builder.put(dataStream); - builder.settings(Settings.builder().put(PROJECT_SETTING.getKey(), 43).build()); final ProjectMetadata projectMetadata = builder.build(); return projectMetadata; @@ -670,8 +665,6 @@ static int expectedChunkCount(ToXContent.Params params, ProjectMetadata project) if (params.paramAsBoolean("multi-project", false)) { // 2 chunks for wrapping reserved state + 1 chunk for each item chunkCount += 2 + project.reservedStateMetadata().size(); - // 1 chunk for settings - chunkCount += 1; } return Math.toIntExact(chunkCount); diff --git a/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetadataTests.java b/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetadataTests.java index b3fc91dec3e53..f3c0a2a4a5141 100644 --- a/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetadataTests.java +++ b/server/src/test/java/org/elasticsearch/cluster/metadata/ToAndFromJsonMetadataTests.java @@ -285,7 +285,6 @@ public void testToXContentGateway_MultiProject() throws IOException { "index-graveyard" : { "tombstones" : [ ] }, - "settings" : { }, "reserved_state" : { } } ],