Skip to content

Commit 45b63b6

Browse files
author
John Plaisted
authored
feat: add a docker implementation for the ES testing framework. (#48)
Also start publishing these jars, which required some minor javadoc cleanup.
1 parent fd3b06a commit 45b63b6

File tree

9 files changed

+351
-32
lines changed

9 files changed

+351
-32
lines changed

settings.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ include 'dao-impl:neo4j-dao'
88
include 'restli-resources'
99
include 'testing:core-models-testing'
1010
include 'testing:elasticsearch-dao-integ-testing'
11+
include 'testing:elasticsearch-dao-integ-testing-docker'
1112
include 'testing:test-models'
1213
include 'validators'
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
apply plugin: 'java'
2+
3+
apply from: "$rootDir/gradle/java-publishing.gradle"
4+
5+
dependencies {
6+
compile project(':testing:elasticsearch-dao-integ-testing')
7+
8+
compile externalDependency.assertJ
9+
compile externalDependency.junitJupiterApi
10+
compile externalDependency.junitJupiterParams
11+
compile externalDependency.testContainers
12+
compile externalDependency.testContainersJunit
13+
14+
testRuntimeOnly externalDependency.junitJupiterEngine
15+
16+
testCompile project(':testing:test-models')
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
package com.linkedin.metadata.testing;
2+
3+
import java.net.InetAddress;
4+
import java.net.UnknownHostException;
5+
import javax.annotation.Nonnull;
6+
import org.apache.http.HttpHost;
7+
import org.apache.http.impl.nio.reactor.IOReactorConfig;
8+
import org.elasticsearch.client.RestClient;
9+
import org.elasticsearch.client.RestClientBuilder;
10+
import org.elasticsearch.client.RestHighLevelClient;
11+
import org.elasticsearch.client.transport.TransportClient;
12+
import org.elasticsearch.common.settings.Settings;
13+
import org.elasticsearch.common.transport.InetSocketTransportAddress;
14+
import org.elasticsearch.transport.client.PreBuiltTransportClient;
15+
import org.testcontainers.containers.GenericContainer;
16+
17+
18+
/**
19+
* Uses the TestContainers framework to launch an Elasticsearch instance using docker.
20+
*/
21+
@ElasticsearchContainerFactory.Implementation
22+
public final class ElasticsearchContainerFactoryDockerImpl implements ElasticsearchContainerFactory {
23+
private static final String IMAGE_NAME = "docker.elastic.co/elasticsearch/elasticsearch:5.6.8";
24+
private static final int HTTP_PORT = 9200;
25+
private static final int TRANSPORT_PORT = 9300;
26+
27+
/**
28+
* Simple implementation that has no extra behavior and is just used to help with the generic typing.
29+
*/
30+
private static final class GenericContainerImpl extends GenericContainer<GenericContainerImpl> {
31+
public GenericContainerImpl(@Nonnull String dockerImageName) {
32+
super(dockerImageName);
33+
}
34+
}
35+
36+
private GenericContainerImpl _container;
37+
38+
@Nonnull
39+
private static RestHighLevelClient buildRestClient(@Nonnull GenericContainerImpl gc) {
40+
final RestClientBuilder builder = RestClient.builder(new HttpHost("localhost", gc.getMappedPort(HTTP_PORT), "http"))
41+
.setHttpClientConfigCallback(httpAsyncClientBuilder -> httpAsyncClientBuilder.setDefaultIOReactorConfig(
42+
IOReactorConfig.custom().setIoThreadCount(1).build()));
43+
44+
builder.setRequestConfigCallback(requestConfigBuilder -> requestConfigBuilder.
45+
setConnectionRequestTimeout(3000));
46+
47+
return new RestHighLevelClient(builder.build());
48+
}
49+
50+
@Nonnull
51+
private static TransportClient buildTransportClient(@Nonnull GenericContainerImpl gc) throws UnknownHostException {
52+
return new PreBuiltTransportClient(
53+
Settings.builder().put("cluster.name", "docker-cluster").build()).addTransportAddress(
54+
new InetSocketTransportAddress(InetAddress.getLoopbackAddress(), gc.getMappedPort(TRANSPORT_PORT)));
55+
}
56+
57+
@Nonnull
58+
@Override
59+
public ElasticsearchConnection start() throws Exception {
60+
if (_container == null) {
61+
_container = new GenericContainerImpl(IMAGE_NAME).withExposedPorts(HTTP_PORT, TRANSPORT_PORT)
62+
.withEnv("xpack.security.enabled", "false");
63+
_container.start();
64+
}
65+
66+
return new ElasticsearchConnection(buildRestClient(_container), buildTransportClient(_container));
67+
}
68+
69+
@Override
70+
public void close() throws Throwable {
71+
if (_container == null) {
72+
return;
73+
}
74+
75+
try {
76+
_container.close();
77+
} finally {
78+
_container = null;
79+
}
80+
}
81+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package com.linkedin.metadata.testing;
2+
3+
import com.linkedin.metadata.dao.SearchResult;
4+
import com.linkedin.metadata.testing.annotations.SearchIndexMappings;
5+
import com.linkedin.metadata.testing.annotations.SearchIndexSettings;
6+
import com.linkedin.metadata.testing.annotations.SearchIndexType;
7+
import com.linkedin.testing.BarSearchDocument;
8+
import com.linkedin.testing.urn.BarUrn;
9+
import org.elasticsearch.action.admin.indices.settings.get.GetSettingsResponse;
10+
import org.elasticsearch.client.IndicesAdminClient;
11+
import org.elasticsearch.common.settings.Settings;
12+
import org.junit.jupiter.api.Order;
13+
import org.junit.jupiter.api.Test;
14+
15+
import static com.linkedin.metadata.testing.asserts.SearchIndexAssert.assertThat;
16+
import static com.linkedin.metadata.testing.asserts.SearchResultAssert.assertThat;
17+
import static org.assertj.core.api.Assertions.assertThat;
18+
19+
20+
@ElasticsearchIntegrationTest
21+
public class ElasticsearchIntegrationTestTest {
22+
@SearchIndexType(BarSearchDocument.class)
23+
public static SearchIndex<BarSearchDocument> classIndex;
24+
25+
@SearchIndexType(BarSearchDocument.class)
26+
@SearchIndexSettings("/settings.json")
27+
@SearchIndexMappings("/mappings.json")
28+
public SearchIndex<BarSearchDocument> searchIndex;
29+
30+
@SearchIndexType(BarSearchDocument.class)
31+
public SearchIndex<BarSearchDocument> secondSearchIndex;
32+
33+
private static String _classIndexName;
34+
private static String _methodIndexName;
35+
36+
@Test
37+
public void staticIndexInjected() {
38+
assertThat(classIndex).isNotNull();
39+
}
40+
41+
@Test
42+
public void instanceIndexesInjected() {
43+
assertThat(searchIndex).isNotNull();
44+
assertThat(secondSearchIndex).isNotNull();
45+
}
46+
47+
@Test
48+
public void uniqueIndexesAreMadeForEachVariable() {
49+
assertThat(classIndex.getName()).isNotEqualTo(searchIndex.getName());
50+
assertThat(searchIndex.getName()).isNotEqualTo(secondSearchIndex.getName());
51+
}
52+
53+
@Test
54+
@Order(1)
55+
public void saveIndexNames() {
56+
// not a real test, values used to test the life cycle later
57+
_classIndexName = classIndex.getName();
58+
_methodIndexName = searchIndex.getName();
59+
}
60+
61+
@Test
62+
@Order(2)
63+
public void staticIndexIsSame() {
64+
assertThat(_classIndexName).isEqualTo(classIndex.getName());
65+
}
66+
67+
@Test
68+
@Order(2)
69+
public void instanceIndexIsDifferent() {
70+
assertThat(_methodIndexName).isNotEqualTo(searchIndex.getName());
71+
}
72+
73+
@Test
74+
@Order(2)
75+
public void instanceIsCleanedUpBetweenMethods() {
76+
// given
77+
final IndicesAdminClient indicesAdminClient = searchIndex.getConnection().getTransportClient().admin().indices();
78+
79+
// when
80+
final boolean exists = indicesAdminClient.prepareExists(_methodIndexName).get().isExists();
81+
82+
// then
83+
assertThat(exists).isFalse();
84+
}
85+
86+
@Test
87+
public void canWriteToIndex() throws Exception {
88+
// given
89+
final BarSearchDocument searchDocument = new BarSearchDocument().setUrn(new BarUrn(42));
90+
91+
// when
92+
searchIndex.getWriteDao().upsertDocument(searchDocument, "mydoc");
93+
searchIndex.getRequestContainer().flushAndSettle();
94+
95+
// then
96+
assertThat(searchIndex).bulkRequests().documentIds().containsExactly("mydoc");
97+
}
98+
99+
@Test
100+
public void canReadAllFromIndex() throws Exception {
101+
// given
102+
final BarUrn urn = new BarUrn(42);
103+
final BarSearchDocument searchDocument = new BarSearchDocument().setUrn(urn);
104+
searchIndex.getWriteDao().upsertDocument(searchDocument, "mydoc");
105+
searchIndex.getRequestContainer().flushAndSettle();
106+
107+
// when
108+
final SearchResult<BarSearchDocument> result = searchIndex.createReadAllDocumentsDao().search("", null, null, 0, 1);
109+
110+
// then
111+
assertThat(result).hasNoMoreResults();
112+
assertThat(result).hasTotalCount(1);
113+
assertThat(result).documents().containsExactly(searchDocument);
114+
assertThat(result).urns().containsExactly(urn);
115+
}
116+
117+
@Test
118+
public void settingsAndMappingsAnnotation() throws Exception {
119+
// when
120+
final GetSettingsResponse response = searchIndex.getConnection()
121+
.getTransportClient()
122+
.admin()
123+
.indices()
124+
.prepareGetSettings(searchIndex.getName())
125+
.get();
126+
final Settings settings = response.getIndexToSettings().get(searchIndex.getName());
127+
final String actual = settings.get("index.analysis.filter.autocomplete_filter.type");
128+
129+
// then
130+
assertThat(actual).isEqualTo("edge_ngram");
131+
}
132+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package com.linkedin.metadata.testing;
2+
3+
import com.linkedin.metadata.dao.SearchResult;
4+
import com.linkedin.metadata.testing.annotations.SearchIndexMappings;
5+
import com.linkedin.metadata.testing.annotations.SearchIndexSettings;
6+
import com.linkedin.metadata.testing.annotations.SearchIndexType;
7+
import com.linkedin.metadata.testing.asserts.SearchResultAssert;
8+
import com.linkedin.testing.BarSearchDocument;
9+
import com.linkedin.testing.urn.BarUrn;
10+
import org.junit.jupiter.api.Test;
11+
12+
import static com.linkedin.metadata.testing.asserts.SearchIndexAssert.assertThat;
13+
import static org.assertj.core.api.Assertions.assertThat;
14+
15+
16+
@ElasticsearchIntegrationTest
17+
public class ExampleTest {
18+
@SearchIndexType(BarSearchDocument.class)
19+
@SearchIndexSettings("/settings.json")
20+
@SearchIndexMappings("/mappings.json")
21+
public SearchIndex<BarSearchDocument> _searchIndex;
22+
23+
@Test
24+
public void canWriteToIndex() throws Exception {
25+
// given
26+
final BarSearchDocument searchDocument = new BarSearchDocument().setUrn(new BarUrn(42));
27+
28+
// when
29+
_searchIndex.getWriteDao().upsertDocument(searchDocument, "mydoc");
30+
_searchIndex.getRequestContainer().flushAndSettle();
31+
32+
// then
33+
assertThat(_searchIndex).bulkRequests().allRequestsSettled();
34+
assertThat(_searchIndex).bulkRequests().hadNoErrors();
35+
assertThat(_searchIndex).bulkRequests().documentIds().containsExactly("mydoc");
36+
}
37+
38+
@Test
39+
public void canReadAllFromIndex() throws Exception {
40+
// given
41+
final BarUrn urn = new BarUrn(42);
42+
final BarSearchDocument searchDocument = new BarSearchDocument().setUrn(urn);
43+
_searchIndex.getWriteDao().upsertDocument(searchDocument, "mydoc");
44+
_searchIndex.getRequestContainer().flushAndSettle();
45+
46+
// when
47+
final SearchResult<BarSearchDocument> result =
48+
_searchIndex.createReadAllDocumentsDao().search("", null, null, 0, 1);
49+
50+
// then
51+
SearchResultAssert.assertThat(result).hasNoMoreResults();
52+
SearchResultAssert.assertThat(result).hasTotalCount(1);
53+
SearchResultAssert.assertThat(result).documents().containsExactly(searchDocument);
54+
SearchResultAssert.assertThat(result).urns().containsExactly(urn);
55+
}
56+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"properties": {
3+
"urn": {
4+
"type": "keyword",
5+
"normalizer": "custom_normalizer"
6+
}
7+
}
8+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
{
2+
"index": {
3+
"analysis": {
4+
"filter": {
5+
"autocomplete_filter": {
6+
"type": "edge_ngram",
7+
"min_gram": "3",
8+
"max_gram": "20"
9+
},
10+
"custom_delimiter": {
11+
"split_on_numerics": "false",
12+
"split_on_case_change": "false",
13+
"type": "word_delimiter",
14+
"preserve_original": "true",
15+
"catenate_words": "false"
16+
}
17+
},
18+
"normalizer": {
19+
"custom_normalizer": {
20+
"filter": [
21+
"lowercase",
22+
"asciifolding"
23+
],
24+
"type": "custom"
25+
}
26+
},
27+
"analyzer": {
28+
"delimit_edgengram": {
29+
"filter": [
30+
"lowercase",
31+
"custom_delimiter",
32+
"autocomplete_filter"
33+
],
34+
"tokenizer": "whitespace"
35+
},
36+
"delimit": {
37+
"filter": [
38+
"lowercase",
39+
"custom_delimiter"
40+
],
41+
"tokenizer": "whitespace"
42+
},
43+
"lowercase_keyword": {
44+
"filter": [
45+
"lowercase"
46+
],
47+
"type": "custom",
48+
"tokenizer": "keyword"
49+
}
50+
}
51+
}
52+
}
53+
}

testing/elasticsearch-dao-integ-testing/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
apply plugin: 'java'
22

3+
apply from: "$rootDir/gradle/java-publishing.gradle"
4+
35
dependencies {
46
compile project(':dao-impl:elasticsearch-dao')
57

0 commit comments

Comments
 (0)