Merge "Bazel: Harmonize names of external repositories" into stable-2.14
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 03e06e2..8f1fd59 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2792,10 +2792,13 @@
 === Section elasticsearch
 
 WARNING: The Elasticsearch support has only been tested with Elasticsearch
-version 2.4.x. Support for other versions is not guaranteed.
+versions 2.4, 5.6 and 6.2. Support for other versions is not guaranteed.
 
-Open and closed changes are indexed in a single index, separated
-into types `open_changes` and `closed_changes` respectively.
+Open and closed changes are indexed in a single index, separated into types
+`open_changes` and `closed_changes` respectively, if using Elasticsearch
+versions 2.4 or 5.6. Open and closed changes are merged into the default `_doc`
+type otherwise. The latter is also used for accounts and groups indices starting
+with Elasticsearch 6.2.
 
 [[elasticsearch.prefix]]elasticsearch.prefix::
 +
diff --git a/Documentation/dev-bazel.txt b/Documentation/dev-bazel.txt
index 2d5c454..6d5c75a 100644
--- a/Documentation/dev-bazel.txt
+++ b/Documentation/dev-bazel.txt
@@ -269,6 +269,15 @@
 * server
 * ssh
 
+[[elasticsearch]]
+=== Elasticsearch
+
+Successfully running the elasticsearch tests may require setting the local
+link:https://www.elastic.co/guide/en/elasticsearch/reference/current/vm-max-map-count.html[virtual memory].
+
+Bazel link:https://github.com/bazelbuild/bazel/issues/3476[does not currently make container failures visible],
+if any.
+
 == Dependencies
 
 Dependency JARs are normally downloaded as needed, but you can
@@ -377,16 +386,6 @@
 details. Users should watch the cache sizes and clean them manually if
 necessary.
 
-Due to the `--experimental_strict_action_env` option used in `bazelrc`
-it is possible that some commands required by the build are not found
-on the PATH, causing the build to fail. In this case the PATH used in
-the build can be overridden with the `--action_env=PATH` directive in
-the user's `~/.bazelrc` file, for example:
-
-----
-  build --action_env=PATH=/usr/local/opt/coreutils/libexec/gnubin/:/usr/local/bin/:/usr/bin/
-----
-
 GERRIT
 ------
 Part of link:index.html[Gerrit Code Review]
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
index d7fcd38..071600e 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
@@ -51,6 +51,11 @@
     return getConfig(ElasticVersion.V5_6);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV6() {
+    return getConfig(ElasticVersion.V6_2);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     ElasticTestUtils.createAllIndexes(injector);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
new file mode 100644
index 0000000..c72edfb
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/AbstractIndexTests.java
@@ -0,0 +1,67 @@
+// 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.acceptance.ssh;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.FluentIterable;
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.acceptance.PushOneCommit;
+import com.google.gerrit.acceptance.UseSsh;
+import com.google.gerrit.extensions.common.ChangeInfo;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.inject.Injector;
+import java.util.List;
+import org.junit.Test;
+
+@NoHttpd
+@UseSsh
+public abstract class AbstractIndexTests extends AbstractDaemonTest {
+  /** @param injector injector */
+  public abstract void configureIndex(Injector injector) throws Exception;
+
+  @Test
+  public void indexChange() throws Exception {
+    configureIndex(server.getTestInjector());
+
+    PushOneCommit.Result change = createChange("first change", "test1.txt", "test1");
+    String changeId = change.getChangeId();
+    String changeLegacyId = change.getChange().getId().toString();
+
+    disableChangeIndexWrites();
+    amendChange(changeId, "second test", "test2.txt", "test2");
+
+    assertQuery("message:second", change.getChange(), false);
+    enableChangeIndexWrites();
+
+    String cmd = Joiner.on(" ").join("gerrit", "index", "changes", changeLegacyId);
+    adminSshSession.exec(cmd);
+
+    assertQuery("message:second", change.getChange(), true);
+  }
+
+  protected void assertQuery(String q, ChangeData change, Boolean assertTrue) throws Exception {
+    List<ChangeInfo> result = query(q);
+    Iterable<Integer> ids = ids(result);
+    if (assertTrue) assertThat(ids).contains(change.getId().get());
+    else assertThat(ids).doesNotContain(change.getId().get());
+  }
+
+  protected static Iterable<Integer> ids(Iterable<ChangeInfo> changes) {
+    return FluentIterable.from(changes).transform(in -> in._number);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUILD b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUILD
index 91d8d71..dafd494 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUILD
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/BUILD
@@ -1,8 +1,35 @@
 load("//gerrit-acceptance-tests:tests.bzl", "acceptance_tests")
 
+java_library(
+    name = "util",
+    testonly = 1,
+    srcs = ["AbstractIndexTests.java"],
+    deps = ["//gerrit-acceptance-tests:lib"],
+)
+
 acceptance_tests(
-    srcs = glob(["*IT.java"]),
+    srcs = glob(
+        ["*IT.java"],
+        exclude = ["ElasticIndexIT.java"],
+    ),
     group = "ssh",
     labels = ["ssh"],
-    deps = ["//lib/commons:compress"],
+    deps = [
+        ":util",
+        "//lib/commons:compress",
+    ],
+)
+
+acceptance_tests(
+    srcs = ["ElasticIndexIT.java"],
+    group = "elastic",
+    labels = [
+        "elastic",
+        "docker",
+        "ssh",
+    ],
+    deps = [
+        ":util",
+        "//lib/commons:compress",
+    ],
 )
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
new file mode 100644
index 0000000..5750659
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
@@ -0,0 +1,62 @@
+// 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.acceptance.ssh;
+
+import com.google.gerrit.elasticsearch.ElasticVersion;
+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.testutil.ConfigSuite;
+import com.google.inject.Injector;
+import java.util.UUID;
+import org.eclipse.jgit.lib.Config;
+
+public class ElasticIndexIT extends AbstractIndexTests {
+  private static ElasticContainer<?> container;
+
+  private static Config getConfig(ElasticVersion version) {
+    ElasticNodeInfo elasticNodeInfo;
+    try {
+      container = ElasticContainer.createAndStart(version);
+      elasticNodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+    } catch (Throwable t) {
+      return null;
+    }
+    String indicesPrefix = UUID.randomUUID().toString();
+    Config cfg = new Config();
+    ElasticTestUtils.configure(cfg, elasticNodeInfo.port, indicesPrefix);
+    return cfg;
+  }
+
+  @ConfigSuite.Default
+  public static Config elasticsearchV2() {
+    return getConfig(ElasticVersion.V2_4);
+  }
+
+  @ConfigSuite.Config
+  public static Config elasticsearchV5() {
+    return getConfig(ElasticVersion.V5_6);
+  }
+
+  @ConfigSuite.Config
+  public static Config elasticsearchV6() {
+    return getConfig(ElasticVersion.V6_2);
+  }
+
+  @Override
+  public void configureIndex(Injector injector) throws Exception {
+    ElasticTestUtils.createAllIndexes(injector);
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/IndexIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/IndexIT.java
new file mode 100644
index 0000000..196a1e5
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/ssh/IndexIT.java
@@ -0,0 +1,23 @@
+// 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.acceptance.ssh;
+
+import com.google.inject.Injector;
+
+public class IndexIT extends AbstractIndexTests {
+
+  @Override
+  public void configureIndex(Injector injector) throws Exception {}
+}
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index 45bd2b9..7880ffc 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -20,6 +20,7 @@
 
 import com.google.common.collect.FluentIterable;
 import com.google.common.io.CharStreams;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.builders.SearchSourceBuilder;
 import com.google.gerrit.elasticsearch.bulk.DeleteRequest;
 import com.google.gerrit.server.config.SitePaths;
@@ -50,6 +51,7 @@
 
 abstract class AbstractElasticIndex<K, V> implements Index<K, V> {
   protected static final String BULK = "_bulk";
+  protected static final String MAPPINGS = "mappings";
   protected static final String ORDER = "order";
   protected static final String SEARCH = "_search";
 
@@ -80,6 +82,7 @@
   private final SitePaths sitePaths;
   private final String indexNameRaw;
 
+  protected final String type;
   protected final ElasticRestClientProvider client;
   protected final String indexName;
   protected final Gson gson;
@@ -98,6 +101,7 @@
     this.indexName = cfg.getIndexName(indexName, schema.getVersion());
     this.indexNameRaw = indexName;
     this.client = client;
+    this.type = client.adapter().getType(indexName);
   }
 
   @Override
@@ -117,7 +121,7 @@
 
   @Override
   public void delete(K id) throws IOException {
-    String uri = getURI(indexNameRaw, BULK);
+    String uri = getURI(type, BULK);
     Response response = postRequest(getDeleteActions(id), uri, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
@@ -156,8 +160,20 @@
 
   protected abstract String getId(V v);
 
+  protected String getMappingsForSingleType(String candidateType, MappingProperties properties) {
+    return getMappingsFor(client.adapter().getType(candidateType), properties);
+  }
+
+  protected String getMappingsFor(String type, MappingProperties properties) {
+    JsonObject mappingType = new JsonObject();
+    mappingType.add(type, gson.toJsonTree(properties));
+    JsonObject mappings = new JsonObject();
+    mappings.add(MAPPINGS, gson.toJsonTree(mappingType));
+    return gson.toJson(mappings);
+  }
+
   protected String delete(String type, K id) {
-    return new DeleteRequest(id.toString(), indexName, type).toString();
+    return new DeleteRequest(id.toString(), indexName, type, client.adapter()).toString();
   }
 
   protected void addNamedElement(String name, JsonObject element, JsonArray array) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 178014c..5f48bb0 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -16,7 +16,6 @@
 
 import static com.google.gerrit.server.index.account.AccountField.ID;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.builders.QueryBuilder;
@@ -90,9 +89,10 @@
   @Override
   public void replace(AccountState as) throws IOException {
     BulkRequest bulk =
-        new IndexRequest(getId(as), indexName, ACCOUNTS).add(new UpdateRequest<>(schema, as));
+        new IndexRequest(getId(as), indexName, type, client.adapter())
+            .add(new UpdateRequest<>(schema, as));
 
-    String uri = getURI(ACCOUNTS, BULK);
+    String uri = getURI(type, BULK);
     Response response = postRequest(bulk, uri, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
@@ -111,13 +111,12 @@
 
   @Override
   protected String getDeleteActions(Account.Id a) {
-    return delete(ACCOUNTS, a);
+    return delete(type, a);
   }
 
   @Override
   protected String getMappings() {
-    ImmutableMap<String, AccountMapping> mappings = ImmutableMap.of("mappings", mapping);
-    return gson.toJson(mappings);
+    return getMappingsForSingleType(ACCOUNTS, mapping.accounts);
   }
 
   @Override
@@ -152,7 +151,7 @@
     public ResultSet<AccountState> read() throws OrmException {
       try {
         List<AccountState> results = Collections.emptyList();
-        String uri = getURI(ACCOUNTS, SEARCH);
+        String uri = getURI(type, SEARCH);
         Response response = postRequest(search, uri, Collections.emptyMap());
         StatusLine statusLine = response.getStatusLine();
         if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
index bf1d615..e9dce0d 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -83,11 +83,13 @@
   private static final Logger log = LoggerFactory.getLogger(ElasticChangeIndex.class);
 
   public static class ChangeMapping {
+    public MappingProperties changes;
     public MappingProperties openChanges;
     public MappingProperties closedChanges;
 
     public ChangeMapping(Schema<ChangeData> schema, ElasticQueryAdapter adapter) {
       MappingProperties mapping = ElasticMapping.createMapping(schema, adapter);
+      this.changes = mapping;
       this.openChanges = mapping;
       this.closedChanges = mapping;
     }
@@ -137,12 +139,15 @@
       throw new IOException(e);
     }
 
+    ElasticQueryAdapter adapter = client.adapter();
     BulkRequest bulk =
-        new IndexRequest(getId(cd), indexName, insertIndex)
-            .add(new UpdateRequest<>(fillArgs, schema, cd))
-            .add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex));
+        new IndexRequest(getId(cd), indexName, adapter.getType(insertIndex), adapter)
+            .add(new UpdateRequest<>(fillArgs, schema, cd));
+    if (!adapter.usePostV5Type()) {
+      bulk.add(new DeleteRequest(cd.getId().toString(), indexName, deleteIndex, adapter));
+    }
 
-    String uri = getURI(CHANGES, BULK);
+    String uri = getURI(type, BULK);
     Response response = postRequest(bulk, uri, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
@@ -157,23 +162,36 @@
       throws QueryParseException {
     Set<Change.Status> statuses = ChangeIndexRewriter.getPossibleStatus(p);
     List<String> indexes = Lists.newArrayListWithCapacity(2);
-    if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
-      indexes.add(OPEN_CHANGES);
-    }
-    if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
-      indexes.add(CLOSED_CHANGES);
+    if (client.adapter().usePostV5Type()) {
+      if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()
+          || !Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+        indexes.add(ElasticQueryAdapter.POST_V5_TYPE);
+      }
+    } else {
+      if (!Sets.intersection(statuses, OPEN_STATUSES).isEmpty()) {
+        indexes.add(OPEN_CHANGES);
+      }
+      if (!Sets.intersection(statuses, CLOSED_STATUSES).isEmpty()) {
+        indexes.add(CLOSED_CHANGES);
+      }
     }
     return new QuerySource(indexes, p, opts);
   }
 
   @Override
   protected String getDeleteActions(Id c) {
+    if (client.adapter().usePostV5Type()) {
+      return delete(ElasticQueryAdapter.POST_V5_TYPE, c);
+    }
     return delete(OPEN_CHANGES, c) + delete(CLOSED_CHANGES, c);
   }
 
   @Override
   protected String getMappings() {
-    return gson.toJson(ImmutableMap.of("mappings", mapping));
+    if (client.adapter().usePostV5Type()) {
+      return getMappingsFor(ElasticQueryAdapter.POST_V5_TYPE, mapping.changes);
+    }
+    return gson.toJson(ImmutableMap.of(MAPPINGS, mapping));
   }
 
   @Override
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 86997cd..f797b57 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -14,7 +14,6 @@
 
 package com.google.gerrit.elasticsearch;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
 import com.google.gerrit.elasticsearch.builders.QueryBuilder;
@@ -87,9 +86,10 @@
   @Override
   public void replace(AccountGroup group) throws IOException {
     BulkRequest bulk =
-        new IndexRequest(getId(group), indexName, GROUPS).add(new UpdateRequest<>(schema, group));
+        new IndexRequest(getId(group), indexName, type, client.adapter())
+            .add(new UpdateRequest<>(schema, group));
 
-    String uri = getURI(GROUPS, BULK);
+    String uri = getURI(type, BULK);
     Response response = postRequest(bulk, uri, getRefreshParam());
     int statusCode = response.getStatusLine().getStatusCode();
     if (statusCode != HttpStatus.SC_OK) {
@@ -108,13 +108,12 @@
 
   @Override
   protected String getDeleteActions(AccountGroup.UUID g) {
-    return delete(GROUPS, g);
+    return delete(type, g);
   }
 
   @Override
   protected String getMappings() {
-    ImmutableMap<String, GroupMapping> mappings = ImmutableMap.of("mappings", mapping);
-    return gson.toJson(mappings);
+    return getMappingsForSingleType(GROUPS, mapping.groups);
   }
 
   @Override
@@ -149,7 +148,7 @@
     public ResultSet<AccountGroup> read() throws OrmException {
       try {
         List<AccountGroup> results = Collections.emptyList();
-        String uri = getURI(GROUPS, SEARCH);
+        String uri = getURI(type, SEARCH);
         Response response = postRequest(search, uri, Collections.emptyMap());
         StatusLine statusLine = response.getStatusLine();
         if (statusLine.getStatusCode() == HttpStatus.SC_OK) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
index 72af49a..6eb9384 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
@@ -17,7 +17,11 @@
 import com.google.gson.JsonObject;
 
 public class ElasticQueryAdapter {
+  static final String POST_V5_TYPE = "_doc";
+
   private final boolean ignoreUnmapped;
+  private final boolean usePostV5Type;
+
   private final String searchFilteringName;
   private final String indicesExistParam;
   private final String exactFieldType;
@@ -26,6 +30,8 @@
 
   ElasticQueryAdapter(ElasticVersion version) {
     this.ignoreUnmapped = version == ElasticVersion.V2_4;
+    this.usePostV5Type = version == ElasticVersion.V6_2;
+
     switch (version) {
       case V5_6:
       case V6_2:
@@ -52,6 +58,12 @@
     }
   }
 
+  public void setType(JsonObject properties, String type) {
+    if (!usePostV5Type) {
+      properties.addProperty("_type", type);
+    }
+  }
+
   public String searchFilteringName() {
     return searchFilteringName;
   }
@@ -71,4 +83,12 @@
   String indexProperty() {
     return indexProperty;
   }
+
+  boolean usePostV5Type() {
+    return usePostV5Type;
+  }
+
+  String getType(String preV6Type) {
+    return usePostV5Type() ? POST_V5_TYPE : preV6Type;
+  }
 }
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
index c7757b2..7392d09 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.elasticsearch.bulk;
 
+import com.google.gerrit.elasticsearch.ElasticQueryAdapter;
 import com.google.gson.JsonObject;
 
 abstract class ActionRequest extends BulkRequest {
@@ -22,12 +23,15 @@
   private final String id;
   private final String index;
   private final String type;
+  private final ElasticQueryAdapter adapter;
 
-  protected ActionRequest(String action, String id, String index, String type) {
+  protected ActionRequest(
+      String action, String id, String index, String type, ElasticQueryAdapter adapter) {
     this.action = action;
     this.id = id;
     this.index = index;
     this.type = type;
+    this.adapter = adapter;
   }
 
   @Override
@@ -35,7 +39,7 @@
     JsonObject properties = new JsonObject();
     properties.addProperty("_id", id);
     properties.addProperty("_index", index);
-    properties.addProperty("_type", type);
+    adapter.setType(properties, type);
 
     JsonObject jsonAction = new JsonObject();
     jsonAction.add(action, properties);
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
index 7d549ca..570d5a0 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.elasticsearch.bulk;
 
+import com.google.gerrit.elasticsearch.ElasticQueryAdapter;
+
 public class DeleteRequest extends ActionRequest {
 
-  public DeleteRequest(String id, String index, String type) {
-    super("delete", id, index, type);
+  public DeleteRequest(String id, String index, String type, ElasticQueryAdapter adapter) {
+    super("delete", id, index, type, adapter);
   }
 }
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
index b131501..c571a0e 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
@@ -14,9 +14,11 @@
 
 package com.google.gerrit.elasticsearch.bulk;
 
+import com.google.gerrit.elasticsearch.ElasticQueryAdapter;
+
 public class IndexRequest extends ActionRequest {
 
-  public IndexRequest(String id, String index, String type) {
-    super("index", id, index, type);
+  public IndexRequest(String id, String index, String type, ElasticQueryAdapter adapter) {
+    super("index", id, index, type, adapter);
   }
 }
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/testing/ElasticContainer.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/testing/ElasticContainer.java
index eacc43e..0033566 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/testing/ElasticContainer.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/testing/ElasticContainer.java
@@ -48,7 +48,7 @@
       case V5_6:
         return "elasticsearch:5.6.9-alpine";
       case V6_2:
-        return "docker.elastic.co/elasticsearch/elasticsearch:6.2.4";
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.2.4";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
new file mode 100644
index 0000000..f973e36
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryAccountsTest.java
@@ -0,0 +1,68 @@
+// 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;
+
+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 org.eclipse.jgit.lib.Config;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ElasticV6QueryAccountsTest extends AbstractQueryAccountsTest {
+  private static ElasticNodeInfo nodeInfo;
+  private static ElasticContainer<?> container;
+
+  @BeforeClass
+  public static void startIndexService() {
+    if (nodeInfo != null) {
+      // do not start Elasticsearch twice
+      return;
+    }
+
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_2);
+    nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+  }
+
+  @AfterClass
+  public static void stopElasticsearchServer() {
+    if (container != null) {
+      container.stop();
+    }
+  }
+
+  private String testName() {
+    return testName.getMethodName().toLowerCase() + "_";
+  }
+
+  @Override
+  protected void initAfterLifecycleStart() throws Exception {
+    super.initAfterLifecycleStart();
+    ElasticTestUtils.createAllIndexes(injector);
+  }
+
+  @Override
+  protected Injector createInjector() {
+    Config elasticsearchConfig = new Config(config);
+    InMemoryModule.setDefaults(elasticsearchConfig);
+    String indicesPrefix = testName();
+    ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+  }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
new file mode 100644
index 0000000..5e0fd7e
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryChangesTest.java
@@ -0,0 +1,69 @@
+// 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;
+
+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.inject.Guice;
+import com.google.inject.Injector;
+import org.eclipse.jgit.lib.Config;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ElasticV6QueryChangesTest extends AbstractQueryChangesTest {
+
+  private static ElasticNodeInfo nodeInfo;
+  private static ElasticContainer<?> container;
+
+  @BeforeClass
+  public static void startIndexService() {
+    if (nodeInfo != null) {
+      // do not start Elasticsearch twice
+      return;
+    }
+
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_2);
+    nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+  }
+
+  @AfterClass
+  public static void stopElasticsearchServer() {
+    if (container != null) {
+      container.stop();
+    }
+  }
+
+  private String testName() {
+    return testName.getMethodName().toLowerCase() + "_";
+  }
+
+  @Override
+  protected void initAfterLifecycleStart() throws Exception {
+    super.initAfterLifecycleStart();
+    ElasticTestUtils.createAllIndexes(injector);
+  }
+
+  @Override
+  protected Injector createInjector() {
+    Config elasticsearchConfig = new Config(config);
+    InMemoryModule.setDefaults(elasticsearchConfig);
+    String indicesPrefix = testName();
+    ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+  }
+}
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
new file mode 100644
index 0000000..abb715c
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV6QueryGroupsTest.java
@@ -0,0 +1,68 @@
+// 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;
+
+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 org.eclipse.jgit.lib.Config;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+public class ElasticV6QueryGroupsTest extends AbstractQueryGroupsTest {
+  private static ElasticNodeInfo nodeInfo;
+  private static ElasticContainer<?> container;
+
+  @BeforeClass
+  public static void startIndexService() {
+    if (nodeInfo != null) {
+      // do not start Elasticsearch twice
+      return;
+    }
+
+    container = ElasticContainer.createAndStart(ElasticVersion.V6_2);
+    nodeInfo = new ElasticNodeInfo(container.getHttpHost().getPort());
+  }
+
+  @AfterClass
+  public static void stopElasticsearchServer() {
+    if (container != null) {
+      container.stop();
+    }
+  }
+
+  private String testName() {
+    return testName.getMethodName().toLowerCase() + "_";
+  }
+
+  @Override
+  protected void initAfterLifecycleStart() throws Exception {
+    super.initAfterLifecycleStart();
+    ElasticTestUtils.createAllIndexes(injector);
+  }
+
+  @Override
+  protected Injector createInjector() {
+    Config elasticsearchConfig = new Config(config);
+    InMemoryModule.setDefaults(elasticsearchConfig);
+    String indicesPrefix = testName();
+    ElasticTestUtils.configure(elasticsearchConfig, nodeInfo.port, indicesPrefix);
+    return Guice.createInjector(new InMemoryModule(elasticsearchConfig, notesMigration));
+  }
+}
diff --git a/tools/bazel.rc b/tools/bazel.rc
index 8f158e9..7230cf3 100644
--- a/tools/bazel.rc
+++ b/tools/bazel.rc
@@ -2,4 +2,5 @@
 build --disk_cache=~/.gerritcodereview/bazel-cache/cas
 build --repository_cache=~/.gerritcodereview/bazel-cache/repository
 build --experimental_strict_action_env
+build --action_env=PATH
 test --build_tests_only