| /* |
| * 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.ignite.internal.vault; |
| |
| import java.io.IOException; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.concurrent.CompletableFuture; |
| import java.util.concurrent.TimeUnit; |
| import java.util.function.Supplier; |
| import java.util.stream.IntStream; |
| import org.apache.ignite.internal.util.Cursor; |
| import org.apache.ignite.lang.ByteArray; |
| import org.junit.jupiter.api.AfterEach; |
| import org.junit.jupiter.api.BeforeEach; |
| import org.junit.jupiter.api.Test; |
| |
| import static java.util.stream.Collectors.toList; |
| import static java.util.stream.Collectors.toMap; |
| import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willBe; |
| import static org.hamcrest.MatcherAssert.assertThat; |
| import static org.hamcrest.Matchers.empty; |
| import static org.hamcrest.Matchers.equalTo; |
| import static org.hamcrest.Matchers.is; |
| |
| /** |
| * Base class for testing {@link VaultService} implementations. |
| */ |
| public abstract class VaultServiceTest { |
| /** */ |
| private static final int TIMEOUT_SECONDS = 1; |
| |
| /** Vault. */ |
| private VaultService vaultService; |
| |
| /** */ |
| @BeforeEach |
| public void setUp() throws IOException { |
| vaultService = getVaultService(); |
| |
| vaultService.start(); |
| } |
| |
| /** */ |
| @AfterEach |
| void tearDown() throws Exception { |
| vaultService.stop(); |
| } |
| |
| /** |
| * Returns the vault service that will be tested. |
| */ |
| protected abstract VaultService getVaultService(); |
| |
| /** |
| * Tests regular behaviour of the {@link VaultService#put} method. |
| */ |
| @Test |
| public void testPut() throws Exception { |
| ByteArray key = getKey(1); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, null)))); |
| |
| byte[] val = getValue(1); |
| |
| doAwait(() -> vaultService.put(key, val)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, val)))); |
| |
| // test idempotency |
| doAwait(() -> vaultService.put(key, val)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, val)))); |
| } |
| |
| /** |
| * Tests that the {@link VaultService#put} method removes the given {@code key} if {@code value} equalTo {@code null}. |
| */ |
| @Test |
| public void testPutWithNull() throws Exception { |
| ByteArray key = getKey(1); |
| |
| byte[] val = getValue(1); |
| |
| doAwait(() -> vaultService.put(key, val)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, val)))); |
| |
| doAwait(() -> vaultService.put(key, null)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, null)))); |
| } |
| |
| /** |
| * Tests regular behaviour of the {@link VaultService#remove} method. |
| */ |
| @Test |
| public void testRemove() throws Exception { |
| ByteArray key = getKey(1); |
| |
| // Remove non-existent value. |
| doAwait(() -> vaultService.remove(key)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, null)))); |
| |
| byte[] val = getValue(1); |
| |
| doAwait(() -> vaultService.put(key, val)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, val)))); |
| |
| // Remove existing value. |
| doAwait(() -> vaultService.remove(key)); |
| |
| assertThat(vaultService.get(key), willBe(equalTo(new VaultEntry(key, null)))); |
| } |
| |
| /** |
| * Tests regular behaviour of the {@link VaultService#putAll} method. |
| */ |
| @Test |
| public void testPutAll() throws Exception { |
| Map<ByteArray, byte[]> batch = IntStream.range(0, 10) |
| .boxed() |
| .collect(toMap(VaultServiceTest::getKey, VaultServiceTest::getValue)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| batch.forEach((k, v) -> assertThat(vaultService.get(k), willBe(equalTo(new VaultEntry(k, v))))); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| batch.forEach((k, v) -> assertThat(vaultService.get(k), willBe(equalTo(new VaultEntry(k, v))))); |
| } |
| |
| /** |
| * Tests that the {@link VaultService#putAll} method will remove keys, which values are {@code null}. |
| */ |
| @Test |
| public void testPutAllWithNull() throws Exception { |
| Map<ByteArray, byte[]> batch = IntStream.range(0, 10) |
| .boxed() |
| .collect(toMap(VaultServiceTest::getKey, VaultServiceTest::getValue)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| batch.forEach((k, v) -> assertThat(vaultService.get(k), willBe(equalTo(new VaultEntry(k, v))))); |
| |
| Map<ByteArray, byte[]> secondBatch = new HashMap<>(); |
| |
| secondBatch.put(getKey(4), getValue(3)); |
| secondBatch.put(getKey(8), getValue(3)); |
| secondBatch.put(getKey(1), null); |
| secondBatch.put(getKey(3), null); |
| |
| doAwait(() -> vaultService.putAll(secondBatch)); |
| |
| assertThat(vaultService.get(getKey(4)), willBe(equalTo(new VaultEntry(getKey(4), getValue(3))))); |
| assertThat(vaultService.get(getKey(8)), willBe(equalTo(new VaultEntry(getKey(8), getValue(3))))); |
| assertThat(vaultService.get(getKey(1)), willBe(equalTo(new VaultEntry(getKey(1), null)))); |
| assertThat(vaultService.get(getKey(3)), willBe(equalTo(new VaultEntry(getKey(3), null)))); |
| } |
| |
| /** |
| * Tests regular behaviour of the {@link VaultService#range} method. |
| */ |
| @Test |
| public void testRange() throws Exception { |
| List<VaultEntry> entries = getRange(0, 10); |
| |
| Map<ByteArray, byte[]> batch = entries.stream().collect(toMap(VaultEntry::key, VaultEntry::value)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| List<VaultEntry> range = range(getKey(3), getKey(7)); |
| |
| assertThat(range, equalTo(getRange(3, 7))); |
| } |
| |
| /** |
| * Tests that the {@link VaultService#range} returns valid entries when passed a larger range, than the available |
| * data. |
| */ |
| @Test |
| public void testRangeBoundaries() throws Exception { |
| List<VaultEntry> entries = getRange(3, 5); |
| |
| Map<ByteArray, byte[]> batch = entries.stream().collect(toMap(VaultEntry::key, VaultEntry::value)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| List<VaultEntry> range = range(getKey(0), getKey(9)); |
| |
| assertThat(range, equalTo(entries)); |
| } |
| |
| /** |
| * Tests that the {@link VaultService#range} upper bound equalTo not included. |
| */ |
| @Test |
| public void testRangeNotIncludedBoundary() throws Exception { |
| List<VaultEntry> entries = getRange(3, 5); |
| |
| Map<ByteArray, byte[]> batch = entries.stream().collect(toMap(VaultEntry::key, VaultEntry::value)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| List<VaultEntry> range = range(getKey(3), getKey(4)); |
| |
| assertThat(range, equalTo(List.of(new VaultEntry(getKey(3), getValue(3))))); |
| } |
| |
| /** |
| * Tests that an empty result equalTo returned when {@link VaultService#range} contains invalid boundaries. |
| */ |
| @Test |
| public void testRangeInvalidBoundaries() throws Exception { |
| Map<ByteArray, byte[]> batch = getRange(3, 5).stream().collect(toMap(VaultEntry::key, VaultEntry::value)); |
| |
| doAwait(() -> vaultService.putAll(batch)); |
| |
| List<VaultEntry> range = range(getKey(4), getKey(1)); |
| |
| assertThat(range, is(empty())); |
| |
| range = range(getKey(4), getKey(4)); |
| |
| assertThat(range, is(empty())); |
| } |
| |
| /** |
| * Creates a test key. |
| */ |
| private static ByteArray getKey(int k) { |
| return new ByteArray("key" + k); |
| } |
| |
| /** |
| * Creates a test value. |
| */ |
| private static byte[] getValue(int v) { |
| return ("val" + v).getBytes(StandardCharsets.UTF_8); |
| } |
| |
| /** |
| * Creates a range of test Vault entries. |
| */ |
| private List<VaultEntry> getRange(int from, int to) { |
| return IntStream.range(from, to) |
| .mapToObj(i -> new VaultEntry(getKey(i), getValue(i))) |
| .collect(toList()); |
| } |
| |
| /** |
| * Performs the given action and waits for the returned future to complete. |
| */ |
| private static void doAwait(Supplier<CompletableFuture<?>> supplier) throws Exception { |
| supplier.get().get(TIMEOUT_SECONDS, TimeUnit.SECONDS); |
| } |
| |
| /** |
| * Exctracts the given range of values from the Vault. |
| */ |
| private List<VaultEntry> range(ByteArray from, ByteArray to) throws Exception { |
| var result = new ArrayList<VaultEntry>(); |
| |
| try (Cursor<VaultEntry> cursor = vaultService.range(from, to)) { |
| cursor.forEach(result::add); |
| } |
| |
| return result; |
| } |
| } |