Skip to content

Commit

Permalink
Add readonly option for repositories
Browse files Browse the repository at this point in the history
  • Loading branch information
imotov committed Aug 27, 2015
1 parent f64a875 commit 2b87d7d
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -130,4 +130,10 @@ public interface Repository extends LifecycleComponent<Repository> {
*/
void endVerification(String verificationToken);

/**
* Returns true if the repository supports only read operations
* @return true if the repository is read/only
*/
boolean readOnly();

}
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,8 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep

private LegacyBlobStoreFormat<Snapshot> snapshotLegacyFormat;

private final boolean readOnly;

/**
* Constructs new BlobStoreRepository
*
Expand All @@ -181,6 +183,7 @@ protected BlobStoreRepository(String repositoryName, RepositorySettings reposito
this.indexShardRepository = (BlobStoreIndexShardRepository) indexShardRepository;
snapshotRateLimiter = getRateLimiter(repositorySettings, "max_snapshot_bytes_per_sec", new ByteSizeValue(40, ByteSizeUnit.MB));
restoreRateLimiter = getRateLimiter(repositorySettings, "max_restore_bytes_per_sec", new ByteSizeValue(40, ByteSizeUnit.MB));
readOnly = repositorySettings.settings().getAsBoolean("readonly", false);
}

/**
Expand Down Expand Up @@ -260,6 +263,9 @@ protected ByteSizeValue chunkSize() {
*/
@Override
public void initializeSnapshot(SnapshotId snapshotId, List<String> indices, MetaData metaData) {
if (readOnly()) {
throw new RepositoryException(this.repositoryName, "cannot create snapshot in a readonly repository");
}
try {
if (snapshotFormat.exists(snapshotsBlobContainer, snapshotId.getSnapshot()) ||
snapshotLegacyFormat.exists(snapshotsBlobContainer, snapshotId.getSnapshot())) {
Expand All @@ -283,6 +289,9 @@ public void initializeSnapshot(SnapshotId snapshotId, List<String> indices, Meta
*/
@Override
public void deleteSnapshot(SnapshotId snapshotId) {
if (readOnly()) {
throw new RepositoryException(this.repositoryName, "cannot delete snapshot from a readonly repository");
}
List<String> indices = Collections.EMPTY_LIST;
Snapshot snapshot = null;
try {
Expand Down Expand Up @@ -622,23 +631,31 @@ public long restoreThrottleTimeInNanos() {
@Override
public String startVerification() {
try {
String seed = Strings.randomBase64UUID();
byte[] testBytes = Strings.toUTF8Bytes(seed);
BlobContainer testContainer = blobStore().blobContainer(basePath().add(testBlobPrefix(seed)));
String blobName = "master.dat";
try (OutputStream outputStream = testContainer.createOutput(blobName + "-temp")) {
outputStream.write(testBytes);
if (readOnly()) {
// It's readonly - so there is not much we can do here to verify it
return null;
} else {
String seed = Strings.randomBase64UUID();
byte[] testBytes = Strings.toUTF8Bytes(seed);
BlobContainer testContainer = blobStore().blobContainer(basePath().add(testBlobPrefix(seed)));
String blobName = "master.dat";
try (OutputStream outputStream = testContainer.createOutput(blobName + "-temp")) {
outputStream.write(testBytes);
}
// Make sure that move is supported
testContainer.move(blobName + "-temp", blobName);
return seed;
}
// Make sure that move is supported
testContainer.move(blobName + "-temp", blobName);
return seed;
} catch (IOException exp) {
throw new RepositoryVerificationException(repositoryName, "path " + basePath() + " is not accessible on master node", exp);
}
}

@Override
public void endVerification(String seed) {
if (readOnly()) {
throw new UnsupportedOperationException("shouldn't be called");
}
try {
blobStore().delete(basePath().add(testBlobPrefix(seed)));
} catch (IOException exp) {
Expand All @@ -649,4 +666,9 @@ public void endVerification(String seed) {
public static String testBlobPrefix(String seed) {
return TESTS_FILE + seed;
}

@Override
public boolean readOnly() {
return readOnly;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -126,17 +126,6 @@ public List<SnapshotId> snapshots() {
}
}

@Override
public String startVerification() {
//TODO: #7831 Add check that URL exists and accessible
return null;
}

@Override
public void endVerification(String seed) {
throw new UnsupportedOperationException("shouldn't be called");
}

/**
* Makes sure that the url is white listed or if it points to the local file system it matches one on of the root path in path.repo
*/
Expand Down Expand Up @@ -168,4 +157,9 @@ private URL checkURL(URL url) {
throw new RepositoryException(repositoryName, "unsupported url protocol [" + protocol + "] from URL [" + url + "]");
}

@Override
public boolean readOnly() {
return true;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
import org.elasticsearch.index.store.IndexStore;
import org.elasticsearch.indices.InvalidIndexNameException;
import org.elasticsearch.repositories.RepositoriesService;
import org.elasticsearch.repositories.RepositoryException;
import org.elasticsearch.test.junit.annotations.TestLogging;
import org.junit.Test;

Expand Down Expand Up @@ -1317,6 +1318,63 @@ public void urlRepositoryTest() throws Exception {
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(0));
}


@Test
public void readonlyRepositoryTest() throws Exception {
Client client = client();

logger.info("--> creating repository");
Path repositoryLocation = randomRepoPath();
assertAcked(client.admin().cluster().preparePutRepository("test-repo")
.setType("fs").setSettings(Settings.settingsBuilder()
.put("location", repositoryLocation)
.put("compress", randomBoolean())
.put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));

createIndex("test-idx");
ensureGreen();

logger.info("--> indexing some data");
for (int i = 0; i < 100; i++) {
index("test-idx", "doc", Integer.toString(i), "foo", "bar" + i);
}
refresh();

logger.info("--> snapshot");
CreateSnapshotResponse createSnapshotResponse = client.admin().cluster().prepareCreateSnapshot("test-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx").get();
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), greaterThan(0));
assertThat(createSnapshotResponse.getSnapshotInfo().successfulShards(), equalTo(createSnapshotResponse.getSnapshotInfo().totalShards()));

assertThat(client.admin().cluster().prepareGetSnapshots("test-repo").setSnapshots("test-snap").get().getSnapshots().get(0).state(), equalTo(SnapshotState.SUCCESS));

logger.info("--> delete index");
cluster().wipeIndices("test-idx");

logger.info("--> create read-only URL repository");
assertAcked(client.admin().cluster().preparePutRepository("readonly-repo")
.setType("fs").setSettings(Settings.settingsBuilder()
.put("location", repositoryLocation)
.put("compress", randomBoolean())
.put("readonly", true)
.put("chunk_size", randomIntBetween(100, 1000), ByteSizeUnit.BYTES)));
logger.info("--> restore index after deletion");
RestoreSnapshotResponse restoreSnapshotResponse = client.admin().cluster().prepareRestoreSnapshot("readonly-repo", "test-snap").setWaitForCompletion(true).setIndices("test-idx").execute().actionGet();
assertThat(restoreSnapshotResponse.getRestoreInfo().totalShards(), greaterThan(0));

assertThat(client.prepareCount("test-idx").get().getCount(), equalTo(100L));

logger.info("--> list available shapshots");
GetSnapshotsResponse getSnapshotsResponse = client.admin().cluster().prepareGetSnapshots("readonly-repo").get();
assertThat(getSnapshotsResponse.getSnapshots(), notNullValue());
assertThat(getSnapshotsResponse.getSnapshots().size(), equalTo(1));

logger.info("--> try deleting snapshot");
assertThrows(client.admin().cluster().prepareDeleteSnapshot("readonly-repo", "test-snap"), RepositoryException.class, "cannot delete snapshot from a readonly repository");

logger.info("--> try making another snapshot");
assertThrows(client.admin().cluster().prepareCreateSnapshot("readonly-repo", "test-snap-2").setWaitForCompletion(true).setIndices("test-idx"), RepositoryException.class, "cannot create snapshot in a readonly repository");
}

@Test
public void throttlingTest() throws Exception {
Client client = client();
Expand Down
3 changes: 3 additions & 0 deletions docs/plugins/cloud-aws.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,9 @@ The following settings are supported:

Number of retries in case of S3 errors. Defaults to `3`.

`read_only`::

Makes repository read-only. coming[2.1.0] Defaults to `false`.

The S3 repositories use the same credentials as the rest of the AWS services
provided by this plugin (`discovery`). See <<cloud-aws-usage>> for details.
Expand Down
4 changes: 4 additions & 0 deletions docs/plugins/cloud-azure.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,10 @@ The Azure repository supports following settings:
setting doesn't affect index files that are already compressed by default.
Defaults to `false`.

`read_only`::

Makes repository read-only. coming[2.1.0] Defaults to `false`.

Some examples, using scripts:

[source,json]
Expand Down
1 change: 1 addition & 0 deletions docs/reference/modules/snapshots.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ The following settings are supported:
using size value notation, i.e. 1g, 10m, 5k. Defaults to `null` (unlimited chunk size).
`max_restore_bytes_per_sec`:: Throttles per node restore rate. Defaults to `40mb` per second.
`max_snapshot_bytes_per_sec`:: Throttles per node snapshot rate. Defaults to `40mb` per second.
`readonly`:: Makes repository read-only. coming[2.1.0] Defaults to `false`.

[float]
===== Read-only URL Repository
Expand Down

0 comments on commit 2b87d7d

Please sign in to comment.