Merge branch 'stable-2.13' into stable-2.14 * stable-2.13: Use Strings.isNullOrEmpty instead of just null check Code cleanup: use Objects.equals instead of own eq method Clarify and fix sharedDirectory configuration Change-Id: Ie4d44ed063c11617dc7d9baa5e93a3b2ab995514
diff --git a/.buckconfig b/.buckconfig deleted file mode 100644 index 5c0ead0..0000000 --- a/.buckconfig +++ /dev/null
@@ -1,16 +0,0 @@ -[alias] - high-availability = //:high-availability - plugin = //:high-availability - src = //:high-availability-sources - -[java] - jar_spool_mode = direct_to_jar - src_roots = java, resources - -[project] - ignore = .git, eclipse-out/ - parallel_parsing = true - -[cache] - mode = dir - dir = buck-out/cache
diff --git a/.gitignore b/.gitignore index c27e17f..912f8a6 100644 --- a/.gitignore +++ b/.gitignore
@@ -1,9 +1,11 @@ -/.buckd/ -/.buckversion /.classpath /.project /.settings/ -/.watchmanconfig -/buck-out/ -/bucklets +/.primary_build_tool +/bazel-bin +/bazel-genfiles +/bazel-out +/bazel-reviewers +/bazel-testlogs +/bazel-high-availability /eclipse-out/
diff --git a/BUCK b/BUCK deleted file mode 100644 index a4e7846..0000000 --- a/BUCK +++ /dev/null
@@ -1,82 +0,0 @@ -include_defs('//bucklets/gerrit_plugin.bucklet') -include_defs('//bucklets/java_sources.bucklet') -include_defs('//bucklets/maven_jar.bucklet') - -SOURCES = glob(['src/main/java/**/*.java']) -RESOURCES = glob(['src/main/resources/**/*']) - -TEST_DEPS = GERRIT_PLUGIN_API + GERRIT_TESTS + [ - ':high-availability__plugin', - ':mockito', - ':wiremock', -] - -gerrit_plugin( - name = 'high-availability', - srcs = SOURCES, - resources = RESOURCES, - manifest_entries = [ - 'Gerrit-PluginName: high-availability', - 'Gerrit-ApiType: plugin', - 'Gerrit-Module: com.ericsson.gerrit.plugins.highavailability.Module', - 'Gerrit-HttpModule: com.ericsson.gerrit.plugins.highavailability.HttpModule', - 'Gerrit-InitStep: com.ericsson.gerrit.plugins.highavailability.Setup', - 'Implementation-Title: high-availability plugin', - 'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability', - 'Implementation-Vendor: Ericsson', - ], - provided_deps = GERRIT_TESTS, -) - -java_sources( - name = 'high-availability-sources', - srcs = SOURCES + RESOURCES, -) - -java_library( - name = 'classpath', - deps = TEST_DEPS, -) - -java_test( - name = 'high-availability_tests', - srcs = glob(['src/test/java/**/*.java']), - resources = glob(['src/test/resources/**/']), - labels = ['high-availability'], - deps = TEST_DEPS, -) - -maven_jar( - name = 'wiremock', - id = 'com.github.tomakehurst:wiremock-standalone:2.5.1', - sha1 = '9cda1bf1674c8de3a1116bae4d7ce0046a857d30', - license = 'Apache2.0', - attach_source = False, -) - -maven_jar( - name = 'mockito', - id = 'org.mockito:mockito-core:2.7.21', - sha1 = '23e9f7bfb9717e849a05b84c29ee3ac723f1a653', - license = 'DO_NOT_DISTRIBUTE', - deps = [ - ':byte-buddy', - ':objenesis', - ], -) - -maven_jar( - name = 'byte-buddy', - id = 'net.bytebuddy:byte-buddy:1.6.11', - sha1 = '8a8f9409e27f1d62c909c7eef2aa7b3a580b4901', - license = 'DO_NOT_DISTRIBUTE', - attach_source = False, -) - -maven_jar( - name = 'objenesis', - id = 'org.objenesis:objenesis:2.5', - sha1 = '612ecb799912ccf77cba9b3ed8c813da086076e9', - license = 'DO_NOT_DISTRIBUTE', - attach_source = False, -)
diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..a9d055d --- /dev/null +++ b/BUILD
@@ -0,0 +1,45 @@ +load("//tools/bzl:junit.bzl", "junit_tests") +load( + "//tools/bzl:plugin.bzl", + "gerrit_plugin", + "PLUGIN_DEPS", + "PLUGIN_TEST_DEPS", +) + +gerrit_plugin( + name = "high-availability", + srcs = glob(["src/main/java/**/*.java"]), + manifest_entries = [ + "Gerrit-PluginName: high-availability", + "Gerrit-Module: com.ericsson.gerrit.plugins.highavailability.Module", + "Gerrit-HttpModule: com.ericsson.gerrit.plugins.highavailability.HttpModule", + "Implementation-Title: high-availability plugin", + "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/high-availability", + ], + resources = glob(["src/main/resources/**/*"]), +) + +junit_tests( + name = "high_availability_tests", + srcs = glob(["src/test/java/**/*.java"]), + resources = glob(["src/test/resources/**/*"]), + tags = [ + "high-availability", + "local", + ], + deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + ":high-availability__plugin_test_deps", + ":high-availability__plugin", + ], +) + +java_library( + name = "high-availability__plugin_test_deps", + visibility = ["//visibility:public"], + exports = [ + "@byte-buddy//jar", + "@mockito//jar", + "@objenesis//jar", + "@wiremock//jar", + ], +)
diff --git a/WORKSPACE b/WORKSPACE new file mode 100644 index 0000000..bdf54c9 --- /dev/null +++ b/WORKSPACE
@@ -0,0 +1,51 @@ +workspace(name = "high_availability") +load("//:bazlets.bzl", "load_bazlets") + +load_bazlets( + commit = "28aa2290c7f7742261d69b358f3de30d2e87c13b", + #local_path = "/home/ehugare/workspaces/bazlets", +) + +#Snapshot Plugin API +#load( +# "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl", +# "gerrit_api_maven_local", +#) + +# Load snapshot Plugin API +#gerrit_api_maven_local() + +# Release Plugin API +load( + "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl", + "gerrit_api", +) + +# Load release Plugin API +gerrit_api() + +load("@com_googlesource_gerrit_bazlets//tools:maven_jar.bzl", "maven_jar") + +maven_jar( + name = "wiremock", + artifact = "com.github.tomakehurst:wiremock-standalone:2.5.1", + sha1 = "9cda1bf1674c8de3a1116bae4d7ce0046a857d30", +) + +maven_jar( + name = "mockito", + artifact = "org.mockito:mockito-core:2.7.21", + sha1 = "23e9f7bfb9717e849a05b84c29ee3ac723f1a653", +) + +maven_jar( + name = "byte-buddy", + artifact = "net.bytebuddy:byte-buddy:1.6.11", + sha1 = "8a8f9409e27f1d62c909c7eef2aa7b3a580b4901", +) + +maven_jar( + name = "objenesis", + artifact = "org.objenesis:objenesis:2.5", + sha1 = "612ecb799912ccf77cba9b3ed8c813da086076e9", +)
diff --git a/bazlets.bzl b/bazlets.bzl new file mode 100644 index 0000000..e14e488 --- /dev/null +++ b/bazlets.bzl
@@ -0,0 +1,17 @@ +NAME = "com_googlesource_gerrit_bazlets" + +def load_bazlets( + commit, + local_path = None + ): + if not local_path: + native.git_repository( + name = NAME, + remote = "https://gerrit.googlesource.com/bazlets", + commit = commit, + ) + else: + native.local_repository( + name = NAME, + path = local_path, + )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl new file mode 100644 index 0000000..029d997 --- /dev/null +++ b/external_plugin_deps.bzl
@@ -0,0 +1,26 @@ +load("//tools/bzl:maven_jar.bzl", "maven_jar") + +def external_plugin_deps(): + maven_jar( + name = "wiremock", + artifact = "com.github.tomakehurst:wiremock-standalone:2.5.1", + sha1 = "9cda1bf1674c8de3a1116bae4d7ce0046a857d30", + ) + + maven_jar( + name = "mockito", + artifact = "org.mockito:mockito-core:2.7.21", + sha1 = "23e9f7bfb9717e849a05b84c29ee3ac723f1a653", + ) + + maven_jar( + name = "byte-buddy", + artifact = "net.bytebuddy:byte-buddy:1.6.11", + sha1 = "8a8f9409e27f1d62c909c7eef2aa7b3a580b4901", + ) + + maven_jar( + name = "objenesis", + artifact = "org.objenesis:objenesis:2.5", + sha1 = "612ecb799912ccf77cba9b3ed8c813da086076e9", + )
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK deleted file mode 100644 index b7a40b3..0000000 --- a/lib/gerrit/BUCK +++ /dev/null
@@ -1,22 +0,0 @@ -include_defs('//bucklets/maven_jar.bucklet') - -VER = '2.13.8' -REPO = MAVEN_CENTRAL - -maven_jar( - name = 'acceptance-framework', - id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER, - sha1 = 'b35d038d0727889837f0b9710a8a0442471ba8b6', - license = 'Apache2.0', - attach_source = False, - repository = REPO, -) - -maven_jar( - name = 'plugin-api', - id = 'com.google.gerrit:gerrit-plugin-api:' + VER, - sha1 = 'd8137cc9b0cb34429959374ca44d5d2bcf0eff4b', - license = 'Apache2.0', - attach_source = False, - repository = REPO, -)
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java index 99dff1d..651f609 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/Forwarder.java
@@ -44,6 +44,14 @@ boolean deleteChangeFromIndex(int changeId); /** + * Forward a group indexing event to the other master. + * + * @param uuid the group to index. + * @return true if successful, otherwise false. + */ + boolean indexGroup(String uuid); + + /** * Forward a stream event to the other master. * * @param event the event to forward.
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java new file mode 100644 index 0000000..fca807f --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractIndexRestApiServlet.java
@@ -0,0 +1,130 @@ +// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; +import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED; +import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; +import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; + +import com.ericsson.gerrit.plugins.highavailability.forwarder.Context; +import com.google.gwtorm.server.OrmException; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public abstract class AbstractIndexRestApiServlet<T> extends HttpServlet { + private static final long serialVersionUID = -1L; + private static final Logger logger = LoggerFactory.getLogger(AbstractIndexRestApiServlet.class); + private final Map<T, AtomicInteger> idLocks = new HashMap<>(); + private final String type; + private final boolean allowDelete; + + enum Operation { + INDEX, + DELETE + } + + abstract T parse(String id); + + abstract void index(T id, Operation operation) throws IOException, OrmException; + + AbstractIndexRestApiServlet(String type, boolean allowDelete) { + this.type = type; + this.allowDelete = allowDelete; + } + + AbstractIndexRestApiServlet(String type) { + this(type, false); + } + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse rsp) + throws IOException, ServletException { + process(req, rsp, Operation.INDEX); + } + + @Override + protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) + throws IOException, ServletException { + if (!allowDelete) { + sendError(rsp, SC_METHOD_NOT_ALLOWED, String.format("cannot delete %s from index", type)); + } else { + process(req, rsp, Operation.DELETE); + } + } + + private void process(HttpServletRequest req, HttpServletResponse rsp, Operation operation) { + rsp.setContentType("text/plain"); + rsp.setCharacterEncoding(UTF_8.name()); + String path = req.getPathInfo(); + T id = parse(path.substring(path.lastIndexOf('/') + 1)); + try { + Context.setForwardedEvent(true); + AtomicInteger idLock = getAndIncrementIdLock(id); + synchronized (idLock) { + index(id, operation); + } + if (idLock.decrementAndGet() == 0) { + removeIdLock(id); + } + rsp.setStatus(SC_NO_CONTENT); + } catch (IOException e) { + sendError(rsp, SC_CONFLICT, e.getMessage()); + logger.error(String.format("Unable to update %s index", type), e); + } catch (OrmException e) { + String msg = String.format("Error trying to find %s \n", type); + sendError(rsp, SC_NOT_FOUND, msg); + logger.debug(msg, e); + } finally { + Context.unsetForwardedEvent(); + } + } + + private AtomicInteger getAndIncrementIdLock(T id) { + synchronized (idLocks) { + AtomicInteger lock = idLocks.get(id); + if (lock == null) { + lock = new AtomicInteger(1); + idLocks.put(id, lock); + } else { + lock.incrementAndGet(); + } + return lock; + } + } + + private void removeIdLock(T id) { + synchronized (idLocks) { + idLocks.remove(id); + } + } + + private void sendError(HttpServletResponse rsp, int statusCode, String message) { + try { + rsp.sendError(statusCode, message); + } catch (IOException e) { + logger.error("Failed to send error messsage: " + e.getMessage(), e); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java index b743db9..94c9a4f 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServlet.java
@@ -14,93 +14,35 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; -import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; -import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; - -import com.ericsson.gerrit.plugins.highavailability.forwarder.Context; import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.server.index.account.AccountIndexer; import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton -class IndexAccountRestApiServlet extends HttpServlet { +class IndexAccountRestApiServlet extends AbstractIndexRestApiServlet<Account.Id> { private static final long serialVersionUID = -1L; private static final Logger logger = LoggerFactory.getLogger(IndexAccountRestApiServlet.class); - private static final Map<Account.Id, AtomicInteger> accountIdLocks = new HashMap<>(); private final AccountIndexer indexer; @Inject IndexAccountRestApiServlet(AccountIndexer indexer) { + super("account"); this.indexer = indexer; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) - throws IOException, ServletException { - rsp.setContentType("text/plain"); - rsp.setCharacterEncoding("UTF-8"); - String path = req.getPathInfo(); - String accountId = path.substring(path.lastIndexOf('/') + 1); - Account.Id id = Account.Id.parse(accountId); - try { - Context.setForwardedEvent(true); - index(id); - rsp.setStatus(SC_NO_CONTENT); - } catch (IOException e) { - sendError(rsp, SC_CONFLICT, e.getMessage()); - logger.error("Unable to update account index", e); - } finally { - Context.unsetForwardedEvent(); - } + Account.Id parse(String id) { + return Account.Id.parse(id); } - private static void sendError(HttpServletResponse rsp, int statusCode, String message) { - try { - rsp.sendError(statusCode, message); - } catch (IOException e) { - logger.error("Failed to send error messsage: " + e.getMessage(), e); - } - } - - private void index(Account.Id id) throws IOException { - AtomicInteger accountIdLock = getAndIncrementAccountIdLock(id); - synchronized (accountIdLock) { - indexer.index(id); - logger.debug("Account {} successfully indexed", id); - } - if (accountIdLock.decrementAndGet() == 0) { - removeAccountIdLock(id); - } - } - - private AtomicInteger getAndIncrementAccountIdLock(Account.Id id) { - synchronized (accountIdLocks) { - AtomicInteger accountIdLock = accountIdLocks.get(id); - if (accountIdLock == null) { - accountIdLock = new AtomicInteger(1); - accountIdLocks.put(id, accountIdLock); - } else { - accountIdLock.incrementAndGet(); - } - return accountIdLock; - } - } - - private void removeAccountIdLock(Account.Id id) { - synchronized (accountIdLocks) { - accountIdLocks.remove(id); - } + @Override + void index(Account.Id id, Operation operation) throws IOException { + indexer.index(id); + logger.debug("Account {} successfully indexed", id); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java index 75ca59b..f8a3c42 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServlet.java
@@ -14,11 +14,6 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; -import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; -import static javax.servlet.http.HttpServletResponse.SC_NOT_FOUND; -import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; - -import com.ericsson.gerrit.plugins.highavailability.forwarder.Context; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.server.index.change.ChangeIndexer; @@ -27,77 +22,33 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicInteger; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @Singleton -class IndexChangeRestApiServlet extends HttpServlet { +class IndexChangeRestApiServlet extends AbstractIndexRestApiServlet<Change.Id> { private static final long serialVersionUID = -1L; private static final Logger logger = LoggerFactory.getLogger(IndexChangeRestApiServlet.class); - private static final Map<Change.Id, AtomicInteger> changeIdLocks = new HashMap<>(); private final ChangeIndexer indexer; private final SchemaFactory<ReviewDb> schemaFactory; @Inject IndexChangeRestApiServlet(ChangeIndexer indexer, SchemaFactory<ReviewDb> schemaFactory) { + super("change", true); this.indexer = indexer; this.schemaFactory = schemaFactory; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) - throws IOException, ServletException { - process(req, rsp, "index"); + Change.Id parse(String id) { + return Change.Id.parse(id); } @Override - protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) - throws IOException, ServletException { - process(req, rsp, "delete"); - } - - private void process(HttpServletRequest req, HttpServletResponse rsp, String operation) { - rsp.setContentType("text/plain"); - rsp.setCharacterEncoding("UTF-8"); - String path = req.getPathInfo(); - String changeId = path.substring(path.lastIndexOf('/') + 1); - Change.Id id = Change.Id.parse(changeId); - try { - Context.setForwardedEvent(true); - index(id, operation); - rsp.setStatus(SC_NO_CONTENT); - } catch (IOException e) { - sendError(rsp, SC_CONFLICT, e.getMessage()); - logger.error("Unable to update change index", e); - } catch (OrmException e) { - String msg = "Error trying to find a change \n"; - sendError(rsp, SC_NOT_FOUND, msg); - logger.debug(msg, e); - } finally { - Context.unsetForwardedEvent(); - } - } - - private static void sendError(HttpServletResponse rsp, int statusCode, String message) { - try { - rsp.sendError(statusCode, message); - } catch (IOException e) { - logger.error("Failed to send error messsage: " + e.getMessage(), e); - } - } - - private void index(Change.Id id, String operation) throws IOException, OrmException { - AtomicInteger changeIdLock = getAndIncrementChangeIdLock(id); - synchronized (changeIdLock) { - if ("index".equals(operation)) { + void index(Change.Id id, Operation operation) throws IOException, OrmException { + switch (operation) { + case INDEX: try (ReviewDb db = schemaFactory.open()) { Change change = db.changes().get(id); if (change == null) { @@ -107,33 +58,11 @@ indexer.index(db, change); } logger.debug("Change {} successfully indexed", id); - } - if ("delete".equals(operation)) { + break; + case DELETE: indexer.delete(id); logger.debug("Change {} successfully deleted from index", id); - } - } - if (changeIdLock.decrementAndGet() == 0) { - removeChangeIdLock(id); - } - } - - private AtomicInteger getAndIncrementChangeIdLock(Change.Id id) { - synchronized (changeIdLocks) { - AtomicInteger changeIdLock = changeIdLocks.get(id); - if (changeIdLock == null) { - changeIdLock = new AtomicInteger(1); - changeIdLocks.put(id, changeIdLock); - } else { - changeIdLock.incrementAndGet(); - } - return changeIdLock; - } - } - - private void removeChangeIdLock(Change.Id id) { - synchronized (changeIdLocks) { - changeIdLocks.remove(id); + break; } } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java new file mode 100644 index 0000000..0fcb0ca --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServlet.java
@@ -0,0 +1,48 @@ +// Copyright (C) 2017 Ericsson +// +// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest; + +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.server.index.group.GroupIndexer; +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.io.IOException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Singleton +class IndexGroupRestApiServlet extends AbstractIndexRestApiServlet<AccountGroup.UUID> { + private static final long serialVersionUID = -1L; + private static final Logger logger = LoggerFactory.getLogger(IndexGroupRestApiServlet.class); + + private final GroupIndexer indexer; + + @Inject + IndexGroupRestApiServlet(GroupIndexer indexer) { + super("group"); + this.indexer = indexer; + } + + @Override + AccountGroup.UUID parse(String id) { + return AccountGroup.UUID.parse(id); + } + + @Override + void index(AccountGroup.UUID uuid, Operation operation) throws IOException { + indexer.index(uuid); + logger.debug("Group {} successfully indexed", uuid); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java index 6d18be3..18c8645 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarder.java
@@ -74,6 +74,16 @@ }.execute(); } + @Override + public boolean indexGroup(final String uuid) { + return new Request("index group " + uuid) { + @Override + HttpResult send() throws IOException { + return httpSession.post(Joiner.on("/").join(pluginRelativePath, "index/group", uuid)); + } + }.execute(); + } + private String buildIndexEndpoint(int changeId) { return Joiner.on("/").join(pluginRelativePath, "index/change", changeId); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java index bd093ae..d5027d1 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderServletModule.java
@@ -21,6 +21,7 @@ protected void configureServlets() { serveRegex("/index/account/\\d+$").with(IndexAccountRestApiServlet.class); serveRegex("/index/change/\\d+$").with(IndexChangeRestApiServlet.class); + serveRegex("/index/group/\\w+$").with(IndexGroupRestApiServlet.class); serve("/event").with(EventRestApiServlet.class); serve("/cache/*").with(CacheRestApiServlet.class); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java index b08052a..525c7ed 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandler.java
@@ -20,13 +20,15 @@ import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.events.AccountIndexedListener; import com.google.gerrit.extensions.events.ChangeIndexedListener; +import com.google.gerrit.extensions.events.GroupIndexedListener; import com.google.inject.Inject; import java.util.Collections; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; -class IndexEventHandler implements ChangeIndexedListener, AccountIndexedListener { +class IndexEventHandler + implements ChangeIndexedListener, AccountIndexedListener, GroupIndexedListener { private final Executor executor; private final Forwarder forwarder; private final String pluginName; @@ -61,6 +63,16 @@ executeIndexChangeTask(id, true); } + @Override + public void onGroupIndexed(String groupUUID) { + if (!Context.isForwardedEvent()) { + IndexGroupTask task = new IndexGroupTask(groupUUID); + if (queuedTasks.add(task)) { + executor.execute(task); + } + } + } + private void executeIndexChangeTask(int id, boolean deleted) { if (!Context.isForwardedEvent()) { IndexChangeTask task = new IndexChangeTask(id, deleted); @@ -71,12 +83,6 @@ } abstract class IndexTask implements Runnable { - protected int id; - - IndexTask(int id) { - this.id = id; - } - @Override public void run() { queuedTasks.remove(this); @@ -88,24 +94,25 @@ class IndexChangeTask extends IndexTask { private boolean deleted; + private int changeId; IndexChangeTask(int changeId, boolean deleted) { - super(changeId); + this.changeId = changeId; this.deleted = deleted; } @Override public void execute() { if (deleted) { - forwarder.deleteChangeFromIndex(id); + forwarder.deleteChangeFromIndex(changeId); } else { - forwarder.indexChange(id); + forwarder.indexChange(changeId); } } @Override public int hashCode() { - return Objects.hashCode(IndexChangeTask.class, id, deleted); + return Objects.hashCode(IndexChangeTask.class, changeId, deleted); } @Override @@ -114,29 +121,30 @@ return false; } IndexChangeTask other = (IndexChangeTask) obj; - return id == other.id && deleted == other.deleted; + return changeId == other.changeId && deleted == other.deleted; } @Override public String toString() { - return String.format("[%s] Index change %s in target instance", pluginName, id); + return String.format("[%s] Index change %s in target instance", pluginName, changeId); } } class IndexAccountTask extends IndexTask { + private int accountId; IndexAccountTask(int accountId) { - super(accountId); + this.accountId = accountId; } @Override public void execute() { - forwarder.indexAccount(id); + forwarder.indexAccount(accountId); } @Override public int hashCode() { - return Objects.hashCode(IndexAccountTask.class, id); + return Objects.hashCode(accountId); } @Override @@ -145,12 +153,44 @@ return false; } IndexAccountTask other = (IndexAccountTask) obj; - return id == other.id; + return accountId == other.accountId; } @Override public String toString() { - return String.format("[%s] Index account %s in target instance", pluginName, id); + return String.format("[%s] Index account %s in target instance", pluginName, accountId); + } + } + + class IndexGroupTask extends IndexTask { + private String groupUUID; + + IndexGroupTask(String groupUUID) { + this.groupUUID = groupUUID; + } + + @Override + public void execute() { + forwarder.indexGroup(groupUUID); + } + + @Override + public int hashCode() { + return Objects.hashCode(IndexGroupTask.class, groupUUID); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof IndexGroupTask)) { + return false; + } + IndexGroupTask other = (IndexGroupTask) obj; + return groupUUID == other.groupUUID; + } + + @Override + public String toString() { + return String.format("[%s] Index group %s in target instance", pluginName, groupUUID); } } }
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md index 3babed2..b243edc 100644 --- a/src/main/resources/Documentation/build.md +++ b/src/main/resources/Documentation/build.md
@@ -1,94 +1,91 @@ -Build -===== +# Build -This plugin is built with Buck. +This plugin can be built with Bazel, and two build modes are supported: -Two build modes are supported: Standalone and in Gerrit tree. Standalone -build mode is recommended, as this mode doesn't require local Gerrit -tree to exist. +* Standalone +* In Gerrit tree -Build standalone ----------------- +Standalone build mode is recommended, as this mode doesn't require local Gerrit +tree to exist. Moreover, there are some limitations and additional manual steps +required when building in Gerrit tree mode (see corresponding sections). -Clone bucklets library: - -``` - git clone https://gerrit.googlesource.com/bucklets - -``` -and link it to @PLUGIN@ directory: - -``` - cd @PLUGIN@ && ln -s ../bucklets . -``` - -Add link to the .buckversion file: - -``` - cd @PLUGIN@ && ln -s bucklets/buckversion .buckversion -``` - -Add link to the .watchmanconfig file: - -``` - cd @PLUGIN@ && ln -s bucklets/watchmanconfig .watchmanconfig -``` +## Build standalone To build the plugin, issue the following command: ``` - buck build plugin + bazel build @PLUGIN@ +``` + +The output is created in + +``` + bazel-genfiles/@PLUGIN@.jar +``` + +To package the plugin sources run: + +``` + bazel build lib@PLUGIN@__plugin-src.jar ``` The output is created in: ``` - buck-out/gen/@PLUGIN@.jar -``` - -This project can be imported into the Eclipse IDE: - -``` - ./bucklets/tools/eclipse.py + bazel-bin/lib@PLUGIN@__plugin-src.jar ``` To execute the tests run: ``` - buck test -``` - -To build plugin sources run: - -``` - buck build src -``` - -The output is created in: - -``` - buck-out/gen/@PLUGIN@-sources.jar -``` - -Build in Gerrit tree --------------------- - -Clone or link this plugin to the plugins directory of Gerrit's source -tree, and issue the command: - -``` - buck build plugins/@PLUGIN@ -``` - -The output is created in: - -``` - buck-out/gen/plugins/@PLUGIN@/@PLUGIN@.jar + bazel test high_availability_tests ``` This project can be imported into the Eclipse IDE: ``` + ./tools/eclipse.py +``` + +## Build in Gerrit tree + +Clone or link this plugin to the plugins directory of Gerrit's +source tree. Put the external dependency Bazel build file into +the Gerrit /plugins directory, replacing the existing empty one. + +``` + cd gerrit/plugins + rm external_plugin_deps.bzl + ln -s @PLUGIN@/external_plugin_deps.bzl . +``` + +From Gerrit source tree issue the command: + +``` + bazel build plugins/@PLUGIN@ +``` + +Note that due to a [known issue in Bazel][bazelissue], if the plugin +has previously been built in standalone mode, it is necessary to clean +the workspace before building in-tree: + +``` + cd plugins/@PLUGIN@ + bazel clean --expunge +``` + +The output is created in + +``` + bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar +``` + +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 ``` @@ -104,3 +101,4 @@ [Back to @PLUGIN@ documentation index][index] [index]: index.html +[bazelissue]: https://github.com/bazelbuild/bazel/issues/2797
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java index 11fc496..f3d6a9d 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/cache/CacheEvictionIT.java
@@ -21,37 +21,48 @@ import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; import static com.github.tomakehurst.wiremock.client.WireMock.verify; import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options; +import static org.junit.Assert.fail; import com.github.tomakehurst.wiremock.http.Request; import com.github.tomakehurst.wiremock.http.RequestListener; import com.github.tomakehurst.wiremock.http.Response; import com.github.tomakehurst.wiremock.junit.WireMockRule; -import com.google.common.base.Throwables; -import com.google.gerrit.acceptance.GerritConfig; -import com.google.gerrit.acceptance.GerritConfigs; +import com.google.gerrit.acceptance.GlobalPluginConfig; +import com.google.gerrit.acceptance.LightweightPluginDaemonTest; import com.google.gerrit.acceptance.NoHttpd; -import com.google.gerrit.acceptance.PluginDaemonTest; +import com.google.gerrit.acceptance.TestPlugin; +import com.google.gerrit.acceptance.UseLocalDisk; +import com.google.gerrit.acceptance.UseSsh; import java.util.concurrent.BrokenBarrierException; import java.util.concurrent.CyclicBarrier; import java.util.concurrent.TimeUnit; import org.apache.http.HttpStatus; -import org.junit.Ignore; import org.junit.Rule; import org.junit.Test; @NoHttpd -@Ignore -public class CacheEvictionIT extends PluginDaemonTest { +@UseSsh +@TestPlugin( + name = "high-availability", + sysModule = "com.ericsson.gerrit.plugins.highavailability.Module", + httpModule = "com.ericsson.gerrit.plugins.highavailability.HttpModule" +) +public class CacheEvictionIT extends LightweightPluginDaemonTest { + private static final int PORT = 18888; + private static final String URL = "http://localhost:" + PORT; - @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(18888), false); + @Rule public WireMockRule wireMockRule = new WireMockRule(options().port(PORT), false); @Test - @GerritConfigs({ - @GerritConfig(name = "plugin.high-availability.url", value = "http://localhost:18888"), - @GerritConfig(name = "plugin.high-availability.user", value = "admin"), - @GerritConfig(name = "plugin.high-availability.cacheThreadPoolSize", value = "10"), - @GerritConfig(name = "plugin.high-availability.sharedDirectory", value = "directory") - }) + @UseLocalDisk + @GlobalPluginConfig(pluginName = "high-availability", name = "peerInfo.url", value = URL) + @GlobalPluginConfig(pluginName = "high-availability", name = "http.user", value = "admin") + @GlobalPluginConfig(pluginName = "high-availability", name = "cache.threadPoolSize", value = "10") + @GlobalPluginConfig( + pluginName = "high-availability", + name = "main.sharedDirectory", + value = "directory" + ) public void flushAndSendPost() throws Exception { final String flushRequest = "/plugins/high-availability/cache/" + Constants.PROJECT_LIST; final CyclicBarrier checkPoint = new CyclicBarrier(2); @@ -63,7 +74,7 @@ try { checkPoint.await(); } catch (InterruptedException | BrokenBarrierException e) { - Throwables.propagateIfPossible(e); + fail(); } } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java index cc80fbb..9893c0a 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexAccountRestApiServletTest.java
@@ -15,6 +15,7 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; +import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.times; @@ -66,6 +67,12 @@ } @Test + public void cannotDeleteAccount() throws Exception { + servlet.doDelete(req, rsp); + verify(rsp).sendError(SC_METHOD_NOT_ALLOWED, "cannot delete account from index"); + } + + @Test public void indexerThrowsIOExceptionTryingToIndexAccount() throws Exception { doThrow(new IOException("io-error")).when(indexer).index(id); servlet.doPost(req, rsp);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java index 0c791b8..af6c31a 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexChangeRestApiServletTest.java
@@ -95,7 +95,7 @@ public void schemaThrowsExceptionWhenLookingUpForChange() throws Exception { setupPostMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION); indexRestApiServlet.doPost(req, rsp); - verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find a change \n"); + verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find change \n"); } @Test @@ -123,10 +123,10 @@ public void sendErrorThrowsIOException() throws Exception { doThrow(new IOException("someError")) .when(rsp) - .sendError(SC_NOT_FOUND, "Error trying to find a change \n"); + .sendError(SC_NOT_FOUND, "Error trying to find change \n"); setupPostMocks(CHANGE_EXISTS, THROW_ORM_EXCEPTION); indexRestApiServlet.doPost(req, rsp); - verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find a change \n"); + verify(rsp).sendError(SC_NOT_FOUND, "Error trying to find change \n"); verifyZeroInteractions(indexer); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java new file mode 100644 index 0000000..0994b9b --- /dev/null +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexGroupRestApiServletTest.java
@@ -0,0 +1,89 @@ +// Copyright (C) 2017 Ericsson +// +// 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.ericsson.gerrit.plugins.highavailability.forwarder.rest; + +import static javax.servlet.http.HttpServletResponse.SC_CONFLICT; +import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED; +import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.server.index.group.GroupIndexer; +import com.google.gwtorm.client.KeyUtil; +import com.google.gwtorm.server.StandardKeyEncoder; +import java.io.IOException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class IndexGroupRestApiServletTest { + private static final String UUID = "we235jdf92nfj2351"; + + @Mock private GroupIndexer indexer; + @Mock private HttpServletRequest req; + @Mock private HttpServletResponse rsp; + + private AccountGroup.UUID uuid; + private IndexGroupRestApiServlet servlet; + + @BeforeClass + public static void setup() { + KeyUtil.setEncoderImpl(new StandardKeyEncoder()); + } + + @Before + public void setUpMocks() { + servlet = new IndexGroupRestApiServlet(indexer); + uuid = AccountGroup.UUID.parse(UUID); + when(req.getPathInfo()).thenReturn("/index/group/" + UUID); + } + + @Test + public void groupIsIndexed() throws Exception { + servlet.doPost(req, rsp); + verify(indexer, times(1)).index(uuid); + verify(rsp).setStatus(SC_NO_CONTENT); + } + + @Test + public void cannotDeleteGroup() throws Exception { + servlet.doDelete(req, rsp); + verify(rsp).sendError(SC_METHOD_NOT_ALLOWED, "cannot delete group from index"); + } + + @Test + public void indexerThrowsIOExceptionTryingToIndexGroup() throws Exception { + doThrow(new IOException("io-error")).when(indexer).index(uuid); + servlet.doPost(req, rsp); + verify(rsp).sendError(SC_CONFLICT, "io-error"); + } + + @Test + public void sendErrorThrowsIOException() throws Exception { + doThrow(new IOException("io-error")).when(indexer).index(uuid); + doThrow(new IOException("someError")).when(rsp).sendError(SC_CONFLICT, "io-error"); + servlet.doPost(req, rsp); + verify(rsp).sendError(SC_CONFLICT, "io-error"); + } +}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java index 3a736cb..e19f6f2 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderTest.java
@@ -50,6 +50,9 @@ private static final int ACCOUNT_NUMBER = 2; private static final String INDEX_ACCOUNT_ENDPOINT = Joiner.on("/").join("/plugins", PLUGIN_NAME, "index/account", ACCOUNT_NUMBER); + private static final String UUID = "we235jdf92nfj2351"; + private static final String INDEX_GROUP_ENDPOINT = + Joiner.on("/").join("/plugins", PLUGIN_NAME, "index/group", UUID); //Event private static final String EVENT_ENDPOINT = @@ -95,6 +98,25 @@ } @Test + public void testIndexGroupOK() throws Exception { + when(httpSessionMock.post(INDEX_GROUP_ENDPOINT)) + .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); + assertThat(forwarder.indexGroup(UUID)).isTrue(); + } + + @Test + public void testIndexGroupFailed() throws Exception { + when(httpSessionMock.post(INDEX_GROUP_ENDPOINT)).thenReturn(new HttpResult(FAILED, EMPTY_MSG)); + assertThat(forwarder.indexGroup(UUID)).isFalse(); + } + + @Test + public void testIndexGroupThrowsException() throws Exception { + doThrow(new IOException()).when(httpSessionMock).post(INDEX_GROUP_ENDPOINT); + assertThat(forwarder.indexGroup(UUID)).isFalse(); + } + + @Test public void testIndexChangeOK() throws Exception { when(httpSessionMock.post(INDEX_CHANGE_ENDPOINT)) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG));
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java index e50d713..e34b902 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/IndexEventHandlerTest.java
@@ -24,8 +24,10 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexAccountTask; import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexChangeTask; +import com.ericsson.gerrit.plugins.highavailability.index.IndexEventHandler.IndexGroupTask; import com.google.common.util.concurrent.MoreExecutors; import com.google.gerrit.reviewdb.client.Account; +import com.google.gerrit.reviewdb.client.AccountGroup; import com.google.gerrit.reviewdb.client.Change; import com.google.gerrit.server.git.WorkQueue.Executor; import com.google.gwtorm.client.KeyUtil; @@ -42,11 +44,13 @@ private static final String PLUGIN_NAME = "high-availability"; private static final int CHANGE_ID = 1; private static final int ACCOUNT_ID = 2; + private static final String UUID = "3"; private IndexEventHandler indexEventHandler; @Mock private Forwarder forwarder; private Change.Id changeId; private Account.Id accountId; + private AccountGroup.UUID accountGroupUUID; @BeforeClass public static void setUp() { @@ -57,6 +61,7 @@ public void setUpMocks() { changeId = Change.Id.parse(Integer.toString(CHANGE_ID)); accountId = Account.Id.parse(Integer.toString(ACCOUNT_ID)); + accountGroupUUID = AccountGroup.UUID.parse(UUID); indexEventHandler = new IndexEventHandler(MoreExecutors.directExecutor(), PLUGIN_NAME, forwarder); } @@ -80,6 +85,12 @@ } @Test + public void shouldIndexInRemoteOnGroupIndexedEvent() throws Exception { + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + verify(forwarder).indexGroup(UUID); + } + + @Test public void shouldNotCallRemoteWhenChangeEventIsForwarded() throws Exception { Context.setForwardedEvent(true); indexEventHandler.onChangeIndexed(changeId.get()); @@ -98,6 +109,15 @@ } @Test + public void shouldNotCallRemoteWhenGroupEventIsForwarded() throws Exception { + Context.setForwardedEvent(true); + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + Context.unsetForwardedEvent(); + verifyZeroInteractions(forwarder); + } + + @Test public void duplicateChangeEventOfAQueuedEventShouldGetDiscarded() { Executor poolMock = mock(Executor.class); indexEventHandler = new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder); @@ -116,6 +136,15 @@ } @Test + public void duplicateGroupEventOfAQueuedEventShouldGetDiscarded() { + Executor poolMock = mock(Executor.class); + indexEventHandler = new IndexEventHandler(poolMock, PLUGIN_NAME, forwarder); + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + indexEventHandler.onGroupIndexed(accountGroupUUID.get()); + verify(poolMock, times(1)).execute(indexEventHandler.new IndexGroupTask(UUID)); + } + + @Test public void testIndexChangeTaskToString() throws Exception { IndexChangeTask task = indexEventHandler.new IndexChangeTask(CHANGE_ID, false); assertThat(task.toString()) @@ -132,11 +161,19 @@ } @Test + public void testIndexGroupTaskToString() throws Exception { + IndexGroupTask task = indexEventHandler.new IndexGroupTask(UUID); + assertThat(task.toString()) + .isEqualTo(String.format("[%s] Index group %s in target instance", PLUGIN_NAME, UUID)); + } + + @Test public void testIndexChangeTaskHashCodeAndEquals() { IndexChangeTask task = indexEventHandler.new IndexChangeTask(CHANGE_ID, false); - assertThat(task.equals(task)).isTrue(); - assertThat(task.hashCode()).isEqualTo(task.hashCode()); + IndexChangeTask sameTask = task; + assertThat(task.equals(sameTask)).isTrue(); + assertThat(task.hashCode()).isEqualTo(sameTask.hashCode()); IndexChangeTask identicalTask = indexEventHandler.new IndexChangeTask(CHANGE_ID, false); assertThat(task.equals(identicalTask)).isTrue(); @@ -159,8 +196,9 @@ public void testIndexAccountTaskHashCodeAndEquals() { IndexAccountTask task = indexEventHandler.new IndexAccountTask(ACCOUNT_ID); - assertThat(task.equals(task)).isTrue(); - assertThat(task.hashCode()).isEqualTo(task.hashCode()); + IndexAccountTask sameTask = task; + assertThat(task.equals(sameTask)).isTrue(); + assertThat(task.hashCode()).isEqualTo(sameTask.hashCode()); IndexAccountTask identicalTask = indexEventHandler.new IndexAccountTask(ACCOUNT_ID); assertThat(task.equals(identicalTask)).isTrue(); @@ -174,4 +212,25 @@ assertThat(task.equals(differentAccountIdTask)).isFalse(); assertThat(task.hashCode()).isNotEqualTo(differentAccountIdTask.hashCode()); } + + @Test + public void testIndexGroupTaskHashCodeAndEquals() { + IndexGroupTask task = indexEventHandler.new IndexGroupTask(UUID); + + IndexGroupTask sameTask = task; + assertThat(task.equals(sameTask)).isTrue(); + assertThat(task.hashCode()).isEqualTo(sameTask.hashCode()); + + IndexGroupTask identicalTask = indexEventHandler.new IndexGroupTask(UUID); + assertThat(task.equals(identicalTask)).isTrue(); + assertThat(task.hashCode()).isEqualTo(identicalTask.hashCode()); + + assertThat(task.equals(null)).isFalse(); + assertThat(task.equals("test")).isFalse(); + assertThat(task.hashCode()).isNotEqualTo("test".hashCode()); + + IndexGroupTask differentGroupIdTask = indexEventHandler.new IndexGroupTask("123"); + assertThat(task.equals(differentGroupIdTask)).isFalse(); + assertThat(task.hashCode()).isNotEqualTo(differentGroupIdTask.hashCode()); + } }
diff --git a/tools/BUILD b/tools/BUILD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/BUILD
diff --git a/tools/bazel.rc b/tools/bazel.rc new file mode 100644 index 0000000..4ed16cf --- /dev/null +++ b/tools/bazel.rc
@@ -0,0 +1,2 @@ +build --workspace_status_command=./tools/workspace-status.sh +test --build_tests_only
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/tools/bzl/BUILD
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl new file mode 100644 index 0000000..dfcbe9c --- /dev/null +++ b/tools/bzl/classpath.bzl
@@ -0,0 +1,2 @@ +load("@com_googlesource_gerrit_bazlets//tools:classpath.bzl", + "classpath_collector")
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl new file mode 100644 index 0000000..3af7e58 --- /dev/null +++ b/tools/bzl/junit.bzl
@@ -0,0 +1,4 @@ +load( + "@com_googlesource_gerrit_bazlets//tools:junit.bzl", + "junit_tests", +)
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl new file mode 100644 index 0000000..a2e438f --- /dev/null +++ b/tools/bzl/plugin.bzl
@@ -0,0 +1,6 @@ +load( + "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl", + "gerrit_plugin", + "PLUGIN_DEPS", + "PLUGIN_TEST_DEPS", +)
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD new file mode 100644 index 0000000..a70caed --- /dev/null +++ b/tools/eclipse/BUILD
@@ -0,0 +1,24 @@ +load("//tools/bzl:plugin.bzl", "PLUGIN_DEPS", "PLUGIN_TEST_DEPS") +load("//tools/bzl:classpath.bzl", "classpath_collector") + +java_library( + name = "classpath", + runtime_deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + "@wiremock//jar", + "@mockito//jar", + "@byte-buddy//jar", + "@objenesis//jar", + "//:high-availability__plugin", + ], +) + +classpath_collector( + name = "main_classpath_collect", + deps = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + "@wiremock//jar", + "@mockito//jar", + "@byte-buddy//jar", + "@objenesis//jar", + "//:high-availability__plugin", + ], +)
diff --git a/tools/eclipse/project.py b/tools/eclipse/project.py new file mode 100755 index 0000000..88ca4b0 --- /dev/null +++ b/tools/eclipse/project.py
@@ -0,0 +1,179 @@ +#!/usr/bin/env python +# 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. +# + +from __future__ import print_function +# TODO(davido): use Google style for importing instead: +# import optparse +# +# optparse.OptionParser +from optparse import OptionParser +from os import environ, path, makedirs +from subprocess import CalledProcessError, check_call, check_output +from xml.dom import minidom +import re +import sys + +MAIN = '//tools/eclipse:classpath' +JRE = '/'.join([ + 'org.eclipse.jdt.launching.JRE_CONTAINER', + 'org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType', + 'JavaSE-1.8', +]) +# Map of targets to corresponding classpath collector rules +cp_targets = { + MAIN: '//tools/eclipse:main_classpath_collect', +} + +ROOT = path.abspath(__file__) +while not path.exists(path.join(ROOT, 'WORKSPACE')): + ROOT = path.dirname(ROOT) + +opts = OptionParser() +opts.add_option('--name', help='name of the generated project', + action='store', default='sync-index', dest='project_name') +args, _ = opts.parse_args() + +def retrieve_ext_location(): + return check_output(['bazel', 'info', 'output_base']).strip() + +def gen_primary_build_tool(): + bazel = check_output(['which', 'bazel']).strip() + with open(path.join(ROOT, ".primary_build_tool"), 'w') as fd: + fd.write("bazel=%s\n" % bazel) + fd.write("PATH=%s\n" % environ["PATH"]) + +def _query_classpath(target): + deps = [] + t = cp_targets[target] + try: + check_call(['bazel', 'build', t]) + except CalledProcessError: + exit(1) + name = 'bazel-bin/tools/eclipse/' + t.split(':')[1] + '.runtime_classpath' + deps = [line.rstrip('\n') for line in open(name)] + return deps + +def gen_project(name='gerrit', root=ROOT): + p = path.join(root, '.project') + with open(p, 'w') as fd: + print("""\ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>%(name)s</name> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription>\ + """ % {"name": name}, file=fd) + +def gen_classpath(ext): + def make_classpath(): + impl = minidom.getDOMImplementation() + return impl.createDocument(None, 'classpath', None) + + def classpathentry(kind, path, src=None, out=None, exported=None): + e = doc.createElement('classpathentry') + e.setAttribute('kind', kind) + # TODO(davido): Remove this and other exclude BUILD files hack + # when this Bazel bug is fixed: + # https://github.com/bazelbuild/bazel/issues/1083 + if kind == 'src': + e.setAttribute('excluding', '**/BUILD') + e.setAttribute('path', path) + if src: + e.setAttribute('sourcepath', src) + if out: + e.setAttribute('output', out) + if exported: + e.setAttribute('exported', 'true') + doc.documentElement.appendChild(e) + + doc = make_classpath() + src = set() + lib = set() + + # Classpath entries are absolute for cross-cell support + java_library = re.compile('bazel-out/local-fastbuild/bin/lib[^/]+[.]jar$') + srcs = re.compile('(.*/external/[^/]+)/jar/(.*)[.]jar') + for p in _query_classpath(MAIN): + m = java_library.match(p) + if m: + src.add(".") + else: + if p.startswith("external"): + p = path.join(ext, p) + lib.add(p) + + for s in sorted(src): + out = None + + if s.startswith('lib/'): + out = 'eclipse-out/lib' + + p = path.join(s, 'java') + if path.exists(p): + classpathentry('src', p, out=out) + continue + + for env in ['main', 'test']: + o = None + if out: + o = out + '/' + env + elif env == 'test': + o = 'eclipse-out/test' + + for srctype in ['java', 'resources']: + p = path.join(s, 'src', env, srctype) + if path.exists(p): + classpathentry('src', p, out=o) + + for libs in [lib]: + for j in sorted(libs): + s = None + m = srcs.match(j) + if m: + prefix = m.group(1) + suffix = m.group(2) + p = path.join(prefix, "src", "%s-src.jar" % suffix) + if path.exists(p): + s = p + classpathentry('lib', j, s) + + classpathentry('con', JRE) + classpathentry('output', 'eclipse-out/classes') + + p = path.join(ROOT, '.classpath') + with open(p, 'w') as fd: + doc.writexml(fd, addindent='\t', newl='\n', encoding='UTF-8') + +try: + ext_location = retrieve_ext_location() + gen_project(args.project_name) + gen_classpath(ext_location) + gen_primary_build_tool() + + try: + check_call(['bazel', 'build', MAIN]) + except CalledProcessError: + exit(1) +except KeyboardInterrupt: + print('Interrupted by user', file=sys.stderr) + exit(1)
diff --git a/tools/workspace-status.sh b/tools/workspace-status.sh new file mode 100755 index 0000000..c83d416 --- /dev/null +++ b/tools/workspace-status.sh
@@ -0,0 +1,17 @@ +#!/bin/bash + +# This script will be run by bazel when the build process starts to +# generate key-value information that represents the status of the +# workspace. The output should be like +# +# KEY1 VALUE1 +# KEY2 VALUE2 +# +# If the script exits with non-zero code, it's considered as a failure +# and the output will be discarded. + +function rev() { + cd $1; git describe --always --match "v[0-9].*" --dirty +} + +echo STABLE_BUILD_HIGH-AVAILABILITY_LABEL $(rev .)