Merge pagination improvements from Gerrit 3.4
The below changes were cherry-picked from core stable-3.4 onto
the rewritten module history where stable-3.4 would have been.
* 3.4:
Paginate no-limit queries
Introduce a SEARCH_AFTER index pagination type
Change-Id: I11600ad4c11c2c546a23058b1606056d76825cd7
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..b16fb0d
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,96 @@
+load("@rules_java//java:defs.bzl", "java_library")
+load("//tools/bzl:junit.bzl", "junit_tests")
+load(
+ "//tools/bzl:plugin.bzl",
+ "PLUGIN_DEPS",
+ "PLUGIN_TEST_DEPS",
+ "gerrit_plugin",
+)
+
+gerrit_plugin(
+ name = "index-elasticsearch",
+ srcs = glob(["src/main/java/**/*.java"]),
+ deps = [
+ "@elasticsearch-rest-client//jar",
+ "@httpasyncclient//jar",
+ "@httpcore-nio//jar",
+ "@jackson-core//jar",
+ ],
+)
+
+ELASTICSEARCH_DEPS = [
+ "@docker-java-api//jar",
+ "@docker-java-transport//jar",
+ "@duct-tape//jar",
+ "@httpasyncclient//jar",
+ "@jackson-annotations//jar",
+ "@jackson-core//jar",
+ "@jna//jar",
+ "@testcontainers-elasticsearch//jar",
+ "@testcontainers//jar",
+]
+
+java_library(
+ name = "index-elasticsearch__plugin_test_deps",
+ testonly = True,
+ srcs = [],
+ visibility = ["//visibility:public"],
+ exports = ELASTICSEARCH_DEPS,
+)
+
+java_library(
+ name = "elasticsearch_test_utils",
+ testonly = True,
+ srcs = glob(
+ ["src/test/java/**/*.java"],
+ exclude = ["src/test/java/**/*Test.java"],
+ ),
+ visibility = ["//visibility:public"],
+ deps = ELASTICSEARCH_DEPS + PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+ ":index-elasticsearch__plugin",
+ ],
+)
+
+QUERY_TESTS_DEP = "//javatests/com/google/gerrit/server/query/%s:abstract_query_tests"
+
+ACCOUNT_QUERY_TESTS_DEP = "//javatests/com/google/gerrit/server/query/account:abstract_query_tests"
+
+TYPES = [
+ "account",
+ "change",
+ "group",
+ "project",
+]
+
+SUFFIX = "sTest.java"
+
+ELASTICSEARCH_TESTS_V7 = {i: "ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
+
+[junit_tests(
+ name = "elasticsearch_query_%ss_test_V7" % name,
+ size = "enormous",
+ srcs = ["src/test/java/com/google/gerrit/elasticsearch/" + src],
+ tags = [
+ "docker",
+ "elastic",
+ "exclusive",
+ ],
+ deps = ELASTICSEARCH_DEPS + PLUGIN_TEST_DEPS + [
+ QUERY_TESTS_DEP % name,
+ ":elasticsearch_test_utils",
+ ":index-elasticsearch__plugin",
+ ],
+) for name, src in ELASTICSEARCH_TESTS_V7.items()]
+
+junit_tests(
+ name = "index-elasticsearch_tests",
+ size = "small",
+ srcs = glob(
+ ["src/test/java/**/*Test.java"],
+ exclude = ["src/test/java/**/Elastic*Query*" + SUFFIX],
+ ),
+ tags = ["elastic"],
+ deps = PLUGIN_TEST_DEPS + [
+ ":index-elasticsearch__plugin",
+ ],
+)
diff --git a/Jenkinsfile b/Jenkinsfile
new file mode 100644
index 0000000..a8708e7
--- /dev/null
+++ b/Jenkinsfile
@@ -0,0 +1,2 @@
+pluginPipeline(formatCheckId: 'gerritforge:index-elasticsearch-code-style',
+ buildCheckId: 'gerritforge:index-elasticsearch-build-test')
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..41d4279
--- /dev/null
+++ b/README.md
@@ -0,0 +1,29 @@
+# Index backend for Gerrit, based on ElasticSearch
+
+Indexing backend libModule for [Gerrit Code Review](https://gerritcodereview.com)
+based on [ElasticSearch](https://www.elastic.co/elasticsearch/).
+
+This module was originally part of Gerrit core and then extracted into a separate
+component from v3.5.0-rc3 as part of [Change-Id: Ib7b5167ce](https://gerrit-review.googlesource.com/c/gerrit/+/323676).
+
+Note that, ElasticSearch source code is no longer Apache 2.0-licensed for versions
+7.11 and newer. See ElasticSearch [2021 license change](https://www.elastic.co/pricing/faq/licensing)
+for more information.
+
+## How to build
+
+This libModule is built like a Gerrit in-tree plugin, using Bazelisk. See the
+[build instructions](src/main/resources/Documentation/build.md) for more details.
+
+## Setup
+
+See the [setup instructions](src/main/resources/Documentation/setup.md) for how to install the
+index-elasticsearch module.
+
+For further information and supported options, refer to the [config](src/main/resources/Documentation/config.md)
+documentation.
+
+## Integration test
+
+This libModule runs tests like a Gerrit in-tree plugin, using Bazelisk. See the
+[test instructions](src/main/resources/Documentation/build.md#Integration-test) for more details.
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
new file mode 100644
index 0000000..8b44d57
--- /dev/null
+++ b/external_plugin_deps.bzl
@@ -0,0 +1,93 @@
+load("//tools/bzl:maven_jar.bzl", "maven_jar")
+
+TESTCONTAINERS_VERSION = "1.15.3"
+
+# Ensure artifacts compatibility by selecting them from the Bill Of Materials
+# https://search.maven.org/artifact/net.openhft/chronicle-bom/2.20.191/pom
+def external_plugin_deps():
+ maven_jar(
+ name = "testcontainers",
+ artifact = "org.testcontainers:testcontainers:" + TESTCONTAINERS_VERSION,
+ sha1 = "95c6cfde71c2209f0c29cb14e432471e0b111880",
+ )
+
+ # When upgrading elasticsearch-rest-client, also upgrade httpcore-nio
+ # and httpasyncclient as necessary in tools/nongoogle.bzl. Consider
+ # also the other org.apache.httpcomponents dependencies in
+ # WORKSPACE.
+ maven_jar(
+ name = "elasticsearch-rest-client",
+ artifact = "org.elasticsearch.client:elasticsearch-rest-client:8.3.2",
+ sha1 = "bb5cb3dbd82ea75a6d49b9011ca5b1d125b30f00",
+ )
+
+ maven_jar(
+ name = "testcontainers-elasticsearch",
+ artifact = "org.testcontainers:elasticsearch:" + TESTCONTAINERS_VERSION,
+ sha1 = "595e3a50f59cd3c1d281ca6c1bc4037e277a1353",
+ )
+
+ maven_jar(
+ name = "duct-tape",
+ artifact = "org.rnorth.duct-tape:duct-tape:1.0.8",
+ sha1 = "92edc22a9ab2f3e17c9bf700aaee377d50e8b530",
+ )
+
+ maven_jar(
+ name = "visible-assertions",
+ artifact = "org.rnorth.visible-assertions:visible-assertions:2.1.2",
+ sha1 = "20d31a578030ec8e941888537267d3123c2ad1c1",
+ )
+
+ maven_jar(
+ name = "jna",
+ artifact = "net.java.dev.jna:jna:5.5.0",
+ sha1 = "0e0845217c4907822403912ad6828d8e0b256208",
+ )
+
+ DOCKER_JAVA_VERS = "3.2.8"
+
+ maven_jar(
+ name = "docker-java-api",
+ artifact = "com.github.docker-java:docker-java-api:" + DOCKER_JAVA_VERS,
+ sha1 = "4ac22a72d546a9f3523cd4b5fabffa77c4a6ec7c",
+ )
+
+ maven_jar(
+ name = "docker-java-transport",
+ artifact = "com.github.docker-java:docker-java-transport:" + DOCKER_JAVA_VERS,
+ sha1 = "c3b5598c67d0a5e2e780bf48f520da26b9915eab",
+ )
+
+ # elasticsearch-rest-client explicitly depends on this version
+ maven_jar(
+ name = "httpasyncclient",
+ artifact = "org.apache.httpcomponents:httpasyncclient:4.1.4",
+ sha1 = "f3a3240681faae3fa46b573a4c7e50cec9db0d86",
+ )
+
+ # elasticsearch-rest-client explicitly depends on this version
+ maven_jar(
+ name = "httpcore-nio",
+ artifact = "org.apache.httpcomponents:httpcore-nio:4.4.12",
+ sha1 = "84cd29eca842f31db02987cfedea245af020198b",
+ )
+
+ maven_jar(
+ name = "jackson-core",
+ artifact = "com.fasterxml.jackson.core:jackson-core:2.11.3",
+ sha1 = "c2351800432bdbdd8284c3f5a7f0782a352aa84a",
+ )
+
+ maven_jar(
+ name = "jackson-annotations",
+ artifact = "com.fasterxml.jackson.core:jackson-annotations:2.10.3",
+ sha1 = "0f63b3b1da563767d04d2e4d3fc1ae0cdeffebe7",
+ )
+
+ # elasticsearch-rest-client explicitly depends on this version
+ maven_jar(
+ name = "httpasyncclient",
+ artifact = "org.apache.httpcomponents:httpasyncclient:4.1.4",
+ sha1 = "f3a3240681faae3fa46b573a4c7e50cec9db0d86",
+ )
diff --git a/java/com/google/gerrit/elasticsearch/BUILD b/java/com/google/gerrit/elasticsearch/BUILD
deleted file mode 100644
index 8bab80b..0000000
--- a/java/com/google/gerrit/elasticsearch/BUILD
+++ /dev/null
@@ -1,33 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-
-java_library(
- name = "elasticsearch",
- srcs = glob(["**/*.java"]),
- visibility = ["//visibility:public"],
- deps = [
- "//java/com/google/gerrit/common:annotations",
- "//java/com/google/gerrit/entities",
- "//java/com/google/gerrit/exceptions",
- "//java/com/google/gerrit/extensions:api",
- "//java/com/google/gerrit/index",
- "//java/com/google/gerrit/index:query_exception",
- "//java/com/google/gerrit/index/project",
- "//java/com/google/gerrit/lifecycle",
- "//java/com/google/gerrit/proto",
- "//java/com/google/gerrit/server",
- "//lib:gson",
- "//lib:guava",
- "//lib:jgit",
- "//lib:protobuf",
- "//lib/commons:lang",
- "//lib/elasticsearch-rest-client",
- "//lib/flogger:api",
- "//lib/guice",
- "//lib/guice:guice-assistedinject",
- "//lib/httpcomponents:httpasyncclient",
- "//lib/httpcomponents:httpclient",
- "//lib/httpcomponents:httpcore",
- "//lib/httpcomponents:httpcore-nio",
- "//lib/jackson:jackson-core",
- ],
-)
diff --git a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
deleted file mode 100644
index 162654d..0000000
--- a/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
+++ /dev/null
@@ -1,431 +0,0 @@
-// Copyright (C) 2014 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 static java.nio.charset.StandardCharsets.UTF_8;
-import static java.util.Objects.requireNonNull;
-
-import com.google.common.collect.FluentIterable;
-import com.google.common.collect.ImmutableListMultimap;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Iterables;
-import com.google.common.collect.ListMultimap;
-import com.google.common.collect.MultimapBuilder;
-import com.google.common.collect.Sets;
-import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
-import com.google.gerrit.elasticsearch.bulk.BulkRequest;
-import com.google.gerrit.elasticsearch.bulk.IndexRequest;
-import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
-import com.google.gerrit.entities.Account;
-import com.google.gerrit.entities.Change;
-import com.google.gerrit.entities.Project;
-import com.google.gerrit.entities.converter.ChangeProtoConverter;
-import com.google.gerrit.entities.converter.PatchSetApprovalProtoConverter;
-import com.google.gerrit.entities.converter.PatchSetProtoConverter;
-import com.google.gerrit.exceptions.StorageException;
-import com.google.gerrit.index.FieldDef;
-import com.google.gerrit.index.QueryOptions;
-import com.google.gerrit.index.RefState;
-import com.google.gerrit.index.Schema;
-import com.google.gerrit.index.query.DataSource;
-import com.google.gerrit.index.query.Predicate;
-import com.google.gerrit.index.query.QueryParseException;
-import com.google.gerrit.server.ReviewerByEmailSet;
-import com.google.gerrit.server.ReviewerSet;
-import com.google.gerrit.server.StarredChangesUtil;
-import com.google.gerrit.server.change.MergeabilityComputationBehavior;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.config.SitePaths;
-import com.google.gerrit.server.index.IndexUtils;
-import com.google.gerrit.server.index.change.ChangeField;
-import com.google.gerrit.server.index.change.ChangeIndex;
-import com.google.gerrit.server.project.SubmitRuleOptions;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gson.JsonArray;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonObject;
-import com.google.inject.Inject;
-import com.google.inject.assistedinject.Assisted;
-import java.sql.Timestamp;
-import java.time.Instant;
-import java.time.format.DateTimeFormatter;
-import java.util.Collections;
-import java.util.Optional;
-import java.util.Set;
-import org.apache.http.HttpStatus;
-import org.eclipse.jgit.lib.Config;
-import org.elasticsearch.client.Response;
-
-/** Secondary index implementation using Elasticsearch. */
-class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
- implements ChangeIndex {
- static class ChangeMapping {
- final MappingProperties changes;
- final MappingProperties openChanges;
- final MappingProperties closedChanges;
-
- ChangeMapping(Schema<ChangeData> schema, ElasticQueryAdapter adapter) {
- MappingProperties mapping = ElasticMapping.createMapping(schema, adapter);
- this.changes = mapping;
- this.openChanges = mapping;
- this.closedChanges = mapping;
- }
- }
-
- private static final String CHANGES = "changes";
-
- private final ChangeMapping mapping;
- private final ChangeData.Factory changeDataFactory;
- private final Schema<ChangeData> schema;
- private final FieldDef<ChangeData, ?> idField;
- private final ImmutableSet<String> skipFields;
-
- @Inject
- ElasticChangeIndex(
- ElasticConfiguration cfg,
- ChangeData.Factory changeDataFactory,
- SitePaths sitePaths,
- ElasticRestClientProvider clientBuilder,
- @GerritServerConfig Config gerritConfig,
- @Assisted Schema<ChangeData> schema) {
- super(cfg, sitePaths, schema, clientBuilder, CHANGES);
- this.changeDataFactory = changeDataFactory;
- this.schema = schema;
- this.mapping = new ChangeMapping(schema, client.adapter());
- this.idField =
- this.schema.useLegacyNumericFields() ? ChangeField.LEGACY_ID : ChangeField.LEGACY_ID_STR;
- this.skipFields =
- MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex()
- ? ImmutableSet.of()
- : ImmutableSet.of(ChangeField.MERGEABLE.getName());
- }
-
- @Override
- public void replace(ChangeData cd) {
- BulkRequest bulk =
- new IndexRequest(getId(cd), indexName).add(new UpdateRequest<>(schema, cd, skipFields));
-
- String uri = getURI(BULK);
- Response response = postRequest(uri, bulk, getRefreshParam());
- int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
- throw new StorageException(
- String.format(
- "Failed to replace change %s in index %s: %s", cd.getId(), indexName, statusCode));
- }
- }
-
- @Override
- public DataSource<ChangeData> getSource(Predicate<ChangeData> p, QueryOptions opts)
- throws QueryParseException {
- QueryOptions filteredOpts =
- opts.filterFields(o -> IndexUtils.changeFields(o, schema.useLegacyNumericFields()));
- return new ElasticQuerySource(p, filteredOpts, getSortArray());
- }
-
- private JsonArray getSortArray() {
- JsonObject properties = new JsonObject();
- properties.addProperty(ORDER, DESC_SORT_ORDER);
-
- JsonArray sortArray = new JsonArray();
- addNamedElement(ChangeField.UPDATED.getName(), properties, sortArray);
- addNamedElement(ChangeField.MERGED_ON.getName(), getMergedOnSortOptions(), sortArray);
- addNamedElement(idField.getName(), properties, sortArray);
- return sortArray;
- }
-
- private JsonObject getMergedOnSortOptions() {
- JsonObject sortOptions = new JsonObject();
- sortOptions.addProperty(ORDER, DESC_SORT_ORDER);
- // Ignore the sort field if it does not exist in index. Otherwise the search would fail on open
- // changes, because the corresponding documents do not have mergedOn field.
- sortOptions.addProperty(UNMAPPED_TYPE, ElasticMapping.TIMESTAMP_FIELD_TYPE);
- return sortOptions;
- }
-
- @Override
- protected String getDeleteActions(Change.Id c) {
- return getDeleteRequest(c);
- }
-
- @Override
- protected String getMappings() {
- return getMappingsFor(mapping.changes);
- }
-
- @Override
- protected String getId(ChangeData cd) {
- return cd.getId().toString();
- }
-
- @Override
- protected ChangeData fromDocument(JsonObject json, Set<String> fields) {
- JsonElement sourceElement = json.get("_source");
- if (sourceElement == null) {
- sourceElement = json.getAsJsonObject().get("fields");
- }
- JsonObject source = sourceElement.getAsJsonObject();
- JsonElement c = source.get(ChangeField.CHANGE.getName());
-
- if (c == null) {
- int id = source.get(idField.getName()).getAsInt();
- // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
- String projectName = requireNonNull(source.get(ChangeField.PROJECT.getName()).getAsString());
- return changeDataFactory.create(Project.nameKey(projectName), Change.id(id));
- }
-
- ChangeData cd =
- changeDataFactory.create(
- parseProtoFrom(decodeBase64(c.getAsString()), ChangeProtoConverter.INSTANCE));
-
- // Any decoding that is done here must also be done in {@link LuceneChangeIndex}.
-
- // Patch sets.
- cd.setPatchSets(
- decodeProtos(source, ChangeField.PATCH_SET.getName(), PatchSetProtoConverter.INSTANCE));
-
- // Approvals.
- if (source.get(ChangeField.APPROVAL.getName()) != null) {
- cd.setCurrentApprovals(
- decodeProtos(
- source, ChangeField.APPROVAL.getName(), PatchSetApprovalProtoConverter.INSTANCE));
- } else if (fields.contains(ChangeField.APPROVAL.getName())) {
- cd.setCurrentApprovals(Collections.emptyList());
- }
-
- // Added & Deleted.
- JsonElement addedElement = source.get(ChangeField.ADDED.getName());
- JsonElement deletedElement = source.get(ChangeField.DELETED.getName());
- if (addedElement != null && deletedElement != null) {
- // Changed lines.
- int added = addedElement.getAsInt();
- int deleted = deletedElement.getAsInt();
- cd.setChangedLines(added, deleted);
- }
-
- // Star.
- JsonElement starredElement = source.get(ChangeField.STAR.getName());
- if (starredElement != null) {
- ListMultimap<Account.Id, String> stars = MultimapBuilder.hashKeys().arrayListValues().build();
- JsonArray starBy = starredElement.getAsJsonArray();
- if (starBy.size() > 0) {
- for (int i = 0; i < starBy.size(); i++) {
- String[] indexableFields = starBy.get(i).getAsString().split(":");
- Optional<Account.Id> id = Account.Id.tryParse(indexableFields[0]);
- if (id.isPresent()) {
- stars.put(id.get(), indexableFields[1]);
- }
- }
- }
- cd.setStars(stars);
- }
-
- // Mergeable.
- JsonElement mergeableElement = source.get(ChangeField.MERGEABLE.getName());
- if (mergeableElement != null && !skipFields.contains(ChangeField.MERGEABLE.getName())) {
- String mergeable = mergeableElement.getAsString();
- if ("1".equals(mergeable)) {
- cd.setMergeable(true);
- } else if ("0".equals(mergeable)) {
- cd.setMergeable(false);
- }
- }
-
- // Reviewed-by.
- if (source.get(ChangeField.REVIEWEDBY.getName()) != null) {
- JsonArray reviewedBy = source.get(ChangeField.REVIEWEDBY.getName()).getAsJsonArray();
- if (reviewedBy.size() > 0) {
- Set<Account.Id> accounts = Sets.newHashSetWithExpectedSize(reviewedBy.size());
- for (int i = 0; i < reviewedBy.size(); i++) {
- int aId = reviewedBy.get(i).getAsInt();
- if (reviewedBy.size() == 1 && aId == ChangeField.NOT_REVIEWED) {
- break;
- }
- accounts.add(Account.id(aId));
- }
- cd.setReviewedBy(accounts);
- }
- } else if (fields.contains(ChangeField.REVIEWEDBY.getName())) {
- cd.setReviewedBy(Collections.emptySet());
- }
-
- // Hashtag.
- if (source.get(ChangeField.HASHTAG.getName()) != null) {
- JsonArray hashtagArray = source.get(ChangeField.HASHTAG.getName()).getAsJsonArray();
- if (hashtagArray.size() > 0) {
- Set<String> hashtags = Sets.newHashSetWithExpectedSize(hashtagArray.size());
- for (int i = 0; i < hashtagArray.size(); i++) {
- hashtags.add(hashtagArray.get(i).getAsString());
- }
- cd.setHashtags(hashtags);
- }
- } else if (fields.contains(ChangeField.HASHTAG.getName())) {
- cd.setHashtags(Collections.emptySet());
- }
-
- // Star.
- if (source.get(ChangeField.STAR.getName()) != null) {
- JsonArray starArray = source.get(ChangeField.STAR.getName()).getAsJsonArray();
- if (starArray.size() > 0) {
- ListMultimap<Account.Id, String> stars =
- MultimapBuilder.hashKeys().arrayListValues().build();
- for (int i = 0; i < starArray.size(); i++) {
- StarredChangesUtil.StarField starField =
- StarredChangesUtil.StarField.parse(starArray.get(i).getAsString());
- stars.put(starField.accountId(), starField.label());
- }
- cd.setStars(stars);
- }
- } else if (fields.contains(ChangeField.STAR.getName())) {
- cd.setStars(ImmutableListMultimap.of());
- }
-
- // Reviewer.
- if (source.get(ChangeField.REVIEWER.getName()) != null) {
- cd.setReviewers(
- ChangeField.parseReviewerFieldValues(
- cd.getId(),
- FluentIterable.from(source.get(ChangeField.REVIEWER.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.REVIEWER.getName())) {
- cd.setReviewers(ReviewerSet.empty());
- }
-
- // Reviewer-by-email.
- if (source.get(ChangeField.REVIEWER_BY_EMAIL.getName()) != null) {
- cd.setReviewersByEmail(
- ChangeField.parseReviewerByEmailFieldValues(
- cd.getId(),
- FluentIterable.from(
- source.get(ChangeField.REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.REVIEWER_BY_EMAIL.getName())) {
- cd.setReviewersByEmail(ReviewerByEmailSet.empty());
- }
-
- // Pending-reviewer.
- if (source.get(ChangeField.PENDING_REVIEWER.getName()) != null) {
- cd.setPendingReviewers(
- ChangeField.parseReviewerFieldValues(
- cd.getId(),
- FluentIterable.from(
- source.get(ChangeField.PENDING_REVIEWER.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.PENDING_REVIEWER.getName())) {
- cd.setPendingReviewers(ReviewerSet.empty());
- }
-
- // Pending-reviewer-by-email.
- if (source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()) != null) {
- cd.setPendingReviewersByEmail(
- ChangeField.parseReviewerByEmailFieldValues(
- cd.getId(),
- FluentIterable.from(
- source.get(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName()).getAsJsonArray())
- .transform(JsonElement::getAsString)));
- } else if (fields.contains(ChangeField.PENDING_REVIEWER_BY_EMAIL.getName())) {
- cd.setPendingReviewersByEmail(ReviewerByEmailSet.empty());
- }
-
- // Stored-submit-record-strict.
- decodeSubmitRecords(
- source,
- ChangeField.STORED_SUBMIT_RECORD_STRICT.getName(),
- ChangeField.SUBMIT_RULE_OPTIONS_STRICT,
- cd);
-
- // Stored-submit-record-lenient.
- decodeSubmitRecords(
- source,
- ChangeField.STORED_SUBMIT_RECORD_LENIENT.getName(),
- ChangeField.SUBMIT_RULE_OPTIONS_LENIENT,
- cd);
-
- // Ref-state.
- if (fields.contains(ChangeField.REF_STATE.getName())) {
- cd.setRefStates(RefState.parseStates(getByteArray(source, ChangeField.REF_STATE.getName())));
- }
-
- // Ref-state-pattern.
- if (fields.contains(ChangeField.REF_STATE_PATTERN.getName())) {
- cd.setRefStatePatterns(getByteArray(source, ChangeField.REF_STATE_PATTERN.getName()));
- }
-
- // Unresolved-comment-count.
- decodeUnresolvedCommentCount(source, ChangeField.UNRESOLVED_COMMENT_COUNT.getName(), cd);
-
- // Attention set.
- if (fields.contains(ChangeField.ATTENTION_SET_FULL.getName())) {
- ChangeField.parseAttentionSet(
- FluentIterable.from(source.getAsJsonArray(ChangeField.ATTENTION_SET_FULL.getName()))
- .transform(ElasticChangeIndex::decodeBase64JsonElement)
- .toSet(),
- cd);
- }
-
- if (fields.contains(ChangeField.MERGED_ON.getName())) {
- decodeMergedOn(source, cd);
- }
-
- return cd;
- }
-
- private Iterable<byte[]> getByteArray(JsonObject source, String name) {
- JsonElement element = source.get(name);
- return element != null
- ? Iterables.transform(element.getAsJsonArray(), e -> decodeBase64(e.getAsString()))
- : Collections.emptyList();
- }
-
- private void decodeSubmitRecords(
- JsonObject doc, String fieldName, SubmitRuleOptions opts, ChangeData out) {
- JsonArray records = doc.getAsJsonArray(fieldName);
- if (records == null) {
- return;
- }
- ChangeField.parseSubmitRecords(
- FluentIterable.from(records)
- .transform(ElasticChangeIndex::decodeBase64JsonElement)
- .toList(),
- opts,
- out);
- }
-
- private static String decodeBase64JsonElement(JsonElement input) {
- return new String(decodeBase64(input.getAsString()), UTF_8);
- }
-
- private void decodeUnresolvedCommentCount(JsonObject doc, String fieldName, ChangeData out) {
- JsonElement count = doc.get(fieldName);
- if (count == null) {
- return;
- }
- out.setUnresolvedCommentCount(count.getAsInt());
- }
-
- private void decodeMergedOn(JsonObject doc, ChangeData out) {
- JsonElement mergedOnField = doc.get(ChangeField.MERGED_ON.getName());
-
- Timestamp mergedOn = null;
- if (mergedOnField != null) {
- // Parse from ElasticMapping.TIMESTAMP_FIELD_FORMAT.
- // We currently use built-in ISO-based dateOptionalTime.
- // https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-date-format.html#built-in-date-formats
- DateTimeFormatter isoFormatter = DateTimeFormatter.ISO_INSTANT;
- mergedOn = Timestamp.from(Instant.from(isoFormatter.parse(mergedOnField.getAsString())));
- }
- out.setMergedOn(mergedOn);
- }
-}
diff --git a/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java b/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java
deleted file mode 100644
index f086ab1..0000000
--- a/java/com/google/gerrit/pgm/init/index/elasticsearch/ElasticIndexModuleOnInit.java
+++ /dev/null
@@ -1,41 +0,0 @@
-// Copyright (C) 2017 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.pgm.init.index.elasticsearch;
-
-import com.google.gerrit.elasticsearch.ElasticAccountIndex;
-import com.google.gerrit.elasticsearch.ElasticGroupIndex;
-import com.google.gerrit.pgm.init.index.IndexModuleOnInit;
-import com.google.gerrit.server.index.account.AccountIndex;
-import com.google.gerrit.server.index.group.GroupIndex;
-import com.google.inject.AbstractModule;
-import com.google.inject.assistedinject.FactoryModuleBuilder;
-
-public class ElasticIndexModuleOnInit extends AbstractModule {
-
- @Override
- protected void configure() {
- install(
- new FactoryModuleBuilder()
- .implement(AccountIndex.class, ElasticAccountIndex.class)
- .build(AccountIndex.Factory.class));
-
- install(
- new FactoryModuleBuilder()
- .implement(GroupIndex.class, ElasticGroupIndex.class)
- .build(GroupIndex.Factory.class));
-
- install(new IndexModuleOnInit());
- }
-}
diff --git a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java b/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
deleted file mode 100644
index f23cc10..0000000
--- a/javatests/com/google/gerrit/acceptance/pgm/ElasticReindexIT.java
+++ /dev/null
@@ -1,36 +0,0 @@
-// 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.pgm;
-
-import static com.google.gerrit.elasticsearch.ElasticTestUtils.createAllIndexes;
-import static com.google.gerrit.elasticsearch.ElasticTestUtils.getConfig;
-
-import com.google.gerrit.elasticsearch.ElasticVersion;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.inject.Injector;
-import org.eclipse.jgit.lib.Config;
-
-public class ElasticReindexIT extends AbstractReindexTests {
-
- @ConfigSuite.Default
- public static Config elasticsearchV7() {
- return getConfig(ElasticVersion.V7_8);
- }
-
- @Override
- public void configureIndex(Injector injector) {
- createAllIndexes(injector);
- }
-}
diff --git a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java b/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
deleted file mode 100644
index f35bcb7..0000000
--- a/javatests/com/google/gerrit/acceptance/ssh/ElasticIndexIT.java
+++ /dev/null
@@ -1,38 +0,0 @@
-// 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.gerrit.elasticsearch.ElasticTestUtils.createAllIndexes;
-import static com.google.gerrit.elasticsearch.ElasticTestUtils.getConfig;
-
-import com.google.gerrit.elasticsearch.ElasticVersion;
-import com.google.gerrit.index.IndexType;
-import com.google.gerrit.testing.ConfigSuite;
-import com.google.inject.Injector;
-import org.eclipse.jgit.lib.Config;
-
-/** Tests for every supported {@link IndexType#isElasticsearch()} most recent index version. */
-public class ElasticIndexIT extends AbstractIndexTests {
-
- @ConfigSuite.Default
- public static Config elasticsearchV7() {
- return getConfig(ElasticVersion.V7_8);
- }
-
- @Override
- public void configureIndex(Injector injector) {
- createAllIndexes(injector);
- }
-}
diff --git a/javatests/com/google/gerrit/elasticsearch/BUILD b/javatests/com/google/gerrit/elasticsearch/BUILD
deleted file mode 100644
index 3036811..0000000
--- a/javatests/com/google/gerrit/elasticsearch/BUILD
+++ /dev/null
@@ -1,85 +0,0 @@
-load("@rules_java//java:defs.bzl", "java_library")
-load("//tools/bzl:junit.bzl", "junit_tests")
-
-java_library(
- name = "elasticsearch_test_utils",
- testonly = True,
- srcs = [
- "ElasticContainer.java",
- "ElasticTestUtils.java",
- ],
- visibility = ["//visibility:public"],
- deps = [
- "//java/com/google/gerrit/elasticsearch",
- "//java/com/google/gerrit/index",
- "//lib:guava",
- "//lib:jgit",
- "//lib:junit",
- "//lib/guice",
- "//lib/httpcomponents:httpcore",
- "//lib/jackson:jackson-annotations",
- "//lib/log:api",
- "//lib/testcontainers",
- "//lib/testcontainers:docker-java-api",
- "//lib/testcontainers:docker-java-transport",
- "//lib/testcontainers:testcontainers-elasticsearch",
- ],
-)
-
-ELASTICSEARCH_DEPS = [
- ":elasticsearch_test_utils",
- "//java/com/google/gerrit/elasticsearch",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//lib/guice",
- "//lib:jgit",
-]
-
-HTTP_TEST_DEPS = [
- "//lib/httpcomponents:httpasyncclient",
- "//lib/httpcomponents:httpclient",
-]
-
-QUERY_TESTS_DEP = "//javatests/com/google/gerrit/server/query/%s:abstract_query_tests"
-
-TYPES = [
- "account",
- "change",
- "group",
- "project",
-]
-
-SUFFIX = "sTest.java"
-
-ELASTICSEARCH_TESTS_V7 = {i: "ElasticV7Query" + i.capitalize() + SUFFIX for i in TYPES}
-
-ELASTICSEARCH_TAGS = [
- "docker",
- "elastic",
-]
-
-[junit_tests(
- name = "elasticsearch_query_%ss_test_V7" % name,
- size = "large",
- srcs = [src],
- tags = ELASTICSEARCH_TAGS,
- deps = ELASTICSEARCH_DEPS + [QUERY_TESTS_DEP % name] + HTTP_TEST_DEPS,
-) for name, src in ELASTICSEARCH_TESTS_V7.items()]
-
-junit_tests(
- name = "elasticsearch_tests",
- size = "small",
- srcs = glob(
- ["*Test.java"],
- exclude = ["Elastic*Query*" + SUFFIX],
- ),
- tags = ["elastic"],
- deps = [
- "//java/com/google/gerrit/elasticsearch",
- "//java/com/google/gerrit/testing:gerrit-test-util",
- "//lib:guava",
- "//lib:jgit",
- "//lib/guice",
- "//lib/httpcomponents:httpcore",
- "//lib/truth",
- ],
-)
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
deleted file mode 100644
index dcc6880..0000000
--- a/javatests/com/google/gerrit/elasticsearch/ElasticTestUtils.java
+++ /dev/null
@@ -1,54 +0,0 @@
-// Copyright (C) 2016 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.index.IndexDefinition;
-import com.google.inject.Injector;
-import com.google.inject.Key;
-import com.google.inject.TypeLiteral;
-import java.util.Collection;
-import java.util.UUID;
-import org.eclipse.jgit.lib.Config;
-
-public final class ElasticTestUtils {
- public static void configure(Config config, ElasticContainer container, String prefix) {
- String hostname = container.getHttpHost().getHostName();
- int port = container.getHttpHost().getPort();
- config.setString("index", null, "type", "elasticsearch");
- config.setString("elasticsearch", null, "server", "http://" + hostname + ":" + port);
- config.setString("elasticsearch", null, "prefix", prefix);
- config.setInt("index", null, "maxLimit", 10000);
- }
-
- public static void createAllIndexes(Injector injector) {
- Collection<IndexDefinition<?, ?, ?>> indexDefs =
- injector.getInstance(Key.get(new TypeLiteral<Collection<IndexDefinition<?, ?, ?>>>() {}));
- for (IndexDefinition<?, ?, ?> indexDef : indexDefs) {
- indexDef.getIndexCollection().getSearchIndex().deleteAll();
- }
- }
-
- public static Config getConfig(ElasticVersion version) {
- ElasticContainer container = ElasticContainer.createAndStart(version);
- String indicesPrefix = UUID.randomUUID().toString();
- Config cfg = new Config();
- configure(cfg, container, indicesPrefix);
- return cfg;
- }
-
- private ElasticTestUtils() {
- // hide default constructor
- }
-}
diff --git a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java b/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
similarity index 91%
rename from java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
rename to src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
index a564272..3b300cf 100644
--- a/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/AbstractElasticIndex.java
@@ -19,6 +19,7 @@
import static com.google.gson.FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES;
import static java.nio.charset.StandardCharsets.UTF_8;
+import com.google.common.base.Preconditions;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
@@ -49,6 +50,7 @@
import com.google.gerrit.proto.Protos;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonArray;
@@ -64,7 +66,6 @@
import java.net.URLEncoder;
import java.sql.Timestamp;
import java.util.Collections;
-import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -75,6 +76,7 @@
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.nio.entity.NStringEntity;
+import org.apache.http.util.EntityUtils;
import org.elasticsearch.client.Request;
import org.elasticsearch.client.Response;
@@ -90,7 +92,7 @@
protected static final String SEARCH = "_search";
protected static final String SETTINGS = "settings";
- protected static byte[] decodeBase64(String base64String) {
+ static byte[] decodeBase64(String base64String) {
return BaseEncoding.base64().decode(base64String);
}
@@ -129,6 +131,7 @@
private final Schema<V> schema;
private final SitePaths sitePaths;
private final String indexNameRaw;
+ private final Map<String, String> refreshParam;
protected final ElasticRestClientProvider client;
protected final String indexName;
@@ -140,7 +143,8 @@
SitePaths sitePaths,
Schema<V> schema,
ElasticRestClientProvider client,
- String indexName) {
+ String indexName,
+ AutoFlush autoFlush) {
this.config = config;
this.sitePaths = sitePaths;
this.schema = schema;
@@ -149,6 +153,15 @@
this.indexName = config.getIndexName(indexName, schema.getVersion());
this.indexNameRaw = indexName;
this.client = client;
+ this.refreshParam =
+ Map.of(
+ "refresh",
+ autoFlush == AutoFlush.ENABLED ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+ }
+
+ @Override
+ public void insert(V obj) {
+ replace(obj);
}
@Override
@@ -169,7 +182,7 @@
@Override
public void delete(K id) {
String uri = getURI(BULK);
- Response response = postRequest(uri, getDeleteActions(id), getRefreshParam());
+ Response response = postRequestWithRefreshParam(uri, getDeleteActions(id));
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode != HttpStatus.SC_OK) {
throw new StorageException(
@@ -260,6 +273,20 @@
return new FieldBundle(rawFields);
}
+ protected boolean hasErrors(Response response) {
+ try {
+ String contentType = response.getEntity().getContentType().getValue();
+ Preconditions.checkState(
+ contentType.equals(ContentType.APPLICATION_JSON.toString()),
+ String.format("Expected %s, but was: %s", ContentType.APPLICATION_JSON, contentType));
+ String responseStr = EntityUtils.toString(response.getEntity());
+ JsonObject responseJson = (JsonObject) new JsonParser().parse(responseStr);
+ return responseJson.get("errors").getAsBoolean();
+ } catch (IOException e) {
+ throw new StorageException(e);
+ }
+ }
+
protected String toAction(String type, String id, String action) {
JsonObject properties = new JsonObject();
properties.addProperty("_id", id);
@@ -277,12 +304,6 @@
array.add(arrayElement);
}
- protected Map<String, String> getRefreshParam() {
- Map<String, String> params = new HashMap<>();
- params.put("refresh", "true");
- return params;
- }
-
protected String getSearch(SearchSourceBuilder searchSource, JsonArray sortArray) {
JsonObject search = new JsonParser().parse(searchSource.toString()).getAsJsonObject();
search.add("sort", sortArray);
@@ -306,12 +327,8 @@
}
}
- protected Response postRequest(String uri, Object payload) {
- return performRequest("POST", uri, payload);
- }
-
- protected Response postRequest(String uri, Object payload, Map<String, String> params) {
- return performRequest("POST", uri, payload, params);
+ protected Response postRequestWithRefreshParam(String uri, Object payload) {
+ return performRequest("POST", uri, payload, refreshParam);
}
private String concatJsonString(String target, String addition) {
diff --git a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
similarity index 94%
rename from java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
index 8967789..1da80ae 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticAccountIndex.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.account.AccountField;
import com.google.gerrit.server.index.account.AccountIndex;
+import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -64,8 +65,9 @@
SitePaths sitePaths,
Provider<AccountCache> accountCache,
ElasticRestClientProvider client,
+ AutoFlush autoFlush,
@Assisted Schema<AccountState> schema) {
- super(cfg, sitePaths, schema, client, ACCOUNTS);
+ super(cfg, sitePaths, schema, client, ACCOUNTS, autoFlush);
this.accountCache = accountCache;
this.mapping = new AccountMapping(schema, client.adapter());
this.schema = schema;
@@ -78,9 +80,9 @@
.add(new UpdateRequest<>(schema, as, ImmutableSet.of()));
String uri = getURI(BULK);
- Response response = postRequest(uri, bulk, getRefreshParam());
+ Response response = postRequestWithRefreshParam(uri, bulk);
int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
+ if (hasErrors(response) || statusCode != HttpStatus.SC_OK) {
throw new StorageException(
String.format(
"Failed to replace account %s in index %s: %s",
diff --git a/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
new file mode 100644
index 0000000..a4674bf
--- /dev/null
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticChangeIndex.java
@@ -0,0 +1,183 @@
+// Copyright (C) 2014 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 static java.util.Objects.requireNonNull;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.gerrit.elasticsearch.ElasticMapping.MappingProperties;
+import com.google.gerrit.elasticsearch.bulk.BulkRequest;
+import com.google.gerrit.elasticsearch.bulk.IndexRequest;
+import com.google.gerrit.elasticsearch.bulk.UpdateRequest;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.entities.Project;
+import com.google.gerrit.entities.converter.ChangeProtoConverter;
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.index.FieldDef;
+import com.google.gerrit.index.QueryOptions;
+import com.google.gerrit.index.Schema;
+import com.google.gerrit.index.query.DataSource;
+import com.google.gerrit.index.query.Predicate;
+import com.google.gerrit.index.query.QueryParseException;
+import com.google.gerrit.server.change.MergeabilityComputationBehavior;
+import com.google.gerrit.server.config.GerritServerConfig;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.change.ChangeField;
+import com.google.gerrit.server.index.change.ChangeIndex;
+import com.google.gerrit.server.index.options.AutoFlush;
+import com.google.gerrit.server.query.change.ChangeData;
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.inject.Inject;
+import com.google.inject.assistedinject.Assisted;
+import java.util.Set;
+import org.apache.http.HttpStatus;
+import org.eclipse.jgit.lib.Config;
+import org.elasticsearch.client.Response;
+
+/** Secondary index implementation using Elasticsearch. */
+class ElasticChangeIndex extends AbstractElasticIndex<Change.Id, ChangeData>
+ implements ChangeIndex {
+ static class ChangeMapping {
+ final MappingProperties changes;
+ final MappingProperties openChanges;
+ final MappingProperties closedChanges;
+
+ ChangeMapping(Schema<ChangeData> schema, ElasticQueryAdapter adapter) {
+ MappingProperties mapping = ElasticMapping.createMapping(schema, adapter);
+ this.changes = mapping;
+ this.openChanges = mapping;
+ this.closedChanges = mapping;
+ }
+ }
+
+ private static final String CHANGES = "changes";
+
+ private final ChangeMapping mapping;
+ private final ChangeData.Factory changeDataFactory;
+ private final Schema<ChangeData> schema;
+ private final FieldDef<ChangeData, ?> idField;
+ private final ImmutableSet<String> skipFields;
+
+ @Inject
+ ElasticChangeIndex(
+ ElasticConfiguration cfg,
+ ChangeData.Factory changeDataFactory,
+ SitePaths sitePaths,
+ ElasticRestClientProvider clientBuilder,
+ @GerritServerConfig Config gerritConfig,
+ AutoFlush autoFlush,
+ @Assisted Schema<ChangeData> schema) {
+ super(cfg, sitePaths, schema, clientBuilder, CHANGES, autoFlush);
+ this.changeDataFactory = changeDataFactory;
+ this.schema = schema;
+ this.mapping = new ChangeMapping(schema, client.adapter());
+ this.idField =
+ this.schema.useLegacyNumericFields() ? ChangeField.LEGACY_ID : ChangeField.LEGACY_ID_STR;
+ this.skipFields =
+ MergeabilityComputationBehavior.fromConfig(gerritConfig).includeInIndex()
+ ? ImmutableSet.of()
+ : ImmutableSet.of(ChangeField.MERGEABLE.getName());
+ }
+
+ @Override
+ public void replace(ChangeData cd) {
+ BulkRequest bulk =
+ new IndexRequest(getId(cd), indexName).add(new UpdateRequest<>(schema, cd, skipFields));
+
+ String uri = getURI(BULK);
+ Response response = postRequestWithRefreshParam(uri, bulk);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (hasErrors(response) || statusCode != HttpStatus.SC_OK) {
+ throw new StorageException(
+ String.format(
+ "Failed to replace change %s in index %s: %s", cd.getId(), indexName, statusCode));
+ }
+ }
+
+ @Override
+ public DataSource<ChangeData> getSource(Predicate<ChangeData> p, QueryOptions opts)
+ throws QueryParseException {
+ QueryOptions filteredOpts =
+ opts.filterFields(o -> IndexUtils.changeFields(o, schema.useLegacyNumericFields()));
+ return new ElasticQuerySource(p, filteredOpts, getSortArray());
+ }
+
+ private JsonArray getSortArray() {
+ JsonObject properties = new JsonObject();
+ properties.addProperty(ORDER, DESC_SORT_ORDER);
+
+ JsonArray sortArray = new JsonArray();
+ addNamedElement(ChangeField.UPDATED.getName(), properties, sortArray);
+ addNamedElement(ChangeField.MERGED_ON.getName(), getMergedOnSortOptions(), sortArray);
+ addNamedElement(idField.getName(), properties, sortArray);
+ return sortArray;
+ }
+
+ private JsonObject getMergedOnSortOptions() {
+ JsonObject sortOptions = new JsonObject();
+ sortOptions.addProperty(ORDER, DESC_SORT_ORDER);
+ // Ignore the sort field if it does not exist in index. Otherwise the search would fail on open
+ // changes, because the corresponding documents do not have mergedOn field.
+ sortOptions.addProperty(UNMAPPED_TYPE, ElasticMapping.TIMESTAMP_FIELD_TYPE);
+ return sortOptions;
+ }
+
+ @Override
+ protected String getDeleteActions(Change.Id c) {
+ return getDeleteRequest(c);
+ }
+
+ @Override
+ protected String getMappings() {
+ return getMappingsFor(mapping.changes);
+ }
+
+ @Override
+ protected String getId(ChangeData cd) {
+ return cd.getId().toString();
+ }
+
+ @Override
+ protected ChangeData fromDocument(JsonObject json, Set<String> fields) {
+ JsonElement sourceElement = json.get("_source");
+ if (sourceElement == null) {
+ sourceElement = json.getAsJsonObject().get("fields");
+ }
+ JsonObject source = sourceElement.getAsJsonObject();
+ JsonElement c = source.get(ChangeField.CHANGE.getName());
+
+ if (c == null) {
+ int id = source.get(idField.getName()).getAsInt();
+ // IndexUtils#changeFields ensures either CHANGE or PROJECT is always present.
+ String projectName = requireNonNull(source.get(ChangeField.PROJECT.getName()).getAsString());
+ return changeDataFactory.create(Project.nameKey(projectName), Change.id(id));
+ }
+
+ ChangeData cd =
+ changeDataFactory.create(
+ parseProtoFrom(decodeBase64(c.getAsString()), ChangeProtoConverter.INSTANCE));
+
+ for (FieldDef<ChangeData, ?> field : getSchema().getFields().values()) {
+ if (fields.contains(field.getName()) && source.get(field.getName()) != null) {
+ field.setIfPossible(cd, new ElasticStoredValue(source.get(field.getName())));
+ }
+ }
+
+ return cd;
+ }
+}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
similarity index 97%
rename from java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
index c443529..e544d9f 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticConfiguration.java
@@ -32,7 +32,7 @@
import org.elasticsearch.client.RestClientBuilder;
@Singleton
-class ElasticConfiguration {
+public class ElasticConfiguration {
private static final FluentLogger logger = FluentLogger.forEnclosingClass();
static final String SECTION_ELASTICSEARCH = "elasticsearch";
@@ -50,7 +50,7 @@
static final String DEFAULT_USERNAME = "elastic";
static final int DEFAULT_NUMBER_OF_SHARDS = 1;
static final int DEFAULT_NUMBER_OF_REPLICAS = 1;
- static final int DEFAULT_MAX_RESULT_WINDOW = 10000;
+ static final int DEFAULT_MAX_RESULT_WINDOW = Integer.MAX_VALUE;
static final int DEFAULT_CONNECT_TIMEOUT = RestClientBuilder.DEFAULT_CONNECT_TIMEOUT_MILLIS;
static final int DEFAULT_SOCKET_TIMEOUT = RestClientBuilder.DEFAULT_SOCKET_TIMEOUT_MILLIS;
diff --git a/java/com/google/gerrit/elasticsearch/ElasticException.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticException.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticException.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticException.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
similarity index 93%
rename from java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
index 781ed43..626203a 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticGroupIndex.java
@@ -32,6 +32,7 @@
import com.google.gerrit.server.index.IndexUtils;
import com.google.gerrit.server.index.group.GroupField;
import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
@@ -64,8 +65,9 @@
SitePaths sitePaths,
Provider<GroupCache> groupCache,
ElasticRestClientProvider client,
+ AutoFlush autoFlush,
@Assisted Schema<InternalGroup> schema) {
- super(cfg, sitePaths, schema, client, GROUPS);
+ super(cfg, sitePaths, schema, client, GROUPS, autoFlush);
this.groupCache = groupCache;
this.mapping = new GroupMapping(schema, client.adapter());
this.schema = schema;
@@ -78,9 +80,9 @@
.add(new UpdateRequest<>(schema, group, ImmutableSet.of()));
String uri = getURI(BULK);
- Response response = postRequest(uri, bulk, getRefreshParam());
+ Response response = postRequestWithRefreshParam(uri, bulk);
int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
+ if (hasErrors(response) || statusCode != HttpStatus.SC_OK) {
throw new StorageException(
String.format(
"Failed to replace group %s in index %s: %s",
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
similarity index 64%
rename from java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
index 15d6126..b1bb7b1 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexModule.java
@@ -14,32 +14,53 @@
package com.google.gerrit.elasticsearch;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.flogger.FluentLogger;
import com.google.gerrit.index.project.ProjectIndex;
+import com.google.gerrit.server.ModuleImpl;
import com.google.gerrit.server.index.AbstractIndexModule;
import com.google.gerrit.server.index.VersionManager;
import com.google.gerrit.server.index.account.AccountIndex;
import com.google.gerrit.server.index.change.ChangeIndex;
import com.google.gerrit.server.index.group.GroupIndex;
+import com.google.gerrit.server.index.options.AutoFlush;
+import com.google.inject.Inject;
import java.util.Map;
+@ModuleImpl(name = AbstractIndexModule.INDEX_MODULE)
public class ElasticIndexModule extends AbstractIndexModule {
+ private static final FluentLogger logger = FluentLogger.forEnclosingClass();
+
+ private final AutoFlush autoFlush;
+
+ @VisibleForTesting
public static ElasticIndexModule singleVersionWithExplicitVersions(
Map<String, Integer> versions, int threads, boolean slave) {
- return new ElasticIndexModule(versions, threads, slave);
+ return new ElasticIndexModule(versions, threads, slave, AutoFlush.ENABLED);
}
- public static ElasticIndexModule latestVersion(boolean slave) {
- return new ElasticIndexModule(null, 0, slave);
+ public static ElasticIndexModule singleVersionWithExplicitVersions(
+ Map<String, Integer> versions, int threads, boolean slave, AutoFlush autoFlush) {
+ return new ElasticIndexModule(versions, threads, slave, autoFlush);
}
- private ElasticIndexModule(Map<String, Integer> singleVersions, int threads, boolean slave) {
+ @Inject
+ public ElasticIndexModule() {
+ this(null, 0, false, AutoFlush.ENABLED);
+ }
+
+ protected ElasticIndexModule(
+ Map<String, Integer> singleVersions, int threads, boolean slave, AutoFlush autoFlush) {
super(singleVersions, threads, slave);
+ this.autoFlush = autoFlush;
}
@Override
public void configure() {
+ logger.atInfo().log("Gerrit index backend set to ElasticSearch");
super.configure();
install(ElasticRestClientProvider.module());
+ bind(AutoFlush.class).toInstance(autoFlush);
}
@Override
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionDiscovery.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticIndexVersionManager.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticMapping.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticMapping.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticMapping.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
similarity index 94%
rename from java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
index b8bfc38..99b202d 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticProjectIndex.java
@@ -31,6 +31,7 @@
import com.google.gerrit.index.query.QueryParseException;
import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.index.IndexUtils;
+import com.google.gerrit.server.index.options.AutoFlush;
import com.google.gerrit.server.project.ProjectCache;
import com.google.gerrit.server.project.ProjectState;
import com.google.gson.JsonArray;
@@ -66,8 +67,9 @@
SitePaths sitePaths,
Provider<ProjectCache> projectCache,
ElasticRestClientProvider client,
+ AutoFlush autoFlush,
@Assisted Schema<ProjectData> schema) {
- super(cfg, sitePaths, schema, client, PROJECTS);
+ super(cfg, sitePaths, schema, client, PROJECTS, autoFlush);
this.projectCache = projectCache;
this.schema = schema;
this.mapping = new ProjectMapping(schema, client.adapter());
@@ -80,9 +82,9 @@
.add(new UpdateRequest<>(schema, projectState, ImmutableSet.of()));
String uri = getURI(BULK);
- Response response = postRequest(uri, bulk, getRefreshParam());
+ Response response = postRequestWithRefreshParam(uri, bulk);
int statusCode = response.getStatusLine().getStatusCode();
- if (statusCode != HttpStatus.SC_OK) {
+ if (hasErrors(response) || statusCode != HttpStatus.SC_OK) {
throw new StorageException(
String.format(
"Failed to replace project %s in index %s: %s",
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticQueryAdapter.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticRestClientProvider.java
diff --git a/java/com/google/gerrit/elasticsearch/ElasticSetting.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticSetting.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/ElasticSetting.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticSetting.java
diff --git a/src/main/java/com/google/gerrit/elasticsearch/ElasticStoredValue.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticStoredValue.java
new file mode 100644
index 0000000..a02a715
--- /dev/null
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticStoredValue.java
@@ -0,0 +1,86 @@
+// Copyright (C) 2021 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 static com.google.common.collect.ImmutableList.toImmutableList;
+
+import com.google.gerrit.index.StoredValue;
+import com.google.gson.JsonElement;
+import java.sql.Timestamp;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.stream.StreamSupport;
+
+/** Bridge to recover fields from the elastic index. */
+public class ElasticStoredValue implements StoredValue {
+ private final JsonElement field;
+
+ ElasticStoredValue(JsonElement field) {
+ this.field = field;
+ }
+
+ @Override
+ public String asString() {
+ return field.getAsString();
+ }
+
+ @Override
+ public Iterable<String> asStrings() {
+ return StreamSupport.stream(field.getAsJsonArray().spliterator(), false)
+ .map(f -> f.getAsString())
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public Integer asInteger() {
+ return field.getAsInt();
+ }
+
+ @Override
+ public Iterable<Integer> asIntegers() {
+ return StreamSupport.stream(field.getAsJsonArray().spliterator(), false)
+ .map(f -> f.getAsInt())
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public Long asLong() {
+ return field.getAsLong();
+ }
+
+ @Override
+ public Iterable<Long> asLongs() {
+ return StreamSupport.stream(field.getAsJsonArray().spliterator(), false)
+ .map(f -> f.getAsLong())
+ .collect(toImmutableList());
+ }
+
+ @Override
+ public Timestamp asTimestamp() {
+ return Timestamp.from(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(field.getAsString())));
+ }
+
+ @Override
+ public byte[] asByteArray() {
+ return AbstractElasticIndex.decodeBase64(field.getAsString());
+ }
+
+ @Override
+ public Iterable<byte[]> asByteArrays() {
+ return StreamSupport.stream(field.getAsJsonArray().spliterator(), false)
+ .map(f -> AbstractElasticIndex.decodeBase64(f.getAsString()))
+ .collect(toImmutableList());
+ }
+}
diff --git a/java/com/google/gerrit/elasticsearch/ElasticVersion.java b/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
similarity index 95%
rename from java/com/google/gerrit/elasticsearch/ElasticVersion.java
rename to src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
index c6400df..dffdf3e 100644
--- a/java/com/google/gerrit/elasticsearch/ElasticVersion.java
+++ b/src/main/java/com/google/gerrit/elasticsearch/ElasticVersion.java
@@ -18,9 +18,7 @@
import java.util.regex.Pattern;
public enum ElasticVersion {
- V7_6("7.6.*"),
- V7_7("7.7.*"),
- V7_8("7.8.*");
+ V7_16("7.16.*");
private final String version;
private final Pattern pattern;
@@ -45,7 +43,6 @@
*
* @param version for which to return an ElasticVersion
* @return the corresponding ElasticVersion if supported
- * @throws UnsupportedVersion
*/
public static ElasticVersion forVersion(String version) {
for (ElasticVersion value : ElasticVersion.values()) {
diff --git a/src/main/java/com/google/gerrit/elasticsearch/PrimaryElasticIndexModule.java b/src/main/java/com/google/gerrit/elasticsearch/PrimaryElasticIndexModule.java
new file mode 100644
index 0000000..823c657
--- /dev/null
+++ b/src/main/java/com/google/gerrit/elasticsearch/PrimaryElasticIndexModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2014 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.server.ModuleImpl;
+import com.google.gerrit.server.index.AbstractIndexModule;
+import com.google.gerrit.server.index.options.AutoFlush;
+
+@ModuleImpl(name = AbstractIndexModule.INDEX_MODULE)
+public class PrimaryElasticIndexModule extends ElasticIndexModule {
+
+ public PrimaryElasticIndexModule() {
+ super(null, 0, false, AutoFlush.ENABLED);
+ }
+}
diff --git a/src/main/java/com/google/gerrit/elasticsearch/ReplicaElasticIndexModule.java b/src/main/java/com/google/gerrit/elasticsearch/ReplicaElasticIndexModule.java
new file mode 100644
index 0000000..86ccac6
--- /dev/null
+++ b/src/main/java/com/google/gerrit/elasticsearch/ReplicaElasticIndexModule.java
@@ -0,0 +1,27 @@
+// Copyright (C) 2021 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.server.ModuleImpl;
+import com.google.gerrit.server.index.AbstractIndexModule;
+import com.google.gerrit.server.index.options.AutoFlush;
+
+@ModuleImpl(name = AbstractIndexModule.INDEX_MODULE)
+public class ReplicaElasticIndexModule extends ElasticIndexModule {
+
+ public ReplicaElasticIndexModule() {
+ super(null, 0, true, AutoFlush.ENABLED);
+ }
+}
diff --git a/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/BoolQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/ExistsQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/MatchAllQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/MatchQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java b/src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/QueryBuilders.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/QuerySourceBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/RangeQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/RegexpQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/SearchAfterBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/SearchAfterBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/SearchAfterBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/SearchAfterBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/SearchSourceBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/TermQueryBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java b/src/main/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
rename to src/main/java/com/google/gerrit/elasticsearch/builders/XContentBuilder.java
diff --git a/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java b/src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
rename to src/main/java/com/google/gerrit/elasticsearch/bulk/ActionRequest.java
diff --git a/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java b/src/main/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
rename to src/main/java/com/google/gerrit/elasticsearch/bulk/BulkRequest.java
diff --git a/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java b/src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
rename to src/main/java/com/google/gerrit/elasticsearch/bulk/DeleteRequest.java
diff --git a/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java b/src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
rename to src/main/java/com/google/gerrit/elasticsearch/bulk/IndexRequest.java
diff --git a/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java b/src/main/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
similarity index 100%
rename from java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
rename to src/main/java/com/google/gerrit/elasticsearch/bulk/UpdateRequest.java
diff --git a/src/main/java/com/google/gerrit/elasticsearch/init/InitElasticSearchIndex.java b/src/main/java/com/google/gerrit/elasticsearch/init/InitElasticSearchIndex.java
new file mode 100644
index 0000000..76b2d3b
--- /dev/null
+++ b/src/main/java/com/google/gerrit/elasticsearch/init/InitElasticSearchIndex.java
@@ -0,0 +1,73 @@
+package com.google.gerrit.elasticsearch.init;
+
+import com.google.common.collect.Iterables;
+import com.google.gerrit.index.IndexType;
+import com.google.gerrit.index.SchemaDefinitions;
+import com.google.gerrit.pgm.init.api.ConsoleUI;
+import com.google.gerrit.pgm.init.api.InitFlags;
+import com.google.gerrit.pgm.init.api.InitStep;
+import com.google.gerrit.pgm.init.api.Section;
+import com.google.gerrit.server.config.SitePaths;
+import com.google.gerrit.server.index.IndexModule;
+import com.google.gerrit.server.index.IndexUtils;
+import com.google.inject.Inject;
+import java.io.IOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+public class InitElasticSearchIndex implements InitStep {
+ private final ConsoleUI ui;
+ private final Section index;
+ private final SitePaths site;
+ private final InitFlags initFlags;
+ private final Section gerrit;
+ private final Section.Factory sections;
+
+ @Inject
+ InitElasticSearchIndex(
+ ConsoleUI ui, Section.Factory sections, SitePaths site, InitFlags initFlags) {
+ this.ui = ui;
+ this.index = sections.get("index", null);
+ this.gerrit = sections.get("gerrit", null);
+ this.site = site;
+ this.initFlags = initFlags;
+ this.sections = sections;
+ }
+
+ @Override
+ public void run() throws IOException {
+ ui.header("Index");
+ IndexType type =
+ new IndexType(
+ index.select("Type", "type", IndexType.getDefault(), IndexType.getKnownTypes()));
+
+ Section elasticsearch = sections.get("elasticsearch", null);
+ elasticsearch.string("Index Prefix", "prefix", "gerrit_");
+ elasticsearch.string("Server", "server", "http://localhost:9200");
+ index.string("Result window size", "maxLimit", "10000");
+
+ if ((site.isNew || isEmptySite()) && type.isLucene()) {
+ for (SchemaDefinitions<?> def : IndexModule.ALL_SCHEMA_DEFS) {
+ IndexUtils.setReady(site, def.getName(), def.getLatest().getVersion(), true);
+ }
+ } else {
+ String message =
+ String.format(
+ "\nThe index must be %sbuilt before starting Gerrit:\n"
+ + " java -jar gerrit.war reindex -d site_path\n",
+ site.isNew ? "" : "re");
+ ui.message(message);
+ initFlags.autoStart = false;
+ }
+ }
+
+ private boolean isEmptySite() {
+ try (DirectoryStream<Path> files =
+ Files.newDirectoryStream(site.resolve(gerrit.get("basePath")))) {
+ return Iterables.isEmpty(files);
+ } catch (IOException e) {
+ return true;
+ }
+ }
+}
diff --git a/src/main/main.iml b/src/main/main.iml
new file mode 100644
index 0000000..908ad4f
--- /dev/null
+++ b/src/main/main.iml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="false" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
new file mode 100644
index 0000000..59fe0a3
--- /dev/null
+++ b/src/main/resources/Documentation/about.md
@@ -0,0 +1,7 @@
+# Index backend for Gerrit, based on ElasticSearch
+
+Indexing backend libModule for [Gerrit Code Review](https://gerritcodereview.com)
+based on [ElasticSearch](https://www.elastic.co/elasticsearch/).
+
+This module was originally part of Gerrit core and then extracted into a separate
+component from v3.5.0-rc3 as part of [Change-Id: Ib7b5167ce](https://gerrit-review.googlesource.com/c/gerrit/+/323676).
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
new file mode 100644
index 0000000..acb8d85
--- /dev/null
+++ b/src/main/resources/Documentation/build.md
@@ -0,0 +1,71 @@
+# Build
+
+This plugin is built with Bazel in-tree build. This plugin depends on the Elasticsearch Java Low
+Level REST Client (abbreviated as LLRC by the ES dev team) for integration with an Elasticsearch
+cluster. The LLRC is licensed as Apache v2 (even after ES itself has moved to SSPL) and is
+compatible with all ES versions. See the [LLRC
+docs](https://www.elastic.co/guide/en/elasticsearch/client/java-api-client/8.3/java-rest-low-usage-maven.html)
+for more information.
+
+## Build in Gerrit tree
+
+Create a symbolic link of the repository source to the Gerrit source
+tree plugins/index-elasticsearch directory, and the external_plugin_deps.bzl
+dependencies linked to plugins/external_plugin_deps.bzl.
+
+Example:
+
+```sh
+git clone https://gerrit.googlesource.com/gerrit
+git clone https://gerrit.googlesource.com/modules/index-elasticsearch
+cd gerrit/plugins
+ln -s ../../index-elasticsearch index-elasticsearch
+ln -sf ../../external_plugin_deps.bzl .
+```
+
+From the Gerrit source tree issue the command `bazelisk build plugins/index-elasticsearch`.
+
+Example:
+
+```sh
+bazelisk build plugins/index-elasticsearch
+```
+
+The libModule jar file is created under `bazel-bin/plugins/index-elasticsearch/index-elasticsearch.jar`
+
+## Integration test
+
+There are two different ways to run tests for this module. You can either run only the tests
+provided by the module or you can run all Gerrit core acceptance tests with the indexing backend set
+to this module.
+
+To run only the tests provided by this plugin:
+
+```sh
+bazelisk test plugins/index-elasticsearch/...
+```
+
+Gerrit acceptance tests allow the execution with an alternate implementation of
+the indexing backend using the `GERRIT_INDEX_MODULE` environment variable.
+
+```sh
+bazelisk test --test_env=GERRIT_INDEX_MODULE=com.google.gerrit.elasticsearch.ElasticIndexModule //...
+```
+
+## IDE setup
+
+This project can be imported into the Eclipse IDE.
+Add the plugin name to the `CUSTOM_PLUGINS` and to the
+`CUSTOM_PLUGINS_TEST_DEPS` set in Gerrit core in
+`tools/bzl/plugins.bzl`, and execute:
+
+```
+ ./tools/eclipse/project.py
+```
+
+More information about Bazel can be found in the [Gerrit
+documentation](../../../Documentation/dev-bazel.html).
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
new file mode 100644
index 0000000..ad5d5d0
--- /dev/null
+++ b/src/main/resources/Documentation/config.md
@@ -0,0 +1,97 @@
+# Configuration
+
+## Section index
+
+### index.maxLimit
+
+Maximum limit to allow for search queries. Requesting results above this limit will truncate the
+list (but will still set `_more_changes` on result lists). Set to 0 for no limit. This value
+should not exceed the `index.max_result_window` value configured on the Elasticsearch server. If a
+value is not configured during site initialization, defaults to 10000, which is the default value
+of `index.max_result_window` in Elasticsearch.
+
+## Section elasticsearch
+
+WARNING: Support for Elasticsearch is still experimental and is not recommended for production
+use. For compatibility information, please refer to the [project homepage](https://www.gerritcodereview.com/elasticsearch.html).
+
+Note that when Gerrit is configured to use Elasticsearch, the Elasticsearch
+server(s) must be reachable during the site initialization.
+
+### elasticsearch.prefix
+
+This setting can be used to prefix index names to allow multiple Gerrit instances in a single
+Elasticsearch cluster. Prefix `gerrit1_` would result in a change index named
+`gerrit1_changes_0001`.
+
+Not set by default.
+
+### elasticsearch.server
+
+Elasticsearch server URI in the form `http[s]://hostname:port`. The `port` is optional and defaults
+to `9200` if not specified.
+
+At least one server must be specified. May be specified multiple times to configure multiple
+Elasticsearch servers.
+
+Note that the site initialization program only allows to configure a single
+server. To configure multiple servers the `gerrit.config` file must be edited
+manually.
+
+### elasticsearch.numberOfShards
+
+Sets the number of shards to use per index. Refer to the
+[Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings) for details.
+
+Defaults to 1.
+
+### elasticsearch.numberOfReplicas
+
+Sets the number of replicas to use per index. Refer to the
+[Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings) for details.
+
+Defaults to 1.
+
+### elasticsearch.maxResultWindow
+
+Sets the maximum value of `from + size` for searches to use per index. Refer to the
+[Elasticsearch documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings) for details.
+
+Defaults to 10000.
+
+### elasticsearch.connectTimeout
+
+Sets the timeout for connecting to elasticsearch.
+
+Defaults to `1 second`.
+
+### elasticsearch.socketTimeout
+
+Sets the timeout for the underlying connection. For more information, refer to
+[`httpd.idleTimeout`](https://gerrit-documentation.storage.googleapis.com/Documentation/3.5.2/config-gerrit.html#httpd.idleTimeout).
+
+Defaults to `30 seconds`.
+
+## Elasticsearch Security
+
+When security is enabled in Elasticsearch, the username and password must be provided. Note that
+the same username and password are used for all servers.
+
+For further information about Elasticsearch security, please refer to
+[the documentation](https://www.elastic.co/guide/en/elasticsearch/reference/current/security-getting-started.html). This is the current documentation link. Select another Elasticsearch version from the dropdown menu available on that page if need be.
+
+### elasticsearch.username
+
+Username used to connect to Elasticsearch.
+
+If a password is set, defaults to `elastic`, otherwise not set by default.
+
+### elasticsearch.password
+
+Password used to connect to Elasticsearch.
+
+Not set by default.
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
diff --git a/src/main/resources/Documentation/setup.md b/src/main/resources/Documentation/setup.md
new file mode 100644
index 0000000..7877b29
--- /dev/null
+++ b/src/main/resources/Documentation/setup.md
@@ -0,0 +1,26 @@
+# Setup
+
+* Install index-elasticsearch module
+
+Install the index-elasticsearch.jar into the `$GERRIT_SITE/lib` directory.
+
+Add the index-elasticsearch module to `$GERRIT_SITE/etc/gerrit.config` as follows:
+
+```ini
+[gerrit]
+ installIndexModule = com.google.gerrit.elasticsearch.ElasticIndexModule
+```
+
+When installing the module on Gerrit replicas, use following example:
+
+```ini
+[gerrit]
+ installIndexModule = com.google.gerrit.elasticsearch.ReplicaElasticIndexModule
+```
+
+For further information and supported options, refer to [config](config.html)
+documentation.
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
similarity index 100%
rename from javatests/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticConfigurationTest.java
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
similarity index 69%
rename from javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
index c330961..4fddc72 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticContainer.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticContainer.java
@@ -14,45 +14,41 @@
package com.google.gerrit.elasticsearch;
+import com.google.common.flogger.FluentLogger;
import org.apache.http.HttpHost;
-import org.junit.AssumptionViolatedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.testcontainers.containers.ContainerLaunchException;
import org.testcontainers.elasticsearch.ElasticsearchContainer;
import org.testcontainers.utility.DockerImageName;
/* Helper class for running ES integration tests in docker container */
public class ElasticContainer extends ElasticsearchContainer {
+ private static FluentLogger logger = FluentLogger.forEnclosingClass();
private static final int ELASTICSEARCH_DEFAULT_PORT = 9200;
public static ElasticContainer createAndStart(ElasticVersion version) {
- // Assumption violation is not natively supported by Testcontainers.
- // See https://github.com/testcontainers/testcontainers-java/issues/343
+ ElasticContainer container = new ElasticContainer(version);
try {
- ElasticContainer container = new ElasticContainer(version);
container.start();
- return container;
- } catch (Throwable t) {
- throw new AssumptionViolatedException("Unable to start container", t);
+ } catch (ContainerLaunchException e) {
+ logger.atSevere().log(
+ "Failed to launch elastic container. Logs from container :\n" + container.getLogs());
+ throw e;
}
+ return container;
}
private static String getImageName(ElasticVersion version) {
switch (version) {
- case V7_6:
- return "blacktop/elasticsearch:7.6.2";
- case V7_7:
- return "blacktop/elasticsearch:7.7.1";
- case V7_8:
- return "blacktop/elasticsearch:7.8.1";
+ case V7_16:
+ return "docker.elastic.co/elasticsearch/elasticsearch:7.16.2";
}
throw new IllegalStateException("No tests for version: " + version.name());
}
private ElasticContainer(ElasticVersion version) {
- super(
- DockerImageName.parse(getImageName(version))
- .asCompatibleSubstituteFor("docker.elastic.co/elasticsearch/elasticsearch"));
+ super(DockerImageName.parse(getImageName(version)));
}
@Override
diff --git a/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
new file mode 100644
index 0000000..f5ba9db
--- /dev/null
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticTestUtils.java
@@ -0,0 +1,108 @@
+// Copyright (C) 2016 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 static java.util.concurrent.TimeUnit.MINUTES;
+
+import com.google.gerrit.index.IndexDefinition;
+import com.google.gerrit.server.LibModuleType;
+import com.google.gerrit.testing.GerritTestName;
+import com.google.gerrit.testing.InMemoryModule;
+import com.google.gerrit.testing.IndexConfig;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Key;
+import com.google.inject.TypeLiteral;
+import java.util.Collection;
+import java.util.UUID;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.eclipse.jgit.lib.Config;
+
+public final class ElasticTestUtils {
+ public static void configure(Config config, ElasticContainer container, String prefix) {
+ String hostname = container.getHttpHost().getHostName();
+ int port = container.getHttpHost().getPort();
+ config.setString("index", null, "type", "elasticsearch");
+ config.setString("elasticsearch", null, "server", "http://" + hostname + ":" + port);
+ config.setString("elasticsearch", null, "prefix", prefix);
+ config.setInt("index", null, "maxLimit", 10000);
+ }
+
+ public static void createAllIndexes(Injector injector) {
+ Collection<IndexDefinition<?, ?, ?>> indexDefs =
+ injector.getInstance(Key.get(new TypeLiteral<Collection<IndexDefinition<?, ?, ?>>>() {}));
+ for (IndexDefinition<?, ?, ?> indexDef : indexDefs) {
+ indexDef.getIndexCollection().getSearchIndex().deleteAll();
+ }
+ }
+
+ public static Config getConfig(ElasticVersion version) {
+ ElasticContainer container = ElasticContainer.createAndStart(version);
+ String indicesPrefix = UUID.randomUUID().toString();
+ Config cfg = new Config();
+ configure(cfg, container, indicesPrefix);
+ return cfg;
+ }
+
+ public static Config createConfig() {
+ Config cfg = IndexConfig.create();
+
+ // For some reason enabling the staleness checker increases the flakiness of the Elasticsearch
+ // tests. Hence disable the staleness checker.
+ cfg.setBoolean("index", null, "autoReindexIfStale", false);
+
+ return cfg;
+ }
+
+ public static void configureElasticModule(Config elasticsearchConfig) {
+ elasticsearchConfig.setString(
+ "index",
+ null,
+ "install" + LibModuleType.INDEX_MODULE_TYPE.getConfigKey(),
+ "com.google.gerrit.elasticsearch.ElasticIndexModule");
+ }
+
+ public static Injector createInjector(
+ Config config, GerritTestName testName, ElasticContainer container) {
+ Config elasticsearchConfig = new Config(config);
+ ElasticTestUtils.configureElasticModule(elasticsearchConfig);
+ InMemoryModule.setDefaults(elasticsearchConfig);
+ String indicesPrefix = testName.getSanitizedMethodName();
+ ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
+ return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
+ }
+
+ public static void closeIndex(
+ CloseableHttpAsyncClient client, ElasticContainer container, GerritTestName testName)
+ throws Exception {
+ client
+ .execute(
+ new HttpPost(
+ String.format(
+ "http://%s:%d/%s*/_close",
+ container.getHttpHost().getHostName(),
+ container.getHttpHost().getPort(),
+ testName.getSanitizedMethodName())),
+ HttpClientContext.create(),
+ null)
+ .get(5, MINUTES);
+ }
+
+ private ElasticTestUtils() {
+ // hide default constructor
+ }
+}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
similarity index 66%
rename from javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
index 628fe2f..752a1e7 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryAccountsTest.java
@@ -14,20 +14,24 @@
package com.google.gerrit.elasticsearch;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.server.query.account.AbstractQueryAccountsTest;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
import com.google.inject.Injector;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Test;
public class ElasticV7QueryAccountsTest extends AbstractQueryAccountsTest {
@ConfigSuite.Default
public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
+ return ElasticTestUtils.createConfig();
}
@ConfigSuite.Config
@@ -38,12 +42,15 @@
}
private static ElasticContainer container;
+ private static CloseableHttpAsyncClient client;
@BeforeClass
public static void startIndexService() {
if (container == null) {
// Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V7_8);
+ container = ElasticContainer.createAndStart(ElasticVersion.V7_16);
+ client = HttpAsyncClients.createDefault();
+ client.start();
}
}
@@ -62,10 +69,16 @@
@Override
protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = testName.getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
+ return ElasticTestUtils.createInjector(config, testName, container);
+ }
+
+ @Test
+ public void testErrorResponseFromAccountIndex() throws Exception {
+ gApi.accounts().self().index();
+
+ ElasticTestUtils.closeIndex(client, container, testName);
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> gApi.accounts().self().index());
+ assertThat(thrown).hasMessageThat().contains("Failed to replace account");
}
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
similarity index 69%
rename from javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
index ea172b0..9a85129 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryChangesTest.java
@@ -14,29 +14,30 @@
package com.google.gerrit.elasticsearch;
-import static java.util.concurrent.TimeUnit.MINUTES;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+import com.google.gerrit.entities.Change;
+import com.google.gerrit.exceptions.StorageException;
import com.google.gerrit.server.query.change.AbstractQueryChangesTest;
import com.google.gerrit.testing.ConfigSuite;
import com.google.gerrit.testing.GerritTestName;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
+import com.google.gerrit.testing.InMemoryRepositoryManager;
import com.google.inject.Injector;
-import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClients;
+import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Rule;
+import org.junit.Test;
public class ElasticV7QueryChangesTest extends AbstractQueryChangesTest {
@ConfigSuite.Default
public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
+ return ElasticTestUtils.createConfig();
}
@ConfigSuite.Config
@@ -53,7 +54,7 @@
public static void startIndexService() {
if (container == null) {
// Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V7_8);
+ container = ElasticContainer.createAndStart(ElasticVersion.V7_16);
client = HttpAsyncClients.createDefault();
client.start();
}
@@ -72,17 +73,7 @@
public void closeIndex() throws Exception {
// Close the index after each test to prevent exceeding Elasticsearch's
// shard limit (see Issue 10120).
- client
- .execute(
- new HttpPost(
- String.format(
- "http://%s:%d/%s*/_close",
- container.getHttpHost().getHostName(),
- container.getHttpHost().getPort(),
- testName.getSanitizedMethodName())),
- HttpClientContext.create(),
- null)
- .get(5, MINUTES);
+ ElasticTestUtils.closeIndex(client, container, testName);
}
@Override
@@ -93,10 +84,18 @@
@Override
protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = testName.getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
+ return ElasticTestUtils.createInjector(config, testName, container);
+ }
+
+ @Test
+ public void testErrorResponseFromChangeIndex() throws Exception {
+ TestRepository<InMemoryRepositoryManager.Repo> repo = createProject("repo");
+ Change c = insert(repo, newChangeWithStatus(repo, Change.Status.NEW));
+ gApi.changes().id(c.getChangeId()).index();
+
+ ElasticTestUtils.closeIndex(client, container, testName);
+ StorageException thrown =
+ assertThrows(StorageException.class, () -> gApi.changes().id(c.getChangeId()).index());
+ assertThat(thrown).hasMessageThat().contains("Failed to reindex change");
}
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
similarity index 64%
rename from javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
index f54ed1d..6eef24c 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryGroupsTest.java
@@ -14,20 +14,25 @@
package com.google.gerrit.elasticsearch;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.api.groups.GroupApi;
import com.google.gerrit.server.query.group.AbstractQueryGroupsTest;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
import com.google.inject.Injector;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Test;
public class ElasticV7QueryGroupsTest extends AbstractQueryGroupsTest {
@ConfigSuite.Default
public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
+ return ElasticTestUtils.createConfig();
}
@ConfigSuite.Config
@@ -38,12 +43,15 @@
}
private static ElasticContainer container;
+ private static CloseableHttpAsyncClient client;
@BeforeClass
public static void startIndexService() {
if (container == null) {
// Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V7_8);
+ container = ElasticContainer.createAndStart(ElasticVersion.V7_16);
+ client = HttpAsyncClients.createDefault();
+ client.start();
}
}
@@ -62,10 +70,16 @@
@Override
protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = testName.getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
+ return ElasticTestUtils.createInjector(config, testName, container);
+ }
+
+ @Test
+ public void testErrorResponseFromGroupIndex() throws Exception {
+ GroupApi group = gApi.groups().create("test");
+ group.index();
+
+ ElasticTestUtils.closeIndex(client, container, testName);
+ StorageException thrown = assertThrows(StorageException.class, () -> group.index());
+ assertThat(thrown).hasMessageThat().contains("Failed to replace group");
}
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
similarity index 64%
rename from javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
index 6a75f82..70cd7de 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticV7QueryProjectsTest.java
@@ -14,20 +14,25 @@
package com.google.gerrit.elasticsearch;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.testing.GerritJUnit.assertThrows;
+
+import com.google.gerrit.exceptions.StorageException;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
import com.google.gerrit.server.query.project.AbstractQueryProjectsTest;
import com.google.gerrit.testing.ConfigSuite;
-import com.google.gerrit.testing.InMemoryModule;
-import com.google.gerrit.testing.IndexConfig;
-import com.google.inject.Guice;
import com.google.inject.Injector;
+import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
+import org.apache.http.impl.nio.client.HttpAsyncClients;
import org.eclipse.jgit.lib.Config;
import org.junit.AfterClass;
import org.junit.BeforeClass;
+import org.junit.Test;
public class ElasticV7QueryProjectsTest extends AbstractQueryProjectsTest {
@ConfigSuite.Default
public static Config defaultConfig() {
- return IndexConfig.createForElasticsearch();
+ return ElasticTestUtils.createConfig();
}
@ConfigSuite.Config
@@ -38,12 +43,15 @@
}
private static ElasticContainer container;
+ private static CloseableHttpAsyncClient client;
@BeforeClass
public static void startIndexService() {
if (container == null) {
// Only start Elasticsearch once
- container = ElasticContainer.createAndStart(ElasticVersion.V7_8);
+ container = ElasticContainer.createAndStart(ElasticVersion.V7_16);
+ client = HttpAsyncClients.createDefault();
+ client.start();
}
}
@@ -62,10 +70,16 @@
@Override
protected Injector createInjector() {
- Config elasticsearchConfig = new Config(config);
- InMemoryModule.setDefaults(elasticsearchConfig);
- String indicesPrefix = testName.getSanitizedMethodName();
- ElasticTestUtils.configure(elasticsearchConfig, container, indicesPrefix);
- return Guice.createInjector(new InMemoryModule(elasticsearchConfig));
+ return ElasticTestUtils.createInjector(config, testName, container);
+ }
+
+ @Test
+ public void testErrorResponseFromProjectIndex() throws Exception {
+ ProjectApi project = gApi.projects().create("test");
+ project.index(false);
+
+ ElasticTestUtils.closeIndex(client, container, testName);
+ StorageException thrown = assertThrows(StorageException.class, () -> project.index(false));
+ assertThat(thrown).hasMessageThat().contains("Failed to replace project");
}
}
diff --git a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java b/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
similarity index 72%
rename from javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
rename to src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
index 2ce3a2c..ea7782b 100644
--- a/javatests/com/google/gerrit/elasticsearch/ElasticVersionTest.java
+++ b/src/test/java/com/google/gerrit/elasticsearch/ElasticVersionTest.java
@@ -22,14 +22,8 @@
public class ElasticVersionTest {
@Test
public void supportedVersion() throws Exception {
- assertThat(ElasticVersion.forVersion("7.6.0")).isEqualTo(ElasticVersion.V7_6);
- assertThat(ElasticVersion.forVersion("7.6.1")).isEqualTo(ElasticVersion.V7_6);
-
- assertThat(ElasticVersion.forVersion("7.7.0")).isEqualTo(ElasticVersion.V7_7);
- assertThat(ElasticVersion.forVersion("7.7.1")).isEqualTo(ElasticVersion.V7_7);
-
- assertThat(ElasticVersion.forVersion("7.8.0")).isEqualTo(ElasticVersion.V7_8);
- assertThat(ElasticVersion.forVersion("7.8.1")).isEqualTo(ElasticVersion.V7_8);
+ assertThat(ElasticVersion.forVersion("7.16.0")).isEqualTo(ElasticVersion.V7_16);
+ assertThat(ElasticVersion.forVersion("7.16.1")).isEqualTo(ElasticVersion.V7_16);
}
@Test
diff --git a/src/test/test.iml b/src/test/test.iml
new file mode 100644
index 0000000..a0e49a3
--- /dev/null
+++ b/src/test/test.iml
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<module type="JAVA_MODULE" version="4">
+ <component name="NewModuleRootManager" inherit-compiler-output="true">
+ <exclude-output />
+ <content url="file://$MODULE_DIR$">
+ <sourceFolder url="file://$MODULE_DIR$/java" isTestSource="true" />
+ </content>
+ <orderEntry type="inheritedJdk" />
+ <orderEntry type="sourceFolder" forTests="false" />
+ </component>
+</module>
\ No newline at end of file