|
@@ -34,6 +34,7 @@ import org.apache.lucene.store.IndexOutput;
|
|
|
import org.apache.lucene.store.RateLimiter;
|
|
|
import org.apache.lucene.util.BytesRef;
|
|
|
import org.apache.lucene.util.BytesRefBuilder;
|
|
|
+import org.apache.lucene.util.SetOnce;
|
|
|
import org.elasticsearch.ElasticsearchParseException;
|
|
|
import org.elasticsearch.ExceptionsHelper;
|
|
|
import org.elasticsearch.ResourceNotFoundException;
|
|
@@ -102,6 +103,7 @@ import org.elasticsearch.snapshots.SnapshotId;
|
|
|
import org.elasticsearch.snapshots.SnapshotInfo;
|
|
|
import org.elasticsearch.snapshots.SnapshotMissingException;
|
|
|
import org.elasticsearch.snapshots.SnapshotShardFailure;
|
|
|
+import org.elasticsearch.threadpool.ThreadPool;
|
|
|
|
|
|
import java.io.FilterInputStream;
|
|
|
import java.io.IOException;
|
|
@@ -126,8 +128,8 @@ import static org.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSna
|
|
|
/**
|
|
|
* BlobStore - based implementation of Snapshot Repository
|
|
|
* <p>
|
|
|
- * This repository works with any {@link BlobStore} implementation. The blobStore should be initialized in the derived
|
|
|
- * class before {@link #doStart()} is called.
|
|
|
+ * This repository works with any {@link BlobStore} implementation. The blobStore could be (and preferred) lazy initialized in
|
|
|
+ * {@link #createBlobStore()}.
|
|
|
* <p>
|
|
|
* BlobStoreRepository maintains the following structure in the blob store
|
|
|
* <pre>
|
|
@@ -169,8 +171,6 @@ import static org.elasticsearch.index.snapshots.blobstore.BlobStoreIndexShardSna
|
|
|
*/
|
|
|
public abstract class BlobStoreRepository extends AbstractLifecycleComponent implements Repository {
|
|
|
|
|
|
- private BlobContainer snapshotsBlobContainer;
|
|
|
-
|
|
|
protected final RepositoryMetaData metadata;
|
|
|
|
|
|
protected final NamedXContentRegistry namedXContentRegistry;
|
|
@@ -225,6 +225,12 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
private final ChecksumBlobStoreFormat<BlobStoreIndexShardSnapshots> indexShardSnapshotsFormat;
|
|
|
|
|
|
+ private final Object lock = new Object();
|
|
|
+
|
|
|
+ private final SetOnce<BlobContainer> blobContainer = new SetOnce<>();
|
|
|
+
|
|
|
+ private final SetOnce<BlobStore> blobStore = new SetOnce<>();
|
|
|
+
|
|
|
/**
|
|
|
* Constructs new BlobStoreRepository
|
|
|
*
|
|
@@ -251,7 +257,6 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
@Override
|
|
|
protected void doStart() {
|
|
|
- this.snapshotsBlobContainer = blobStore().blobContainer(basePath());
|
|
|
globalMetaDataFormat = new ChecksumBlobStoreFormat<>(METADATA_CODEC, METADATA_NAME_FORMAT,
|
|
|
MetaData::fromXContent, namedXContentRegistry, isCompress());
|
|
|
indexMetaDataFormat = new ChecksumBlobStoreFormat<>(INDEX_METADATA_CODEC, METADATA_NAME_FORMAT,
|
|
@@ -265,17 +270,82 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
@Override
|
|
|
protected void doClose() {
|
|
|
- try {
|
|
|
- blobStore().close();
|
|
|
- } catch (Exception t) {
|
|
|
- logger.warn("cannot close blob store", t);
|
|
|
+ BlobStore store;
|
|
|
+ // to close blobStore if blobStore initialization is started during close
|
|
|
+ synchronized (lock) {
|
|
|
+ store = blobStore.get();
|
|
|
+ }
|
|
|
+ if (store != null) {
|
|
|
+ try {
|
|
|
+ store.close();
|
|
|
+ } catch (Exception t) {
|
|
|
+ logger.warn("cannot close blob store", t);
|
|
|
+ }
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ // package private, only use for testing
|
|
|
+ BlobContainer getBlobContainer() {
|
|
|
+ return blobContainer.get();
|
|
|
+ }
|
|
|
+
|
|
|
+ // for test purposes only
|
|
|
+ protected BlobStore getBlobStore() {
|
|
|
+ return blobStore.get();
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
- * Returns the BlobStore to read and write data.
|
|
|
+ * maintains single lazy instance of {@link BlobContainer}
|
|
|
*/
|
|
|
- protected abstract BlobStore blobStore();
|
|
|
+ protected BlobContainer blobContainer() {
|
|
|
+ assertSnapshotOrGenericThread();
|
|
|
+
|
|
|
+ BlobContainer blobContainer = this.blobContainer.get();
|
|
|
+ if (blobContainer == null) {
|
|
|
+ synchronized (lock) {
|
|
|
+ blobContainer = this.blobContainer.get();
|
|
|
+ if (blobContainer == null) {
|
|
|
+ blobContainer = blobStore().blobContainer(basePath());
|
|
|
+ this.blobContainer.set(blobContainer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return blobContainer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * maintains single lazy instance of {@link BlobStore}
|
|
|
+ */
|
|
|
+ protected BlobStore blobStore() {
|
|
|
+ assertSnapshotOrGenericThread();
|
|
|
+
|
|
|
+ BlobStore store = blobStore.get();
|
|
|
+ if (store == null) {
|
|
|
+ synchronized (lock) {
|
|
|
+ store = blobStore.get();
|
|
|
+ if (store == null) {
|
|
|
+ if (lifecycle.started() == false) {
|
|
|
+ throw new RepositoryException(metadata.name(), "repository is not in started state");
|
|
|
+ }
|
|
|
+ try {
|
|
|
+ store = createBlobStore();
|
|
|
+ } catch (RepositoryException e) {
|
|
|
+ throw e;
|
|
|
+ } catch (Exception e) {
|
|
|
+ throw new RepositoryException(metadata.name(), "cannot create blob store" , e);
|
|
|
+ }
|
|
|
+ blobStore.set(store);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return store;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Creates new BlobStore to read and write data.
|
|
|
+ */
|
|
|
+ protected abstract BlobStore createBlobStore() throws Exception;
|
|
|
|
|
|
/**
|
|
|
* Returns base path of the repository
|
|
@@ -319,12 +389,12 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
if (repositoryData.getAllSnapshotIds().stream().anyMatch(s -> s.getName().equals(snapshotName))) {
|
|
|
throw new InvalidSnapshotNameException(metadata.name(), snapshotId.getName(), "snapshot with the same name already exists");
|
|
|
}
|
|
|
- if (snapshotFormat.exists(snapshotsBlobContainer, snapshotId.getUUID())) {
|
|
|
+ if (snapshotFormat.exists(blobContainer(), snapshotId.getUUID())) {
|
|
|
throw new InvalidSnapshotNameException(metadata.name(), snapshotId.getName(), "snapshot with the same name already exists");
|
|
|
}
|
|
|
|
|
|
// Write Global MetaData
|
|
|
- globalMetaDataFormat.write(clusterMetaData, snapshotsBlobContainer, snapshotId.getUUID());
|
|
|
+ globalMetaDataFormat.write(clusterMetaData, blobContainer(), snapshotId.getUUID());
|
|
|
|
|
|
// write the index metadata for each index in the snapshot
|
|
|
for (IndexId index : indices) {
|
|
@@ -421,7 +491,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
private void deleteSnapshotBlobIgnoringErrors(final SnapshotInfo snapshotInfo, final String blobId) {
|
|
|
try {
|
|
|
- snapshotFormat.delete(snapshotsBlobContainer, blobId);
|
|
|
+ snapshotFormat.delete(blobContainer(), blobId);
|
|
|
} catch (IOException e) {
|
|
|
if (snapshotInfo != null) {
|
|
|
logger.warn(() -> new ParameterizedMessage("[{}] Unable to delete snapshot file [{}]",
|
|
@@ -434,7 +504,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
private void deleteGlobalMetaDataBlobIgnoringErrors(final SnapshotInfo snapshotInfo, final String blobId) {
|
|
|
try {
|
|
|
- globalMetaDataFormat.delete(snapshotsBlobContainer, blobId);
|
|
|
+ globalMetaDataFormat.delete(blobContainer(), blobId);
|
|
|
} catch (IOException e) {
|
|
|
if (snapshotInfo != null) {
|
|
|
logger.warn(() -> new ParameterizedMessage("[{}] Unable to delete global metadata file [{}]",
|
|
@@ -472,7 +542,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
startTime, failure, System.currentTimeMillis(), totalShards, shardFailures,
|
|
|
includeGlobalState);
|
|
|
try {
|
|
|
- snapshotFormat.write(blobStoreSnapshot, snapshotsBlobContainer, snapshotId.getUUID());
|
|
|
+ snapshotFormat.write(blobStoreSnapshot, blobContainer(), snapshotId.getUUID());
|
|
|
final RepositoryData repositoryData = getRepositoryData();
|
|
|
writeIndexGen(repositoryData.addSnapshot(snapshotId, blobStoreSnapshot.state(), indices), repositoryStateId);
|
|
|
} catch (FileAlreadyExistsException ex) {
|
|
@@ -490,7 +560,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
@Override
|
|
|
public SnapshotInfo getSnapshotInfo(final SnapshotId snapshotId) {
|
|
|
try {
|
|
|
- return snapshotFormat.read(snapshotsBlobContainer, snapshotId.getUUID());
|
|
|
+ return snapshotFormat.read(blobContainer(), snapshotId.getUUID());
|
|
|
} catch (NoSuchFileException ex) {
|
|
|
throw new SnapshotMissingException(metadata.name(), snapshotId, ex);
|
|
|
} catch (IOException | NotXContentException ex) {
|
|
@@ -501,7 +571,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
@Override
|
|
|
public MetaData getSnapshotGlobalMetaData(final SnapshotId snapshotId) {
|
|
|
try {
|
|
|
- return globalMetaDataFormat.read(snapshotsBlobContainer, snapshotId.getUUID());
|
|
|
+ return globalMetaDataFormat.read(blobContainer(), snapshotId.getUUID());
|
|
|
} catch (NoSuchFileException ex) {
|
|
|
throw new SnapshotMissingException(metadata.name(), snapshotId, ex);
|
|
|
} catch (IOException ex) {
|
|
@@ -543,11 +613,21 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
return restoreRateLimitingTimeInNanos.count();
|
|
|
}
|
|
|
|
|
|
+ protected void assertSnapshotOrGenericThread() {
|
|
|
+ assert Thread.currentThread().getName().contains(ThreadPool.Names.SNAPSHOT)
|
|
|
+ || Thread.currentThread().getName().contains(ThreadPool.Names.GENERIC) :
|
|
|
+ "Expected current thread [" + Thread.currentThread() + "] to be the snapshot or generic thread.";
|
|
|
+ }
|
|
|
+
|
|
|
@Override
|
|
|
public String startVerification() {
|
|
|
try {
|
|
|
if (isReadOnly()) {
|
|
|
- // It's readonly - so there is not much we can do here to verify it
|
|
|
+ // TODO: add repository verification for read-only repositories
|
|
|
+
|
|
|
+ // It's readonly - so there is not much we can do here to verify it apart try to create blobStore()
|
|
|
+ // and check that is is accessible on the master
|
|
|
+ blobStore();
|
|
|
return null;
|
|
|
} else {
|
|
|
String seed = UUIDs.randomBase64UUID();
|
|
@@ -584,7 +664,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
final String snapshotsIndexBlobName = INDEX_FILE_PREFIX + Long.toString(indexGen);
|
|
|
|
|
|
RepositoryData repositoryData;
|
|
|
- try (InputStream blob = snapshotsBlobContainer.readBlob(snapshotsIndexBlobName)) {
|
|
|
+ try (InputStream blob = blobContainer().readBlob(snapshotsIndexBlobName)) {
|
|
|
BytesStreamOutput out = new BytesStreamOutput();
|
|
|
Streams.copy(blob, out);
|
|
|
// EMPTY is safe here because RepositoryData#fromXContent calls namedObject
|
|
@@ -598,7 +678,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
}
|
|
|
|
|
|
// now load the incompatible snapshot ids, if they exist
|
|
|
- try (InputStream blob = snapshotsBlobContainer.readBlob(INCOMPATIBLE_SNAPSHOTS_BLOB)) {
|
|
|
+ try (InputStream blob = blobContainer().readBlob(INCOMPATIBLE_SNAPSHOTS_BLOB)) {
|
|
|
BytesStreamOutput out = new BytesStreamOutput();
|
|
|
Streams.copy(blob, out);
|
|
|
try (XContentParser parser = XContentHelper.createParser(NamedXContentRegistry.EMPTY,
|
|
@@ -636,11 +716,6 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
return readOnly;
|
|
|
}
|
|
|
|
|
|
- // package private, only use for testing
|
|
|
- BlobContainer blobContainer() {
|
|
|
- return snapshotsBlobContainer;
|
|
|
- }
|
|
|
-
|
|
|
protected void writeIndexGen(final RepositoryData repositoryData, final long repositoryStateId) throws IOException {
|
|
|
assert isReadOnly() == false; // can not write to a read only repository
|
|
|
final long currentGen = latestIndexBlobId();
|
|
@@ -668,7 +743,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
// delete the N-2 index file if it exists, keep the previous one around as a backup
|
|
|
if (isReadOnly() == false && newGen - 2 >= 0) {
|
|
|
final String oldSnapshotIndexFile = INDEX_FILE_PREFIX + Long.toString(newGen - 2);
|
|
|
- snapshotsBlobContainer.deleteBlobIgnoringIfNotExists(oldSnapshotIndexFile);
|
|
|
+ blobContainer().deleteBlobIgnoringIfNotExists(oldSnapshotIndexFile);
|
|
|
}
|
|
|
|
|
|
// write the current generation to the index-latest file
|
|
@@ -736,7 +811,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
// package private for testing
|
|
|
long readSnapshotIndexLatestBlob() throws IOException {
|
|
|
- try (InputStream blob = snapshotsBlobContainer.readBlob(INDEX_LATEST_BLOB)) {
|
|
|
+ try (InputStream blob = blobContainer().readBlob(INDEX_LATEST_BLOB)) {
|
|
|
BytesStreamOutput out = new BytesStreamOutput();
|
|
|
Streams.copy(blob, out);
|
|
|
return Numbers.bytesToLong(out.bytes().toBytesRef());
|
|
@@ -744,7 +819,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
}
|
|
|
|
|
|
private long listBlobsToGetLatestIndexId() throws IOException {
|
|
|
- Map<String, BlobMetaData> blobs = snapshotsBlobContainer.listBlobsByPrefix(INDEX_FILE_PREFIX);
|
|
|
+ Map<String, BlobMetaData> blobs = blobContainer().listBlobsByPrefix(INDEX_FILE_PREFIX);
|
|
|
long latest = RepositoryData.EMPTY_REPO_GEN;
|
|
|
if (blobs.isEmpty()) {
|
|
|
// no snapshot index blobs have been written yet
|
|
@@ -766,7 +841,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
private void writeAtomic(final String blobName, final BytesReference bytesRef, boolean failIfAlreadyExists) throws IOException {
|
|
|
try (InputStream stream = bytesRef.streamInput()) {
|
|
|
- snapshotsBlobContainer.writeBlobAtomic(blobName, stream, bytesRef.length(), failIfAlreadyExists);
|
|
|
+ blobContainer().writeBlobAtomic(blobName, stream, bytesRef.length(), failIfAlreadyExists);
|
|
|
}
|
|
|
}
|
|
|
|
|
@@ -806,6 +881,7 @@ public abstract class BlobStoreRepository extends AbstractLifecycleComponent imp
|
|
|
|
|
|
@Override
|
|
|
public void verify(String seed, DiscoveryNode localNode) {
|
|
|
+ assertSnapshotOrGenericThread();
|
|
|
BlobContainer testBlobContainer = blobStore().blobContainer(basePath().add(testBlobPrefix(seed)));
|
|
|
if (testBlobContainer.blobExists("master.dat")) {
|
|
|
try {
|