Add support for Elasticsearch 7

The integration for Elasticsearch 6 works with Elasticsearch 7 without
any adjustments (meaning all the tests pass), however all POSTs to the
_search URL result in a deprecation warning:

  "The {index}/{type}/_search endpoint is deprecated, use {index}/_search instead"

Add handling of this in the query adapter so that the type is omitted
on _search requests for Elasticsearch 7.

Change-Id: Ib3796f5f41336b5c80c1186aa87809fd5d571fb8
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 ce4b5ae..3d59ad3 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
@@ -35,6 +35,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
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
index a746eb9..cf8503d 100644
--- 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
@@ -34,6 +34,11 @@
     return getConfig(ElasticVersion.V6_5);
   }
 
+  @ConfigSuite.Config
+  public static Config elasticsearchV7() {
+    return getConfig(ElasticVersion.V7_0);
+  }
+
   @Override
   public void configureIndex(Injector injector) throws Exception {
     createAllIndexes(injector);
diff --git a/gerrit-elasticsearch/BUILD b/gerrit-elasticsearch/BUILD
index 7d968154..7e278fd 100644
--- a/gerrit-elasticsearch/BUILD
+++ b/gerrit-elasticsearch/BUILD
@@ -71,6 +71,8 @@
 
 ELASTICSEARCH_TESTS_V6 = {i: "src/test/java/com/google/gerrit/elasticsearch/ElasticV6Query" + i.capitalize() + SUFFIX for i in TYPES}
 
+ELASTICSEARCH_TESTS_V7 = {i: "src/test/java/com/google/gerrit/elasticsearch/ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
+
 ELASTICSEARCH_TAGS = [
     "docker",
     "elastic",
@@ -93,6 +95,14 @@
     deps = ELASTICSEARCH_DEPS,
 ) for name, src in ELASTICSEARCH_TESTS_V6.items()]
 
+[junit_tests(
+    name = "elasticsearch_query_%ss_test_v7" % name,
+    size = "large",
+    srcs = [src],
+    tags = ELASTICSEARCH_TAGS,
+    deps = ELASTICSEARCH_DEPS,
+) for name, src in ELASTICSEARCH_TESTS_V7.items()]
+
 junit_tests(
     name = "elasticsearch_tests",
     size = "small",
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 06a30b5b..c21299f 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
@@ -226,8 +226,11 @@
   }
 
   protected String getURI(String type, String request) throws UnsupportedEncodingException {
-    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     String encodedIndexName = URLEncoder.encode(indexName, UTF_8.toString());
+    if (SEARCH.equals(request) && client.adapter().omitTypeFromSearch()) {
+      return encodedIndexName + "/" + request;
+    }
+    String encodedType = URLEncoder.encode(type, UTF_8.toString());
     return encodedIndexName + "/" + encodedType + "/" + request;
   }
 
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 e2ed315..0c28dd1 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
@@ -21,6 +21,7 @@
 
   private final boolean ignoreUnmapped;
   private final boolean usePostV5Type;
+  private final boolean omitTypeFromSearch;
 
   private final String searchFilteringName;
   private final String indicesExistParam;
@@ -31,8 +32,9 @@
 
   ElasticQueryAdapter(ElasticVersion version) {
     this.ignoreUnmapped = false;
-    this.usePostV5Type = version.isV6();
-    this.versionDiscoveryUrl = version.isV6() ? "/%s*" : "/%s*/_aliases";
+    this.usePostV5Type = version.isV6OrLater();
+    this.omitTypeFromSearch = version.isV7OrLater();
+    this.versionDiscoveryUrl = version.isV6OrLater() ? "/%s*" : "/%s*/_aliases";
     this.searchFilteringName = "_source";
     this.indicesExistParam = "?allow_no_indices=false";
     this.exactFieldType = "keyword";
@@ -76,8 +78,12 @@
     return usePostV5Type;
   }
 
-  String getType(String preV6Type) {
-    return usePostV5Type() ? POST_V5_TYPE : preV6Type;
+  boolean omitTypeFromSearch() {
+    return omitTypeFromSearch;
+  }
+
+  String getType(String type) {
+    return usePostV5Type() ? POST_V5_TYPE : type;
   }
 
   String getVersionDiscoveryUrl(String name) {
diff --git a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index ac580b6..b69f8f9 100644
--- a/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/gerrit-elasticsearch/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -22,7 +22,8 @@
   V6_2("6.2.*"),
   V6_3("6.3.*"),
   V6_4("6.4.*"),
-  V6_5("6.5.*");
+  V6_5("6.5.*"),
+  V7_0("7.0.*");
 
   private final String version;
   private final Pattern pattern;
@@ -55,8 +56,16 @@
     return Joiner.on(", ").join(ElasticVersion.values());
   }
 
-  public boolean isV6() {
-    return version.startsWith("6.");
+  public boolean isV6OrLater() {
+    return isAtLeastVersion(6);
+  }
+
+  public boolean isV7OrLater() {
+    return isAtLeastVersion(7);
+  }
+
+  private boolean isAtLeastVersion(int v) {
+    return Integer.valueOf(version.split("\\.")[0]) >= v;
   }
 
   @Override
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
index 2e05bb3..6d8eb3f 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -48,6 +48,8 @@
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.4.3";
       case V6_5:
         return "docker.elastic.co/elasticsearch/elasticsearch-oss:6.5.1";
+      case V7_0:
+        return "docker.elastic.co/elasticsearch/elasticsearch-oss:7.0.0-alpha1";
     }
     throw new IllegalStateException("No tests for version: " + version.name());
   }
diff --git a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
new file mode 100644
index 0000000..9efe12d
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -0,0 +1,66 @@
+// 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.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 ElasticV7QueryAccountsTest 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.V7_0);
+    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/ElasticV7QueryChangesTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
new file mode 100644
index 0000000..698e01e
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.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.elasticsearch;
+
+import com.google.gerrit.elasticsearch.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 ElasticV7QueryChangesTest 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.V7_0);
+    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/ElasticV7QueryGroupsTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
new file mode 100644
index 0000000..ba4b3b6
--- /dev/null
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -0,0 +1,66 @@
+// 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.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 ElasticV7QueryGroupsTest 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.V7_0);
+    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/ElasticVersionTest.java b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index bb7eb04..550a5d1 100644
--- a/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/gerrit-elasticsearch/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -51,9 +51,19 @@
 
   @Test
   public void version6() throws Exception {
-    assertThat(ElasticVersion.V6_2.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_3.isV6()).isTrue();
-    assertThat(ElasticVersion.V6_4.isV6()).isTrue();
-    assertThat(ElasticVersion.V5_6.isV6()).isFalse();
+    assertThat(ElasticVersion.V5_6.isV6OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_3.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V6_4.isV6OrLater()).isTrue();
+    assertThat(ElasticVersion.V7_0.isV6OrLater()).isTrue();
+  }
+
+  @Test
+  public void version7() throws Exception {
+    assertThat(ElasticVersion.V5_6.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_2.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_3.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V6_4.isV7OrLater()).isFalse();
+    assertThat(ElasticVersion.V7_0.isV7OrLater()).isTrue();
   }
 }