Skip to content
This repository was archived by the owner on Dec 27, 2023. It is now read-only.

Commit 1435113

Browse files
authored
fix: fix CI to run tests of EtcdStorageITCase (#380)
* Fix some EtcdStorage methods to pass failed tests * Introduce EtcdMeta, metadata class for EtcdStorage * Enable EtcdStorageITCase and pull automatically Etcd docker image before all tests Close: #309
1 parent 0ba492e commit 1435113

File tree

4 files changed

+157
-33
lines changed

4 files changed

+157
-33
lines changed

pom.xml

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -240,20 +240,12 @@ SOFTWARE.
240240
<plugins>
241241
<plugin>
242242
<groupId>org.apache.maven.plugins</groupId>
243-
<artifactId>maven-surefire-plugin</artifactId>
244-
<version>2.22.2</version>
245-
<dependencies>
246-
<dependency>
247-
<groupId>org.apache.maven.surefire</groupId>
248-
<artifactId>surefire-junit-platform</artifactId>
249-
<version>2.22.2</version>
250-
</dependency>
251-
</dependencies>
252-
</plugin>
253-
<plugin>
254243
<artifactId>maven-failsafe-plugin</artifactId>
244+
<version>3.0.0-M5</version>
255245
<configuration>
256-
<parallel>none</parallel>
246+
<forkCount>1</forkCount>
247+
<reuseForks>true</reuseForks>
248+
<useSystemClassLoader>true</useSystemClassLoader>
257249
</configuration>
258250
<executions>
259251
<execution>
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* The MIT License (MIT) Copyright (c) 2020-2021 artipie.com
3+
* https://github.com/artipie/asto/LICENSE.txt
4+
*/
5+
package com.artipie.asto.etcd;
6+
7+
import com.artipie.asto.Meta;
8+
import io.etcd.jetcd.KeyValue;
9+
import java.time.Instant;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
13+
/**
14+
* Metadata from Etcd key value.
15+
* @since 1.9
16+
*/
17+
final class EtcdMeta implements Meta {
18+
19+
/**
20+
* Key value.
21+
*/
22+
private final KeyValue kvs;
23+
24+
/**
25+
* New metadata.
26+
* @param kvs Key value
27+
*/
28+
EtcdMeta(final KeyValue kvs) {
29+
this.kvs = kvs;
30+
}
31+
32+
@Override
33+
public <T> T read(final ReadOperator<T> opr) {
34+
final Map<String, String> raw = new HashMap<>();
35+
Meta.OP_SIZE.put(raw, Long.valueOf(this.kvs.getValue().size()));
36+
Meta.OP_CREATED_AT.put(raw, Instant.ofEpochMilli(this.kvs.getCreateRevision()));
37+
Meta.OP_UPDATED_AT.put(raw, Instant.ofEpochMilli(this.kvs.getModRevision()));
38+
return opr.take(raw);
39+
}
40+
}

src/main/java/com/artipie/asto/etcd/EtcdStorage.java

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@
1515
import com.artipie.asto.ext.PublisherAs;
1616
import io.etcd.jetcd.ByteSequence;
1717
import io.etcd.jetcd.Client;
18+
import io.etcd.jetcd.KeyValue;
19+
import io.etcd.jetcd.kv.GetResponse;
1820
import io.etcd.jetcd.options.GetOption;
1921
import io.etcd.jetcd.options.GetOption.SortOrder;
2022
import io.vavr.NotImplementedError;
2123
import java.nio.charset.StandardCharsets;
2224
import java.util.Collection;
25+
import java.util.Comparator;
2326
import java.util.concurrent.CompletableFuture;
2427
import java.util.concurrent.CompletionStage;
2528
import java.util.function.Function;
@@ -36,6 +39,7 @@
3639
* </p>
3740
* @since 1.0
3841
* @checkstyle ReturnCountCheck (200 lines)
42+
* @checkstyle ClassDataAbstractionCouplingCheck (500 lines)
3943
*/
4044
public final class EtcdStorage implements Storage {
4145

@@ -44,6 +48,12 @@ public final class EtcdStorage implements Storage {
4448
*/
4549
private static final long MAX_SIZE = 1024 * 1024 * 10;
4650

51+
/**
52+
* Etcd root key.
53+
*/
54+
private static final ByteSequence ETCD_ROOT_KEY =
55+
ByteSequence.from("\0", StandardCharsets.UTF_8);
56+
4757
/**
4858
* Etcd client.
4959
*/
@@ -67,13 +77,27 @@ public CompletableFuture<Boolean> exists(final Key key) {
6777

6878
@Override
6979
public CompletableFuture<Collection<Key>> list(final Key prefix) {
70-
return this.client.getKVClient().get(
71-
keyToSeq(prefix),
72-
GetOption.newBuilder()
73-
.withKeysOnly(true)
74-
.withSortOrder(SortOrder.ASCEND)
75-
.build()
76-
).thenApply(
80+
final CompletableFuture<GetResponse> future;
81+
if (prefix == Key.ROOT) {
82+
future = this.client.getKVClient().get(
83+
EtcdStorage.ETCD_ROOT_KEY,
84+
GetOption.newBuilder()
85+
.withKeysOnly(true)
86+
.withSortOrder(SortOrder.ASCEND)
87+
.withRange(EtcdStorage.ETCD_ROOT_KEY)
88+
.build()
89+
);
90+
} else {
91+
future = this.client.getKVClient().get(
92+
keyToSeq(prefix),
93+
GetOption.newBuilder()
94+
.withKeysOnly(true)
95+
.withSortOrder(SortOrder.ASCEND)
96+
.isPrefix(true)
97+
.build()
98+
);
99+
}
100+
return future.thenApply(
77101
rsp -> rsp.getKvs().stream()
78102
.map(kv -> new String(kv.getKey().getBytes(), StandardCharsets.UTF_8))
79103
.map(str -> new Key.From(str))
@@ -108,14 +132,22 @@ public CompletableFuture<Void> move(final Key source, final Key destination) {
108132

109133
@Override
110134
public CompletableFuture<? extends Meta> metadata(final Key key) {
111-
return CompletableFuture.completedFuture(Meta.EMPTY);
135+
return this.client.getKVClient().get(keyToSeq(key)).thenApply(
136+
rsp -> rsp.getKvs().stream().max(
137+
Comparator.comparingLong(KeyValue::getVersion)
138+
)
139+
).thenApply(
140+
kv -> kv.orElseThrow(
141+
() -> new ValueNotFoundException(key)
142+
)
143+
).thenApply(kv -> new EtcdMeta(kv));
112144
}
113145

114146
@Override
115147
public CompletableFuture<Content> value(final Key key) {
116148
return this.client.getKVClient().get(keyToSeq(key)).thenApply(
117149
rsp -> rsp.getKvs().stream().max(
118-
(left, right) -> Long.compare(left.getVersion(), right.getVersion())
150+
Comparator.comparingLong(KeyValue::getVersion)
119151
)
120152
).thenApply(
121153
kv -> kv.orElseThrow(

src/test/java/com/artipie/asto/etcd/EtcdStorageITCase.java

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,54 +5,114 @@
55

66
package com.artipie.asto.etcd;
77

8+
import com.artipie.asto.Content;
89
import com.artipie.asto.Key;
910
import com.artipie.asto.Storage;
1011
import com.artipie.asto.blocking.BlockingStorage;
12+
import com.github.dockerjava.api.DockerClient;
1113
import io.etcd.jetcd.Client;
12-
import io.etcd.jetcd.launcher.EtcdCluster;
14+
import io.etcd.jetcd.launcher.EtcdContainer;
1315
import io.etcd.jetcd.test.EtcdClusterExtension;
16+
import java.nio.charset.StandardCharsets;
1417
import java.util.concurrent.CompletionException;
1518
import org.hamcrest.MatcherAssert;
1619
import org.hamcrest.Matchers;
1720
import org.hamcrest.core.IsEqual;
21+
import org.junit.jupiter.api.AfterAll;
1822
import org.junit.jupiter.api.Assertions;
23+
import org.junit.jupiter.api.BeforeAll;
1924
import org.junit.jupiter.api.BeforeEach;
20-
import org.junit.jupiter.api.Disabled;
2125
import org.junit.jupiter.api.Test;
22-
import org.junit.jupiter.api.extension.RegisterExtension;
26+
import org.junit.jupiter.api.condition.DisabledOnOs;
27+
import org.junit.jupiter.api.condition.OS;
28+
import org.testcontainers.DockerClientFactory;
2329

2430
/**
2531
* Test case for etcd-storage.
2632
* @since 1.0
27-
* @todo #306:90min The test is disabled,
28-
* the CI can't start ETCD extension which depends on testcontainer with
29-
* etcd Docker image. Let's fix this issue on CI and enable the test.
3033
* @todo #306:90min Add etcd storage to StorageExtension to run all common
3134
* tests on EtcdStorage too. It could be not an easy task, since etcd
3235
* test depends on etcd clust with at least one node. For this test
3336
* it starts using testcontainers and `EtcdCluster` junit extension.
37+
* @todo #309:30min Run Etcd in windows containers while testing on windows.
38+
* Currently, when we try to run integration tests based on testcontainers within a platform
39+
* windows, we notice that Etcd container (presently based on Linux) doesn't work. We have to build
40+
* and publish an Etcd docker image based on windows to avoid this issue.
41+
* Please, build an Etcd image for windows (version 3.5.1) and write tests so as to detect before
42+
* running integration tests, the type of platform (linux or windows) in order to pull the right
43+
* docker image. After that, enable the test below for windows by removing
44+
* {@code @DisabledOnOs(OS.WINDOWS)}.
3445
*/
35-
@Disabled
36-
public final class EtcdStorageITCase {
46+
@SuppressWarnings("PMD.AvoidDuplicateLiterals")
47+
@DisabledOnOs(OS.WINDOWS)
48+
final class EtcdStorageITCase {
3749

3850
/**
3951
* Test cluster.
4052
*/
41-
@RegisterExtension
42-
static final EtcdCluster ETCD = new EtcdClusterExtension("test-etcd", 1);
53+
static final EtcdClusterExtension ETCD = new EtcdClusterExtension(
54+
"test-etcd",
55+
1,
56+
false,
57+
"--data-dir",
58+
"/data.etcd0"
59+
);
4360

4461
/**
4562
* Storage.
4663
*/
4764
private Storage storage;
4865

66+
@BeforeAll
67+
static void beforeAll() throws InterruptedException {
68+
final DockerClient client = DockerClientFactory.instance().client();
69+
client.pullImageCmd(EtcdContainer.ETCD_DOCKER_IMAGE_NAME)
70+
.start()
71+
.awaitCompletion();
72+
ETCD.start();
73+
}
74+
4975
@BeforeEach
5076
void setUp() {
5177
this.storage = new EtcdStorage(
5278
Client.builder().endpoints(ETCD.getClientEndpoints()).build()
5379
);
5480
}
5581

82+
@AfterAll
83+
static void afterAll() {
84+
ETCD.close();
85+
}
86+
87+
@Test
88+
void listsItems() {
89+
final Key one = new Key.From("one");
90+
final Key two = new Key.From("a/two");
91+
final Key three = new Key.From("a/three");
92+
this.storage.save(
93+
one,
94+
new Content.From("data 1".getBytes(StandardCharsets.UTF_8))
95+
).join();
96+
this.storage.save(
97+
two,
98+
new Content.From("data 2".getBytes(StandardCharsets.UTF_8))
99+
).join();
100+
this.storage.save(
101+
three,
102+
new Content.From("data 3".getBytes(StandardCharsets.UTF_8))
103+
).join();
104+
MatcherAssert.assertThat(
105+
"Should list all items",
106+
new BlockingStorage(this.storage).list(Key.ROOT),
107+
Matchers.hasItems(one, two, three)
108+
);
109+
MatcherAssert.assertThat(
110+
"Should list prefixed items",
111+
new BlockingStorage(this.storage).list(new Key.From("a")),
112+
Matchers.hasItems(two, three)
113+
);
114+
}
115+
56116
@Test
57117
void readAndWrite() {
58118
final Key key = new Key.From("one", "two", "three");
@@ -114,8 +174,8 @@ void failsIfNothingToDelete() {
114174
() -> bsto.delete(key)
115175
);
116176
MatcherAssert.assertThat(
117-
cex.getCause().getMessage(),
118-
Matchers.stringContainsInOrder("Key", key.toString(), "was not found")
177+
cex.getCause().getCause().getMessage(),
178+
new IsEqual<>(String.format("No value for key: %s", key))
119179
);
120180
}
121181
}

0 commit comments

Comments
 (0)