Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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 @@ -52,6 +52,7 @@
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;

Expand Down Expand Up @@ -129,6 +130,32 @@ private static StorageType fromString(String string) {
@Nullable // if any snapshot UUID will do
private String snapshotUuid;

/**
* Alias write index policy for controlling how writeIndex attribute is handled during restore
*
* @opensearch.api
*/
@PublicApi(since = "3.0.0")
public enum AliasWriteIndexPolicy {
PRESERVE,
STRIP_WRITE_INDEX,
CUSTOM_SUFFIX;

public static AliasWriteIndexPolicy fromString(String value) {
try {
return valueOf(value.toUpperCase(Locale.ROOT));
} catch (IllegalArgumentException e) {
throw new IllegalArgumentException(
"Unknown alias_write_index_policy [" + value + "]. Valid values are: " + Arrays.toString(values())
);
}
}
}

private AliasWriteIndexPolicy aliasWriteIndexPolicy = AliasWriteIndexPolicy.PRESERVE;
@Nullable
private String aliasSuffix;

public RestoreSnapshotRequest() {}

/**
Expand Down Expand Up @@ -172,6 +199,10 @@ public RestoreSnapshotRequest(StreamInput in) throws IOException {
if (in.getVersion().onOrAfter(Version.V_2_18_0)) {
renameAliasReplacement = in.readOptionalString();
}
if (in.getVersion().onOrAfter(Version.V_3_3_0)) {
aliasWriteIndexPolicy = in.readEnum(AliasWriteIndexPolicy.class);
aliasSuffix = in.readOptionalString();
}
}

@Override
Expand Down Expand Up @@ -205,6 +236,10 @@ public void writeTo(StreamOutput out) throws IOException {
if (out.getVersion().onOrAfter(Version.V_2_18_0)) {
out.writeOptionalString(renameAliasReplacement);
}
if (out.getVersion().onOrAfter(Version.V_3_3_0)) {
out.writeEnum(aliasWriteIndexPolicy);
out.writeOptionalString(aliasSuffix);
}
}

@Override
Expand Down Expand Up @@ -640,6 +675,47 @@ public String getSourceRemoteTranslogRepository() {
return sourceRemoteTranslogRepository;
}

/**
* Sets alias write index policy for controlling how writeIndex attribute is handled during restore
*
* @param policy the policy to apply
* @return this request
*/
public RestoreSnapshotRequest aliasWriteIndexPolicy(AliasWriteIndexPolicy policy) {
this.aliasWriteIndexPolicy = Objects.requireNonNull(policy);
return this;
}

/**
* Returns alias write index policy
*
* @return alias write index policy
*/
public AliasWriteIndexPolicy aliasWriteIndexPolicy() {
return aliasWriteIndexPolicy;
}

/**
* Sets suffix to append to alias names when using CUSTOM_SUFFIX policy
*
* @param suffix the suffix to append
* @return this request
*/
public RestoreSnapshotRequest aliasSuffix(@Nullable String suffix) {
this.aliasSuffix = suffix;
return this;
}

/**
* Returns alias suffix
*
* @return alias suffix
*/
@Nullable
public String aliasSuffix() {
return aliasSuffix;
}

/**
* Parses restore definition
*
Expand Down Expand Up @@ -729,6 +805,10 @@ public RestoreSnapshotRequest source(Map<String, Object> source) {
} else {
throw new IllegalArgumentException("malformed source_remote_translog_repository");
}
} else if ("alias_write_index_policy".equals(name)) {
aliasWriteIndexPolicy(AliasWriteIndexPolicy.fromString((String) entry.getValue()));
} else if ("alias_suffix".equals(name)) {
aliasSuffix((String) entry.getValue());
} else {
if (IndicesOptions.isIndicesOptions(name) == false) {
throw new IllegalArgumentException("Unknown parameter " + name);
Expand Down Expand Up @@ -786,6 +866,10 @@ public XContentBuilder toXContent(XContentBuilder builder, Params params) throws
if (sourceRemoteTranslogRepository != null) {
builder.field("source_remote_translog_repository", sourceRemoteTranslogRepository);
}
builder.field("alias_write_index_policy", aliasWriteIndexPolicy.name().toLowerCase(Locale.ROOT));
if (aliasSuffix != null) {
builder.field("alias_suffix", aliasSuffix);
}
builder.endObject();
return builder;
}
Expand Down Expand Up @@ -817,7 +901,9 @@ public boolean equals(Object o) {
&& Objects.equals(snapshotUuid, that.snapshotUuid)
&& Objects.equals(storageType, that.storageType)
&& Objects.equals(sourceRemoteStoreRepository, that.sourceRemoteStoreRepository)
&& Objects.equals(sourceRemoteTranslogRepository, that.sourceRemoteTranslogRepository);
&& Objects.equals(sourceRemoteTranslogRepository, that.sourceRemoteTranslogRepository)
&& aliasWriteIndexPolicy == that.aliasWriteIndexPolicy
&& Objects.equals(aliasSuffix, that.aliasSuffix);
return equals;
}

Expand All @@ -840,7 +926,9 @@ public int hashCode() {
snapshotUuid,
storageType,
sourceRemoteStoreRepository,
sourceRemoteTranslogRepository
sourceRemoteTranslogRepository,
aliasWriteIndexPolicy,
aliasSuffix
);
result = 31 * result + Arrays.hashCode(indices);
result = 31 * result + Arrays.hashCode(ignoreIndexSettings);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
import org.opensearch.cluster.routing.allocation.AllocationService;
import org.opensearch.cluster.service.ClusterManagerTaskThrottler;
import org.opensearch.cluster.service.ClusterService;
import org.opensearch.common.Nullable;
import org.opensearch.common.Priority;
import org.opensearch.common.UUIDs;
import org.opensearch.common.lucene.Lucene;
Expand Down Expand Up @@ -479,7 +480,7 @@ public ClusterState execute(ClusterState currentState) {
// Remove all aliases - they shouldn't be restored
indexMdBuilder.removeAllAliases();
} else {
applyAliasesWithRename(snapshotIndexMetadata, indexMdBuilder, aliases);
applyAliasesWithRename(snapshotIndexMetadata.getAliases(), request, indexMdBuilder, aliases);
}
IndexMetadata updatedIndexMetadata = indexMdBuilder.build();
if (partial) {
Expand Down Expand Up @@ -524,7 +525,7 @@ public ClusterState execute(ClusterState currentState) {
indexMdBuilder.putAlias(alias);
}
} else {
applyAliasesWithRename(snapshotIndexMetadata, indexMdBuilder, aliases);
applyAliasesWithRename(snapshotIndexMetadata.getAliases(), request, indexMdBuilder, aliases);
}
final Settings.Builder indexSettingsBuilder = Settings.builder()
.put(snapshotIndexMetadata.getSettings())
Expand Down Expand Up @@ -655,22 +656,37 @@ private void checkAliasNameConflicts(Map<String, String> renamedIndices, Set<Str
}

private void applyAliasesWithRename(
IndexMetadata snapshotIndexMetadata,
Map<String, AliasMetadata> snapshotAliases,
RestoreSnapshotRequest request,
IndexMetadata.Builder indexMdBuilder,
Set<String> aliases
) {
if (request.renameAliasPattern() == null || request.renameAliasReplacement() == null) {
aliases.addAll(snapshotIndexMetadata.getAliases().keySet());
for (final Map.Entry<String, AliasMetadata> alias : snapshotAliases.entrySet()) {
AliasMetadata transformedAlias = applyAliasWriteIndexPolicy(
alias.getValue(),
request.aliasWriteIndexPolicy(),
request.aliasSuffix()
);
indexMdBuilder.removeAlias(alias.getKey());
indexMdBuilder.putAlias(transformedAlias);
aliases.add(transformedAlias.alias());
}
} else {
Pattern renameAliasPattern = Pattern.compile(request.renameAliasPattern());
for (final Map.Entry<String, AliasMetadata> alias : snapshotIndexMetadata.getAliases().entrySet()) {
for (final Map.Entry<String, AliasMetadata> alias : snapshotAliases.entrySet()) {
String currentAliasName = alias.getKey();
indexMdBuilder.removeAlias(currentAliasName);
String newAliasName = renameAliasPattern.matcher(currentAliasName)
.replaceAll(request.renameAliasReplacement());
AliasMetadata newAlias = AliasMetadata.newAliasMetadata(alias.getValue(), newAliasName);
indexMdBuilder.putAlias(newAlias);
aliases.add(newAliasName);
AliasMetadata renamedAlias = AliasMetadata.newAliasMetadata(alias.getValue(), newAliasName);
AliasMetadata transformedAlias = applyAliasWriteIndexPolicy(
renamedAlias,
request.aliasWriteIndexPolicy(),
request.aliasSuffix()
);
indexMdBuilder.putAlias(transformedAlias);
aliases.add(transformedAlias.alias());
}
}
}
Expand Down Expand Up @@ -1369,6 +1385,46 @@ public void applyClusterState(ClusterChangedEvent event) {
}
}

/**
* Apply alias write index policy to transform alias metadata during restore.
* Package-private for testing.
*/
static AliasMetadata applyAliasWriteIndexPolicy(
AliasMetadata aliasMd,
RestoreSnapshotRequest.AliasWriteIndexPolicy policy,
@Nullable String suffix
) {
switch (policy) {
case STRIP_WRITE_INDEX:
if (Boolean.TRUE.equals(aliasMd.writeIndex())) {
return AliasMetadata.builder(aliasMd.alias())
.filter(aliasMd.filter())
.indexRouting(aliasMd.indexRouting())
.searchRouting(aliasMd.searchRouting())
.isHidden(aliasMd.isHidden())
.writeIndex(false)
.build();
}
return aliasMd;

case CUSTOM_SUFFIX:
if (suffix == null || suffix.isEmpty()) {
suffix = "_follower";
}
return AliasMetadata.builder(aliasMd.alias() + suffix)
.filter(aliasMd.filter())
.indexRouting(aliasMd.indexRouting())
.searchRouting(aliasMd.searchRouting())
.isHidden(aliasMd.isHidden())
.writeIndex(false)
.build();

case PRESERVE:
default:
return aliasMd;
}
}

private static IndexMetadata addSnapshotToIndexSettings(IndexMetadata metadata, Snapshot snapshot, IndexId indexId) {
final Settings newSettings = Settings.builder()
.put(metadata.getSettings())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/*
* SPDX-License-Identifier: Apache-2.0
*
* The OpenSearch Contributors require contributions made to
* this file be licensed under the Apache-2.0 license or a
* compatible open source license.
*/

package org.opensearch.action.admin.cluster.snapshots.restore;

import org.opensearch.common.io.stream.BytesStreamOutput;
import org.opensearch.core.common.io.stream.StreamInput;
import org.opensearch.core.xcontent.ToXContent;
import org.opensearch.core.xcontent.XContentBuilder;
import org.opensearch.core.xcontent.XContentParser;
import org.opensearch.test.OpenSearchTestCase;

import java.io.IOException;
import java.util.Map;

import static org.opensearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.hamcrest.Matchers.equalTo;

public class AliasWriteIndexPolicyRequestTests extends OpenSearchTestCase {

public void testEqualsAndHashCode() {
RestoreSnapshotRequest request1 = new RestoreSnapshotRequest("repo", "snapshot").indices("index1", "index2")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.STRIP_WRITE_INDEX)
.aliasSuffix("_backup");

RestoreSnapshotRequest request2 = new RestoreSnapshotRequest("repo", "snapshot").indices("index1", "index2")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.STRIP_WRITE_INDEX)
.aliasSuffix("_backup");

assertEquals(request1, request2);
assertEquals(request1.hashCode(), request2.hashCode());

// Test with different policy
RestoreSnapshotRequest request3 = new RestoreSnapshotRequest("repo", "snapshot").indices("index1", "index2")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.CUSTOM_SUFFIX)
.aliasSuffix("_backup");

assertNotEquals(request1, request3);

// Test with different suffix
RestoreSnapshotRequest request4 = new RestoreSnapshotRequest("repo", "snapshot").indices("index1", "index2")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.STRIP_WRITE_INDEX)
.aliasSuffix("_different");

assertNotEquals(request1, request4);
}

public void testSerialization() throws IOException {
RestoreSnapshotRequest request = new RestoreSnapshotRequest("test-repo", "test-snapshot").indices("index1", "index2")
.renamePattern("(.+)")
.renameReplacement("restored-$1")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.CUSTOM_SUFFIX)
.aliasSuffix("_restored")
.includeAliases(true)
.partial(true)
.waitForCompletion(false);

BytesStreamOutput out = new BytesStreamOutput();
request.writeTo(out);

StreamInput in = out.bytes().streamInput();
RestoreSnapshotRequest deserialized = new RestoreSnapshotRequest(in);

assertEquals(request.repository(), deserialized.repository());
assertEquals(request.snapshot(), deserialized.snapshot());
assertArrayEquals(request.indices(), deserialized.indices());
assertEquals(request.aliasWriteIndexPolicy(), deserialized.aliasWriteIndexPolicy());
assertEquals(request.aliasSuffix(), deserialized.aliasSuffix());
assertEquals(request.includeAliases(), deserialized.includeAliases());
}

public void testXContentRoundTrip() throws IOException {
RestoreSnapshotRequest request = new RestoreSnapshotRequest("test-repo", "test-snapshot").indices("index1", "index2")
.aliasWriteIndexPolicy(RestoreSnapshotRequest.AliasWriteIndexPolicy.STRIP_WRITE_INDEX)
.aliasSuffix("_test")
.includeAliases(true)
.includeGlobalState(false)
.partial(true);

// Convert to XContent
XContentBuilder builder = jsonBuilder();
request.toXContent(builder, ToXContent.EMPTY_PARAMS);

// Parse from XContent
XContentParser parser = createParser(builder);
Map<String, Object> source = parser.map();
RestoreSnapshotRequest parsed = new RestoreSnapshotRequest().source(source);

// Verify key fields
assertArrayEquals(request.indices(), parsed.indices());
assertEquals(request.aliasWriteIndexPolicy(), parsed.aliasWriteIndexPolicy());
assertEquals(request.aliasSuffix(), parsed.aliasSuffix());
assertEquals(request.includeAliases(), parsed.includeAliases());
assertEquals(request.includeGlobalState(), parsed.includeGlobalState());
assertEquals(request.partial(), parsed.partial());
}

public void testPolicyFromString() {
assertEquals(
RestoreSnapshotRequest.AliasWriteIndexPolicy.PRESERVE,
RestoreSnapshotRequest.AliasWriteIndexPolicy.fromString("preserve")
);
assertEquals(
RestoreSnapshotRequest.AliasWriteIndexPolicy.STRIP_WRITE_INDEX,
RestoreSnapshotRequest.AliasWriteIndexPolicy.fromString("strip_write_index")
);
assertEquals(
RestoreSnapshotRequest.AliasWriteIndexPolicy.CUSTOM_SUFFIX,
RestoreSnapshotRequest.AliasWriteIndexPolicy.fromString("CUSTOM_SUFFIX")
);

expectThrows(IllegalArgumentException.class, () -> RestoreSnapshotRequest.AliasWriteIndexPolicy.fromString("invalid"));
}

public void testDefaultValues() {
RestoreSnapshotRequest request = new RestoreSnapshotRequest();
assertThat(request.aliasWriteIndexPolicy(), equalTo(RestoreSnapshotRequest.AliasWriteIndexPolicy.PRESERVE));
assertNull(request.aliasSuffix());
}
}
Loading
Loading