Migrate the reserved repository action to be per-project (#130155)

Resolves: ES-10479
This commit is contained in:
Yang Wang 2025-07-03 10:25:27 +10:00 committed by GitHub
parent f3c5eb7815
commit c17bfcbfc2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 40 additions and 29 deletions

View File

@ -85,4 +85,16 @@ public class TransportDeleteRepositoryAction extends AcknowledgedTransportMaster
public Set<String> modifiedKeys(DeleteRepositoryRequest request) {
return Set.of(request.name());
}
@Override
protected void validateForReservedState(DeleteRepositoryRequest request, ClusterState state) {
super.validateForReservedState(request, state);
validateForReservedState(
projectResolver.getProjectMetadata(state).reservedStateMetadata().values(),
reservedStateHandlerName().get(),
modifiedKeys(request),
request.toString()
);
}
}

View File

@ -12,9 +12,8 @@ package org.elasticsearch.action.admin.cluster.repositories.reservedstate;
import org.elasticsearch.action.admin.cluster.repositories.put.PutRepositoryRequest;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.ProjectId;
import org.elasticsearch.core.FixForMultiProject;
import org.elasticsearch.repositories.RepositoriesService;
import org.elasticsearch.reservedstate.ReservedClusterStateHandler;
import org.elasticsearch.reservedstate.ReservedProjectStateHandler;
import org.elasticsearch.reservedstate.TransformState;
import org.elasticsearch.xcontent.XContentParser;
import org.elasticsearch.xcontent.XContentParserConfiguration;
@ -36,7 +35,7 @@ import static org.elasticsearch.common.xcontent.XContentHelper.mapToXContentPars
* It is used by the ReservedClusterStateService to add/update or remove snapshot repositories. Typical usage
* for this action is in the context of file based settings.
*/
public class ReservedRepositoryAction implements ReservedClusterStateHandler<List<PutRepositoryRequest>> {
public class ReservedRepositoryAction implements ReservedProjectStateHandler<List<PutRepositoryRequest>> {
public static final String NAME = "snapshot_repositories";
private final RepositoriesService repositoriesService;
@ -56,14 +55,12 @@ public class ReservedRepositoryAction implements ReservedClusterStateHandler<Lis
}
@SuppressWarnings("unchecked")
public Collection<PutRepositoryRequest> prepare(Object input) {
public Collection<PutRepositoryRequest> prepare(ProjectId projectId, Object input) {
List<PutRepositoryRequest> repositories = (List<PutRepositoryRequest>) input;
for (var repositoryRequest : repositories) {
validate(repositoryRequest);
RepositoriesService.validateRepositoryName(repositoryRequest.name());
@FixForMultiProject(description = "resolve the actual projectId, ES-10479")
final var projectId = ProjectId.DEFAULT;
repositoriesService.validateRepositoryCanBeCreated(projectId, repositoryRequest);
}
@ -71,13 +68,11 @@ public class ReservedRepositoryAction implements ReservedClusterStateHandler<Lis
}
@Override
public TransformState transform(List<PutRepositoryRequest> source, TransformState prevState) throws Exception {
var requests = prepare(source);
public TransformState transform(ProjectId projectId, List<PutRepositoryRequest> source, TransformState prevState) throws Exception {
var requests = prepare(projectId, source);
ClusterState state = prevState.state();
@FixForMultiProject(description = "resolve the actual projectId, ES-10479")
final var projectId = ProjectId.DEFAULT;
for (var request : requests) {
RepositoriesService.RegisterRepositoryTask task = new RepositoriesService.RegisterRepositoryTask(
repositoriesService,

View File

@ -1125,7 +1125,7 @@ class NodeConstruction {
indicesService
);
actionModule.getReservedClusterStateService().installClusterStateHandler(new ReservedRepositoryAction(repositoriesService));
actionModule.getReservedClusterStateService().installProjectStateHandler(new ReservedRepositoryAction(repositoriesService));
actionModule.getReservedClusterStateService().installProjectStateHandler(new ReservedPipelineAction());
var fileSettingsHealthIndicatorPublisher = new FileSettingsService.FileSettingsHealthIndicatorPublisherImpl(clusterService, client);

View File

@ -14,6 +14,7 @@ import org.elasticsearch.client.internal.node.NodeClient;
import org.elasticsearch.cluster.ClusterName;
import org.elasticsearch.cluster.ClusterState;
import org.elasticsearch.cluster.metadata.ProjectId;
import org.elasticsearch.cluster.metadata.ProjectMetadata;
import org.elasticsearch.cluster.metadata.RepositoryMetadata;
import org.elasticsearch.cluster.service.ClusterService;
import org.elasticsearch.common.settings.Settings;
@ -34,7 +35,6 @@ import java.util.Set;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@ -44,9 +44,10 @@ import static org.mockito.Mockito.spy;
*/
public class ReservedRepositoryActionTests extends ESTestCase {
private TransformState processJSON(ReservedRepositoryAction action, TransformState prevState, String json) throws Exception {
private TransformState processJSON(ProjectId projectId, ReservedRepositoryAction action, TransformState prevState, String json)
throws Exception {
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, json)) {
return action.transform(action.fromXContent(parser), prevState);
return action.transform(projectId, action.fromXContent(parser), prevState);
}
}
@ -69,20 +70,24 @@ public class ReservedRepositoryActionTests extends ESTestCase {
assertEquals(
"[repo] repository type [inter_planetary] does not exist",
expectThrows(RepositoryException.class, () -> processJSON(action, prevState, badPolicyJSON)).getMessage()
expectThrows(RepositoryException.class, () -> processJSON(randomProjectIdOrDefault(), action, prevState, badPolicyJSON))
.getMessage()
);
}
public void testAddRepo() throws Exception {
var repositoriesService = mockRepositoriesService();
final var projectId = randomProjectIdOrDefault();
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch"))
.putProjectMetadata(ProjectMetadata.builder(projectId))
.build();
TransformState prevState = new TransformState(state, Collections.emptySet());
ReservedRepositoryAction action = new ReservedRepositoryAction(repositoriesService);
String emptyJSON = "";
TransformState updatedState = processJSON(action, prevState, emptyJSON);
TransformState updatedState = processJSON(projectId, action, prevState, emptyJSON);
assertEquals(0, updatedState.keys().size());
assertEquals(prevState.state(), updatedState.state());
@ -103,14 +108,17 @@ public class ReservedRepositoryActionTests extends ESTestCase {
}""";
prevState = updatedState;
updatedState = processJSON(action, prevState, settingsJSON);
updatedState = processJSON(projectId, action, prevState, settingsJSON);
assertThat(updatedState.keys(), containsInAnyOrder("repo", "repo1"));
}
public void testRemoveRepo() {
var repositoriesService = mockRepositoriesService();
final var projectId = randomProjectIdOrDefault();
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch")).build();
ClusterState state = ClusterState.builder(new ClusterName("elasticsearch"))
.putProjectMetadata(ProjectMetadata.builder(projectId))
.build();
TransformState prevState = new TransformState(state, Set.of("repo1"));
ReservedRepositoryAction action = new ReservedRepositoryAction(repositoriesService);
@ -120,7 +128,7 @@ public class ReservedRepositoryActionTests extends ESTestCase {
// missing is sufficient to tell that we attempted to delete that repo
assertEquals(
"[repo1] missing",
expectThrows(RepositoryMissingException.class, () -> processJSON(action, prevState, emptyJSON)).getMessage()
expectThrows(RepositoryMissingException.class, () -> processJSON(projectId, action, prevState, emptyJSON)).getMessage()
);
}
@ -153,7 +161,7 @@ public class ReservedRepositoryActionTests extends ESTestCase {
throw new RepositoryException(request.name(), "repository type [" + request.type() + "] does not exist");
}
return null;
}).when(repositoriesService).validateRepositoryCanBeCreated(eq(ProjectId.DEFAULT), any());
}).when(repositoriesService).validateRepositoryCanBeCreated(any(ProjectId.class), any());
return repositoriesService;
}

View File

@ -290,8 +290,8 @@ public class ReservedSnapshotLifecycleStateServiceTests extends ESTestCase {
ReservedClusterStateService controller = new ReservedClusterStateService(
clusterService,
null,
List.of(new ReservedClusterSettingsAction(clusterSettings), new ReservedRepositoryAction(repositoriesService)),
List.of()
List.of(new ReservedClusterSettingsAction(clusterSettings)),
List.of(new ReservedRepositoryAction(repositoriesService))
);
String testJSON = """
@ -362,12 +362,8 @@ public class ReservedSnapshotLifecycleStateServiceTests extends ESTestCase {
controller = new ReservedClusterStateService(
clusterService,
null,
List.of(
new ReservedClusterSettingsAction(clusterSettings),
new ReservedSnapshotAction(),
new ReservedRepositoryAction(repositoriesService)
),
List.of()
List.of(new ReservedClusterSettingsAction(clusterSettings), new ReservedSnapshotAction()),
List.of(new ReservedRepositoryAction(repositoriesService))
);
try (XContentParser parser = XContentType.JSON.xContent().createParser(XContentParserConfiguration.EMPTY, testJSON)) {