-
Notifications
You must be signed in to change notification settings - Fork 25.7k
Add default snapshot repo cluster setting #139155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
8024dea
8d6dfaa
35467c5
004eadb
67011c1
d4fa7a0
e117940
d8acbba
c12a1fa
fd0c58d
967739d
29d8ac6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,172 @@ | ||
| --- | ||
| setup: | ||
| - requires: | ||
| test_runner_features: [capabilities] | ||
| capabilities: | ||
| - method: PUT | ||
| path: /_cluster/settings | ||
| capabilities: ["repositories.default_repository_setting"] | ||
| reason: "Default repository setting capability check" | ||
|
|
||
| # Create two test repositories without verification | ||
| - do: | ||
| snapshot.create_repository: | ||
| repository: test_repo_1 | ||
| verify: false | ||
| body: | ||
| type: fs | ||
| settings: | ||
| location: "test_repo_1_loc" | ||
|
|
||
| - do: | ||
| snapshot.create_repository: | ||
| repository: test_repo_2 | ||
| verify: false | ||
| body: | ||
| type: fs | ||
| settings: | ||
| location: "test_repo_2_loc" | ||
|
|
||
| --- | ||
| teardown: | ||
| # Clean up cluster settings | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: null | ||
|
|
||
| # Clean up repositories (if they still exist) | ||
| - do: | ||
| catch: missing | ||
| snapshot.delete_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| - do: | ||
| catch: missing | ||
| snapshot.delete_repository: | ||
| repository: test_repo_2 | ||
|
|
||
| --- | ||
| "Set and validate default repository": | ||
| # Set first repo as default | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "test_repo_1" | ||
|
|
||
| - match: { persistent.repositories.default_repository: "test_repo_1" } | ||
|
|
||
| # Verify the setting is persisted | ||
| - do: | ||
| cluster.get_settings: {} | ||
|
|
||
| - match: { persistent.repositories.default_repository: "test_repo_1" } | ||
|
|
||
| --- | ||
| "Cannot delete default repository": | ||
| # Set first repo as default | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "test_repo_1" | ||
|
|
||
| # Try to delete the default repository - should fail | ||
| - do: | ||
| catch: /cannot delete the default repository/ | ||
| snapshot.delete_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| # Verify repo still exists | ||
| - do: | ||
| snapshot.get_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| - is_true: test_repo_1 | ||
|
|
||
| --- | ||
| "Cannot set non-existent repository as default": | ||
| # Try to set a non-existent repository as default | ||
| - do: | ||
| catch: /Repository \[non_existent_repo\] is not registered/ | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "non_existent_repo" | ||
|
|
||
| --- | ||
| "Change default repository and delete previous default": | ||
| # Set first repo as default | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "test_repo_1" | ||
|
|
||
| # Change default to second repo | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "test_repo_2" | ||
|
|
||
| - match: { persistent.repositories.default_repository: "test_repo_2" } | ||
|
|
||
| # Now we can delete first repo (no longer default) | ||
| - do: | ||
| snapshot.delete_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| - match: { acknowledged: true } | ||
|
|
||
| # Verify first repo is gone | ||
| - do: | ||
| catch: missing | ||
| snapshot.get_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| # Verify second repo still exists and is default | ||
| - do: | ||
| snapshot.get_repository: | ||
| repository: test_repo_2 | ||
|
|
||
| - is_true: test_repo_2 | ||
|
|
||
| - do: | ||
| cluster.get_settings: {} | ||
|
|
||
| - match: { persistent.repositories.default_repository: "test_repo_2" } | ||
|
|
||
| --- | ||
| "Clear default repository setting": | ||
| # Set a repository as default | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: "test_repo_1" | ||
|
|
||
| # Clear the default by setting to null | ||
| - do: | ||
| cluster.put_settings: | ||
| body: | ||
| persistent: | ||
| repositories.default_repository: null | ||
|
|
||
| - match: { persistent: {} } | ||
|
|
||
| # Verify the setting is cleared | ||
| - do: | ||
| cluster.get_settings: {} | ||
|
|
||
| - is_false: persistent.repositories.default_repository | ||
|
|
||
| # Now we can delete the repository | ||
| - do: | ||
| snapshot.delete_repository: | ||
| repository: test_repo_1 | ||
|
|
||
| - match: { acknowledged: true } | ||
|
|
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -111,6 +111,16 @@ public class RepositoriesService extends AbstractLifecycleComponent implements C | |
| Setting.Property.NodeScope | ||
| ); | ||
|
|
||
| /** | ||
| * Setting that specifies the default repository to use for snapshot and restore operations when no repository is explicitly | ||
| * specified. If not set, some snapshot and restore operations may require an explicit repository name. | ||
| */ | ||
| public static final Setting<String> DEFAULT_REPOSITORY_SETTING = Setting.simpleString( | ||
| "repositories.default_repository", | ||
| Setting.Property.NodeScope, | ||
| Setting.Property.Dynamic | ||
| ); | ||
|
|
||
| private final Map<String, Repository.Factory> typesRegistry; | ||
| private final Map<String, Repository.Factory> internalTypesRegistry; | ||
|
|
||
|
|
@@ -125,6 +135,8 @@ public class RepositoriesService extends AbstractLifecycleComponent implements C | |
|
|
||
| private final List<BiConsumer<Snapshot, IndexVersion>> preRestoreChecks; | ||
|
|
||
| private volatile String defaultRepository; | ||
|
|
||
| @SuppressWarnings("this-escape") | ||
| public RepositoriesService( | ||
| Settings settings, | ||
|
|
@@ -154,9 +166,74 @@ public RepositoriesService( | |
| threadPool.relativeTimeInMillisSupplier() | ||
| ); | ||
| this.preRestoreChecks = preRestoreChecks; | ||
| this.defaultRepository = DEFAULT_REPOSITORY_SETTING.get(settings); | ||
| clusterService.getClusterSettings() | ||
| .addSettingsUpdateConsumer(DEFAULT_REPOSITORY_SETTING, this::setDefaultRepository, this::validateDefaultRepository); | ||
|
Comment on lines
+170
to
+171
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should get someone from the Distributed team to have a look at this validation, to verify if there's any reason this validation could cause issues. I'll add the area label to the PR, could you reach out to #es-destrib and ask for a review? |
||
| snapshotMetrics.createSnapshotShardsInProgressMetric(this::getShardSnapshotsInProgress); | ||
| } | ||
|
|
||
| /** | ||
| * Gets the configured default repository. | ||
| * | ||
| * @return the default repository name, or an empty string if not configured | ||
| */ | ||
| public String getDefaultRepository() { | ||
| return defaultRepository; | ||
| } | ||
|
|
||
| /** | ||
| * Sets the default repository value. | ||
| * This is called by the settings update consumer when the setting changes. | ||
| * | ||
| * @param repositoryName the new default repository name | ||
| */ | ||
| private void setDefaultRepository(String repositoryName) { | ||
| this.defaultRepository = repositoryName; | ||
| logger.info("Default repository set to [{}]", repositoryName); | ||
| } | ||
|
|
||
| /** | ||
| * Validates that the default repository is registered. | ||
| * This validator is called when the default repository setting is updated. | ||
| * Empty strings are allowed to clear the default repository setting. | ||
| * | ||
| * @param repositoryName the repository name to validate | ||
| * @throws IllegalArgumentException if the repository name is not empty and the repository doesn't exist | ||
| */ | ||
| private void validateDefaultRepository(String repositoryName) { | ||
| if (Strings.isEmpty(repositoryName)) { | ||
| logger.info("Default repository cleared"); | ||
| return; | ||
| } | ||
| if (isRepositoryRegistered(repositoryName) == false) { | ||
| throw new IllegalArgumentException( | ||
| "Repository [" | ||
| + repositoryName | ||
| + "] is not registered. " | ||
| + "Cannot set as default repository. Please register the repository prior to setting as default using PUT /_snapshot/" | ||
| + repositoryName | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a repository is registered in the default project, any other project, or internal repositories. | ||
| * | ||
| * @param repositoryName the repository name to check | ||
| * @return true if the repository is registered, false otherwise | ||
| */ | ||
| private boolean isRepositoryRegistered(String repositoryName) { | ||
| if (repositoryOrNull(ProjectId.DEFAULT, repositoryName) != null) { | ||
| return true; | ||
| } | ||
| boolean found = repositories.values().stream().anyMatch(projectRepos -> projectRepos.containsKey(repositoryName)); | ||
| if (found) { | ||
| return true; | ||
| } | ||
| found = internalRepositories.values().stream().anyMatch(projectRepos -> projectRepos.containsKey(repositoryName)); | ||
| return found; | ||
| } | ||
|
Comment on lines
+225
to
+235
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Two comments:
|
||
|
|
||
| /** | ||
| * Registers new repository in the cluster | ||
| * <p> | ||
|
|
@@ -568,6 +645,7 @@ public ClusterState execute(ClusterState currentState) { | |
| for (RepositoryMetadata repositoryMetadata : repositories.repositories()) { | ||
| if (Regex.simpleMatch(request.name(), repositoryMetadata.name())) { | ||
| ensureRepositoryNotInUse(projectState, repositoryMetadata.name()); | ||
| ensureRepositoryNotDefault(currentState, repositoryMetadata.name()); | ||
| ensureNoSearchableSnapshotsIndicesInUse(currentState, repositoryMetadata); | ||
| deletedRepositories.add(repositoryMetadata.name()); | ||
| changed = true; | ||
|
|
@@ -1135,6 +1213,24 @@ private static void ensureRepositoryNotInUse(ProjectState projectState, String r | |
| } | ||
| } | ||
|
|
||
| /** | ||
| * Ensures that the repository being deleted is not currently set as the default repository. | ||
| * | ||
| * @param currentState the current cluster state | ||
| * @param repository the repository name to check | ||
| * @throws RepositoryException if the repository is the default repository | ||
| */ | ||
| private static void ensureRepositoryNotDefault(ClusterState currentState, String repository) { | ||
| String defaultRepository = DEFAULT_REPOSITORY_SETTING.get(currentState.metadata().settings()); | ||
| if (Strings.isEmpty(defaultRepository) == false && repository.equals(defaultRepository)) { | ||
| throw newRepositoryConflictException( | ||
| repository, | ||
| "cannot delete the default repository. Please change the default repository cluster setting " | ||
| + "repositories.default_repository before deleting this repository." | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| public static boolean isReadOnly(Settings repositorySettings) { | ||
| return Boolean.TRUE.equals(repositorySettings.getAsBoolean(BlobStoreRepository.READONLY_SETTING_KEY, null)); | ||
| } | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.