Skip to content
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

Searchable snapshot rolling restart #66369

Merged
Show file tree
Hide file tree
Changes from 1 commit
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
@@ -0,0 +1,120 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.searchablesnapshots;

import org.elasticsearch.action.admin.cluster.health.ClusterHealthResponse;
import org.elasticsearch.client.Requests;
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.unit.ByteSizeUnit;
import org.elasticsearch.common.unit.ByteSizeValue;
import org.elasticsearch.plugins.Plugin;
import org.elasticsearch.snapshots.SnapshotId;
import org.elasticsearch.snapshots.mockstore.MockRepository;
import org.elasticsearch.test.ESIntegTestCase;
import org.elasticsearch.xpack.searchablesnapshots.cache.CacheService;
import org.hamcrest.Matchers;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import static org.elasticsearch.test.hamcrest.ElasticsearchAssertions.assertAcked;

@ESIntegTestCase.ClusterScope(scope = ESIntegTestCase.Scope.TEST)
public class SearchableSnapshotEnableAllocationDeciderIntegTests extends BaseSearchableSnapshotsIntegTestCase {

@Override
protected Collection<Class<? extends Plugin>> nodePlugins() {
List<Class<? extends Plugin>> plugins = new ArrayList<>(super.nodePlugins());
plugins.add(MockRepository.Plugin.class);
return plugins;
}

@Override
protected Settings nodeSettings(int nodeOrdinal) {
return Settings.builder()
.put(super.nodeSettings(nodeOrdinal))
// Use an unbound cache so we can recover the searchable snapshot completely all the times
.put(CacheService.SNAPSHOT_CACHE_SIZE_SETTING.getKey(), new ByteSizeValue(Long.MAX_VALUE, ByteSizeUnit.BYTES))
.build();
}

public void testAllocationDisabled() throws Exception {
final String restoredIndexName = setupMountedIndex();
int numPrimaries = getNumShards(restoredIndexName).numPrimaries;
setEnableAllocation(EnableAllocationDecider.Allocation.PRIMARIES);
if (randomBoolean()) {
setSearchableSnapshotPrimariesAllocation(EnableAllocationDecider.Allocation.NONE);
}
Set<String> indexNodes = internalCluster().nodesInclude(restoredIndexName);
for (String indexNode : indexNodes) {
internalCluster().restartNode(indexNode);
}

assertBusy(() -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we don't need to assertBusy() here, if this fails even once then that's a fail.

ClusterHealthResponse response =
client().admin().cluster().health(Requests.clusterHealthRequest(restoredIndexName)).actionGet();
assertThat(response.getUnassignedShards(), Matchers.equalTo(numPrimaries));
});

setSearchableSnapshotPrimariesAllocation(EnableAllocationDecider.Allocation.PRIMARIES);
ensureGreen(restoredIndexName);
}

public void testAllocationEnabled() throws Exception {
final String restoredIndexName = setupMountedIndex();
if (randomBoolean()) {
setEnableAllocation(EnableAllocationDecider.Allocation.PRIMARIES);
}
setSearchableSnapshotPrimariesAllocation(EnableAllocationDecider.Allocation.PRIMARIES);
Set<String> indexNodes = internalCluster().nodesInclude(restoredIndexName);
for (String indexNode : indexNodes) {
internalCluster().restartNode(indexNode);
}

ensureGreen(restoredIndexName);
}

private String setupMountedIndex() throws Exception {
final String indexName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
createAndPopulateIndex(indexName, Settings.builder());

final String repositoryName = randomAlphaOfLength(10).toLowerCase(Locale.ROOT);
createRepository(repositoryName, "mock");

final SnapshotId snapshotId = createSnapshot(repositoryName, "snapshot-1", List.of(indexName)).snapshotId();
assertAcked(client().admin().indices().prepareDelete(indexName));
return mountSnapshot(repositoryName, snapshotId.getName(), indexName, Settings.EMPTY);
}

public void setEnableAllocation(EnableAllocationDecider.Allocation allocation) {
setAllocation(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING, allocation);
}

public void setSearchableSnapshotPrimariesAllocation(EnableAllocationDecider.Allocation allocation) {
setAllocation(SearchableSnapshotEnableAllocationDecider.SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING, allocation);
}

private void setAllocation(Setting<EnableAllocationDecider.Allocation> setting, EnableAllocationDecider.Allocation allocation) {
logger.info("--> setting allocation to [{}]", allocation);
assertAcked(
client().admin()
.cluster()
.prepareUpdateSettings()
.setPersistentSettings(
Settings.builder()
.put(setting.getKey(), allocation.name())
.build()
)
.get()
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License;
* you may not use this file except in compliance with the Elastic License.
*/

package org.elasticsearch.xpack.searchablesnapshots;

import org.elasticsearch.cluster.metadata.IndexMetadata;
import org.elasticsearch.cluster.routing.RecoverySource;
import org.elasticsearch.cluster.routing.RoutingNode;
import org.elasticsearch.cluster.routing.ShardRouting;
import org.elasticsearch.cluster.routing.allocation.RoutingAllocation;
import org.elasticsearch.cluster.routing.allocation.decider.AllocationDecider;
import org.elasticsearch.cluster.routing.allocation.decider.Decision;
import org.elasticsearch.cluster.routing.allocation.decider.EnableAllocationDecider;
import org.elasticsearch.common.settings.ClusterSettings;
import org.elasticsearch.common.settings.Setting;
import org.elasticsearch.common.settings.Settings;

public class SearchableSnapshotEnableAllocationDecider extends AllocationDecider {

static final String NAME = "searchable_snapshots_enable";

/**
* This setting indicates the behavior of searchable snapshots when cluster.routing.allocation.enable=primaries
*
*/
public static final Setting<EnableAllocationDecider.Allocation> SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING =
new Setting<>("xpack.searchable.snapshot.allocation.enable.primaries", EnableAllocationDecider.Allocation.NONE.toString(),
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm this means we have these options:

xpack.searchable.snapshot.allocation.enable.primaries: none
xpack.searchable.snapshot.allocation.enable.primaries: primaries

The repetition of primaries seems odd to me. I see that this is a bit nicer than a straight boolean, given the relationship to the other allocation decider, but how about these options instead?

xpack.searchable.snapshot.allocation.enable: none
xpack.searchable.snapshot.allocation.enable: all

Copy link
Contributor

Choose a reason for hiding this comment

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

I misunderstood the precise meaning of this setting, sorry, it defaults to none!

We discussed this in another channel and decided a boolean called xpack.searchable.snapshot.allocate_on_rolling_restart which defaults to false.

SearchableSnapshotEnableAllocationDecider::parseSetting,
Setting.Property.Dynamic, Setting.Property.NodeScope);

private static EnableAllocationDecider.Allocation parseSetting(String value) {
EnableAllocationDecider.Allocation allocation = EnableAllocationDecider.Allocation.parse(value);
if (allocation == EnableAllocationDecider.Allocation.ALL || allocation == EnableAllocationDecider.Allocation.NEW_PRIMARIES) {
throw new IllegalArgumentException("[" + SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING.getKey() + "=" + allocation
+ "] is not valid");
}
return allocation;
}

private volatile EnableAllocationDecider.Allocation enableAllocation;
private volatile EnableAllocationDecider.Allocation primariesAllocation;

public SearchableSnapshotEnableAllocationDecider(Settings settings, ClusterSettings clusterSettings) {
this.enableAllocation = EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.get(settings);
this.primariesAllocation = SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING.get(settings);
assertSettingValue();
clusterSettings.addSettingsUpdateConsumer(EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING,
this::setEnableAllocation);
clusterSettings.addSettingsUpdateConsumer(SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING,
this::setPrimariesAllocation);
}

public void assertSettingValue() {
assert this.primariesAllocation != EnableAllocationDecider.Allocation.ALL && this.primariesAllocation != EnableAllocationDecider.Allocation.NEW_PRIMARIES;
}

private void setPrimariesAllocation(EnableAllocationDecider.Allocation allocation) {
this.primariesAllocation = allocation;
assertSettingValue();
}

private void setEnableAllocation(EnableAllocationDecider.Allocation allocation) {
this.enableAllocation = allocation;
}

@Override
public Decision canAllocate(ShardRouting shardRouting, RoutingNode node, RoutingAllocation allocation) {
return canAllocate(shardRouting, allocation);
}

@Override
public Decision canAllocate(ShardRouting shardRouting, RoutingAllocation allocation) {
final IndexMetadata indexMetadata = allocation.metadata().getIndexSafe(shardRouting.index());
if (SearchableSnapshotsConstants.isSearchableSnapshotStore(indexMetadata.getSettings())) {
EnableAllocationDecider.Allocation enableAllocation = this.enableAllocation;
EnableAllocationDecider.Allocation primariesAllocation = this.primariesAllocation;
if (enableAllocation == EnableAllocationDecider.Allocation.PRIMARIES) {
if (primariesAllocation == EnableAllocationDecider.Allocation.NONE) {
return allocation.decision(Decision.NO, NAME,
"no allocations of searchable snapshots allowed due to [%s=%s] and [%s=%s]",
EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey(),
enableAllocation,
SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING.getKey(),
primariesAllocation
);
} else {
return allocation.decision(Decision.YES, NAME, "decider relies on generic enable decider");
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this message is wrong, the decider return YES here because xpack.searchable.snapshot.allocation.enable.primaries: primaries permits it.

}
} else {
return allocation.decision(Decision.YES, NAME, "decider only active when [%s=primaries]",
EnableAllocationDecider.CLUSTER_ROUTING_ALLOCATION_ENABLE_SETTING.getKey());
}
} else {
return allocation.decision(Decision.YES, NAME, "decider only applicable for indices backed by searchable snapshots");
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,8 @@ public List<Setting<?>> getSettings() {
CacheService.SNAPSHOT_CACHE_RANGE_SIZE_SETTING,
CacheService.SNAPSHOT_CACHE_SYNC_INTERVAL_SETTING,
CacheService.SNAPSHOT_CACHE_MAX_FILES_TO_SYNC_AT_ONCE_SETTING,
CacheService.SNAPSHOT_CACHE_SYNC_SHUTDOWN_TIMEOUT
CacheService.SNAPSHOT_CACHE_SYNC_SHUTDOWN_TIMEOUT,
SearchableSnapshotEnableAllocationDecider.SEARCHABLE_SNAPSHOTS_ALLOCATION_ENABLE_PRIMARIES_SETTING
);
}

Expand Down Expand Up @@ -334,7 +335,8 @@ protected XPackLicenseState getLicenseState() {
@Override
public Collection<AllocationDecider> createAllocationDeciders(Settings settings, ClusterSettings clusterSettings) {
return List.of(
new SearchableSnapshotAllocationDecider(() -> getLicenseState().isAllowed(XPackLicenseState.Feature.SEARCHABLE_SNAPSHOTS))
new SearchableSnapshotAllocationDecider(() -> getLicenseState().isAllowed(XPackLicenseState.Feature.SEARCHABLE_SNAPSHOTS)),
new SearchableSnapshotEnableAllocationDecider(settings, clusterSettings)
);
}

Expand Down