Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
111 changes: 84 additions & 27 deletions server/src/main/java/org/opensearch/snapshots/RestoreService.java
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,78 @@ public class RestoreService implements ClusterStateApplier {
USER_UNREMOVABLE_SETTINGS = unmodifiableSet(unremovable);
}

/**
* Creates a settings filter predicate that separates internal ignore patterns from user ignore patterns.
* Internal ignore patterns override protection and can filter any setting.
* User ignore patterns respect protected settings and cannot filter them.
*
* @param userIgnoreSettings array of user-provided settings to ignore
* @param internalIgnoreSettings array of internal settings to ignore (override protection)
* @param protectedSettings set of settings that user cannot remove
* @return a predicate that returns true if the setting should be kept, false if it should be filtered out
*/
static Predicate<String> createSettingsFilterPredicate(
String[] userIgnoreSettings,
String[] internalIgnoreSettings,
Set<String> protectedSettings
) {
Set<String> userKeyFilters = new HashSet<>();
List<String> userSimpleMatchPatterns = new ArrayList<>();
Set<String> internalKeyFilters = new HashSet<>();
List<String> internalSimpleMatchPatterns = new ArrayList<>();

// Process user ignore settings
for (String ignoredSetting : userIgnoreSettings) {
if (!Regex.isSimpleMatchPattern(ignoredSetting)) {
userKeyFilters.add(ignoredSetting);
} else {
userSimpleMatchPatterns.add(ignoredSetting);
}
}

// Process internal ignore settings
for (String ignoredSetting : internalIgnoreSettings) {
if (!Regex.isSimpleMatchPattern(ignoredSetting)) {
internalKeyFilters.add(ignoredSetting);
} else {
internalSimpleMatchPatterns.add(ignoredSetting);
}
}

return k -> {
// Check internal ignore patterns first (they override protection)
if (internalKeyFilters.contains(k)) {
return false;
}
for (String pattern : internalSimpleMatchPatterns) {
if (Regex.simpleMatch(pattern, k)) {
return false;
}
}

// Check user ignore patterns only for non-protected settings
if (!protectedSettings.contains(k)) {
if (userKeyFilters.contains(k)) {
return false;
}
for (String pattern : userSimpleMatchPatterns) {
if (Regex.simpleMatch(pattern, k)) {
return false;
}
}
}
return true;
};
}

/**
* Returns the set of settings that users cannot remove during restore.
* Exposed for testing purposes.
*/
static Set<String> getUserUnremovableSettings() {
return USER_UNREMOVABLE_SETTINGS;
}

private final ClusterService clusterService;

private final RepositoriesService repositoriesService;
Expand Down Expand Up @@ -818,8 +890,7 @@ private IndexMetadata updateIndexSettings(
}
IndexMetadata.Builder builder = IndexMetadata.builder(indexMetadata);
Settings settings = indexMetadata.getSettings();
Set<String> keyFilters = new HashSet<>();
List<String> simpleMatchPatterns = new ArrayList<>();

for (String ignoredSetting : ignoreSettings) {
if (!Regex.isSimpleMatchPattern(ignoredSetting)) {
if (USER_UNREMOVABLE_SETTINGS.contains(ignoredSetting)) {
Expand All @@ -832,38 +903,24 @@ private IndexMetadata updateIndexSettings(
snapshot,
"cannot remove UnmodifiableOnRestore setting [" + ignoredSetting + "] on restore"
);
} else {
keyFilters.add(ignoredSetting);
}
} else {
simpleMatchPatterns.add(ignoredSetting);
}
}

// add internal settings to ignore settings list
for (String ignoredSetting : ignoreSettingsInternal) {
if (!Regex.isSimpleMatchPattern(ignoredSetting)) {
keyFilters.add(ignoredSetting);
} else {
simpleMatchPatterns.add(ignoredSetting);
// Build combined protected settings set including dynamic unmodifiable settings
Set<String> protectedSettings = new HashSet<>(USER_UNREMOVABLE_SETTINGS);
for (String key : settings.keySet()) {
if (indexScopedSettings.isUnmodifiableOnRestoreSetting(key)) {
protectedSettings.add(key);
}
}

Predicate<String> settingsFilter = k -> {
if (USER_UNREMOVABLE_SETTINGS.contains(k) == false && !indexScopedSettings.isUnmodifiableOnRestoreSetting(k)) {
for (String filterKey : keyFilters) {
if (k.equals(filterKey)) {
return false;
}
}
for (String pattern : simpleMatchPatterns) {
if (Regex.simpleMatch(pattern, k)) {
return false;
}
}
}
return true;
};
Predicate<String> settingsFilter = createSettingsFilterPredicate(
ignoreSettings,
ignoreSettingsInternal,
protectedSettings
);

Settings.Builder settingsBuilder = Settings.builder()
.put(settings.filter(settingsFilter))
.put(normalizedChangeSettings.filter(k -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -159,4 +159,36 @@ public void testValidateReplicationTypeRestoreSettings_WhenSnapshotIsSegment_Res
() -> RestoreService.validateReplicationTypeRestoreSettings(snapshot, ReplicationType.SEGMENT.toString(), indexMetadata)
);
}

// Tests for internal vs user ignore settings filter separation (PR #20494)

public void testInternalIgnoreOverridesProtection() {
var filter = RestoreService.createSettingsFilterPredicate(
new String[] {},
new String[] { "index.remote_store.*" },
RestoreService.getUserUnremovableSettings()
);
// Internal pattern can filter protected settings
assertFalse(filter.test("index.remote_store.enabled"));
}

public void testUserIgnoreRespectsProtection() {
var filter = RestoreService.createSettingsFilterPredicate(
new String[] { "index.number_of_replicas" },
new String[] {},
RestoreService.getUserUnremovableSettings()
);
// User cannot filter protected settings
assertTrue(filter.test("index.number_of_replicas"));
}

public void testUserIgnoreWorksForNonProtected() {
var filter = RestoreService.createSettingsFilterPredicate(
new String[] { "index.custom.*" },
new String[] {},
RestoreService.getUserUnremovableSettings()
);
// User can filter non-protected settings
assertFalse(filter.test("index.custom.setting"));
}
}
Loading