Browse Source

Add `readonly` option for repositories

Closes #7831
Closes #11753
Igor Motov 10 years ago
parent
commit
2b87d7d919

+ 6 - 0
core/src/main/java/org/elasticsearch/repositories/Repository.java

@@ -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();
+
 }

+ 31 - 9
core/src/main/java/org/elasticsearch/repositories/blobstore/BlobStoreRepository.java

@@ -168,6 +168,8 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
 
     private LegacyBlobStoreFormat<Snapshot> snapshotLegacyFormat;
 
+    private final boolean readOnly;
+
     /**
      * Constructs new BlobStoreRepository
      *
@@ -181,6 +183,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
         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);
     }
 
     /**
@@ -260,6 +263,9 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
      */
     @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())) {
@@ -283,6 +289,9 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
      */
     @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 {
@@ -622,16 +631,21 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
     @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);
         }
@@ -639,6 +653,9 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
 
     @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) {
@@ -649,4 +666,9 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent<Rep
     public static String testBlobPrefix(String seed) {
         return TESTS_FILE + seed;
     }
+
+    @Override
+    public boolean readOnly() {
+        return readOnly;
+    }
 }

+ 5 - 11
core/src/main/java/org/elasticsearch/repositories/uri/URLRepository.java

@@ -126,17 +126,6 @@ public class URLRepository extends BlobStoreRepository {
         }
     }
 
-    @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
      */
@@ -168,4 +157,9 @@ public class URLRepository extends BlobStoreRepository {
         throw new RepositoryException(repositoryName, "unsupported url protocol [" + protocol + "] from URL [" + url + "]");
     }
 
+    @Override
+    public boolean readOnly() {
+        return true;
+    }
+
 }

+ 58 - 0
core/src/test/java/org/elasticsearch/snapshots/SharedClusterSnapshotRestoreIT.java

@@ -64,6 +64,7 @@ import org.elasticsearch.index.shard.ShardId;
 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;
 
@@ -1317,6 +1318,63 @@ public class SharedClusterSnapshotRestoreIT extends AbstractSnapshotIntegTestCas
         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();

+ 3 - 0
docs/plugins/cloud-aws.asciidoc

@@ -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.

+ 4 - 0
docs/plugins/cloud-azure.asciidoc

@@ -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]

+ 1 - 0
docs/reference/modules/snapshots.asciidoc

@@ -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