Skip to content

Commit

Permalink
Acceptance tests: Replace embedded ES with docker testcontainer
Browse files Browse the repository at this point in the history
Testcontainers is a Java 8 library that supports JUnit tests, providing
lightweight, throwaway instances of common databases, Selenium web
browsers, or anything else that can run in a Docker container.

This change replaces ES integration test that currently uses embedded
ES mode with ES docker image using Testcontainers library. All this is
done from within acceptance tests.

This change removes dependency on ES server stack for the test code. As
the consequence, this stack can be removed.

Prerequisite for this change is installed docker service on the SUT. If
docker service is not installed, assumption violation is raised so that
the tests don't fail. Unfortunately, due to this missing Bazel feature,
JUnit assumption violations are not reflected on the Bazel UI: [1] yet.

[1] bazelbuild/bazel#3476

Change-Id: Iccf44310292cc44bff9173f2a4ea757b43f77183
  • Loading branch information
davido authored and dpursehouse committed May 28, 2018
1 parent 6547ded commit 8add7b7
Show file tree
Hide file tree
Showing 6 changed files with 104 additions and 167 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Copyright (C) 2018 The Android Open Source Project
//
// Licensed 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 com.google.gerrit.elasticsearch.testing;

import com.google.common.collect.ImmutableSet;
import java.util.Set;
import org.apache.http.HttpHost;
import org.testcontainers.containers.GenericContainer;

/* Helper class for running ES integration tests in docker container */
public class ElasticContainer<SELF extends ElasticContainer<SELF>> extends GenericContainer<SELF> {
private static final String NAME = "elasticsearch";
private static final String VERSION = "2.4.6-alpine";
private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;

public ElasticContainer() {
this(NAME + ":" + VERSION);
}

public ElasticContainer(String dockerImageName) {
super(dockerImageName);
}

@Override
protected void configure() {
addExposedPort(ELASTICSEARCH_DEFAULT_PORT);

// https://github.com/docker-library/elasticsearch/issues/58
addEnv("-Ees.network.host", "0.0.0.0");
}

@Override
protected Set<Integer> getLivenessCheckPorts() {
return ImmutableSet.of(getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
}

public HttpHost getHttpHost() {
return new HttpHost(getContainerIpAddress(), getMappedPort(ELASTICSEARCH_DEFAULT_PORT));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,91 +14,31 @@

package com.google.gerrit.elasticsearch.testing;

import static com.google.common.truth.Truth.assertThat;

import com.google.common.base.Strings;
import com.google.common.io.Files;
import com.google.gerrit.server.index.IndexDefinition;
import com.google.gerrit.server.index.IndexModule.IndexType;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.TypeLiteral;
import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.lib.Config;
import org.elasticsearch.action.admin.cluster.node.info.NodesInfoRequest;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;

public final class ElasticTestUtils {
public static class ElasticNodeInfo {
public final Node node;
public final String port;
public final File elasticDir;
public final int port;

private ElasticNodeInfo(Node node, File rootDir, String port) {
this.node = node;
public ElasticNodeInfo(int port) {
this.port = port;
this.elasticDir = rootDir;
}
}

public static void configure(Config config, String port, String prefix) {
public static void configure(Config config, int port, String prefix) {
config.setEnum("index", null, "type", IndexType.ELASTICSEARCH);
config.setString("elasticsearch", "test", "protocol", "http");
config.setString("elasticsearch", "test", "hostname", "localhost");
config.setString("elasticsearch", "test", "port", port);
config.setInt("elasticsearch", "test", "port", port);
config.setString("elasticsearch", null, "prefix", prefix);
}

public static ElasticNodeInfo startElasticsearchNode()
throws InterruptedException, ExecutionException {
File elasticDir = Files.createTempDir();
Path elasticDirPath = elasticDir.toPath();
Settings settings =
Settings.settingsBuilder()
.put("cluster.name", "gerrit")
.put("node.name", "Gerrit Elasticsearch Test Node")
.put("node.local", true)
.put("discovery.zen.ping.multicast.enabled", false)
.put("index.store.fs.memory.enabled", true)
.put("index.gateway.type", "none")
.put("index.max_result_window", Integer.MAX_VALUE)
.put("gateway.type", "default")
.put("http.port", 0)
.put("discovery.zen.ping.unicast.hosts", "[\"localhost\"]")
.put("path.home", elasticDirPath.toAbsolutePath())
.put("path.data", elasticDirPath.resolve("data").toAbsolutePath())
.put("path.work", elasticDirPath.resolve("work").toAbsolutePath())
.put("path.logs", elasticDirPath.resolve("logs").toAbsolutePath())
.put("transport.tcp.connect_timeout", "60s")
.build();

// Start the node
Node node = NodeBuilder.nodeBuilder().settings(settings).node();

// Wait for it to be ready
node.client().admin().cluster().prepareHealth().setWaitForYellowStatus().execute().actionGet();

assertThat(node.isClosed()).isFalse();
return new ElasticNodeInfo(node, elasticDir, getHttpPort(node));
}

public static class NodeInfo {
String httpAddress;
}

public static class Info {
Map<String, NodeInfo> nodes;
config.setString("index", null, "maxLimit", "10000");
}

public static void createAllIndexes(Injector injector) throws IOException {
Expand All @@ -109,28 +49,6 @@ public static void createAllIndexes(Injector injector) throws IOException {
}
}

private static String getHttpPort(Node node) throws InterruptedException, ExecutionException {
String nodes =
node.client().admin().cluster().nodesInfo(new NodesInfoRequest("*")).get().toString();
Gson gson =
new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.create();
Info info = gson.fromJson(nodes, Info.class);
if (info.nodes == null || info.nodes.size() != 1) {
throw new RuntimeException("Cannot extract local Elasticsearch http port");
}
Iterator<NodeInfo> values = info.nodes.values().iterator();
String httpAddress = values.next().httpAddress;
if (Strings.isNullOrEmpty(httpAddress)) {
throw new RuntimeException("Cannot extract local Elasticsearch http port");
}
if (httpAddress.indexOf(':') < 0) {
throw new RuntimeException("Seems that port is not included in Elasticsearch http_address");
}
return httpAddress.substring(httpAddress.indexOf(':') + 1, httpAddress.length());
}

private ElasticTestUtils() {
// hide default constructor
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,45 @@

package com.google.gerrit.elasticsearch;

import com.google.gerrit.elasticsearch.testing.ElasticContainer;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.account.AbstractQueryAccountsTest;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.internal.AssumptionViolatedException;

public class ElasticQueryAccountsTest extends AbstractQueryAccountsTest {
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;

@BeforeClass
public static void startIndexService() throws InterruptedException, ExecutionException {
public static void startIndexService() {
if (nodeInfo != null) {
// do not start Elasticsearch twice
return;
}
nodeInfo = ElasticTestUtils.startElasticsearchNode();

// Assumption violation is not natively supported by Testcontainers.
// See https://github.com/testcontainers/testcontainers-java/issues/343
try {
container = new ElasticContainer<>();
container.start();
} catch (Throwable t) {
throw new AssumptionViolatedException("Unable to start container[might be docker related]");
}

nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}

@AfterClass
public static void stopElasticsearchServer() {
if (nodeInfo != null) {
nodeInfo.node.close();
nodeInfo.elasticDir.delete();
nodeInfo = null;
if (container != null) {
container.stop();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,42 +14,50 @@

package com.google.gerrit.elasticsearch;

import com.google.gerrit.elasticsearch.testing.ElasticContainer;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.gerrit.testutil.InMemoryRepositoryManager.Repo;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.internal.AssumptionViolatedException;
import org.junit.rules.TestName;

public class ElasticQueryChangesTest extends AbstractQueryChangesTest {
@Rule public final TestName testName = new TestName();

private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;

@BeforeClass
public static void startIndexService() throws InterruptedException, ExecutionException {
public static void startIndexService() {
if (nodeInfo != null) {
// do not start Elasticsearch twice
return;
}
nodeInfo = ElasticTestUtils.startElasticsearchNode();

try {
container = new ElasticContainer<>();
container.start();
} catch (Throwable t) {
throw new AssumptionViolatedException("Unable to start container[might be docker related]");
}

nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}

@AfterClass
public static void stopElasticsearchServer() {
if (nodeInfo != null) {
nodeInfo.node.close();
nodeInfo.elasticDir.delete();
nodeInfo = null;
if (container != null) {
container.stop();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,43 @@

package com.google.gerrit.elasticsearch;

import com.google.gerrit.elasticsearch.testing.ElasticContainer;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils;
import com.google.gerrit.elasticsearch.testing.ElasticTestUtils.ElasticNodeInfo;
import com.google.gerrit.server.query.group.AbstractQueryGroupsTest;
import com.google.gerrit.testutil.InMemoryModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import java.util.concurrent.ExecutionException;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.internal.AssumptionViolatedException;

public class ElasticQueryGroupsTest extends AbstractQueryGroupsTest {
private static ElasticNodeInfo nodeInfo;
private static ElasticContainer<?> container;

@BeforeClass
public static void startIndexService() throws InterruptedException, ExecutionException {
public static void startIndexService() {
if (nodeInfo != null) {
// do not start Elasticsearch twice
return;
}
nodeInfo = ElasticTestUtils.startElasticsearchNode();

try {
container = new ElasticContainer<>();
container.start();
} catch (Throwable t) {
throw new AssumptionViolatedException("Unable to start container[might be docker related]");
}

nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
}

@AfterClass
public static void stopElasticsearchServer() {
if (nodeInfo != null) {
nodeInfo.node.close();
nodeInfo.elasticDir.delete();
nodeInfo = null;
if (container != null) {
container.stop();
}
}

Expand Down
Loading

0 comments on commit 8add7b7

Please sign in to comment.