Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class ClusterStateRequest extends LocalClusterStateRequest implements Ind
private TimeValue waitForTimeout = DEFAULT_WAIT_FOR_NODE_TIMEOUT;
private String[] indices = Strings.EMPTY_ARRAY;
private IndicesOptions indicesOptions = IndicesOptions.lenientExpandOpen();
private boolean multiproject = false;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ClusterStateRequest is LocalClusterStateRequest, so doesn't require a new transport version


public ClusterStateRequest(TimeValue masterNodeTimeout) {
super(masterNodeTimeout);
Expand Down Expand Up @@ -140,6 +141,15 @@ public boolean customs() {
return customs;
}

public ClusterStateRequest multiproject(boolean multiproject) {
this.multiproject = multiproject;
return this;
}

public boolean multiproject() {
return multiproject;
}

public TimeValue waitForTimeout() {
return waitForTimeout;
}
Expand Down Expand Up @@ -200,5 +210,4 @@ public String getDescription() {
stringBuilder.append("master timeout [").append(masterTimeout()).append("]]");
return stringBuilder.toString();
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -108,4 +108,12 @@ public ClusterStateRequestBuilder setWaitForTimeOut(TimeValue waitForTimeout) {
request.waitForTimeout(waitForTimeout);
return this;
}

/**
* When set then the response will be in multi-project format
*/
public ClusterStateRequestBuilder setMultiproject(boolean multiproject) {
request.multiproject(multiproject);
return this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.ProjectId;
import org.elasticsearch.cluster.metadata.ProjectMetadata;
import org.elasticsearch.cluster.metadata.ReservedStateHandlerMetadata;
import org.elasticsearch.cluster.metadata.ReservedStateMetadata;
import org.elasticsearch.cluster.project.ProjectResolver;
import org.elasticsearch.cluster.project.ProjectStateRegistry;
import org.elasticsearch.cluster.routing.GlobalRoutingTable;
Expand All @@ -47,7 +49,9 @@

import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
Expand Down Expand Up @@ -255,6 +259,22 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
// If there are no requested indices, then we want all the metadata, except for customs that aren't exposed via the API
mdBuilder = Metadata.builder(currentState.metadata());
mdBuilder.removeCustomIf(notApi);

if (request.multiproject() == false) {
ProjectStateRegistry projectStateRegistry = ProjectStateRegistry.get(currentState);
if (projectStateRegistry.size() > 1) {
throw new Metadata.MultiProjectPendingException(
"There are multiple projects " + projectStateRegistry.knownProjects()
);
}
var reservedStateMetadata = new HashMap<>(currentState.metadata().reservedStateMetadata());
var singleProjectReservedStateMetadata = projectStateRegistry.reservedStateMetadata(projectResolver.getProjectId());
singleProjectReservedStateMetadata.forEach(
(key, value) -> reservedStateMetadata.merge(key, value, this::mergeReservedStateMetadata)
);

mdBuilder.put(reservedStateMetadata);
}
}

for (ProjectMetadata project : currentState.metadata().projects().values()) {
Expand Down Expand Up @@ -308,4 +328,30 @@ private ClusterStateResponse buildResponse(final ClusterStateRequest request, fi
return new ClusterStateResponse(currentState.getClusterName(), builder.build(), false);
}

private ReservedStateMetadata mergeReservedStateMetadata(ReservedStateMetadata metadata1, ReservedStateMetadata metadata2) {
if (Objects.equals(metadata1.version(), metadata2.version()) == false) {
logger.warn(
"Reserved state metadata version is different for Metadata ({}) and the only project ({})",
metadata2.version(),
metadata1.version()
);
}
ReservedStateMetadata.Builder builder = ReservedStateMetadata.builder(metadata1.namespace())
.version(Math.max(metadata1.version(), metadata2.version()));

for (ReservedStateHandlerMetadata handler : metadata1.handlers().values()) {
builder.putHandler(handler);
}
for (ReservedStateHandlerMetadata handler : metadata2.handlers().values()) {
builder.putHandler(handler);
}

if (metadata2.errorMetadata() != null) {
builder.errorMetadata(metadata2.errorMetadata());
} else if (metadata1.errorMetadata() != null) {
builder.errorMetadata(metadata1.errorMetadata());
}

return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -199,8 +199,7 @@ public long getProjectsMarkedForDeletionGeneration() {
return projectsMarkedForDeletionGeneration;
}

// visible for testing
Set<ProjectId> knownProjects() {
public Set<ProjectId> knownProjects() {
return projectsEntries.keySet();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ public RestChannelConsumer prepareRequest(final RestRequest request, final NodeC
final Map<String, String> params;
if (request.paramAsBoolean("multi_project", false)) {
params = Map.of(Metadata.CONTEXT_MODE_PARAM, Metadata.CONTEXT_MODE_API, "multi-project", "true");
clusterStateRequest.multiproject(true);
Comment on lines 114 to +115
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now we have the new field, it feels an opportunity to remove the use of XContent parameter here as suggest by the above @FixForMultiProject annotation. It does not need to be this PR.

} else {
params = Map.of(Metadata.CONTEXT_MODE_PARAM, Metadata.CONTEXT_MODE_API);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@
import org.elasticsearch.cluster.metadata.Metadata;
import org.elasticsearch.cluster.metadata.ProjectId;
import org.elasticsearch.cluster.metadata.ProjectMetadata;
import org.elasticsearch.cluster.metadata.ReservedStateHandlerMetadata;
import org.elasticsearch.cluster.metadata.ReservedStateMetadata;
import org.elasticsearch.cluster.node.VersionInformation;
import org.elasticsearch.cluster.project.DefaultProjectResolver;
import org.elasticsearch.cluster.project.ProjectResolver;
Expand Down Expand Up @@ -94,7 +96,7 @@ public void testGetClusterStateWithDefaultProjectOnly() throws Exception {
final ProjectResolver projectResolver = DefaultProjectResolver.INSTANCE;

final Set<String> indexNames = randomSet(1, 8, () -> randomAlphaOfLengthBetween(4, 12));
final ClusterStateRequest request = buildRandomRequest(indexNames);
final ClusterStateRequest request = buildRandomRequest(indexNames, false);
final String[] expectedIndices = getExpectedIndices(request, indexNames);

final ProjectId projectId = Metadata.DEFAULT_PROJECT_ID;
Expand All @@ -112,7 +114,7 @@ public void testGetClusterStateForOneProjectOfMany() throws Exception {
final ProjectId projectId = randomUniqueProjectId();

final ProjectResolver projectResolver = TestProjectResolvers.singleProject(projectId);
final ClusterStateRequest request = buildRandomRequest(indexNames);
final ClusterStateRequest request = buildRandomRequest(indexNames, false);
final String[] expectedIndices = getExpectedIndices(request, indexNames);

final int numberOfProjects = randomIntBetween(2, 5);
Expand Down Expand Up @@ -141,7 +143,7 @@ public void testGetClusterStateForManyProjects() throws Exception {
final ClusterState state = buildClusterState(projects);

final ProjectResolver projectResolver = TestProjectResolvers.allProjects();
final ClusterStateRequest request = buildRandomRequest(indexNames);
final ClusterStateRequest request = buildRandomRequest(indexNames, true);
final Set<String> requestedIndices = Set.of(getExpectedIndices(request, indexNames));

final ClusterStateResponse response = executeAction(projectResolver, request, state);
Expand Down Expand Up @@ -190,6 +192,22 @@ private static void assertSingleProjectResponse(
assertThat(metadata.projects().keySet(), contains(projectId));
if (request.metadata()) {
assertThat(metadata.getProject(projectId).indices().keySet(), containsInAnyOrder(expectedIndices));

if (request.indices().length == 0) {
Map<String, ReservedStateMetadata> reservedStateMetadataMap = metadata.reservedStateMetadata();
assertThat(reservedStateMetadataMap, aMapWithSize(1));
ReservedStateMetadata fileSettings = reservedStateMetadataMap.get("file_settings");
assertNotNull(fileSettings);
assertThat(fileSettings.version(), equalTo(43L));
Map<String, ReservedStateHandlerMetadata> handlers = fileSettings.handlers();
assertThat(handlers, aMapWithSize(2));
ReservedStateHandlerMetadata clusterSettingsHandler = handlers.get("cluster_settings");
assertNotNull(clusterSettingsHandler);
assertThat(clusterSettingsHandler.keys(), containsInAnyOrder("setting_1", "setting_2"));
ReservedStateHandlerMetadata projectSettingsHandler = handlers.get("project_settings");
assertNotNull(projectSettingsHandler);
assertThat(projectSettingsHandler.keys(), containsInAnyOrder("setting_1"));
}
} else {
assertThat(metadata.getProject(projectId).indices(), anEmptyMap());
}
Expand Down Expand Up @@ -235,7 +253,7 @@ private static String[] getExpectedIndices(ClusterStateRequest request, Set<Stri
}
}

private static ClusterStateRequest buildRandomRequest(Set<String> indexNames) {
private static ClusterStateRequest buildRandomRequest(Set<String> indexNames, boolean multipleProjects) {
final ClusterStateRequest request = new ClusterStateRequest(TEST_REQUEST_TIMEOUT);
if (randomBoolean()) {
final int numberSelectedIndices = randomIntBetween(1, indexNames.size());
Expand All @@ -248,18 +266,31 @@ private static ClusterStateRequest buildRandomRequest(Set<String> indexNames) {
request.routingTable(randomBoolean());
request.blocks(randomBoolean());
request.customs(true);
request.multiproject(multipleProjects);
return request;
}

private static ClusterState buildClusterState(ProjectMetadata.Builder... projects) {
final Metadata.Builder metadataBuilder = Metadata.builder();
metadataBuilder.put(
ReservedStateMetadata.builder("file_settings")
.version(43L)
.putHandler(new ReservedStateHandlerMetadata("cluster_settings", Set.of("setting_1", "setting_2")))
.build()
);
Arrays.stream(projects).forEach(metadataBuilder::put);
final var metadata = metadataBuilder.build();

ClusterState.Builder csBuilder = ClusterState.builder(new ClusterName(randomAlphaOfLengthBetween(4, 12)));
ProjectStateRegistry.Builder psBuilder = ProjectStateRegistry.builder();
for (ProjectMetadata.Builder project : projects) {
psBuilder.putProjectSettings(project.getId(), Settings.builder().put("setting_1", randomIdentifier()).build());
psBuilder.putReservedStateMetadata(
project.getId(),
ReservedStateMetadata.builder("file_settings")
.version(43L)
.putHandler(new ReservedStateHandlerMetadata("project_settings", Set.of("setting_1")))
.build()
).putProjectSettings(project.getId(), Settings.builder().put("setting_1", randomIdentifier()).build());
}
return csBuilder.metadata(metadata)
.routingTable(GlobalRoutingTableTestHelper.buildRoutingTable(metadata, RoutingTable.Builder::addAsNew))
Expand Down
Loading