This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 7cfa6fe9d5909b414ee61ac10e460f1024087884 Author: Benoit Tellier <[email protected]> AuthorDate: Fri Jan 3 18:06:59 2020 +0700 JAMES-2921 Propose an Hybrid blobStore --- .../apache/james/blob/union/HybridBlobStore.java | 182 +++++++ .../apache/james/blob/union/UnionBlobStore.java | 223 -------- .../james/blob/union/HybridBlobStoreTest.java | 516 ++++++++++++++++++ .../james/blob/union/UnionBlobStoreTest.java | 604 --------------------- .../blobstore/BlobStoreChoosingConfiguration.java | 6 +- .../modules/blobstore/BlobStoreChoosingModule.java | 10 +- .../BlobStoreChoosingConfigurationTest.java | 14 +- .../blobstore/BlobStoreChoosingModuleTest.java | 14 +- 8 files changed, 720 insertions(+), 849 deletions(-) diff --git a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java new file mode 100644 index 0000000..45d1813 --- /dev/null +++ b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/HybridBlobStore.java @@ -0,0 +1,182 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.blob.union; + +import java.io.BufferedInputStream; +import java.io.IOException; +import java.io.InputStream; + +import org.apache.james.blob.api.BlobId; +import org.apache.james.blob.api.BlobStore; +import org.apache.james.blob.api.BucketName; +import org.apache.james.blob.api.ObjectNotFoundException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.MoreObjects; +import com.google.common.base.Preconditions; + +import reactor.core.publisher.Mono; + +public class HybridBlobStore implements BlobStore { + @FunctionalInterface + public interface RequireLowCost { + RequirePerforming lowCost(BlobStore blobStore); + } + + @FunctionalInterface + public interface RequirePerforming { + Builder highPerformance(BlobStore blobStore); + } + + public static class Builder { + private final BlobStore lowCostBlobStore; + private final BlobStore highPerformanceBlobStore; + + Builder(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore) { + this.lowCostBlobStore = lowCostBlobStore; + this.highPerformanceBlobStore = highPerformanceBlobStore; + } + + public HybridBlobStore build() { + return new HybridBlobStore( + lowCostBlobStore, + highPerformanceBlobStore); + } + } + + private static final Logger LOGGER = LoggerFactory.getLogger(HybridBlobStore.class); + private static final int SIZE_THRESHOLD = 32 * 1024; + + public static RequireLowCost builder() { + return lowCost -> highPerformance -> new Builder(lowCost, highPerformance); + } + + private final BlobStore lowCostBlobStore; + private final BlobStore highPerformanceBlobStore; + + private HybridBlobStore(BlobStore lowCostBlobStore, BlobStore highPerformanceBlobStore) { + this.lowCostBlobStore = lowCostBlobStore; + this.highPerformanceBlobStore = highPerformanceBlobStore; + } + + @Override + public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { + return selectBlobStore(storagePolicy, Mono.just(data.length > SIZE_THRESHOLD)) + .flatMap(blobStore -> blobStore.save(bucketName, data, storagePolicy)); + } + + @Override + public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { + Preconditions.checkNotNull(data); + + BufferedInputStream bufferedInputStream = new BufferedInputStream(data, SIZE_THRESHOLD + 1); + return selectBlobStore(storagePolicy, Mono.fromCallable(() -> isItABigStream(bufferedInputStream))) + .flatMap(blobStore -> blobStore.save(bucketName, bufferedInputStream, storagePolicy)); + } + + private Mono<BlobStore> selectBlobStore(StoragePolicy storagePolicy, Mono<Boolean> largeData) { + switch (storagePolicy) { + case LOW_COST: + return Mono.just(lowCostBlobStore); + case SIZE_BASED: + return largeData.map(isLarge -> { + if (isLarge) { + return lowCostBlobStore; + } + return highPerformanceBlobStore; + }); + case HIGH_PERFORMANCE: + return Mono.just(highPerformanceBlobStore); + default: + throw new RuntimeException("Unknown storage policy: " + storagePolicy); + } + } + + private boolean isItABigStream(InputStream bufferedData) throws IOException { + bufferedData.mark(0); + bufferedData.skip(SIZE_THRESHOLD); + boolean isItABigStream = bufferedData.read() != -1; + bufferedData.reset(); + return isItABigStream; + } + + @Override + public BucketName getDefaultBucketName() { + Preconditions.checkState( + lowCostBlobStore.getDefaultBucketName() + .equals(highPerformanceBlobStore.getDefaultBucketName()), + "lowCostBlobStore and highPerformanceBlobStore doen't have same defaultBucketName which could lead to " + + "unexpected result when interact with other APIs"); + + return lowCostBlobStore.getDefaultBucketName(); + } + + @Override + public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { + return Mono.defer(() -> highPerformanceBlobStore.readBytes(bucketName, blobId)) + .onErrorResume(this::logAndReturnEmpty) + .switchIfEmpty(Mono.defer(() -> lowCostBlobStore.readBytes(bucketName, blobId))); + } + + @Override + public InputStream read(BucketName bucketName, BlobId blobId) { + try { + return highPerformanceBlobStore.read(bucketName, blobId); + } catch (ObjectNotFoundException e) { + return lowCostBlobStore.read(bucketName, blobId); + } catch (Exception e) { + LOGGER.error("Error reading {} {} in {}, falling back to {}", bucketName, blobId, highPerformanceBlobStore, lowCostBlobStore); + return lowCostBlobStore.read(bucketName, blobId); + } + } + + @Override + public Mono<Void> deleteBucket(BucketName bucketName) { + return Mono.defer(() -> lowCostBlobStore.deleteBucket(bucketName)) + .and(highPerformanceBlobStore.deleteBucket(bucketName)) + .onErrorResume(this::logDeleteFailureAndReturnEmpty); + } + + @Override + public Mono<Void> delete(BucketName bucketName, BlobId blobId) { + return Mono.defer(() -> lowCostBlobStore.delete(bucketName, blobId)) + .and(highPerformanceBlobStore.delete(bucketName, blobId)) + .onErrorResume(this::logDeleteFailureAndReturnEmpty); + } + + private <T> Mono<T> logAndReturnEmpty(Throwable throwable) { + LOGGER.error("error happens from current blob store, fall back to lowCost blob store", throwable); + return Mono.empty(); + } + + private <T> Mono<T> logDeleteFailureAndReturnEmpty(Throwable throwable) { + LOGGER.error("Cannot delete from either lowCost or highPerformance blob store", throwable); + return Mono.empty(); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .add("lowCostBlobStore", lowCostBlobStore) + .add("highPerformanceBlobStore", highPerformanceBlobStore) + .toString(); + } +} diff --git a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java b/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java deleted file mode 100644 index 37f7c8a..0000000 --- a/server/blob/blob-union/src/main/java/org/apache/james/blob/union/UnionBlobStore.java +++ /dev/null @@ -1,223 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.blob.union; - -import java.io.IOException; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.Optional; -import java.util.function.Function; - -import org.apache.james.blob.api.BlobId; -import org.apache.james.blob.api.BlobStore; -import org.apache.james.blob.api.BucketName; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.github.fge.lambdas.Throwing; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.MoreObjects; -import com.google.common.base.Preconditions; - -import reactor.core.publisher.Mono; - -public class UnionBlobStore implements BlobStore { - - @FunctionalInterface - public interface StorageOperation<T> { - Mono<BlobId> save(BucketName bucketName, T data, StoragePolicy storagePolicy); - } - - @FunctionalInterface - public interface RequireCurrent { - RequireLegacy current(BlobStore blobStore); - } - - @FunctionalInterface - public interface RequireLegacy { - Builder legacy(BlobStore blobStore); - } - - public static class Builder { - private final BlobStore currentBlobStore; - private final BlobStore legacyBlobStore; - - Builder(BlobStore currentBlobStore, BlobStore legacyBlobStore) { - this.currentBlobStore = currentBlobStore; - this.legacyBlobStore = legacyBlobStore; - } - - public UnionBlobStore build() { - return new UnionBlobStore( - currentBlobStore, - legacyBlobStore); - } - } - - private static final Logger LOGGER = LoggerFactory.getLogger(UnionBlobStore.class); - private static final int UNAVAILABLE = -1; - - public static RequireCurrent builder() { - return current -> legacy -> new Builder(current, legacy); - } - - private final BlobStore currentBlobStore; - private final BlobStore legacyBlobStore; - - private UnionBlobStore(BlobStore currentBlobStore, BlobStore legacyBlobStore) { - this.currentBlobStore = currentBlobStore; - this.legacyBlobStore = legacyBlobStore; - } - - @Override - public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { - try { - return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy, - currentBlobStore::save, - legacyBlobStore::save); - } catch (Exception e) { - LOGGER.error("exception directly happens while saving bytes data, fall back to legacy blob store", e); - return legacyBlobStore.save(bucketName, data, storagePolicy); - } - } - - @Override - public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) { - try { - return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy, - currentBlobStore::save, - legacyBlobStore::save); - } catch (Exception e) { - LOGGER.error("exception directly happens while saving String data, fall back to legacy blob store", e); - return legacyBlobStore.save(bucketName, data, storagePolicy); - } - } - - @Override - public BucketName getDefaultBucketName() { - Preconditions.checkState( - currentBlobStore.getDefaultBucketName() - .equals(legacyBlobStore.getDefaultBucketName()), - "currentBlobStore and legacyBlobStore doen't have same defaultBucketName which could lead to " + - "unexpected result when interact with other APIs"); - - return currentBlobStore.getDefaultBucketName(); - } - - @Override - public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { - try { - return saveToCurrentFallbackIfFails(bucketName, data, storagePolicy, - currentBlobStore::save, - legacyBlobStore::save); - } catch (Exception e) { - LOGGER.error("exception directly happens while saving InputStream data, fall back to legacy blob store", e); - return legacyBlobStore.save(bucketName, data, storagePolicy); - } - } - - @Override - public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { - try { - return readBytesFallBackIfFailsOrEmptyResult(bucketName, blobId); - } catch (Exception e) { - LOGGER.error("exception directly happens while readBytes, fall back to legacy blob store", e); - return Mono.defer(() -> legacyBlobStore.readBytes(bucketName, blobId)); - } - } - - @Override - public InputStream read(BucketName bucketName, BlobId blobId) { - try { - return readFallBackIfEmptyResult(bucketName, blobId); - } catch (Exception e) { - LOGGER.error("exception directly happens while read, fall back to legacy blob store", e); - return legacyBlobStore.read(bucketName, blobId); - } - } - - @Override - public Mono<Void> deleteBucket(BucketName bucketName) { - return Mono.defer(() -> currentBlobStore.deleteBucket(bucketName)) - .and(legacyBlobStore.deleteBucket(bucketName)) - .onErrorResume(this::logDeleteFailureAndReturnEmpty); - } - - @Override - public Mono<Void> delete(BucketName bucketName, BlobId blobId) { - return Mono.defer(() -> currentBlobStore.delete(bucketName, blobId)) - .and(legacyBlobStore.delete(bucketName, blobId)) - .onErrorResume(this::logDeleteFailureAndReturnEmpty); - } - - private InputStream readFallBackIfEmptyResult(BucketName bucketName, BlobId blobId) { - return Optional.ofNullable(currentBlobStore.read(bucketName, blobId)) - .map(PushbackInputStream::new) - .filter(Throwing.predicate(this::streamHasContent).sneakyThrow()) - .<InputStream>map(Function.identity()) - .orElseGet(() -> legacyBlobStore.read(bucketName, blobId)); - } - - @VisibleForTesting - boolean streamHasContent(PushbackInputStream pushBackIS) throws IOException { - int byteRead = pushBackIS.read(); - if (byteRead != UNAVAILABLE) { - pushBackIS.unread(byteRead); - return true; - } - return false; - } - - private Mono<byte[]> readBytesFallBackIfFailsOrEmptyResult(BucketName bucketName, BlobId blobId) { - return Mono.defer(() -> currentBlobStore.readBytes(bucketName, blobId)) - .onErrorResume(this::logAndReturnEmpty) - .switchIfEmpty(legacyBlobStore.readBytes(bucketName, blobId)); - } - - private <T> Mono<BlobId> saveToCurrentFallbackIfFails( - BucketName bucketName, - T data, - StoragePolicy storagePolicy, - StorageOperation<T> currentSavingOperation, - StorageOperation<T> fallbackSavingOperationSupplier) { - - return Mono.defer(() -> currentSavingOperation.save(bucketName, data, storagePolicy)) - .onErrorResume(this::logAndReturnEmpty) - .switchIfEmpty(Mono.defer(() -> fallbackSavingOperationSupplier.save(bucketName, data, storagePolicy))); - } - - private <T> Mono<T> logAndReturnEmpty(Throwable throwable) { - LOGGER.error("error happens from current blob store, fall back to legacy blob store", throwable); - return Mono.empty(); - } - - private <T> Mono<T> logDeleteFailureAndReturnEmpty(Throwable throwable) { - LOGGER.error("Cannot delete from either legacy or current blob store", throwable); - return Mono.empty(); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .add("currentBlobStore", currentBlobStore) - .add("legacyBlobStore", legacyBlobStore) - .toString(); - } -} diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java new file mode 100644 index 0000000..97bb738 --- /dev/null +++ b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/HybridBlobStoreTest.java @@ -0,0 +1,516 @@ +/**************************************************************** + * Licensed to the Apache Software Foundation (ASF) under one * + * or more contributor license agreements. See the NOTICE file * + * distributed with this work for additional information * + * regarding copyright ownership. The ASF licenses this file * + * to you under the Apache License, Version 2.0 (the * + * "License"); you may not use this file except in compliance * + * with the License. You may obtain a copy of the License at * + * * + * http://www.apache.org/licenses/LICENSE-2.0 * + * * + * Unless required by applicable law or agreed to in writing, * + * software distributed under the License is distributed on an * + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * + * KIND, either express or implied. See the License for the * + * specific language governing permissions and limitations * + * under the License. * + ****************************************************************/ + +package org.apache.james.blob.union; + +import static org.apache.james.blob.api.BlobStore.StoragePolicy.LOW_COST; +import static org.apache.james.blob.api.BlobStore.StoragePolicy.HIGH_PERFORMANCE; +import static org.apache.james.blob.api.BlobStore.StoragePolicy.SIZE_BASED; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.io.ByteArrayInputStream; +import java.io.InputStream; + +import org.apache.james.blob.api.BlobId; +import org.apache.james.blob.api.BlobStore; +import org.apache.james.blob.api.BlobStoreContract; +import org.apache.james.blob.api.BucketName; +import org.apache.james.blob.api.HashBlobId; +import org.apache.james.blob.api.ObjectNotFoundException; +import org.apache.james.blob.api.ObjectStoreException; +import org.apache.james.blob.memory.MemoryBlobStore; +import org.assertj.core.api.SoftAssertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import com.github.fge.lambdas.Throwing; +import com.google.common.base.MoreObjects; + +import reactor.core.publisher.Mono; + +class HybridBlobStoreTest implements BlobStoreContract { + + private static class FailingBlobStore implements BlobStore { + @Override + public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public BucketName getDefaultBucketName() { + return BucketName.DEFAULT; + } + + @Override + public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public InputStream read(BucketName bucketName, BlobId blobId) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public Mono<Void> deleteBucket(BucketName bucketName) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public Mono<Void> delete(BucketName bucketName, BlobId blobId) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .toString(); + } + } + + private static class ThrowingBlobStore implements BlobStore { + + @Override + public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public BucketName getDefaultBucketName() { + return BucketName.DEFAULT; + } + + @Override + public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public InputStream read(BucketName bucketName, BlobId blobId) { + throw new RuntimeException("broken everywhere"); + } + + @Override + public Mono<Void> deleteBucket(BucketName bucketName) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public Mono<Void> delete(BucketName bucketName, BlobId blobId) { + return Mono.error(new RuntimeException("broken everywhere")); + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(this) + .toString(); + } + } + + private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory(); + private static final String STRING_CONTENT = "blob content"; + private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes(); + + private MemoryBlobStore lowCostBlobStore; + private MemoryBlobStore highPerformanceBlobStore; + private HybridBlobStore hybridBlobStore; + + @BeforeEach + void setup() { + lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + hybridBlobStore = HybridBlobStore.builder() + .lowCost(lowCostBlobStore) + .highPerformance(highPerformanceBlobStore) + .build(); + } + + @Override + public BlobStore testee() { + return hybridBlobStore; + } + + @Override + public BlobId.Factory blobIdFactory() { + return BLOB_ID_FACTORY; + } + + @Nested + class StoragePolicyTests { + @Test + void saveShouldRelyOnLowCostWhenLowCost() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveShouldRelyOnPerformingWhenPerforming() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveShouldRelyOnPerformingWhenSizeBasedAndSmall() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, SIZE_BASED).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveShouldRelyOnLowCostWhenSizeBasedAndBig() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, TWELVE_MEGABYTES, SIZE_BASED).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0))); + softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveInputStreamShouldRelyOnLowCostWhenLowCost() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveInputStreamShouldRelyOnPerformingWhenPerforming() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), HIGH_PERFORMANCE).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveInputStreamShouldRelyOnPerformingWhenSizeBasedAndSmall() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(BLOB_CONTENT), SIZE_BASED).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + softly.assertThatThrownBy(() -> lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + + @Test + void saveInputStreamShouldRelyOnLowCostWhenSizeBasedAndBig() { + BlobId blobId = hybridBlobStore.save(BucketName.DEFAULT, new ByteArrayInputStream(TWELVE_MEGABYTES), SIZE_BASED).block(); + + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(lowCostBlobStore.read(BucketName.DEFAULT, blobId)) + .satisfies(Throwing.consumer(inputStream -> assertThat(inputStream.read()).isGreaterThan(0))); + softly.assertThatThrownBy(() -> highPerformanceBlobStore.read(BucketName.DEFAULT, blobId)) + .isInstanceOf(ObjectNotFoundException.class); + }); + } + } + + @Nested + class LowCostSaveThrowsExceptionDirectly { + @Test + void saveShouldFailWhenException() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new ThrowingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + + assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block()) + .isInstanceOf(RuntimeException.class); + } + + @Test + void saveInputStreamShouldFailWhenException() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new ThrowingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + + assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block()) + .isInstanceOf(RuntimeException.class); + } + } + + @Nested + class LowCostSaveCompletesExceptionally { + + @Test + void saveShouldFailWhenLowCostCompletedExceptionally() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new FailingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + + assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block()) + .isInstanceOf(RuntimeException.class); + } + + @Test + void saveInputStreamShouldFallBackToPerformingWhenLowCostCompletedExceptionally() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new FailingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + + assertThatThrownBy(() -> hybridBlobStore.save(hybridBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LOW_COST).block()) + .isInstanceOf(RuntimeException.class); + } + + } + + @Nested + class LowCostReadThrowsExceptionDirectly { + + @Test + void readShouldReturnFallbackToPerformingWhenLowCostGotException() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new ThrowingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + } + + @Test + void readBytesShouldReturnFallbackToPerformingWhenLowCostGotException() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new ThrowingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block()) + .isEqualTo(BLOB_CONTENT); + } + + } + + @Nested + class LowCostReadCompletesExceptionally { + + @Test + void readShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new FailingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + } + + @Test + void readBytesShouldReturnFallbackToPerformingWhenLowCostCompletedExceptionally() { + MemoryBlobStore highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); + HybridBlobStore hybridBlobStore = HybridBlobStore.builder() + .lowCost(new FailingBlobStore()) + .highPerformance(highPerformanceBlobStore) + .build(); + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block()) + .isEqualTo(BLOB_CONTENT); + } + } + + @Test + void readShouldReturnFromLowCostWhenAvailable() { + BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + } + + @Test + void readShouldReturnFromPerformingWhenLowCostNotAvailable() { + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.read(hybridBlobStore.getDefaultBucketName(), blobId)) + .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); + } + + @Test + void readBytesShouldReturnFromLowCostWhenAvailable() { + BlobId blobId = lowCostBlobStore.save(lowCostBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.readBytes(lowCostBlobStore.getDefaultBucketName(), blobId).block()) + .isEqualTo(BLOB_CONTENT); + } + + @Test + void readBytesShouldReturnFromPerformingWhenLowCostNotAvailable() { + BlobId blobId = highPerformanceBlobStore.save(hybridBlobStore.getDefaultBucketName(), BLOB_CONTENT, LOW_COST).block(); + + assertThat(hybridBlobStore.readBytes(hybridBlobStore.getDefaultBucketName(), blobId).block()) + .isEqualTo(BLOB_CONTENT); + } + + @Test + void deleteBucketShouldDeleteBothLowCostAndPerformingBuckets() { + BlobId blobId1 = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + BlobId blobId2 = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + hybridBlobStore.deleteBucket(BucketName.DEFAULT).block(); + + assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block()) + .isInstanceOf(ObjectStoreException.class); + assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteBucketShouldDeleteLowCostBucketEvenWhenPerformingDoesNotExist() { + BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + hybridBlobStore.deleteBucket(BucketName.DEFAULT).block(); + + assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteBucketShouldDeletePerformingBucketEvenWhenLowCostDoesNotExist() { + BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + hybridBlobStore.deleteBucket(BucketName.DEFAULT).block(); + + assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteBucketShouldNotThrowWhenLowCostAndPerformingBucketsDoNotExist() { + assertThatCode(() -> hybridBlobStore.deleteBucket(BucketName.DEFAULT).block()) + .doesNotThrowAnyException(); + } + + @Test + void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() { + lowCostBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("lowCost")); + highPerformanceBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("highPerformance")); + hybridBlobStore = HybridBlobStore.builder() + .lowCost(lowCostBlobStore) + .highPerformance(highPerformanceBlobStore) + .build(); + + assertThatThrownBy(() -> hybridBlobStore.getDefaultBucketName()) + .isInstanceOf(IllegalStateException.class); + } + + @Test + void deleteShouldDeleteBothLowCostAndPerformingBlob() { + BlobId blobId1 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + BlobId blobId2 = hybridBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, HIGH_PERFORMANCE).block(); + + hybridBlobStore.delete(BucketName.DEFAULT, blobId1).block(); + + assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId1).block()) + .isInstanceOf(ObjectStoreException.class); + assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId2).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteShouldDeleteLowCostBlobEvenWhenPerformingDoesNotExist() { + BlobId blobId = lowCostBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + hybridBlobStore.delete(BucketName.DEFAULT, blobId).block(); + + assertThatThrownBy(() -> lowCostBlobStore.readBytes(BucketName.DEFAULT, blobId).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteShouldDeletePerformingBlobEvenWhenLowCostDoesNotExist() { + BlobId blobId = highPerformanceBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LOW_COST).block(); + + hybridBlobStore.delete(BucketName.DEFAULT, blobId).block(); + + assertThatThrownBy(() -> highPerformanceBlobStore.readBytes(BucketName.DEFAULT, blobId).block()) + .isInstanceOf(ObjectStoreException.class); + } + + @Test + void deleteShouldNotThrowWhenLowCostAndPerformingBlobsDoNotExist() { + assertThatCode(() -> hybridBlobStore.delete(BucketName.DEFAULT, blobIdFactory().randomId()).block()) + .doesNotThrowAnyException(); + } +} diff --git a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java b/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java deleted file mode 100644 index e8ccd81..0000000 --- a/server/blob/blob-union/src/test/java/org/apache/james/blob/union/UnionBlobStoreTest.java +++ /dev/null @@ -1,604 +0,0 @@ -/**************************************************************** - * Licensed to the Apache Software Foundation (ASF) under one * - * or more contributor license agreements. See the NOTICE file * - * distributed with this work for additional information * - * regarding copyright ownership. The ASF licenses this file * - * to you under the Apache License, Version 2.0 (the * - * "License"); you may not use this file except in compliance * - * with the License. You may obtain a copy of the License at * - * * - * http://www.apache.org/licenses/LICENSE-2.0 * - * * - * Unless required by applicable law or agreed to in writing, * - * software distributed under the License is distributed on an * - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * - * KIND, either express or implied. See the License for the * - * specific language governing permissions and limitations * - * under the License. * - ****************************************************************/ - -package org.apache.james.blob.union; - -import static org.apache.james.blob.api.BlobStore.StoragePolicy.LowCost; -import static org.apache.james.blob.api.BlobStore.StoragePolicy.LowCost; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.io.ByteArrayInputStream; -import java.io.InputStream; -import java.io.PushbackInputStream; -import java.util.List; -import java.util.function.Function; -import java.util.stream.Stream; - -import org.apache.james.blob.api.BlobId; -import org.apache.james.blob.api.BlobStore; -import org.apache.james.blob.api.BlobStoreContract; -import org.apache.james.blob.api.BucketName; -import org.apache.james.blob.api.HashBlobId; -import org.apache.james.blob.api.ObjectStoreException; -import org.apache.james.blob.memory.MemoryBlobStore; -import org.apache.james.util.StreamUtils; -import org.assertj.core.api.SoftAssertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; - -import com.google.common.base.MoreObjects; -import com.google.common.collect.ImmutableList; - -import reactor.core.publisher.Mono; - -class UnionBlobStoreTest implements BlobStoreContract { - - private static class FailingBlobStore implements BlobStore { - @Override - public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public BucketName getDefaultBucketName() { - return BucketName.DEFAULT; - } - - @Override - public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public InputStream read(BucketName bucketName, BlobId blobId) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public Mono<Void> deleteBucket(BucketName bucketName) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public Mono<Void> delete(BucketName bucketName, BlobId blobId) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .toString(); - } - } - - private static class ThrowingBlobStore implements BlobStore { - - @Override - public Mono<BlobId> save(BucketName bucketName, byte[] data, StoragePolicy storagePolicy) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public Mono<BlobId> save(BucketName bucketName, String data, StoragePolicy storagePolicy) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public BucketName getDefaultBucketName() { - return BucketName.DEFAULT; - } - - @Override - public Mono<BlobId> save(BucketName bucketName, InputStream data, StoragePolicy storagePolicy) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public Mono<byte[]> readBytes(BucketName bucketName, BlobId blobId) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public InputStream read(BucketName bucketName, BlobId blobId) { - throw new RuntimeException("broken everywhere"); - } - - @Override - public Mono<Void> deleteBucket(BucketName bucketName) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public Mono<Void> delete(BucketName bucketName, BlobId blobId) { - return Mono.error(new RuntimeException("broken everywhere")); - } - - @Override - public String toString() { - return MoreObjects.toStringHelper(this) - .toString(); - } - } - - private static final HashBlobId.Factory BLOB_ID_FACTORY = new HashBlobId.Factory(); - private static final String STRING_CONTENT = "blob content"; - private static final byte [] BLOB_CONTENT = STRING_CONTENT.getBytes(); - - private MemoryBlobStore currentBlobStore; - private MemoryBlobStore legacyBlobStore; - private UnionBlobStore unionBlobStore; - - @BeforeEach - void setup() { - currentBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - unionBlobStore = UnionBlobStore.builder() - .current(currentBlobStore) - .legacy(legacyBlobStore) - .build(); - } - - @Override - public BlobStore testee() { - return unionBlobStore; - } - - @Override - public BlobId.Factory blobIdFactory() { - return BLOB_ID_FACTORY; - } - - @Nested - class CurrentSaveThrowsExceptionDirectly { - - @Test - void saveShouldFallBackToLegacyWhenCurrentGotException() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - }); - } - - @Test - void saveInputStreamShouldFallBackToLegacyWhenCurrentGotException() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block(); - - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - }); - } - } - - @Nested - class CurrentSaveCompletesExceptionally { - - @Test - void saveShouldFallBackToLegacyWhenCurrentCompletedExceptionally() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - }); - } - - @Test - void saveInputStreamShouldFallBackToLegacyWhenCurrentCompletedExceptionally() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block(); - - SoftAssertions.assertSoftly(softly -> { - softly.assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - softly.assertThat(legacyBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - }); - } - - } - - @Nested - class CurrentReadThrowsExceptionDirectly { - - @Test - void readShouldReturnFallbackToLegacyWhenCurrentGotException() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - } - - @Test - void readBytesShouldReturnFallbackToLegacyWhenCurrentGotException() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - } - - @Nested - class CurrentReadCompletesExceptionally { - - @Test - void readShouldReturnFallbackToLegacyWhenCurrentCompletedExceptionally() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - } - - @Test - void readBytesShouldReturnFallbackToLegacyWhenCurrentCompletedExceptionally() { - MemoryBlobStore legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY); - UnionBlobStore unionBlobStore = UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(legacyBlobStore) - .build(); - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - } - - @TestInstance(TestInstance.Lifecycle.PER_CLASS) - @Nested - class CurrentAndLegacyCouldNotComplete { - - - Stream<Function<UnionBlobStore, Mono<?>>> blobStoreOperationsReturnFutures() { - return Stream.of( - blobStore -> blobStore.save(blobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost), - blobStore -> blobStore.save(blobStore.getDefaultBucketName(), STRING_CONTENT, LowCost), - blobStore -> blobStore.save(blobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost), - blobStore -> blobStore.readBytes(blobStore.getDefaultBucketName(), BLOB_ID_FACTORY.randomId())); - } - - Stream<Function<UnionBlobStore, InputStream>> blobStoreOperationsNotReturnFutures() { - return Stream.of( - blobStore -> blobStore.read(blobStore.getDefaultBucketName(), BLOB_ID_FACTORY.randomId())); - } - - Stream<Arguments> blobStoresCauseReturnExceptionallyFutures() { - List<UnionBlobStore> futureThrowingUnionBlobStores = ImmutableList.of( - UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(new FailingBlobStore()) - .build(), - UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(new ThrowingBlobStore()) - .build(), - UnionBlobStore.builder() - .current(new FailingBlobStore()) - .legacy(new FailingBlobStore()) - .build()); - - return blobStoreOperationsReturnFutures() - .flatMap(blobStoreFunction -> futureThrowingUnionBlobStores - .stream() - .map(blobStore -> Arguments.of(blobStore, blobStoreFunction))); - } - - Stream<Arguments> blobStoresCauseThrowExceptions() { - UnionBlobStore throwingUnionBlobStore = UnionBlobStore.builder() - .current(new ThrowingBlobStore()) - .legacy(new ThrowingBlobStore()) - .build(); - - return StreamUtils.flatten( - blobStoreOperationsReturnFutures() - .map(blobStoreFunction -> Arguments.of(throwingUnionBlobStore, blobStoreFunction)), - blobStoreOperationsNotReturnFutures() - .map(blobStoreFunction -> Arguments.of(throwingUnionBlobStore, blobStoreFunction))); - } - - @ParameterizedTest - @MethodSource("blobStoresCauseThrowExceptions") - void operationShouldThrow(UnionBlobStore blobStoreThrowsException, - Function<UnionBlobStore, Mono<?>> blobStoreOperation) { - assertThatThrownBy(() -> blobStoreOperation.apply(blobStoreThrowsException).block()) - .isInstanceOf(RuntimeException.class); - } - - @ParameterizedTest - @MethodSource("blobStoresCauseReturnExceptionallyFutures") - void operationShouldReturnExceptionallyFuture(UnionBlobStore blobStoreReturnsExceptionallyFuture, - Function<UnionBlobStore, Mono<?>> blobStoreOperation) { - Mono<?> mono = blobStoreOperation.apply(blobStoreReturnsExceptionallyFuture); - assertThatThrownBy(mono::block).isInstanceOf(RuntimeException.class); - } - } - - @Test - void readShouldReturnFromCurrentWhenAvailable() { - BlobId blobId = currentBlobStore.save(currentBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - } - - @Test - void readShouldReturnFromLegacyWhenCurrentNotAvailable() { - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.read(unionBlobStore.getDefaultBucketName(), blobId)) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - } - - @Test - void readBytesShouldReturnFromCurrentWhenAvailable() { - BlobId blobId = currentBlobStore.save(currentBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - @Test - void readBytesShouldReturnFromLegacyWhenCurrentNotAvailable() { - BlobId blobId = legacyBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(unionBlobStore.readBytes(unionBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - @Test - void saveShouldWriteToCurrent() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - @Test - void saveShouldNotWriteToLegacy() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), BLOB_CONTENT, LowCost).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void saveStringShouldWriteToCurrent() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), STRING_CONTENT, LowCost).block(); - - assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - @Test - void saveStringShouldNotWriteToLegacy() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), STRING_CONTENT, LowCost).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void saveInputStreamShouldWriteToCurrent() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block(); - - assertThat(currentBlobStore.readBytes(currentBlobStore.getDefaultBucketName(), blobId).block()) - .isEqualTo(BLOB_CONTENT); - } - - @Test - void saveInputStreamShouldNotWriteToLegacy() { - BlobId blobId = unionBlobStore.save(unionBlobStore.getDefaultBucketName(), new ByteArrayInputStream(BLOB_CONTENT), LowCost).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(legacyBlobStore.getDefaultBucketName(), blobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void streamHasContentShouldReturnTrueWhenStreamHasContent() throws Exception { - PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(BLOB_CONTENT)); - - assertThat(unionBlobStore.streamHasContent(pushBackIS)) - .isTrue(); - } - - @Test - void streamHasContentShouldReturnFalseWhenStreamHasNoContent() throws Exception { - PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0])); - - assertThat(unionBlobStore.streamHasContent(pushBackIS)) - .isFalse(); - } - - @Test - void streamHasContentShouldNotThrowWhenStreamHasNoContent() { - PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0])); - - assertThatCode(() -> unionBlobStore.streamHasContent(pushBackIS)) - .doesNotThrowAnyException(); - } - - @Test - void streamHasContentShouldNotDrainPushBackStreamContent() throws Exception { - PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(BLOB_CONTENT)); - unionBlobStore.streamHasContent(pushBackIS); - - assertThat(pushBackIS) - .hasSameContentAs(new ByteArrayInputStream(BLOB_CONTENT)); - } - - @Test - void streamHasContentShouldKeepStreamEmptyWhenStreamIsEmpty() throws Exception { - PushbackInputStream pushBackIS = new PushbackInputStream(new ByteArrayInputStream(new byte[0])); - unionBlobStore.streamHasContent(pushBackIS); - - assertThat(pushBackIS) - .hasSameContentAs(new ByteArrayInputStream(new byte[0])); - } - - @Test - void deleteBucketShouldDeleteBothCurrentAndLegacyBuckets() { - BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.deleteBucket(BucketName.DEFAULT).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteBucketShouldDeleteCurrentBucketEvenWhenLegacyDoesNotExist() { - BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.deleteBucket(BucketName.DEFAULT).block(); - - assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteBucketShouldDeleteLegacyBucketEvenWhenCurrentDoesNotExist() { - BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.deleteBucket(BucketName.DEFAULT).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteBucketShouldNotThrowWhenCurrentAndLegacyBucketsDoNotExist() { - assertThatCode(() -> unionBlobStore.deleteBucket(BucketName.DEFAULT).block()) - .doesNotThrowAnyException(); - } - - @Test - void getDefaultBucketNameShouldThrowWhenBlobStoreDontShareTheSameDefaultBucketName() { - currentBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("current")); - legacyBlobStore = new MemoryBlobStore(BLOB_ID_FACTORY, BucketName.of("legacy")); - unionBlobStore = UnionBlobStore.builder() - .current(currentBlobStore) - .legacy(legacyBlobStore) - .build(); - - assertThatThrownBy(() -> unionBlobStore.getDefaultBucketName()) - .isInstanceOf(IllegalStateException.class); - } - - @Test - void deleteShouldDeleteBothCurrentAndLegacyBlob() { - BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.delete(BucketName.DEFAULT, currentBlobId).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteShouldDeleteCurrentBlobEvenWhenLegacyDoesNotExist() { - BlobId currentBlobId = currentBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.delete(BucketName.DEFAULT, currentBlobId).block(); - - assertThatThrownBy(() -> currentBlobStore.readBytes(BucketName.DEFAULT, currentBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteShouldDeleteLegacyBlobEvenWhenCurrentDoesNotExist() { - BlobId legacyBlobId = legacyBlobStore.save(BucketName.DEFAULT, BLOB_CONTENT, LowCost).block(); - - unionBlobStore.delete(BucketName.DEFAULT, legacyBlobId).block(); - - assertThatThrownBy(() -> legacyBlobStore.readBytes(BucketName.DEFAULT, legacyBlobId).block()) - .isInstanceOf(ObjectStoreException.class); - } - - @Test - void deleteShouldNotThrowWhenCurrentAndLegacyBlobsDoNotExist() { - assertThatCode(() -> unionBlobStore.delete(BucketName.DEFAULT, blobIdFactory().randomId()).block()) - .doesNotThrowAnyException(); - } -} diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java index bde669c..84bc7fb 100644 --- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java +++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfiguration.java @@ -34,7 +34,7 @@ public class BlobStoreChoosingConfiguration { public enum BlobStoreImplName { CASSANDRA("cassandra"), OBJECTSTORAGE("objectstorage"), - UNION("union"); + HYBRID("hybrid"); static String supportedImplNames() { return Stream.of(BlobStoreImplName.values()) @@ -82,8 +82,8 @@ public class BlobStoreChoosingConfiguration { return new BlobStoreChoosingConfiguration(BlobStoreImplName.OBJECTSTORAGE); } - public static BlobStoreChoosingConfiguration union() { - return new BlobStoreChoosingConfiguration(BlobStoreImplName.UNION); + public static BlobStoreChoosingConfiguration hybrid() { + return new BlobStoreChoosingConfiguration(BlobStoreImplName.HYBRID); } private final BlobStoreImplName implementation; diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java index 7011d1e..2ab354d 100644 --- a/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java +++ b/server/container/guice/cassandra-rabbitmq-guice/src/main/java/org/apache/james/modules/blobstore/BlobStoreChoosingModule.java @@ -33,7 +33,7 @@ import org.apache.james.blob.api.MetricableBlobStore; import org.apache.james.blob.cassandra.CassandraBlobModule; import org.apache.james.blob.cassandra.CassandraBlobStore; import org.apache.james.blob.objectstorage.ObjectStorageBlobStore; -import org.apache.james.blob.union.UnionBlobStore; +import org.apache.james.blob.union.HybridBlobStore; import org.apache.james.modules.mailbox.ConfigurationComponent; import org.apache.james.modules.objectstorage.ObjectStorageDependenciesModule; import org.apache.james.utils.PropertiesProvider; @@ -82,10 +82,10 @@ public class BlobStoreChoosingModule extends AbstractModule { return swiftBlobStoreProvider.get(); case CASSANDRA: return cassandraBlobStoreProvider.get(); - case UNION: - return UnionBlobStore.builder() - .current(swiftBlobStoreProvider.get()) - .legacy(cassandraBlobStoreProvider.get()) + case HYBRID: + return HybridBlobStore.builder() + .lowCost(swiftBlobStoreProvider.get()) + .highPerformance(cassandraBlobStoreProvider.get()) .build(); default: throw new RuntimeException(String.format("can not get the right blobstore provider with configuration %s", diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java index a5e1eb1..2ef718d 100644 --- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java +++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingConfigurationTest.java @@ -31,7 +31,7 @@ class BlobStoreChoosingConfigurationTest { private static final String OBJECT_STORAGE = "objectstorage"; private static final String CASSANDRA = "cassandra"; - private static final String UNION = "union"; + private static final String HYBRID = "hybrid"; @Test void shouldMatchBeanContract() { @@ -45,7 +45,7 @@ class BlobStoreChoosingConfigurationTest { assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration)) .isInstanceOf(IllegalStateException.class) - .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union"); + .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid"); } @Test @@ -55,7 +55,7 @@ class BlobStoreChoosingConfigurationTest { assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration)) .isInstanceOf(IllegalStateException.class) - .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union"); + .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid"); } @Test @@ -65,7 +65,7 @@ class BlobStoreChoosingConfigurationTest { assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration)) .isInstanceOf(IllegalStateException.class) - .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, union"); + .hasMessage("implementation property is missing please use one of supported values in: cassandra, objectstorage, hybrid"); } @Test @@ -75,7 +75,7 @@ class BlobStoreChoosingConfigurationTest { assertThatThrownBy(() -> BlobStoreChoosingConfiguration.from(configuration)) .isInstanceOf(IllegalArgumentException.class) - .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage, union"); + .hasMessage("un_supported is not a valid name of BlobStores, please use one of supported values in: cassandra, objectstorage, hybrid"); } @Test @@ -93,13 +93,13 @@ class BlobStoreChoosingConfigurationTest { @Test void fromShouldReturnConfigurationWhenBlobStoreImplIsUnion() { PropertiesConfiguration configuration = new PropertiesConfiguration(); - configuration.addProperty("implementation", UNION); + configuration.addProperty("implementation", HYBRID); assertThat( BlobStoreChoosingConfiguration.from(configuration) .getImplementation() .getName()) - .isEqualTo(UNION); + .isEqualTo(HYBRID); } @Test diff --git a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java index ce4354b..4e04a5b 100644 --- a/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java +++ b/server/container/guice/cassandra-rabbitmq-guice/src/test/java/org/apache/james/modules/blobstore/BlobStoreChoosingModuleTest.java @@ -27,7 +27,7 @@ import org.apache.commons.configuration2.PropertiesConfiguration; import org.apache.james.FakePropertiesProvider; import org.apache.james.blob.cassandra.CassandraBlobStore; import org.apache.james.blob.objectstorage.ObjectStorageBlobStore; -import org.apache.james.blob.union.UnionBlobStore; +import org.apache.james.blob.union.HybridBlobStore; import org.apache.james.modules.blobstore.BlobStoreChoosingConfiguration.BlobStoreImplName; import org.apache.james.modules.mailbox.ConfigurationComponent; import org.junit.jupiter.api.Test; @@ -105,16 +105,16 @@ class BlobStoreChoosingModuleTest { } @Test - void provideChoosingConfigurationShouldReturnUnionConfigurationWhenConfigurationImplIsUnion() throws Exception { + void provideChoosingConfigurationShouldReturnHybridConfigurationWhenConfigurationImplIsHybrid() throws Exception { BlobStoreChoosingModule module = new BlobStoreChoosingModule(); PropertiesConfiguration configuration = new PropertiesConfiguration(); - configuration.addProperty("implementation", BlobStoreImplName.UNION.getName()); + configuration.addProperty("implementation", BlobStoreImplName.HYBRID.getName()); FakePropertiesProvider propertyProvider = FakePropertiesProvider.builder() .register(ConfigurationComponent.NAME, configuration) .build(); assertThat(module.provideChoosingConfiguration(propertyProvider)) - .isEqualTo(BlobStoreChoosingConfiguration.union()); + .isEqualTo(BlobStoreChoosingConfiguration.hybrid()); } @Test @@ -149,11 +149,11 @@ class BlobStoreChoosingModuleTest { } @Test - void provideBlobStoreShouldReturnUnionBlobStoreWhenUnionConfigured() { + void provideBlobStoreShouldReturnHybridBlobStoreWhenHybridConfigured() { BlobStoreChoosingModule module = new BlobStoreChoosingModule(); - assertThat(module.provideBlobStore(BlobStoreChoosingConfiguration.union(), + assertThat(module.provideBlobStore(BlobStoreChoosingConfiguration.hybrid(), CASSANDRA_BLOBSTORE_PROVIDER, OBJECT_STORAGE_BLOBSTORE_PROVIDER)) - .isInstanceOf(UnionBlobStore.class); + .isInstanceOf(HybridBlobStore.class); } } \ No newline at end of file --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
