Skip to content

Introduce repository test kit/analyser #67247

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
5ddd2db
Introduce repository test kit with speed test
DaveCTurner Jan 11, 2021
6dc193c
Include blob-level request in more failure messages
DaveCTurner Jan 12, 2021
5f48376
Everyone loves a sequence diagram
DaveCTurner Jan 12, 2021
6a9aea7
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Jan 15, 2021
d96d5ad
Apparently precommit doesn't like a sequence diagram
DaveCTurner Jan 15, 2021
b744d9e
Add detailed parameter
DaveCTurner Jan 15, 2021
0e904d8
Collect stats
DaveCTurner Jan 15, 2021
e32a377
Rename to summary
DaveCTurner Jan 15, 2021
c237aa5
Fix RandomBlobContentBytesReference
DaveCTurner Jan 15, 2021
36a6e52
Fixes
DaveCTurner Jan 15, 2021
280f982
Mark speed test actions as operator-only
DaveCTurner Jan 15, 2021
64c220d
Report blob-level request on all failures
DaveCTurner Jan 15, 2021
29f5d50
Reroute to arbitrary snapshot node, not the master
DaveCTurner Jan 15, 2021
75705ec
Add TODOs so the github comments don't get lost
DaveCTurner Jan 15, 2021
7180045
Expose magic read-node-count parameters
DaveCTurner Jan 16, 2021
0b78fe4
Remove another magic number
DaveCTurner Jan 16, 2021
40e7e74
Document defaults
DaveCTurner Jan 16, 2021
8db0096
Record max read waiting time
DaveCTurner Jan 16, 2021
24d7392
Document response format
DaveCTurner Jan 16, 2021
2f3094b
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Feb 5, 2021
cbe503b
License headers
DaveCTurner Feb 5, 2021
fbb0ed8
Rename speed test -> analysis
DaveCTurner Feb 5, 2021
32169f4
Implementation details etc
DaveCTurner Feb 5, 2021
452968a
Add TODO for writeBlobRandomly
DaveCTurner Feb 5, 2021
6076d34
Add TODO for delete verification
DaveCTurner Feb 5, 2021
a50c0d1
Add TODO to remove cleanup retries
DaveCTurner Feb 5, 2021
4b6d53b
Add plumbing for max_total_data_size
DaveCTurner Feb 5, 2021
2263d9f
Skip verification, it doesn't work
DaveCTurner Feb 5, 2021
6662e1c
prefer US spelling
DaveCTurner Feb 5, 2021
7b3dabe
Add bugs_detected response field
DaveCTurner Feb 5, 2021
9ecfe46
Respects max total size
DaveCTurner Feb 5, 2021
c4c3cfa
Fix action name
DaveCTurner Feb 5, 2021
d433948
Better tests, corresponding with docs
DaveCTurner Feb 5, 2021
bff99a0
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Feb 8, 2021
59ff6ec
No need for retries when listing
DaveCTurner Feb 8, 2021
b200616
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Feb 15, 2021
28b488b
Add integ tests for failure and success cases
DaveCTurner Feb 15, 2021
b383325
Unrelated change
DaveCTurner Feb 15, 2021
bda3abc
Permit listing a nonexistent FsBlobContainer
DaveCTurner Feb 15, 2021
622fe24
No-action TODOs
DaveCTurner Feb 15, 2021
a5ee4ca
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Feb 15, 2021
032a3a6
Merge branch 'master' into 2021-01-11-repository-speed-test-WIP
DaveCTurner Feb 16, 2021
4cae510
Docs improvements
DaveCTurner Feb 16, 2021
12ccaba
ActionRunnable just to be sure that exceptions are caught
DaveCTurner Feb 16, 2021
6308ccc
Human-readability
DaveCTurner Feb 16, 2021
f10e51c
Javadoc
DaveCTurner Feb 16, 2021
ffee1a7
Close input stream used for reading
DaveCTurner Feb 16, 2021
b3c6bee
TODO also non-powers-of-two
DaveCTurner Feb 16, 2021
d0887fa
Clearer comment
DaveCTurner Feb 16, 2021
505d8f0
Merge branch 'master' into 2021-01-11-repository-speed-test
DaveCTurner Feb 16, 2021
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
479 changes: 479 additions & 0 deletions docs/reference/snapshot-restore/apis/repo-analysis-api.asciidoc

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ For more information, see <<snapshot-restore>>.

include::put-repo-api.asciidoc[]
include::verify-repo-api.asciidoc[]
include::repo-analysis-api.asciidoc[]
include::get-repo-api.asciidoc[]
include::delete-repo-api.asciidoc[]
include::clean-up-repo-api.asciidoc[]
Expand Down
3 changes: 3 additions & 0 deletions docs/reference/snapshot-restore/register-repository.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,9 @@ POST /_snapshot/my_unverified_backup/_verify

It returns a list of nodes where repository was successfully verified or an error message if verification process failed.

If desired, you can also test a repository more thoroughly using the
<<repo-analysis-api,Repository analysis API>>.

[discrete]
[[snapshots-repository-cleanup]]
=== Repository cleanup
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
Expand Down Expand Up @@ -88,7 +89,7 @@ public Map<String, BlobMetadata> listBlobsByPrefix(String blobNamePrefix) throws
Map<String, BlobMetadata> builder = new HashMap<>();

blobNamePrefix = blobNamePrefix == null ? "" : blobNamePrefix;
try (DirectoryStream<Path> stream = Files.newDirectoryStream(path, blobNamePrefix + "*")) {
try (DirectoryStream<Path> stream = newDirectoryStreamIfFound(blobNamePrefix)) {
for (Path file : stream) {
final BasicFileAttributes attrs;
try {
Expand All @@ -105,6 +106,34 @@ public Map<String, BlobMetadata> listBlobsByPrefix(String blobNamePrefix) throws
return unmodifiableMap(builder);
}

private DirectoryStream<Path> newDirectoryStreamIfFound(String blobNamePrefix) throws IOException {
try {
return Files.newDirectoryStream(path, blobNamePrefix + "*");
} catch (FileNotFoundException | NoSuchFileException e) {
// a nonexistent directory contains no blobs
return new DirectoryStream<>() {
@Override
public Iterator<Path> iterator() {
return new Iterator<>() {
@Override
public boolean hasNext() {
return false;
}

@Override
public Path next() {
return null;
}
};
}

@Override
public void close() {
}
};
}
}

@Override
public DeleteResult delete() throws IOException {
final AtomicLong filesDeleted = new AtomicLong(0L);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,7 +380,7 @@ protected void doRun() {
});
}

static boolean isDedicatedVotingOnlyNode(Set<DiscoveryNodeRole> roles) {
public static boolean isDedicatedVotingOnlyNode(Set<DiscoveryNodeRole> roles) {
return roles.contains(DiscoveryNodeRole.MASTER_ROLE) && roles.contains(DiscoveryNodeRole.DATA_ROLE) == false &&
roles.stream().anyMatch(role -> role.roleName().equals("voting_only"));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,11 @@ public RestStatus status() {
public RepositoryVerificationException(StreamInput in) throws IOException{
super(in);
}

@Override
public synchronized Throwable fillInStackTrace() {
// stack trace for a verification failure is uninteresting, the message has all the information we need
return this;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -2471,17 +2471,47 @@ private static ActionListener<Void> fileQueueListener(BlockingQueue<BlobStoreInd
});
}

private static InputStream maybeRateLimit(InputStream stream, Supplier<RateLimiter> rateLimiterSupplier, CounterMetric metric) {
return new RateLimitingInputStream(stream, rateLimiterSupplier, metric::inc);
private static InputStream maybeRateLimit(
InputStream stream,
Supplier<RateLimiter> rateLimiterSupplier,
RateLimitingInputStream.Listener throttleListener) {
return new RateLimitingInputStream(stream, rateLimiterSupplier, throttleListener);
}

/**
* Wrap the restore rate limiter (controlled by the repository setting `max_restore_bytes_per_sec` and the cluster setting
* `indices.recovery.max_bytes_per_sec`) around the given stream. Any throttling is reported to the given listener and not otherwise
* recorded in the value returned by {@link BlobStoreRepository#getRestoreThrottleTimeInNanos}.
*/
public InputStream maybeRateLimitRestores(InputStream stream) {
return maybeRateLimit(maybeRateLimit(stream, () -> restoreRateLimiter, restoreRateLimitingTimeInNanos),
recoverySettings::rateLimiter, restoreRateLimitingTimeInNanos);
return maybeRateLimitRestores(stream, restoreRateLimitingTimeInNanos::inc);
}

/**
* Wrap the restore rate limiter (controlled by the repository setting `max_restore_bytes_per_sec` and the cluster setting
* `indices.recovery.max_bytes_per_sec`) around the given stream. Any throttling is recorded in the value returned by {@link
* BlobStoreRepository#getRestoreThrottleTimeInNanos}.
*/
public InputStream maybeRateLimitRestores(InputStream stream, RateLimitingInputStream.Listener throttleListener) {
return maybeRateLimit(maybeRateLimit(stream, () -> restoreRateLimiter, throttleListener),
recoverySettings::rateLimiter, throttleListener);
}

/**
* Wrap the snapshot rate limiter (controlled by the repository setting `max_snapshot_bytes_per_sec`) around the given stream. Any
* throttling is recorded in the value returned by {@link BlobStoreRepository#getSnapshotThrottleTimeInNanos()}.
*/
public InputStream maybeRateLimitSnapshots(InputStream stream) {
return maybeRateLimit(stream, () -> snapshotRateLimiter, snapshotRateLimitingTimeInNanos);
return maybeRateLimitSnapshots(stream, snapshotRateLimitingTimeInNanos::inc);
}

/**
* Wrap the snapshot rate limiter (controlled by the repository setting `max_snapshot_bytes_per_sec`) around the given stream. Any
* throttling is reported to the given listener and not otherwise recorded in the value returned by {@link
* BlobStoreRepository#getSnapshotThrottleTimeInNanos()}.
*/
public InputStream maybeRateLimitSnapshots(InputStream stream, RateLimitingInputStream.Listener throttleListener) {
return maybeRateLimit(stream, () -> snapshotRateLimiter, throttleListener);
}

@Override
Expand Down Expand Up @@ -2721,6 +2751,10 @@ private static void failStoreIfCorrupted(Store store, Exception e) {
}
}

public boolean supportURLRepo() {
return supportURLRepo;
}

/**
* The result of removing a snapshot from a shard folder in the repository.
*/
Expand Down
12 changes: 12 additions & 0 deletions server/src/main/java/org/elasticsearch/rest/RestRequest.java
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,18 @@ public float paramAsFloat(String key, float defaultValue) {
}
}

public double paramAsDouble(String key, double defaultValue) {
String sValue = param(key);
if (sValue == null) {
return defaultValue;
}
try {
return Double.parseDouble(sValue);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("Failed to parse double parameter [" + key + "] with value [" + sValue + "]", e);
}
}

public int paramAsInt(String key, int defaultValue) {
String sValue = param(key);
if (sValue == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,12 @@ public class OperatorOnlyRegistry {
"cluster:admin/autoscaling/put_autoscaling_policy",
"cluster:admin/autoscaling/delete_autoscaling_policy",
"cluster:admin/autoscaling/get_autoscaling_policy",
"cluster:admin/autoscaling/get_autoscaling_capacity");
"cluster:admin/autoscaling/get_autoscaling_capacity",
// Repository analysis actions are not mentioned in core, literal strings are needed.
"cluster:admin/repository/analyze",
"cluster:admin/repository/analyze/blob",
"cluster:admin/repository/analyze/blob/read"
);

private final ClusterSettings clusterSettings;

Expand Down
30 changes: 30 additions & 0 deletions x-pack/plugin/snapshot-repo-test-kit/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
apply plugin: 'elasticsearch.internal-cluster-test'
apply plugin: 'elasticsearch.esplugin'
esplugin {
name 'snapshot-repo-test-kit'
description 'A plugin for a test kit for snapshot repositories'
classname 'org.elasticsearch.repositories.blobstore.testkit.SnapshotRepositoryTestKit'
extendedPlugins = ['x-pack-core']
}
archivesBaseName = 'x-pack-snapshot-repo-test-kit'

dependencies {
compileOnly project(path: xpackModule('core'), configuration: 'default')
internalClusterTestImplementation project(path: xpackModule('core'), configuration: 'testArtifacts')
}

addQaCheckDependencies()

configurations {
testArtifacts.extendsFrom testRuntime
testArtifacts.extendsFrom testImplementation
}

def testJar = tasks.register("testJar", Jar) {
appendix 'test'
from sourceSets.test.output
}

artifacts {
testArtifacts testJar
}
6 changes: 6 additions & 0 deletions x-pack/plugin/snapshot-repo-test-kit/qa/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
apply plugin: 'elasticsearch.build'
tasks.named("test").configure { enabled = false }

dependencies {
api project(':test:framework')
}
27 changes: 27 additions & 0 deletions x-pack/plugin/snapshot-repo-test-kit/qa/rest/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apply plugin: 'elasticsearch.testclusters'
apply plugin: 'elasticsearch.standalone-rest-test'
apply plugin: 'elasticsearch.rest-test'
apply plugin: 'elasticsearch.rest-resources'

dependencies {
testImplementation project(path: xpackModule('snapshot-repo-test-kit'), configuration: 'testArtifacts')
}

// TODO we want 3rd-party tests for other repository types too
final File repoDir = file("$buildDir/testclusters/repo")

tasks.named("integTest").configure {
systemProperty 'tests.path.repo', repoDir
}

testClusters.matching { it.name == "integTest" }.configureEach {
testDistribution = 'DEFAULT'
setting 'path.repo', repoDir.absolutePath
}

restResources {
restApi {
includeCore 'indices', 'search', 'bulk', 'snapshot', 'nodes', '_common'
includeXpack 'snapshot_repo_test_kit'
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.repositories.blobstore.testkit.rest;

import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.repositories.blobstore.testkit.AbstractSnapshotRepoTestKitRestTestCase;
import org.elasticsearch.repositories.fs.FsRepository;

public class FsSnapshotRepoTestKitIT extends AbstractSnapshotRepoTestKitRestTestCase {

@Override
protected String repositoryType() {
return FsRepository.TYPE;
}

@Override
protected Settings repositorySettings() {
return Settings.builder().put("location", System.getProperty("tests.path.repo")).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.repositories.blobstore.testkit.rest;

import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;

public class SnapshotRepoTestKitClientYamlTestSuiteIT extends ESClientYamlSuiteTestCase {

public SnapshotRepoTestKitClientYamlTestSuiteIT(final ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}

@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
return ESClientYamlSuiteTestCase.createParameters();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
{
"snapshot.repository_analyze":{
"documentation":{
"url":"https://www.elastic.co/guide/en/elasticsearch/reference/master/modules-snapshots.html",
"description":"Analyzes a repository for correctness and performance"
},
"stability":"stable",
"visibility":"public",
"url":{
"paths":[
{
"path":"/_snapshot/{repository}/_analyze",
"methods":[
"POST"
],
"parts":{
"repository":{
"type":"string",
"description":"A repository name"
}
}
}
]
},
"params":{
"blob_count":{
"type":"number",
"description":"Number of blobs to create during the test. Defaults to 100."
},
"concurrency":{
"type":"number",
"description":"Number of operations to run concurrently during the test. Defaults to 10."
},
"read_node_count":{
"type":"number",
"description":"Number of nodes on which to read a blob after writing. Defaults to 10."
},
"early_read_node_count":{
"type":"number",
"description":"Number of nodes on which to perform an early read on a blob, i.e. before writing has completed. Early reads are rare actions so the 'rare_action_probability' parameter is also relevant. Defaults to 2."
},
"seed":{
"type":"number",
"description":"Seed for the random number generator used to create the test workload. Defaults to a random value."
},
"rare_action_probability":{
"type":"number",
"description":"Probability of taking a rare action such as an early read or an overwrite. Defaults to 0.02."
},
"max_blob_size":{
"type":"string",
"description":"Maximum size of a blob to create during the test, e.g '1gb' or '100mb'. Defaults to '10mb'."
},
"max_total_data_size":{
"type":"string",
"description":"Maximum total size of all blobs to create during the test, e.g '1tb' or '100gb'. Defaults to '1gb'."
},
"timeout":{
"type":"time",
"description":"Explicit operation timeout. Defaults to '30s'."
},
"detailed":{
"type":"boolean",
"description":"Whether to return detailed results or a summary. Defaults to 'false' so that only the summary is returned."
}
}
}
}
Loading