Replace global checkpoint sync test

This commit replaces the REST test that the global checkpoint sync
action runs successfully as a privileged user. The test needs to be
replaced because it has a small race condition. Namely, the check that
the post-operation global checkpoint sync was successful could run
before the sync finishes running. To address this, we replace the REST
test with a test where we have a little more control and can assert busy
to avoid this race from failing the test.

Relates elastic/x-pack-elasticsearch#2749

Original commit: elastic/x-pack-elasticsearch@ea585b843c
This commit is contained in:
Jason Tedor 2017-10-13 10:05:59 -04:00 committed by GitHub
parent a6776cef97
commit c35efb7adf
4 changed files with 100 additions and 88 deletions

View File

@ -1,17 +1,11 @@
import org.elasticsearch.gradle.test.RestIntegTestTask apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'
apply plugin: 'elasticsearch.standalone-test'
dependencies { dependencies {
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime') testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'runtime')
testCompile project(path: ':x-pack-elasticsearch:plugin', configuration: 'testArtifacts')
} }
task multiNodeTest(type: RestIntegTestTask) { integTestCluster {
mustRunAfter(precommit)
}
multiNodeTestCluster {
distribution = 'zip' distribution = 'zip'
numNodes = 2 numNodes = 2
clusterName = 'multi-node' clusterName = 'multi-node'
@ -33,6 +27,3 @@ multiNodeTestCluster {
return tmpFile.exists() return tmpFile.exists()
} }
} }
test.enabled = false // no unit tests for multi-node, only the rest integration test
check.dependsOn(multiNodeTest)

View File

@ -0,0 +1,97 @@
/*
* 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.multi_node;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.test.rest.ESRestTestCase;
import org.elasticsearch.test.rest.yaml.ObjectPath;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.elasticsearch.common.xcontent.XContentFactory.jsonBuilder;
import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
import static org.hamcrest.Matchers.equalTo;
public class GlobalCheckpointSyncActionIT extends ESRestTestCase {
@Override
protected Settings restClientSettings() {
return getClientSettings("test-user", "x-pack-test-password");
}
@Override
protected Settings restAdminSettings() {
return getClientSettings("super-user", "x-pack-super-password");
}
private Settings getClientSettings(final String username, final String password) {
final String token = basicAuthHeaderValue(username, new SecureString(password.toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}
/*
* The post-operation global checkpoint sync runs privileged as the system user otherwise the sync would be denied to restricted users.
* This test ensures that these post-operation syncs are successful otherwise the global checkpoint would not have advanced on the
* replica.
*/
public void testGlobalCheckpointSyncActionRunsAsPrivilegedUser() throws Exception {
// create the test-index index
try (XContentBuilder builder = jsonBuilder()) {
builder.startObject();
{
builder.startObject("settings");
{
builder.field("index.number_of_shards", 1);
builder.field("index.number_of_replicas", 1);
}
builder.endObject();
}
builder.endObject();
final StringEntity entity = new StringEntity(builder.string(), ContentType.APPLICATION_JSON);
client().performRequest("PUT", "test-index", Collections.emptyMap(), entity);
}
// wait for the replica to recover
client().performRequest("GET", "/_cluster/health", Collections.singletonMap("wait_for_status", "green"));
// index some documents
final int numberOfDocuments = randomIntBetween(0, 128);
for (int i = 0; i < numberOfDocuments; i++) {
try (XContentBuilder builder = jsonBuilder()) {
builder.startObject();
{
builder.field("foo", i);
}
builder.endObject();
final StringEntity entity = new StringEntity(builder.string(), ContentType.APPLICATION_JSON);
client().performRequest("PUT", "/test-index/test-type/" + i, Collections.emptyMap(), entity);
}
}
// we have to wait for the post-operation global checkpoint sync to propagate to the replica
assertBusy(() -> {
final Map<String, String> params = new HashMap<>(2);
params.put("level", "shards");
params.put("filter_path", "**.seq_no");
final Response response = client().performRequest("GET", "/test-index/_stats", params);
final ObjectPath path = ObjectPath.createFromResponse(response);
// int looks funny here since global checkpoints are longs but the response parser does not know enough to treat them as long
final int shard0GlobalCheckpoint = path.evaluate("indices.test-index.shards.0.0.seq_no.global_checkpoint");
assertThat(shard0GlobalCheckpoint, equalTo(numberOfDocuments - 1));
final int shard1GlobalCheckpoint = path.evaluate("indices.test-index.shards.0.1.seq_no.global_checkpoint");
assertThat(shard1GlobalCheckpoint, equalTo(numberOfDocuments - 1));
});
}
}

View File

@ -1,45 +0,0 @@
/*
* 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.multi_node;
import com.carrotsearch.randomizedtesting.annotations.Name;
import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
import org.elasticsearch.xpack.security.SecurityClusterClientYamlTestCase;
import static org.elasticsearch.xpack.security.authc.support.UsernamePasswordToken.basicAuthHeaderValue;
public class MultiNodeIT extends SecurityClusterClientYamlTestCase {
public MultiNodeIT(@Name("yaml") final ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}
@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
return ESClientYamlSuiteTestCase.createParameters();
}
@Override
protected Settings restClientSettings() {
return getClientSettings("test-user", "x-pack-test-password");
}
@Override
protected Settings restAdminSettings() {
return getClientSettings("super-user", "x-pack-super-password");
}
private Settings getClientSettings(final String username, final String password) {
final String token = basicAuthHeaderValue(username, new SecureString(password.toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}
}

View File

@ -1,31 +0,0 @@
---
"Global checkpoint sync action runs as a privileged user":
- do:
indices.create:
index: test-index
body:
settings:
index:
number_of_shards: 1
number_of_replicas: 1
- do:
cluster.health:
wait_for_status: green
- do:
index:
index: test-index
type: test-type
id: 1
body: { foo: bar }
- do:
indices.stats:
index: test-index
level: shards
filter_path: "**.seq_no"
- match: { indices.test-index.shards.0.0.seq_no.global_checkpoint: 0 }
- match: { indices.test-index.shards.0.1.seq_no.global_checkpoint: 0 }