Merge branch stable-3.13 into stable-3.14 Change-Id: Ieb619594accc3bbbde1c2ca714f4c9c700976647
diff --git a/BUILD b/BUILD index 10896bf..da759b9 100644 --- a/BUILD +++ b/BUILD
@@ -1,11 +1,21 @@ -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", + "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl", "gerrit_plugin", + "gerrit_plugin_tests", ) +load( + "@com_googlesource_gerrit_bazlets//tools:in_gerrit_tree.bzl", + "in_gerrit_tree_enabled", +) +load( + "@com_googlesource_gerrit_bazlets//tools:runtime_jars_allowlist.bzl", + "runtime_jars_allowlist_test", +) +load( + "@com_googlesource_gerrit_bazlets//tools:runtime_jars_overlap.bzl", + "runtime_jars_overlap_test", +) +load("@rules_java//java:defs.bzl", "java_library") gerrit_plugin( name = "high-availability", @@ -22,9 +32,9 @@ resources = glob(["src/main/resources/**/*"]), deps = [ ":global-refdb-neverlink", - "@failsafe//jar", - "@jgroups-kubernetes//jar", - "@jgroups//jar", + "@high-availability_plugin_deps//:dev_failsafe_failsafe", + "@high-availability_plugin_deps//:org_jgroups_jgroups", + "@high-availability_plugin_deps//:org_jgroups_kubernetes_jgroups_kubernetes", ], ) @@ -34,7 +44,7 @@ exports = ["//plugins/global-refdb"], ) -junit_tests( +gerrit_plugin_tests( name = "high-availability_tests", srcs = glob(["src/test/java/**/*.java"]), javacopts = ["-Xep:DoNotMock:OFF"], @@ -52,12 +62,26 @@ name = "high-availability__plugin_test_deps", testonly = 1, visibility = ["//visibility:public"], - exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [ + exports = [ ":high-availability__plugin", - "@global-refdb//jar", - "@wiremock//jar", - "@jgroups//jar", - "@commons-net//jar", - "@failsafe//jar", + "//plugins/global-refdb", + "@high-availability_plugin_deps//:com_github_tomakehurst_wiremock_standalone", + "@high-availability_plugin_deps//:dev_failsafe_failsafe", + "@high-availability_plugin_deps//:org_jgroups_jgroups", ], ) + +runtime_jars_allowlist_test( + name = "check_high-availability_third_party_runtime_jars", + allowlist = ":high-availability_third_party_runtime_jars.allowlist.txt", + hint = "plugins/high-availability:check_high-availability_third_party_runtime_jars_manifest", + target = ":high-availability__plugin", +) + +runtime_jars_overlap_test( + name = "high-availability_no_overlap_with_gerrit", + against = "//:headless.war.jars.txt", + hint = "Exclude overlaps via maven.install(excluded_artifacts=[...]) and re-run this test.", + target = ":high-availability__plugin", + target_compatible_with = in_gerrit_tree_enabled(), +)
diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..7d94e70 --- /dev/null +++ b/MODULE.bazel
@@ -0,0 +1,46 @@ +module(name = "high-availability") + +bazel_dep(name = "rules_jvm_external", version = "6.10") +bazel_dep(name = "com_googlesource_gerrit_bazlets") +git_override( + module_name = "com_googlesource_gerrit_bazlets", + commit = "01ec9c2481186396dde1bc6f5166f7d95bdae134", + remote = "https://gerrit.googlesource.com/bazlets", +) + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") +maven.install( + name = "high-availability_plugin_deps", + artifacts = [ + "com.github.tomakehurst:wiremock-standalone:2.27.2", + "dev.failsafe:failsafe:3.3.2", + "org.jgroups:jgroups:5.2.16.Final", + "org.jgroups.kubernetes:jgroups-kubernetes:2.0.1.Final", + ], + duplicate_version_warning = "error", + excluded_artifacts = [ + # JGroups declares Byteman/BMUnit as optional/test dependencies in its + # Maven POM. They are used only for instrumentation and fault-injection + # tests and are not required for the runtime of the high-availability + # plugin. rules_jvm_external resolves them transitively, so exclude + # them explicitly to avoid bundling unnecessary runtime jars. + "org.jboss.byteman:byteman", + "org.jboss.byteman:byteman-bmunit", + "org.jboss.byteman:byteman-install", + "org.jboss.byteman:byteman-submit", + # Note: mjson classes currently come bundled inside jgroups-kubernetes itself, + # so they appear in the final plugin jar even though there is no separate mjson + # dependency in the Bazel graph. This cannot be fixed via excluded_artifacts. + # Upstream is tracking removal/replacement of mjson in jgroups-kubernetes: + # jgroups-extras/jgroups-kubernetes#288. + ], + fail_if_repin_required = True, + fail_on_missing_checksum = True, + fetch_sources = True, + lock_file = "//:high-availability_plugin_deps.lock.json", + repositories = [ + "https://repo1.maven.org/maven2", + ], + version_conflict_policy = "pinned", +) +use_repo(maven, "high-availability_plugin_deps")
diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..8c48193 --- /dev/null +++ b/MODULE.bazel.lock
@@ -0,0 +1,998 @@ +{ + "lockFileVersion": 13, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.2/MODULE.bazel": "73939767a4686cd9a520d16af5ab440071ed75cec1a876bf2fcfaf1f71987a16", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/MODULE.bazel": "d1086e248cda6576862b4b3fe9ad76a214e08c189af5b42557a6e1888812c5d5", + "https://bcr.bazel.build/modules/abseil-cpp/20250127.0/source.json": "1b996859f840d8efc7c720efc61dcf2a84b1261cb3974cbbe9b6666ebf567775", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/MODULE.bazel": "5ebe5bf853769c65707e5c28f216798f7a4b1042015e6a36e6d03094d94bec8a", + "https://bcr.bazel.build/modules/abseil-py/2.1.0/source.json": "0e8fc4f088ce07099c1cd6594c20c7ddbb48b4b3c0849b7d94ba94be88ff042b", + "https://bcr.bazel.build/modules/apple_support/1.11.1/MODULE.bazel": "1843d7cd8a58369a444fc6000e7304425fba600ff641592161d9f15b179fb896", + "https://bcr.bazel.build/modules/apple_support/1.15.1/MODULE.bazel": "a0556fefca0b1bb2de8567b8827518f94db6a6e7e7d632b4c48dc5f865bc7c85", + "https://bcr.bazel.build/modules/apple_support/1.15.1/source.json": "517f2b77430084c541bc9be2db63fdcbb7102938c5f64c17ee60ffda2e5cf07b", + "https://bcr.bazel.build/modules/apple_support/1.5.0/MODULE.bazel": "50341a62efbc483e8a2a6aec30994a58749bd7b885e18dd96aa8c33031e558ef", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.13.0/MODULE.bazel": "c14c33c7c3c730612bdbe14ebbb5e61936b6f11322ea95a6e91cd1ba962f94df", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.23.0/MODULE.bazel": "fd1ac84bc4e97a5a0816b7fd7d4d4f6d837b0047cf4cbd81652d616af3a6591a", + "https://bcr.bazel.build/modules/bazel_features/1.28.0/MODULE.bazel": "4b4200e6cbf8fa335b2c3f43e1d6ef3e240319c33d43d60cc0fbd4b87ece299d", + "https://bcr.bazel.build/modules/bazel_features/1.3.0/MODULE.bazel": "cdcafe83ec318cda34e02948e81d790aab8df7a929cec6f6969f13a489ccecd9", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/MODULE.bazel": "a14b62d05969a293b80257e72e597c2da7f717e1e69fa8b339703ed6731bec87", + "https://bcr.bazel.build/modules/bazel_features/1.30.0/source.json": "b07e17f067fe4f69f90b03b36ef1e08fe0d1f3cac254c1241a1818773e3423bc", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.8.2/MODULE.bazel": "69ad6927098316848b34a9142bcc975e018ba27f08c4ff403f50c1b6e646ca67", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/MODULE.bazel": "72997b29dfd95c3fa0d0c48322d05590418edef451f8db8db5509c57875fb4b7", + "https://bcr.bazel.build/modules/bazel_skylib/1.9.0/source.json": "7ad77c1e8c1b84222d9b3f3cae016a76639435744c19330b0b37c0a3c9da7dc0", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.1/MODULE.bazel": "02a13b77321773b2042e70ee5e4c5e099c8ddee4cf2da9cd420442c36938d4bd", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/MODULE.bazel": "460aa12d01231a80cce03c548287b433b321d205b0028ae596728c35e5ee442e", + "https://bcr.bazel.build/modules/bazel_worker_api/0.0.4/source.json": "d353c410d47a8b65d09fa98e83d57ebec257a2c2b9c6e42d6fda1cb25e5464a5", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/MODULE.bazel": "82494a01018bb7ef06d4a17ec4cd7a758721f10eb8b6c820a818e70d669500db", + "https://bcr.bazel.build/modules/bazel_worker_java/0.0.4/source.json": "a2d30458fd86cf022c2b6331e652526fa08e17573b2f5034a9dbcacdf9c2583c", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.40.0/MODULE.bazel": "42ba5378ebe845fca43989a53186ab436d956db498acde790685fe0e8f9c6146", + "https://bcr.bazel.build/modules/gazelle/0.40.0/source.json": "1e5ef6e4d8b9b6836d93273c781e78ff829ea2e077afef7a57298040fa4f010a", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/googletest/1.15.2/MODULE.bazel": "6de1edc1d26cafb0ea1a6ab3f4d4192d91a312fd2d360b63adaa213cd00b2108", + "https://bcr.bazel.build/modules/googletest/1.15.2/source.json": "dbdda654dcb3a0d7a8bc5d0ac5fc7e150b58c2a986025ae5bc634bb2cb61f470", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/MODULE.bazel": "2f8d20d3b7d54143213c4dfc3d98225c42de7d666011528dc8fe91591e2e17b0", + "https://bcr.bazel.build/modules/jsoncpp/1.9.6/source.json": "a04756d367a2126c3541682864ecec52f92cdee80a35735a3cb249ce015ca000", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/MODULE.bazel": "6f7b417dcc794d9add9e556673ad25cb3ba835224290f4f848f8e2db1e1fca74", + "https://bcr.bazel.build/modules/nlohmann_json/3.6.1/source.json": "f448c6e8963fdfa7eb831457df83ad63d3d6355018f6574fb017e8169deb43a9", + "https://bcr.bazel.build/modules/package_metadata/0.0.7/MODULE.bazel": "7adb03933fc8401f495800cf4eafcff0edc6da0ff55c7db223ef69d19f689486", + "https://bcr.bazel.build/modules/package_metadata/0.0.7/source.json": "50639625e937b56115012674c797cca7a05a96b4878c87d803c13dc2b31de8a0", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.11/MODULE.bazel": "0daefc49732e227caa8bfa834d65dc52e8cc18a2faf80df25e8caea151a9413f", + "https://bcr.bazel.build/modules/platforms/0.0.11/source.json": "f7e188b79ebedebfe75e9e1d098b8845226c7992b307e28e1496f23112e8fc29", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/platforms/0.0.9/MODULE.bazel": "4a87a60c927b56ddd67db50c89acaa62f4ce2a1d2149ccb63ffd871d5ce29ebc", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/23.1/MODULE.bazel": "88b393b3eb4101d18129e5db51847cd40a5517a53e81216144a8c32dfeeca52a", + "https://bcr.bazel.build/modules/protobuf/24.4/MODULE.bazel": "7bc7ce5f2abf36b3b7b7c8218d3acdebb9426aeb35c2257c96445756f970eb12", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/27.2/MODULE.bazel": "32450b50673882e4c8c3d10a83f3bc82161b213ed2f80d17e38bece8f165c295", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0-rc3/MODULE.bazel": "33c2dfa286578573afc55a7acaea3cada4122b9631007c594bf0729f41c8de92", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/protobuf/31.1/MODULE.bazel": "379a389bb330b7b8c1cdf331cc90bf3e13de5614799b3b52cdb7c6f389f6b38e", + "https://bcr.bazel.build/modules/protobuf/31.1/source.json": "25af5d0219da0c0fc4d1191a24ce438e6ca7f49d2e1a94f354efeba6ef10426f", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34", + "https://bcr.bazel.build/modules/pybind11_bazel/2.12.0/source.json": "6900fdc8a9e95866b8c0d4ad4aba4d4236317b5c1cd04c502df3f0d33afed680", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/MODULE.bazel": "b4963dda9b31080be1905ef085ecd7dd6cd47c05c79b9cdf83ade83ab2ab271a", + "https://bcr.bazel.build/modules/re2/2024-07-02.bcr.1/source.json": "2ff292be6ef3340325ce8a045ecc326e92cbfab47c7cbab4bd85d28971b97ac4", + "https://bcr.bazel.build/modules/re2/2024-07-02/MODULE.bazel": "0eadc4395959969297cbcf31a249ff457f2f1d456228c67719480205aa306daa", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.6.6/MODULE.bazel": "b0fb569752aab65ab1a9db0a8f6cfaf5aa1754965e17e95dcf0e4d88e192a68d", + "https://bcr.bazel.build/modules/rules_android/0.6.6/source.json": "a9d8dc2d5a102dc03269a94acc886a4cab82cdcb9ccbc77b0f665d6d17a6ae09", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/MODULE.bazel": "0d1caf0b8375942ce98ea944be754a18874041e4e0459401d925577624d3a54a", + "https://bcr.bazel.build/modules/rules_apple/3.16.0/source.json": "d8b5fe461272018cc07cfafce11fe369c7525330804c37eec5a82f84cd475366", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_cc/0.1.1/MODULE.bazel": "2f0222a6f229f0bf44cd711dc13c858dad98c62d52bd51d8fc3a764a83125513", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/MODULE.bazel": "88dfc9361e8b5ae1008ac38f7cdfd45ad738e4fa676a3ad67d19204f045a1fd8", + "https://bcr.bazel.build/modules/rules_cc/0.1.5/source.json": "4bb4fed7f5499775d495739f785a5494a1f854645fa1bac5de131264f5acdf01", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.50.1/MODULE.bazel": "b91a308dc5782bb0a8021ad4330c81fea5bda77f96b9e4c117b9b9c8f6665ee0", + "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/MODULE.bazel": "edfc3a9cea7bedb0eaaff37b0d7817c1a4bf72b3c615580b0ffcee6c52690fd4", + "https://bcr.bazel.build/modules/rules_go/0.51.0-rc2/source.json": "6b5cd0b3da2bd0e6949580851db990a04af0a285f072b9a0f059424457cd8cc9", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.3.0/MODULE.bazel": "a97c7678c19f236a956ad260d59c86e10a463badb7eb2eda787490f4c969b963", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.1.0/MODULE.bazel": "30d9135a2b6561c761bd67bd4990da591e6bdc128790ce3e7afd6a3558b2fb64", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.4.0/MODULE.bazel": "a592852f8a3dd539e82ee6542013bf2cadfc4c6946be8941e189d224500a8934", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_java/8.13.0/MODULE.bazel": "0444ebf737d144cf2bb2ccb368e7f1cce735264285f2a3711785827c1686625e", + "https://bcr.bazel.build/modules/rules_java/8.13.0/source.json": "4605c0f676b87dd9d1fabd4d743b71f04d97503bd1a79aad53f87399fb5396de", + "https://bcr.bazel.build/modules/rules_java/8.3.2/MODULE.bazel": "7336d5511ad5af0b8615fdc7477535a2e4e723a357b6713af439fe8cf0195017", + "https://bcr.bazel.build/modules/rules_java/8.5.1/MODULE.bazel": "d8a9e38cc5228881f7055a6079f6f7821a073df3744d441978e7a43e20226939", + "https://bcr.bazel.build/modules/rules_java/8.6.0/MODULE.bazel": "9c064c434606d75a086f15ade5edb514308cccd1544c2b2a89bbac4310e41c71", + "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/6.10/MODULE.bazel": "33e636ca6bc9ee0fa090a38aa33c631ded2d8cf6fead4124181d1b35dc474f7c", + "https://bcr.bazel.build/modules/rules_jvm_external/6.10/source.json": "c191249787625db72616a3fb3cc2786ab57355a2e3b615402b8b3b66b0f995b7", + "https://bcr.bazel.build/modules/rules_jvm_external/6.2/MODULE.bazel": "36a6e52487a855f33cb960724eb56547fa87e2c98a0474c3acad94339d7f8e99", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.6/MODULE.bazel": "153042249c7060536dc95b6bb9f9bb8063b8a0b0cb7acdb381bddbc2374aed55", + "https://bcr.bazel.build/modules/rules_jvm_external/6.7/MODULE.bazel": "e717beabc4d091ecb2c803c2d341b88590e9116b8bf7947915eeb33aab4f96dd", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.5/MODULE.bazel": "043a16a572f610558ec2030db3ff0c9938574e7dd9f58bded1bb07c0192ef025", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/MODULE.bazel": "ce7def6d576aa8d3a9c6d10e13b4d157296229674371f67dbf788dae0afae3d5", + "https://bcr.bazel.build/modules/rules_kotlin/2.1.3/source.json": "0b0dc9400f14b5fbb13d278ad3bf0413cdbaf0da0db337e055b855e35b878a3b", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0-rc1/MODULE.bazel": "1e5b502e2e1a9e825eef74476a5a1ee524a92297085015a052510b09a1a09483", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.22.1/MODULE.bazel": "26114f0c0b5e93018c0c066d6673f1a2c3737c7e90af95eff30cfee38d0bbac7", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.33.2/MODULE.bazel": "3e036c4ad8d804a4dad897d333d8dce200d943df4827cb849840055be8d2e937", + "https://bcr.bazel.build/modules/rules_python/0.37.1/MODULE.bazel": "3faeb2d9fa0a81f8980643ee33f212308f4d93eea4b9ce6f36d0b742e71e9500", + "https://bcr.bazel.build/modules/rules_python/0.37.2/MODULE.bazel": "b5ffde91410745750b6c13be1c5dc4555ef5bc50562af4a89fd77807fdde626a", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/1.0.0/MODULE.bazel": "898a3d999c22caa585eb062b600f88654bf92efb204fa346fb55f6f8edffca43", + "https://bcr.bazel.build/modules/rules_python/1.7.0/MODULE.bazel": "d01f995ecd137abf30238ad9ce97f8fc3ac57289c8b24bd0bf53324d937a14f8", + "https://bcr.bazel.build/modules/rules_python/1.7.0/source.json": "028a084b65dcf8f4dc4f82f8778dbe65df133f234b316828a82e060d81bdce32", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/MODULE.bazel": "d44fec647d0aeb67b9f3b980cf68ba634976f3ae7ccd6c07d790b59b87a4f251", + "https://bcr.bazel.build/modules/rules_robolectric/4.14.1.2/source.json": "37c10335f2361c337c5c1f34ed36d2da70534c23088062b33a8bdaab68aa9dea", + "https://bcr.bazel.build/modules/rules_shell/0.1.2/MODULE.bazel": "66e4ca3ce084b04af0b9ff05ff14cab4e5df7503973818bb91cbc6cda08d32fc", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/MODULE.bazel": "00e501db01bbf4e3e1dd1595959092c2fadf2087b2852d3f553b5370f5633592", + "https://bcr.bazel.build/modules/rules_shell/0.4.1/source.json": "4757bd277fe1567763991c4425b483477bb82e35e777a56fd846eb5cceda324a", + "https://bcr.bazel.build/modules/rules_swift/1.16.0/MODULE.bazel": "4a09f199545a60d09895e8281362b1ff3bb08bbde69c6fc87aff5b92fcc916ca", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/MODULE.bazel": "494900a80f944fc7aa61500c2073d9729dff0b764f0e89b824eb746959bc1046", + "https://bcr.bazel.build/modules/rules_swift/2.1.1/source.json": "40fc69dfaac64deddbb75bd99cdac55f4427d9ca0afbe408576a65428427a186", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.6.2/MODULE.bazel": "7060193196395f5dd668eda046ccbeacebfd98efc77fed418dbe2b82ffaa39fd", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.2/MODULE.bazel": "fc152419aa2ea0f51c29583fab1e8c99ddefd5b3778421845606ee628629e0e5", + "https://bcr.bazel.build/modules/stardoc/0.7.2/source.json": "58b029e5e901d6802967754adf0a9056747e8176f017cfe3607c0851f4d42216", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/MODULE.bazel": "5e463fbfba7b1701d957555ed45097d7f984211330106ccd1352c6e0af0dcf91", + "https://bcr.bazel.build/modules/swift_argument_parser/1.3.1.1/source.json": "32bd87e5f4d7acc57c5b2ff7c325ae3061d5e242c0c4c214ae87e0f1c13e54cb", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/upb/0.0.0-20230516-61a97ef/MODULE.bazel": "c0df5e35ad55e264160417fd0875932ee3c9dda63d9fccace35ac62f45e1b6f9", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/MODULE.bazel": "eec517b5bbe5492629466e11dae908d043364302283de25581e3eb944326c4ca", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.5/source.json": "22bc55c47af97246cfc093d0acf683a7869377de362b5d1c552c2c2e16b7a806", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@apple_support~//crosstool:setup.bzl%apple_cc_configure_extension": { + "general": { + "bzlTransitiveDigest": "ltCGFbl/LQQZXn/LEMXfKX7pGwyqNiOCHcmiQW0tmjM=", + "usagesDigest": "eXsWd8YDJDt7+F94YtjKuTAIRMWBi9JLS8hUiqynRIY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_config_apple_cc_toolchains": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf_toolchains", + "attributes": {} + }, + "local_config_apple_cc": { + "bzlFile": "@@apple_support~//crosstool:setup.bzl", + "ruleClassName": "_apple_cc_autoconf", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@pybind11_bazel~//:internal_configure.bzl%internal_configure_extension": { + "general": { + "bzlTransitiveDigest": "CyAKLVVonohnkTSqg9II/HA7M49sOlnMkgMHL3CmDuc=", + "usagesDigest": "mFrTHX5eCiNU/OIIGVHH3cOILY9Zmjqk8RQYv8o6Thk=", + "recordedFileInputs": { + "@@pybind11_bazel~//MODULE.bazel": "e6f4c20442eaa7c90d7190d8dc539d0ab422f95c65a57cc59562170c58ae3d34" + }, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "pybind11": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "build_file": "@@pybind11_bazel~//:pybind11-BUILD.bazel", + "strip_prefix": "pybind11-2.12.0", + "urls": [ + "https://github.com/pybind/pybind11/archive/v2.12.0.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "pybind11_bazel~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_android~//bzlmod_extensions:apksig.bzl%apksig_extension": { + "general": { + "bzlTransitiveDigest": "+fNvC5PNWx1aA64JjkW33+1zGbG9xgfi70xQhxbglhY=", + "usagesDigest": "TaGkNxVMBZxl1ANdUNPmIjKrA33bO+BpI06yTlpHdos=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "apksig": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://android.googlesource.com/platform/tools/apksig/+archive/24e3075e68ebe17c0b529bb24bfda819db5e2f3b.tar.gz", + "build_file": "@@rules_android~//bzlmod_extensions:apksig.BUILD" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_android~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_android~//bzlmod_extensions:com_android_dex.bzl%com_android_dex_extension": { + "general": { + "bzlTransitiveDigest": "/KJvxHLYhvSLJihEHN0e5Uzdd7OOMvpFb0BFUl1RUTs=", + "usagesDigest": "fh77SU1OUlWN5ivrGpY93+t0MipuVj89/MMUgfTpSa8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_android_dex": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://android.googlesource.com/platform/dalvik/+archive/5a81c499a569731e2395f7c8d13c0e0d4e17a2b6.tar.gz", + "build_file": "@@rules_android~//bzlmod_extensions:com_android_dex.BUILD" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_android~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_android~//rules/android_sdk_repository:rule.bzl%android_sdk_repository_extension": { + "general": { + "bzlTransitiveDigest": "NAy+0M15JNVEBb8Tny6t7j3lKqTnsAMjoBB6LJ+C370=", + "usagesDigest": "weZLGEpNa+fTJZ9CEljWxpN3/kuPzj/ULgebzOt2h4g=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "androidsdk": { + "bzlFile": "@@rules_android~//rules/android_sdk_repository:rule.bzl", + "ruleClassName": "_android_sdk_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_apple~//apple:apple.bzl%provisioning_profile_repository_extension": { + "general": { + "bzlTransitiveDigest": "69pHX7bLYS/DuYEt2ts3jHV2ut8C5uih4oI+b/tjJZk=", + "usagesDigest": "cLx5XGjlbSDOpyA053y/jlr4GcaXFVTeHQic6eyG38E=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "local_provisioning_profiles": { + "bzlFile": "@@rules_apple~//apple/internal:local_provisioning_profiles.bzl", + "ruleClassName": "provisioning_profile_repository", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "apple_support~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_apple~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_apple~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_apple~", + "build_bazel_apple_support", + "apple_support~" + ], + [ + "rules_apple~", + "build_bazel_rules_swift", + "rules_swift~" + ], + [ + "rules_swift~", + "bazel_skylib", + "bazel_skylib~" + ], + [ + "rules_swift~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_swift~", + "build_bazel_apple_support", + "apple_support~" + ], + [ + "rules_swift~", + "build_bazel_rules_swift", + "rules_swift~" + ], + [ + "rules_swift~", + "build_bazel_rules_swift_local_config", + "rules_swift~~non_module_deps~build_bazel_rules_swift_local_config" + ] + ] + } + }, + "@@rules_apple~//apple:extensions.bzl%non_module_deps": { + "general": { + "bzlTransitiveDigest": "LJ3bhNqfW9y6MZFgtKQoPJyUMg/aEZ8H9QXSzK1VOBk=", + "usagesDigest": "5FfEPy/Z0Y2V9RbdJRDabgADFdvlfIIrps/5XYOnMNI=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "xctestrunner": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/google/xctestrunner/archive/b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6.tar.gz" + ], + "strip_prefix": "xctestrunner-b7698df3d435b6491b4b4c0f9fc7a63fbed5e3a6", + "sha256": "ae3a063c985a8633cb7eb566db21656f8db8eb9a0edb8c182312c7f0db53730d" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_apple~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_kotlin~//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "BSndOxBXlbe5qZf1NRxf828dpEK6tM429pCnaFoUFYs=", + "usagesDigest": "Dg5Nih77FaLAipQB7wgwsAy553a3po0eNGQVTZKgCM8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", + "ruleClassName": "kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v2.1.0/kotlin-compiler-2.1.0.zip" + ], + "sha256": "b6698d5728ad8f9edcdd01617d638073191d8a03139cc538a391b4e3759ad297" + } + }, + "com_github_jetbrains_kotlin": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:compiler.bzl", + "ruleClassName": "kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "2.1.0" + } + }, + "com_github_google_ksp": { + "bzlFile": "@@rules_kotlin~//src/main/starlark/core/repositories:ksp.bzl", + "ruleClassName": "ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/2.1.0-1.0.28/artifacts.zip" + ], + "sha256": "fc27b08cadc061a4a989af01cbeccb613feef1995f4aad68f2be0f886a3ee251", + "strip_version": "2.1.0-1.0.28" + } + }, + "com_github_pinterest_ktlint": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_file", + "attributes": { + "sha256": "a9f923be58fbd32670a17f0b729b1df804af882fa57402165741cb26e5440ca1", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.1/ktlint" + ], + "executable": true + } + }, + "kotlinx_serialization_core_jvm": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_jar", + "attributes": { + "sha256": "29c821a8d4e25cbfe4f2ce96cdd4526f61f8f4e69a135f9612a34a81d93b65f1", + "urls": [ + "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-core-jvm/1.6.3/kotlinx-serialization-core-jvm-1.6.3.jar" + ] + } + }, + "kotlinx_serialization_json": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_jar", + "attributes": { + "sha256": "8c0016890a79ab5980dd520a5ab1a6738023c29aa3b6437c482e0e5fdc06dab1", + "urls": [ + "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json/1.6.3/kotlinx-serialization-json-1.6.3.jar" + ] + } + }, + "kotlinx_serialization_json_jvm": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_jar", + "attributes": { + "sha256": "d3234179bcff1886d53d67c11eca47f7f3cf7b63c349d16965f6db51b7f3dd9a", + "urls": [ + "https://repo1.maven.org/maven2/org/jetbrains/kotlinx/kotlinx-serialization-json-jvm/1.6.3/kotlinx-serialization-json-jvm-1.6.3.jar" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin~", + "bazel_tools", + "bazel_tools" + ] + ] + } + }, + "@@rules_python~//python/extensions:config.bzl%config": { + "general": { + "bzlTransitiveDigest": "JEJFuaDD7VQV641t2xy3kzc2EBtIxzHUAHwUerkS5RM=", + "usagesDigest": "n1K7/nlkMpUURDkWJsNS8Nr0tmxSDkFevx8GXKG82eA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "rules_python_internal": { + "bzlFile": "@@rules_python~//python/private:internal_config_repo.bzl", + "ruleClassName": "internal_config_repo", + "attributes": { + "transition_setting_generators": {}, + "transition_settings": [] + } + }, + "pypi__build": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e2/03/f3c8ba0a6b6e30d7d18c40faab90807c9bb5e9a1e3b2fe2008af624a9c97/build-1.2.1-py3-none-any.whl", + "sha256": "75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__click": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/00/2e/d53fa4befbf2cfa713304affc7ca780ce4fc1fd8710527771b58311a3229/click-8.1.7-py3-none-any.whl", + "sha256": "ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__colorama": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", + "sha256": "4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__importlib_metadata": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/2d/0a/679461c511447ffaf176567d5c496d1de27cbe34a87df6677d7171b2fbd4/importlib_metadata-7.1.0-py3-none-any.whl", + "sha256": "30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__installer": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/e5/ca/1172b6638d52f2d6caa2dd262ec4c811ba59eee96d54a7701930726bce18/installer-0.7.0-py3-none-any.whl", + "sha256": "05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__more_itertools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/50/e2/8e10e465ee3987bb7c9ab69efb91d867d93959095f4807db102d07995d94/more_itertools-10.2.0-py3-none-any.whl", + "sha256": "686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__packaging": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/49/df/1fceb2f8900f8639e278b056416d49134fb8d84c5942ffaa01ad34782422/packaging-24.0-py3-none-any.whl", + "sha256": "2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pep517": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/25/6e/ca4a5434eb0e502210f591b97537d322546e4833dcb4d470a48c375c5540/pep517-0.13.1-py3-none-any.whl", + "sha256": "31b206f67165b3536dd577c5c3f1518e8fbaf38cbc57efff8369a392feff1721", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/8a/6a/19e9fe04fca059ccf770861c7d5721ab4c2aebc539889e97c7977528a53b/pip-24.0-py3-none-any.whl", + "sha256": "ba0d021a166865d2265246961bec0152ff124de910c5cc39f1156ce3fa7c69dc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pip_tools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/0d/dc/38f4ce065e92c66f058ea7a368a9c5de4e702272b479c0992059f7693941/pip_tools-7.4.1-py3-none-any.whl", + "sha256": "4c690e5fbae2f21e87843e89c26191f0d9454f362d8acdbd695716493ec8b3a9", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__pyproject_hooks": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/ae/f3/431b9d5fe7d14af7a32340792ef43b8a714e7726f1d7b69cc4e8e7a3f1d7/pyproject_hooks-1.1.0-py3-none-any.whl", + "sha256": "7ceeefe9aec63a1064c18d939bdc3adf2d8aa1988a510afec15151578b232aa2", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__setuptools": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/90/99/158ad0609729111163fc1f674a5a42f2605371a4cf036d0441070e2f7455/setuptools-78.1.1-py3-none-any.whl", + "sha256": "c3a9c4211ff4c309edb8b8c4f1cbfa7ae324c4ba9f91ff254e3d305b9fd54561", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__tomli": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/97/75/10a9ebee3fd790d20926a90a2547f0bf78f371b2f13aa822c759680ca7b9/tomli-2.0.1-py3-none-any.whl", + "sha256": "939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__wheel": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/7d/cd/d7460c9a869b16c3dd4e1e403cce337df165368c71d6af229a74699622ce/wheel-0.43.0-py3-none-any.whl", + "sha256": "55c570405f142630c6b9f72fe09d9b67cf1477fcf543ae5b8dcb1f5b7377da81", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + }, + "pypi__zipp": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "url": "https://files.pythonhosted.org/packages/da/55/a03fd7240714916507e1fcf7ae355bd9d9ed2e6db492595f1a67f61681be/zipp-3.18.2-py3-none-any.whl", + "sha256": "dce197b859eb796242b0622af1b8beb0a722d52aa2f57133ead08edd5bf5374e", + "type": "zip", + "build_file_content": "package(default_visibility = [\"//visibility:public\"])\n\nload(\"@rules_python//python:py_library.bzl\", \"py_library\")\n\npy_library(\n name = \"lib\",\n srcs = glob([\"**/*.py\"]),\n data = glob([\"**/*\"], exclude=[\n # These entries include those put into user-installed dependencies by\n # data_exclude to avoid non-determinism.\n \"**/*.py\",\n \"**/*.pyc\",\n \"**/*.pyc.*\", # During pyc creation, temp files named *.pyc.NNN are created\n \"**/*.dist-info/RECORD\",\n \"BUILD\",\n \"WORKSPACE\",\n ]),\n # This makes this directory a top-level in the python import\n # search path for anything that depends on this.\n imports = [\".\"],\n)\n" + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python~", + "pypi__build", + "rules_python~~config~pypi__build" + ], + [ + "rules_python~", + "pypi__click", + "rules_python~~config~pypi__click" + ], + [ + "rules_python~", + "pypi__colorama", + "rules_python~~config~pypi__colorama" + ], + [ + "rules_python~", + "pypi__importlib_metadata", + "rules_python~~config~pypi__importlib_metadata" + ], + [ + "rules_python~", + "pypi__installer", + "rules_python~~config~pypi__installer" + ], + [ + "rules_python~", + "pypi__more_itertools", + "rules_python~~config~pypi__more_itertools" + ], + [ + "rules_python~", + "pypi__packaging", + "rules_python~~config~pypi__packaging" + ], + [ + "rules_python~", + "pypi__pep517", + "rules_python~~config~pypi__pep517" + ], + [ + "rules_python~", + "pypi__pip", + "rules_python~~config~pypi__pip" + ], + [ + "rules_python~", + "pypi__pip_tools", + "rules_python~~config~pypi__pip_tools" + ], + [ + "rules_python~", + "pypi__pyproject_hooks", + "rules_python~~config~pypi__pyproject_hooks" + ], + [ + "rules_python~", + "pypi__setuptools", + "rules_python~~config~pypi__setuptools" + ], + [ + "rules_python~", + "pypi__tomli", + "rules_python~~config~pypi__tomli" + ], + [ + "rules_python~", + "pypi__wheel", + "rules_python~~config~pypi__wheel" + ], + [ + "rules_python~", + "pypi__zipp", + "rules_python~~config~pypi__zipp" + ] + ] + } + }, + "@@rules_python~//python/uv:uv.bzl%uv": { + "general": { + "bzlTransitiveDigest": "K6zoA/u57+d21o50BApale3BrKDIB1vWM6q455k772s=", + "usagesDigest": "PAumpjVacknBm1rrOwOjhYqel5KPCDgZNHNmdVc9/WY=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "uv": { + "bzlFile": "@@rules_python~//python/uv/private:uv_toolchains_repo.bzl", + "ruleClassName": "uv_toolchains_repo", + "attributes": { + "toolchain_type": "'@@rules_python~//python/uv:uv_toolchain_type'", + "toolchain_names": [ + "none" + ], + "toolchain_implementations": { + "none": "'@@rules_python~//python:none'" + }, + "toolchain_compatible_with": { + "none": [ + "@platforms//:incompatible" + ] + }, + "toolchain_target_settings": {} + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_python~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_python~", + "platforms", + "platforms" + ] + ] + } + }, + "@@rules_swift~//swift:extensions.bzl%non_module_deps": { + "general": { + "bzlTransitiveDigest": "vhXfCi07IAmWf5hLHcxh5xCtTwvEGrmMkOavEMD5pzs=", + "usagesDigest": "/dVEtCLqIAaLXjfRXsJupTS9pTgmYVN/tFdJlGgE5NA=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_apple_swift_protobuf": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-protobuf/archive/1.20.2.tar.gz" + ], + "sha256": "3fb50bd4d293337f202d917b6ada22f9548a0a0aed9d9a4d791e6fbd8a246ebb", + "strip_prefix": "swift-protobuf-1.20.2/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_protobuf/BUILD.overlay" + } + }, + "com_github_grpc_grpc_swift": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/grpc/grpc-swift/archive/1.16.0.tar.gz" + ], + "sha256": "58b60431d0064969f9679411264b82e40a217ae6bd34e17096d92cc4e47556a5", + "strip_prefix": "grpc-swift-1.16.0/", + "build_file": "@@rules_swift~//third_party:com_github_grpc_grpc_swift/BUILD.overlay" + } + }, + "com_github_apple_swift_docc_symbolkit": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-docc-symbolkit/archive/refs/tags/swift-5.10-RELEASE.tar.gz" + ], + "sha256": "de1d4b6940468ddb53b89df7aa1a81323b9712775b0e33e8254fa0f6f7469a97", + "strip_prefix": "swift-docc-symbolkit-swift-5.10-RELEASE", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_docc_symbolkit/BUILD.overlay" + } + }, + "com_github_apple_swift_nio": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio/archive/2.42.0.tar.gz" + ], + "sha256": "e3304bc3fb53aea74a3e54bd005ede11f6dc357117d9b1db642d03aea87194a0", + "strip_prefix": "swift-nio-2.42.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_http2": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-http2/archive/1.26.0.tar.gz" + ], + "sha256": "f0edfc9d6a7be1d587e5b403f2d04264bdfae59aac1d74f7d974a9022c6d2b25", + "strip_prefix": "swift-nio-http2-1.26.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_http2/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_transport_services": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-transport-services/archive/1.15.0.tar.gz" + ], + "sha256": "f3498dafa633751a52b9b7f741f7ac30c42bcbeb3b9edca6d447e0da8e693262", + "strip_prefix": "swift-nio-transport-services-1.15.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_transport_services/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_extras": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-extras/archive/1.4.0.tar.gz" + ], + "sha256": "4684b52951d9d9937bb3e8ccd6b5daedd777021ef2519ea2f18c4c922843b52b", + "strip_prefix": "swift-nio-extras-1.4.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_extras/BUILD.overlay" + } + }, + "com_github_apple_swift_log": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-log/archive/1.4.4.tar.gz" + ], + "sha256": "48fe66426c784c0c20031f15dc17faf9f4c9037c192bfac2f643f65cb2321ba0", + "strip_prefix": "swift-log-1.4.4/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_log/BUILD.overlay" + } + }, + "com_github_apple_swift_nio_ssl": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-nio-ssl/archive/2.23.0.tar.gz" + ], + "sha256": "4787c63f61dd04d99e498adc3d1a628193387e41efddf8de19b8db04544d016d", + "strip_prefix": "swift-nio-ssl-2.23.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_nio_ssl/BUILD.overlay" + } + }, + "com_github_apple_swift_collections": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-collections/archive/1.0.4.tar.gz" + ], + "sha256": "d9e4c8a91c60fb9c92a04caccbb10ded42f4cb47b26a212bc6b39cc390a4b096", + "strip_prefix": "swift-collections-1.0.4/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_collections/BUILD.overlay" + } + }, + "com_github_apple_swift_atomics": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "urls": [ + "https://github.com/apple/swift-atomics/archive/1.1.0.tar.gz" + ], + "sha256": "1bee7f469f7e8dc49f11cfa4da07182fbc79eab000ec2c17bfdce468c5d276fb", + "strip_prefix": "swift-atomics-1.1.0/", + "build_file": "@@rules_swift~//third_party:com_github_apple_swift_atomics/BUILD.overlay" + } + }, + "build_bazel_rules_swift_index_import": { + "bzlFile": "@@bazel_tools//tools/build_defs/repo:http.bzl", + "ruleClassName": "http_archive", + "attributes": { + "build_file": "@@rules_swift~//third_party:build_bazel_rules_swift_index_import/BUILD.overlay", + "canonical_id": "index-import-5.8", + "urls": [ + "https://github.com/MobileNativeFoundation/index-import/releases/download/5.8.0.1/index-import.tar.gz" + ], + "sha256": "28c1ffa39d99e74ed70623899b207b41f79214c498c603915aef55972a851a15" + } + }, + "build_bazel_rules_swift_local_config": { + "bzlFile": "@@rules_swift~//swift/internal:swift_autoconfiguration.bzl", + "ruleClassName": "swift_autoconfiguration", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_swift~", + "bazel_tools", + "bazel_tools" + ], + [ + "rules_swift~", + "build_bazel_rules_swift", + "rules_swift~" + ] + ] + } + } + } +}
diff --git a/README.md b/README.md index 11a33b2..1542d71 100644 --- a/README.md +++ b/README.md
@@ -88,11 +88,7 @@ It is the simplest and safest configuration, where only one Gerrit primary at a time serves the incoming requests. -<<<<<<< HEAD In case of failure of the active primary, the traffic is forwarded to the backup. -======= -In case of failure of the primary, the traffic is forwarded to the backup. ->>>>>>> stable-3.12 Assuming a load-balancing implemented using [HAProxy](http://www.haproxy.org/) associated with the domain name `gerrit.mycompany.com`, exposing Gerrit cluster nodes
diff --git a/external_plugin_deps.MODULE.bazel b/external_plugin_deps.MODULE.bazel new file mode 100644 index 0000000..4f6a9d5 --- /dev/null +++ b/external_plugin_deps.MODULE.bazel
@@ -0,0 +1,18 @@ +# Module-file fragment for plugin modules that bring their own bzlmod deps. +# +# This file is included from the root MODULE.bazel via include(). +# It wires in plugin module and exposes any repos they create (e.g. Maven repos) +# via use_repo(). +# +# NOTE: This file must be self-contained: bindings do not cross include() +# boundaries, so it re-declares the rules_jvm_external extension handle. + +maven = use_extension("@rules_jvm_external//:extensions.bzl", "maven") + +bazel_dep(name = "high-availability") +local_path_override( + module_name = "high-availability", + path = "plugins/high-availability", +) + +use_repo(maven, "high-availability_plugin_deps")
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl deleted file mode 100644 index 02c1df5..0000000 --- a/external_plugin_deps.bzl +++ /dev/null
@@ -1,32 +0,0 @@ -load("//tools/bzl:maven_jar.bzl", "maven_jar") - -def external_plugin_deps(): - maven_jar( - name = "wiremock", - artifact = "com.github.tomakehurst:wiremock-standalone:2.27.2", - sha1 = "327647a19b2319af2526b9c33a5733a2241723e0", - ) - - maven_jar( - name = "jgroups", - artifact = "org.jgroups:jgroups:5.2.16.Final", - sha1 = "d2dceef4c6917239350f2a604b4116745a1e84ae", - ) - - maven_jar( - name = "jgroups-kubernetes", - artifact = "org.jgroups.kubernetes:jgroups-kubernetes:2.0.1.Final", - sha1 = "4e259af98c3b1fbdc8ebaebe42496ef560dfc30f", - ) - - maven_jar( - name = "global-refdb", - artifact = "com.gerritforge:global-refdb:3.7.4", - sha1 = "a5f3fcdbc04b7e98c52ecd50d2a56424e60b0575", - ) - - maven_jar( - name = "failsafe", - artifact = "dev.failsafe:failsafe:3.3.2", - sha1 = "738a986f1f0e4b6c6a49d351dddc772d1378c5a8", - )
diff --git a/high-availability_plugin_deps.lock.json b/high-availability_plugin_deps.lock.json new file mode 100644 index 0000000..e1ba92e --- /dev/null +++ b/high-availability_plugin_deps.lock.json
@@ -0,0 +1,389 @@ +{ + "__AUTOGENERATED_FILE_DO_NOT_MODIFY_THIS_FILE_MANUALLY": "THERE_IS_NO_DATA_ONLY_ZUUL", + "__INPUT_ARTIFACTS_HASH": { + "com.github.tomakehurst:wiremock-standalone": -1985332777, + "dev.failsafe:failsafe": -1314791755, + "org.jboss.byteman:byteman": -1113337, + "org.jboss.byteman:byteman-bmunit": -1229412743, + "org.jboss.byteman:byteman-install": 1382879481, + "org.jboss.byteman:byteman-submit": 313967824, + "org.jgroups.kubernetes:jgroups-kubernetes": 854008106, + "org.jgroups:jgroups": 367944164, + "repositories": -1949687017 + }, + "__RESOLVED_ARTIFACTS_HASH": { + "com.github.tomakehurst:wiremock-standalone": -1133549878, + "com.github.tomakehurst:wiremock-standalone:jar:sources": 669893243, + "dev.failsafe:failsafe": -253921025, + "dev.failsafe:failsafe:jar:sources": 890605467, + "org.jgroups.kubernetes:jgroups-kubernetes": -1611131319, + "org.jgroups.kubernetes:jgroups-kubernetes:jar:sources": 83117823, + "org.jgroups:jgroups": -527672877, + "org.jgroups:jgroups:jar:sources": -1921445947 + }, + "artifacts": { + "com.github.tomakehurst:wiremock-standalone": { + "shasums": { + "jar": "00b14f9d6399a9bba96139f84354f981d9fd1c3f4cf6938d36391f292f76382b", + "sources": "5a532b966f344c1d154262674403586e0e0a0de8e4b7bbfe9b4813875b62ab9d" + }, + "version": "2.27.2" + }, + "dev.failsafe:failsafe": { + "shasums": { + "jar": "2c5dc879a6dac7ea3a7b29d795e27bd49b8e7908b05c2f3e56053c19d79850f5", + "sources": "06ec410527503e74734d656e8cc1934aa6fef44ddd89dd4df13cb11b2bd83a9b" + }, + "version": "3.3.2" + }, + "org.jgroups.kubernetes:jgroups-kubernetes": { + "shasums": { + "jar": "71591cf5b06785d1d8d64a23d6d334fcea8b6431d5cd4936b05690ca844681f6", + "sources": "0d04edc74aed4c4e6366f193201bccb057f163f8e379b67f7130438912610d85" + }, + "version": "2.0.1.Final" + }, + "org.jgroups:jgroups": { + "shasums": { + "jar": "4e751b024d62b31a99cd73c7521db8661f83e63bfad5fc6746824b05ee2dc273", + "sources": "79571486d76779078f0ef4aea2f3e98b3dc36c9dc83ec5742e2526420d96ddc6" + }, + "version": "5.2.16.Final" + } + }, + "dependencies": { + "org.jgroups.kubernetes:jgroups-kubernetes": [ + "org.jgroups:jgroups" + ] + }, + "packages": { + "com.github.tomakehurst:wiremock-standalone": [ + "com.flipkart.zjsonpatch", + "com.github.tomakehurst.wiremock", + "com.github.tomakehurst.wiremock.admin", + "com.github.tomakehurst.wiremock.admin.model", + "com.github.tomakehurst.wiremock.admin.tasks", + "com.github.tomakehurst.wiremock.client", + "com.github.tomakehurst.wiremock.common", + "com.github.tomakehurst.wiremock.common.ssl", + "com.github.tomakehurst.wiremock.common.xml", + "com.github.tomakehurst.wiremock.core", + "com.github.tomakehurst.wiremock.extension", + "com.github.tomakehurst.wiremock.extension.requestfilter", + "com.github.tomakehurst.wiremock.extension.responsetemplating", + "com.github.tomakehurst.wiremock.extension.responsetemplating.helpers", + "com.github.tomakehurst.wiremock.global", + "com.github.tomakehurst.wiremock.http", + "com.github.tomakehurst.wiremock.http.multipart", + "com.github.tomakehurst.wiremock.http.ssl", + "com.github.tomakehurst.wiremock.http.trafficlistener", + "com.github.tomakehurst.wiremock.jetty9", + "com.github.tomakehurst.wiremock.jetty92", + "com.github.tomakehurst.wiremock.junit", + "com.github.tomakehurst.wiremock.matching", + "com.github.tomakehurst.wiremock.recording", + "com.github.tomakehurst.wiremock.security", + "com.github.tomakehurst.wiremock.servlet", + "com.github.tomakehurst.wiremock.standalone", + "com.github.tomakehurst.wiremock.stubbing", + "com.github.tomakehurst.wiremock.verification", + "com.github.tomakehurst.wiremock.verification.diff", + "com.github.tomakehurst.wiremock.verification.notmatched", + "joptsimple", + "joptsimple.internal", + "joptsimple.util", + "org.slf4j", + "org.slf4j.event", + "org.slf4j.helpers", + "org.slf4j.spi", + "wiremock.com.fasterxml.jackson.annotation", + "wiremock.com.fasterxml.jackson.core", + "wiremock.com.fasterxml.jackson.core.async", + "wiremock.com.fasterxml.jackson.core.base", + "wiremock.com.fasterxml.jackson.core.exc", + "wiremock.com.fasterxml.jackson.core.filter", + "wiremock.com.fasterxml.jackson.core.format", + "wiremock.com.fasterxml.jackson.core.io", + "wiremock.com.fasterxml.jackson.core.json", + "wiremock.com.fasterxml.jackson.core.json.async", + "wiremock.com.fasterxml.jackson.core.sym", + "wiremock.com.fasterxml.jackson.core.type", + "wiremock.com.fasterxml.jackson.core.util", + "wiremock.com.fasterxml.jackson.databind", + "wiremock.com.fasterxml.jackson.databind.annotation", + "wiremock.com.fasterxml.jackson.databind.cfg", + "wiremock.com.fasterxml.jackson.databind.deser", + "wiremock.com.fasterxml.jackson.databind.deser.impl", + "wiremock.com.fasterxml.jackson.databind.deser.std", + "wiremock.com.fasterxml.jackson.databind.exc", + "wiremock.com.fasterxml.jackson.databind.ext", + "wiremock.com.fasterxml.jackson.databind.introspect", + "wiremock.com.fasterxml.jackson.databind.json", + "wiremock.com.fasterxml.jackson.databind.jsonFormatVisitors", + "wiremock.com.fasterxml.jackson.databind.jsonschema", + "wiremock.com.fasterxml.jackson.databind.jsontype", + "wiremock.com.fasterxml.jackson.databind.jsontype.impl", + "wiremock.com.fasterxml.jackson.databind.module", + "wiremock.com.fasterxml.jackson.databind.node", + "wiremock.com.fasterxml.jackson.databind.ser", + "wiremock.com.fasterxml.jackson.databind.ser.impl", + "wiremock.com.fasterxml.jackson.databind.ser.std", + "wiremock.com.fasterxml.jackson.databind.type", + "wiremock.com.fasterxml.jackson.databind.util", + "wiremock.com.github.jknack.handlebars", + "wiremock.com.github.jknack.handlebars.cache", + "wiremock.com.github.jknack.handlebars.context", + "wiremock.com.github.jknack.handlebars.helper", + "wiremock.com.github.jknack.handlebars.internal", + "wiremock.com.github.jknack.handlebars.internal.js", + "wiremock.com.github.jknack.handlebars.internal.path", + "wiremock.com.github.jknack.handlebars.io", + "wiremock.com.github.jknack.handlebars.js", + "wiremock.com.google.common.annotations", + "wiremock.com.google.common.base", + "wiremock.com.google.common.base.internal", + "wiremock.com.google.common.cache", + "wiremock.com.google.common.collect", + "wiremock.com.google.common.escape", + "wiremock.com.google.common.eventbus", + "wiremock.com.google.common.graph", + "wiremock.com.google.common.hash", + "wiremock.com.google.common.html", + "wiremock.com.google.common.io", + "wiremock.com.google.common.math", + "wiremock.com.google.common.net", + "wiremock.com.google.common.primitives", + "wiremock.com.google.common.reflect", + "wiremock.com.google.common.util.concurrent", + "wiremock.com.google.common.xml", + "wiremock.com.google.thirdparty.publicsuffix", + "wiremock.com.jayway.jsonpath", + "wiremock.com.jayway.jsonpath.internal", + "wiremock.com.jayway.jsonpath.internal.filter", + "wiremock.com.jayway.jsonpath.internal.function", + "wiremock.com.jayway.jsonpath.internal.function.json", + "wiremock.com.jayway.jsonpath.internal.function.latebinding", + "wiremock.com.jayway.jsonpath.internal.function.numeric", + "wiremock.com.jayway.jsonpath.internal.function.text", + "wiremock.com.jayway.jsonpath.internal.path", + "wiremock.com.jayway.jsonpath.spi.cache", + "wiremock.com.jayway.jsonpath.spi.json", + "wiremock.com.jayway.jsonpath.spi.mapper", + "wiremock.javax.servlet", + "wiremock.javax.servlet.annotation", + "wiremock.javax.servlet.descriptor", + "wiremock.javax.servlet.http", + "wiremock.net.minidev.asm", + "wiremock.net.minidev.asm.ex", + "wiremock.net.minidev.json", + "wiremock.net.minidev.json.annotate", + "wiremock.net.minidev.json.parser", + "wiremock.net.minidev.json.reader", + "wiremock.net.minidev.json.writer", + "wiremock.org.antlr.v4.runtime", + "wiremock.org.antlr.v4.runtime.atn", + "wiremock.org.antlr.v4.runtime.dfa", + "wiremock.org.antlr.v4.runtime.misc", + "wiremock.org.antlr.v4.runtime.tree", + "wiremock.org.antlr.v4.runtime.tree.pattern", + "wiremock.org.antlr.v4.runtime.tree.xpath", + "wiremock.org.apache.commons.codec", + "wiremock.org.apache.commons.codec.binary", + "wiremock.org.apache.commons.codec.cli", + "wiremock.org.apache.commons.codec.digest", + "wiremock.org.apache.commons.codec.language", + "wiremock.org.apache.commons.codec.language.bm", + "wiremock.org.apache.commons.codec.net", + "wiremock.org.apache.commons.fileupload", + "wiremock.org.apache.commons.fileupload.disk", + "wiremock.org.apache.commons.fileupload.portlet", + "wiremock.org.apache.commons.fileupload.servlet", + "wiremock.org.apache.commons.fileupload.util", + "wiremock.org.apache.commons.fileupload.util.mime", + "wiremock.org.apache.commons.io", + "wiremock.org.apache.commons.io.comparator", + "wiremock.org.apache.commons.io.filefilter", + "wiremock.org.apache.commons.io.input", + "wiremock.org.apache.commons.io.monitor", + "wiremock.org.apache.commons.io.output", + "wiremock.org.apache.commons.lang3", + "wiremock.org.apache.commons.lang3.arch", + "wiremock.org.apache.commons.lang3.builder", + "wiremock.org.apache.commons.lang3.concurrent", + "wiremock.org.apache.commons.lang3.event", + "wiremock.org.apache.commons.lang3.exception", + "wiremock.org.apache.commons.lang3.math", + "wiremock.org.apache.commons.lang3.mutable", + "wiremock.org.apache.commons.lang3.reflect", + "wiremock.org.apache.commons.lang3.text", + "wiremock.org.apache.commons.lang3.text.translate", + "wiremock.org.apache.commons.lang3.time", + "wiremock.org.apache.commons.lang3.tuple", + "wiremock.org.apache.commons.logging", + "wiremock.org.apache.commons.logging.impl", + "wiremock.org.apache.http", + "wiremock.org.apache.http.annotation", + "wiremock.org.apache.http.auth", + "wiremock.org.apache.http.auth.params", + "wiremock.org.apache.http.client", + "wiremock.org.apache.http.client.config", + "wiremock.org.apache.http.client.entity", + "wiremock.org.apache.http.client.methods", + "wiremock.org.apache.http.client.params", + "wiremock.org.apache.http.client.protocol", + "wiremock.org.apache.http.client.utils", + "wiremock.org.apache.http.concurrent", + "wiremock.org.apache.http.config", + "wiremock.org.apache.http.conn", + "wiremock.org.apache.http.conn.params", + "wiremock.org.apache.http.conn.routing", + "wiremock.org.apache.http.conn.scheme", + "wiremock.org.apache.http.conn.socket", + "wiremock.org.apache.http.conn.ssl", + "wiremock.org.apache.http.conn.util", + "wiremock.org.apache.http.cookie", + "wiremock.org.apache.http.cookie.params", + "wiremock.org.apache.http.entity", + "wiremock.org.apache.http.impl", + "wiremock.org.apache.http.impl.auth", + "wiremock.org.apache.http.impl.bootstrap", + "wiremock.org.apache.http.impl.client", + "wiremock.org.apache.http.impl.conn", + "wiremock.org.apache.http.impl.conn.tsccm", + "wiremock.org.apache.http.impl.cookie", + "wiremock.org.apache.http.impl.entity", + "wiremock.org.apache.http.impl.execchain", + "wiremock.org.apache.http.impl.io", + "wiremock.org.apache.http.impl.pool", + "wiremock.org.apache.http.io", + "wiremock.org.apache.http.message", + "wiremock.org.apache.http.params", + "wiremock.org.apache.http.pool", + "wiremock.org.apache.http.protocol", + "wiremock.org.apache.http.ssl", + "wiremock.org.apache.http.util", + "wiremock.org.custommonkey.xmlunit", + "wiremock.org.custommonkey.xmlunit.examples", + "wiremock.org.custommonkey.xmlunit.exceptions", + "wiremock.org.custommonkey.xmlunit.jaxp13", + "wiremock.org.custommonkey.xmlunit.util", + "wiremock.org.eclipse.jetty.client", + "wiremock.org.eclipse.jetty.client.api", + "wiremock.org.eclipse.jetty.client.http", + "wiremock.org.eclipse.jetty.client.util", + "wiremock.org.eclipse.jetty.continuation", + "wiremock.org.eclipse.jetty.http", + "wiremock.org.eclipse.jetty.http.pathmap", + "wiremock.org.eclipse.jetty.io", + "wiremock.org.eclipse.jetty.io.ssl", + "wiremock.org.eclipse.jetty.proxy", + "wiremock.org.eclipse.jetty.security", + "wiremock.org.eclipse.jetty.security.authentication", + "wiremock.org.eclipse.jetty.server", + "wiremock.org.eclipse.jetty.server.handler", + "wiremock.org.eclipse.jetty.server.handler.jmx", + "wiremock.org.eclipse.jetty.server.jmx", + "wiremock.org.eclipse.jetty.server.nio", + "wiremock.org.eclipse.jetty.server.session", + "wiremock.org.eclipse.jetty.server.session.jmx", + "wiremock.org.eclipse.jetty.servlet", + "wiremock.org.eclipse.jetty.servlet.jmx", + "wiremock.org.eclipse.jetty.servlet.listener", + "wiremock.org.eclipse.jetty.servlets", + "wiremock.org.eclipse.jetty.servlets.gzip", + "wiremock.org.eclipse.jetty.util", + "wiremock.org.eclipse.jetty.util.annotation", + "wiremock.org.eclipse.jetty.util.component", + "wiremock.org.eclipse.jetty.util.log", + "wiremock.org.eclipse.jetty.util.preventers", + "wiremock.org.eclipse.jetty.util.resource", + "wiremock.org.eclipse.jetty.util.security", + "wiremock.org.eclipse.jetty.util.ssl", + "wiremock.org.eclipse.jetty.util.statistic", + "wiremock.org.eclipse.jetty.util.thread", + "wiremock.org.eclipse.jetty.webapp", + "wiremock.org.eclipse.jetty.xml", + "wiremock.org.objectweb.asm", + "wiremock.org.objectweb.asm.signature", + "wiremock.org.xmlunit", + "wiremock.org.xmlunit.builder", + "wiremock.org.xmlunit.diff", + "wiremock.org.xmlunit.input", + "wiremock.org.xmlunit.placeholder", + "wiremock.org.xmlunit.transform", + "wiremock.org.xmlunit.util", + "wiremock.org.xmlunit.validation", + "wiremock.org.xmlunit.xpath" + ], + "dev.failsafe:failsafe": [ + "dev.failsafe", + "dev.failsafe.event", + "dev.failsafe.function", + "dev.failsafe.internal", + "dev.failsafe.internal.util", + "dev.failsafe.spi" + ], + "org.jgroups.kubernetes:jgroups-kubernetes": [ + "mjson", + "org.jgroups.protocols.kubernetes", + "org.jgroups.protocols.kubernetes.pem", + "org.jgroups.protocols.kubernetes.stream" + ], + "org.jgroups:jgroups": [ + "org.jgroups", + "org.jgroups.annotations", + "org.jgroups.auth", + "org.jgroups.blocks", + "org.jgroups.blocks.atomic", + "org.jgroups.blocks.cs", + "org.jgroups.blocks.locking", + "org.jgroups.client", + "org.jgroups.conf", + "org.jgroups.demos", + "org.jgroups.fork", + "org.jgroups.jmx", + "org.jgroups.logging", + "org.jgroups.nio", + "org.jgroups.protocols", + "org.jgroups.protocols.dns", + "org.jgroups.protocols.pbcast", + "org.jgroups.protocols.relay", + "org.jgroups.protocols.relay.config", + "org.jgroups.stack", + "org.jgroups.tests", + "org.jgroups.tests.perf", + "org.jgroups.tests.rt", + "org.jgroups.tests.rt.transports", + "org.jgroups.util" + ] + }, + "repositories": { + "https://repo1.maven.org/maven2/": [ + "com.github.tomakehurst:wiremock-standalone", + "com.github.tomakehurst:wiremock-standalone:jar:sources", + "dev.failsafe:failsafe", + "dev.failsafe:failsafe:jar:sources", + "org.jgroups.kubernetes:jgroups-kubernetes", + "org.jgroups.kubernetes:jgroups-kubernetes:jar:sources", + "org.jgroups:jgroups", + "org.jgroups:jgroups:jar:sources" + ] + }, + "services": { + "com.github.tomakehurst:wiremock-standalone": { + "wiremock.com.fasterxml.jackson.core.JsonFactory": [ + "wiremock.com.fasterxml.jackson.core.JsonFactory" + ], + "wiremock.com.fasterxml.jackson.core.ObjectCodec": [ + "wiremock.com.fasterxml.jackson.databind.ObjectMapper" + ], + "wiremock.org.xmlunit.placeholder.PlaceholderHandler": [ + "wiremock.org.xmlunit.placeholder.IgnorePlaceholderHandler", + "wiremock.org.xmlunit.placeholder.IsDateTimePlaceholderHandler", + "wiremock.org.xmlunit.placeholder.IsNumberPlaceholderHandler", + "wiremock.org.xmlunit.placeholder.MatchesRegexPlaceholderHandler" + ] + } + }, + "version": "3" +}
diff --git a/high-availability_third_party_runtime_jars.allowlist.txt b/high-availability_third_party_runtime_jars.allowlist.txt new file mode 100644 index 0000000..71d9557 --- /dev/null +++ b/high-availability_third_party_runtime_jars.allowlist.txt
@@ -0,0 +1,3 @@ +failsafe +jgroups +jgroups-kubernetes
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java index 921d750..e0c6061 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/HttpModule.java
@@ -33,7 +33,9 @@ @Override protected void configureServlets() { - install(new RestForwarderServletModule(config)); + if (config.main().transport() == Configuration.Transport.HTTP) { + install(new RestForwarderServletModule(config)); + } if (config.healthCheck().enabled()) { install(new HealthServletModule()); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/EventType.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/EventType.java new file mode 100644 index 0000000..8cfea0c --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/EventType.java
@@ -0,0 +1,29 @@ +// Copyright (C) 2025 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; + +public enum EventType { + CACHE_EVICTION, + EVENT_SENT, + INDEX_ACCOUNT_UPDATE, + INDEX_CHANGE_DELETION, + INDEX_CHANGE_DELETION_ALL_OF_PROJECT, + INDEX_CHANGE_UPDATE, + INDEX_CHANGE_UPDATE_BATCH, + INDEX_GROUP_UPDATE, + INDEX_PROJECT_UPDATE, + PROJECT_LIST_ADDITION, + PROJECT_LIST_DELETION +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java index b2f8141..bee5f2e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexAccountHandler.java
@@ -38,8 +38,13 @@ @Override protected CompletableFuture<Boolean> doIndex(Account.Id id, Optional<IndexEvent> indexEvent) { - indexer.index(id); - log.atFine().log("Account %s successfully indexed", id); + try { + indexer.index(id); + log.atFine().log("Account %s successfully indexed", id); + } catch (RuntimeException e) { + log.atFine().log("Account %s failed to be indexed", id); + throw e; + } return CompletableFuture.completedFuture(true); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java index f0bc802..b93ff18 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexChangeHandler.java
@@ -68,7 +68,8 @@ () -> { try (ManualRequestContext ctx = oneOffCtx.open()) { Context.setForwardedEvent(true); - return indexOnce(id, indexEvent); + boolean result = indexOnce(id, indexEvent); + return result; } }); } @@ -122,11 +123,22 @@ throws IOException { if (ALL_CHANGES_FOR_PROJECT.equals(extractChangeId(id))) { Project.NameKey projectName = parseProject(id); - indexer.deleteAllForProject(projectName); - log.atFine().log("All %s changes successfully deleted from index", projectName.get()); + try { + indexer.deleteAllForProject(projectName); + log.atFine().log("All %s changes successfully deleted from index", projectName.get()); + } catch (RuntimeException e) { + log.atFine().log( + "An error occured during deletion of all %s changes from index", projectName.get()); + throw e; + } } else { - indexer.delete(parseChangeId(id)); - log.atFine().log("Change %s successfully deleted from index", id); + try { + indexer.delete(parseChangeId(id)); + log.atFine().log("Change %s successfully deleted from index", id); + } catch (RuntimeException e) { + log.atFine().log("Change %s could not be deleted from index", id); + throw e; + } } return CompletableFuture.completedFuture(true); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java index 99ac369..01f8680 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwardedIndexGroupHandler.java
@@ -39,8 +39,13 @@ @Override protected CompletableFuture<Boolean> doIndex( AccountGroup.UUID uuid, Optional<IndexEvent> indexEvent) { - indexer.index(uuid); - log.atFine().log("Group %s successfully indexed", uuid); + try { + indexer.index(uuid); + log.atFine().log("Group %s successfully indexed", uuid); + } catch (RuntimeException e) { + log.atFine().log("Group %s could not be indexed", uuid); + throw e; + } return CompletableFuture.completedFuture(true); }
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 3854ee3..3bf08b4 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
@@ -21,6 +21,12 @@ /** Forward indexing, stream events and cache evictions to the other primary */ public interface Forwarder { + public record Result(EventType type, boolean result, boolean isRecoverable) { + public Result(EventType task, boolean result) { + this(task, result, true); + } + } + /** * Forward an account indexing event to the other primary. * @@ -29,7 +35,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent); + CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent); /** * Forward a change indexing event to the other primary. @@ -40,7 +46,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> indexChange(String projectName, int changeId, IndexEvent indexEvent); + CompletableFuture<Result> indexChange(String projectName, int changeId, IndexEvent indexEvent); /** * Forward a change indexing event to the other primary using batch index endpoint. @@ -51,7 +57,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> batchIndexChange( + CompletableFuture<Result> batchIndexChange( String projectName, int changeId, IndexEvent indexEvent); /** @@ -62,7 +68,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent); + CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent); /** * Forward a group indexing event to the other primary. @@ -72,7 +78,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent); + CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent); /** * Forward a project indexing event to the other primary. @@ -82,7 +88,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent); + CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent); /** * Forward a stream event to the other primary. @@ -91,7 +97,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> send(Event event); + CompletableFuture<Result> send(Event event); /** * Forward a cache eviction event to the other primary. @@ -101,7 +107,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> evict(String cacheName, Object key); + CompletableFuture<Result> evict(String cacheName, Object key); /** * Forward an addition to the project list cache to the other primary. @@ -110,7 +116,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> addToProjectList(String projectName); + CompletableFuture<Result> addToProjectList(String projectName); /** * Forward a removal from the project list cache to the other primary. @@ -119,7 +125,7 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> removeFromProjectList(String projectName); + CompletableFuture<Result> removeFromProjectList(String projectName); /** * Forward the removal of all project changes from index to the other primary. @@ -128,5 +134,5 @@ * @return {@link CompletableFuture} of true if successful, otherwise {@link CompletableFuture} of * false. */ - CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName); + CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java new file mode 100644 index 0000000..eecef15 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetrics.java
@@ -0,0 +1,83 @@ +// Copyright (C) 2025 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; + +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.Histogram0; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; +import java.util.Locale; +import java.util.concurrent.TimeUnit; + +public class ForwarderMetrics { + private final Timer0 latencyMetric; + private final Counter0 failureCounterMetric; + private final Counter0 successCounterMetric; + private final Histogram0 retryMetric; + + public interface Factory { + ForwarderMetrics create(EventType eventType); + } + + @AssistedInject + public ForwarderMetrics(MetricMaker metricMaker, @Assisted EventType eventType) { + String event = eventType.toString().toLowerCase(Locale.US); + + this.latencyMetric = + metricMaker.newTimer( + String.format("forwarding_%s_event/latency", event), + new Description( + String.format( + "Time from %s event scheduling to receiving on the other node", event)) + .setCumulative() + .setUnit(Description.Units.MILLISECONDS)); + this.failureCounterMetric = + metricMaker.newCounter( + String.format("forwarding_%s_event/failure", event), + new Description(String.format("%s events forwarding failures count", event)) + .setCumulative() + .setRate()); + this.successCounterMetric = + metricMaker.newCounter( + String.format("forwarding_%s_event/success", event), + new Description(String.format("%s events forwarding success count", event)) + .setCumulative() + .setRate()); + this.retryMetric = + metricMaker.newHistogram( + String.format("forwarding_%s_event/retries", eventType), + new Description(String.format("%s events forwarding retries", eventType)) + .setCumulative()); + } + + public void recordResult(boolean isSuccessful) { + if (isSuccessful) { + successCounterMetric.increment(); + } else { + failureCounterMetric.increment(); + } + } + + public void recordLatency(long latencyMs) { + latencyMetric.record(latencyMs, TimeUnit.MILLISECONDS); + } + + public void recordRetries(int retries) { + retryMetric.record(retries); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetricsRegistry.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetricsRegistry.java new file mode 100644 index 0000000..97f73f6 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderMetricsRegistry.java
@@ -0,0 +1,50 @@ +// Copyright (C) 2025 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; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Singleton +public class ForwarderMetricsRegistry { + + private final ForwarderMetrics.Factory metricsFactory; + + private Map<EventType, ForwarderMetrics> metrics = new HashMap<>(); + + @Inject + public ForwarderMetricsRegistry(ForwarderMetrics.Factory metricsFactory) { + this.metricsFactory = metricsFactory; + this.putAll(Arrays.asList(EventType.values())); + } + + public ForwarderMetrics get(EventType eventType) { + return metrics.get(eventType); + } + + public void put(EventType task) { + metrics.put(task, metricsFactory.create(task)); + } + + public void putAll(Collection<EventType> eventTypes) { + for (EventType eventType : eventTypes) { + put(eventType); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java index 7cb107c..f5c5a39 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderModule.java
@@ -15,18 +15,22 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; import com.ericsson.gerrit.plugins.highavailability.ConfigurableAllowedEventListeners; +import com.google.gerrit.extensions.config.FactoryModule; import com.google.gerrit.extensions.registration.DynamicItem; import com.google.gerrit.server.events.EventDispatcher; -import com.google.inject.AbstractModule; import com.google.inject.Scopes; -public class ForwarderModule extends AbstractModule { - +public class ForwarderModule extends FactoryModule { @Override protected void configure() { bind(AllowedForwardedEventListener.class) .to(ConfigurableAllowedEventListeners.class) .in(Scopes.SINGLETON); + bind(ForwarderMetricsRegistry.class); DynamicItem.bind(binder(), EventDispatcher.class).to(ForwardedAwareEventBroker.class); + factory(ForwarderMetrics.Factory.class); + bind(ForwarderMetricsRegistry.class).in(Scopes.SINGLETON); + factory(ProcessorMetrics.Factory.class); + bind(ProcessorMetricsRegistry.class).in(Scopes.SINGLETON); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java new file mode 100644 index 0000000..f8895e1 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ForwarderTask.java
@@ -0,0 +1,28 @@ +// Copyright (C) 2025 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; + +public enum ForwarderTask { + ADD_TO_PROJECTS_LIST, + BATCH_INDEX_CHANGE, + DELETE_CHANGE_FROM_INDEX, + EVICT_CACHE, + INDEX_ACCOUNT, + INDEX_CHANGE, + INDEX_GROUP, + INDEX_PROJECT, + REMOVE_FROM_PROJECTS_LIST, + SEND_EVENT +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java index 71cf044..129af6b 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/IndexEvent.java
@@ -14,12 +14,13 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder; +import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.format.DateTimeFormatter; public class IndexEvent { - public long eventCreatedOn = System.currentTimeMillis() / 1000; + public Instant eventCreatedOn = Instant.now(); public String targetSha; public String metaSha; @@ -31,8 +32,7 @@ + ((metaSha != null) ? "/meta:" + metaSha : ""); } - public static String format(long eventTs) { - return LocalDateTime.ofEpochSecond(eventTs, 0, ZoneOffset.UTC) - .format(DateTimeFormatter.ISO_DATE_TIME); + public static String format(Instant eventTs) { + return LocalDateTime.ofInstant(eventTs, ZoneOffset.UTC).format(DateTimeFormatter.ISO_DATE_TIME); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InstantTypeAdapter.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InstantTypeAdapter.java new file mode 100644 index 0000000..b48da50 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/InstantTypeAdapter.java
@@ -0,0 +1,40 @@ +// Copyright (C) 2025 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; + +import com.google.gson.JsonDeserializationContext; +import com.google.gson.JsonDeserializer; +import com.google.gson.JsonElement; +import com.google.gson.JsonParseException; +import com.google.gson.JsonSerializationContext; +import com.google.gson.JsonSerializer; +import java.lang.reflect.Type; +import java.time.Instant; + +public class InstantTypeAdapter implements JsonDeserializer<Instant>, JsonSerializer<Instant> { + @Override + public Instant deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) + throws JsonParseException { + if (json == null || !json.isJsonPrimitive() || !json.getAsJsonPrimitive().isNumber()) { + throw new JsonParseException("Invalid Instant value: " + json); + } + return Instant.ofEpochMilli(json.getAsLong()); + } + + @Override + public JsonElement serialize(Instant src, Type typeOfSrc, JsonSerializationContext context) { + return context.serialize(src.toEpochMilli(), Long.class); + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetrics.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetrics.java new file mode 100644 index 0000000..de1e271 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetrics.java
@@ -0,0 +1,94 @@ +// Copyright (C) 2025 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; + +import com.google.gerrit.common.Nullable; +import com.google.gerrit.metrics.Counter0; +import com.google.gerrit.metrics.Description; +import com.google.gerrit.metrics.MetricMaker; +import com.google.gerrit.metrics.Timer0; +import com.google.inject.assistedinject.Assisted; +import com.google.inject.assistedinject.AssistedInject; +import java.time.Duration; +import java.time.Instant; +import java.util.concurrent.TimeUnit; + +public class ProcessorMetrics { + private final Timer0 processingTimeMetric; + private final Timer0 totalTimeMetric; + private final Counter0 failureCounterMetric; + private final Counter0 successCounterMetric; + + public interface Factory { + ProcessorMetrics create(EventType eventType); + } + + @AssistedInject + public ProcessorMetrics(MetricMaker metricMaker, @Assisted EventType eventType) { + this.processingTimeMetric = + metricMaker.newTimer( + String.format("forwarded_%s_event_handler/time_processing", eventType), + new Description( + String.format( + "Time from receiving an %s event to finish processing it.", eventType)) + .setCumulative() + .setUnit(Description.Units.MILLISECONDS)); + this.totalTimeMetric = + metricMaker.newTimer( + String.format("forwarded_%s_event_handler/time_total", eventType), + new Description( + String.format( + "Time from %s event scheduling to finish processing it.", eventType)) + .setCumulative() + .setUnit(Description.Units.MILLISECONDS)); + this.failureCounterMetric = + metricMaker.newCounter( + String.format("forwarded_%s_event_handler/failure", eventType), + new Description(String.format("%s events forwarding failures count", eventType)) + .setCumulative() + .setRate()); + this.successCounterMetric = + metricMaker.newCounter( + String.format("forwarded_%s_event_handler/success", eventType), + new Description(String.format("%s events forwarding success count", eventType)) + .setCumulative() + .setRate()); + } + + public void recordResult(boolean isSuccessful) { + if (isSuccessful) { + successCounterMetric.increment(); + } else { + failureCounterMetric.increment(); + } + } + + public void recordProcessingTime(Long processingTime) { + processingTimeMetric.record(processingTime, TimeUnit.MILLISECONDS); + } + + public void recordTotalTime(Long totalTime) { + totalTimeMetric.record(totalTime, TimeUnit.MILLISECONDS); + } + + public void record(@Nullable Instant eventCreatedOn, Instant startTime, boolean success) { + Instant now = Instant.now(); + recordResult(success); + recordProcessingTime(Duration.between(startTime, now).toMillis()); + if (eventCreatedOn != null) { + recordTotalTime(Duration.between(eventCreatedOn, now).toMillis()); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetricsRegistry.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetricsRegistry.java new file mode 100644 index 0000000..80504ab --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/ProcessorMetricsRegistry.java
@@ -0,0 +1,50 @@ +// Copyright (C) 2025 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; + +import com.google.inject.Inject; +import com.google.inject.Singleton; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +@Singleton +public class ProcessorMetricsRegistry { + + private final ProcessorMetrics.Factory metricsFactory; + + private Map<EventType, ProcessorMetrics> metrics = new HashMap<>(); + + @Inject + public ProcessorMetricsRegistry(ProcessorMetrics.Factory metricsFactory) { + this.metricsFactory = metricsFactory; + this.putAll(Arrays.asList(EventType.values())); + } + + public ProcessorMetrics get(EventType eventType) { + return metrics.get(eventType); + } + + public void put(EventType task) { + metrics.put(task, metricsFactory.create(task)); + } + + public void putAll(Collection<EventType> eventTypes) { + for (EventType eventType : eventTypes) { + put(eventType); + } + } +}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java index f7af2df..6f47da3 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/AddToProjectList.java
@@ -14,13 +14,16 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class AddToProjectList extends Command { - static final String TYPE = "add-to-project-list"; + static final EventType TYPE = EventType.PROJECT_LIST_ADDITION; private final String projectName; - public AddToProjectList(String projectName) { - super(TYPE); + public AddToProjectList(String projectName, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.projectName = projectName; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java index a258d91..3abad1e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/Command.java
@@ -14,10 +14,15 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; -public class Command { - public final String type; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; - protected Command(String type) { +public class Command { + public final EventType type; + public final Instant eventCreatedOn; + + protected Command(EventType type, Instant eventCreatedOn) { this.type = type; + this.eventCreatedOn = eventCreatedOn; } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java index b75f409..cdd217c 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializer.java
@@ -14,6 +14,7 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.google.gson.JsonDeserializationContext; import com.google.gson.JsonDeserializer; import com.google.gson.JsonElement; @@ -29,6 +30,7 @@ private static final List<Class<? extends Command>> CMD_CLASSES = List.of( IndexChange.Update.class, + IndexChange.BatchUpdate.class, IndexChange.Delete.class, IndexAccount.class, IndexGroup.class, @@ -37,13 +39,13 @@ PostEvent.class, AddToProjectList.class, RemoveFromProjectList.class); - private static final Map<String, Class<?>> COMMAND_TYPE_TO_CLASS_MAPPING = new HashMap<>(); + private static final Map<EventType, Class<?>> COMMAND_TYPE_TO_CLASS_MAPPING = new HashMap<>(); static { for (Class<?> clazz : CMD_CLASSES) { try { Field type = clazz.getDeclaredField("TYPE"); - COMMAND_TYPE_TO_CLASS_MAPPING.put((String) type.get(null), clazz); + COMMAND_TYPE_TO_CLASS_MAPPING.put((EventType) type.get(null), clazz); } catch (Exception e) { throw new RuntimeException(e); } @@ -63,7 +65,7 @@ throw new JsonParseException("Type is not a string: " + typeJson); } String type = typeJson.getAsJsonPrimitive().getAsString(); - Class<?> commandClass = COMMAND_TYPE_TO_CLASS_MAPPING.get(type); + Class<?> commandClass = COMMAND_TYPE_TO_CLASS_MAPPING.get(EventType.valueOf(type)); if (commandClass == null) { throw new JsonParseException("Unknown command type: " + type); }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java index 79933fb..81ce445 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/DeleteAllProjectChangesFromIndex.java
@@ -14,15 +14,17 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.google.gerrit.entities.Project; +import java.time.Instant; public class DeleteAllProjectChangesFromIndex extends Command { - static final String TYPE = "delete-all-project-changes-from-index"; + static final EventType TYPE = EventType.INDEX_CHANGE_DELETION_ALL_OF_PROJECT; private final Project.NameKey projectName; - protected DeleteAllProjectChangesFromIndex(Project.NameKey projectName) { - super(TYPE); + protected DeleteAllProjectChangesFromIndex(Project.NameKey projectName, Instant createdOn) { + super(TYPE, createdOn); this.projectName = projectName; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java index c4ae6d0..9ca05f1 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/EvictCache.java
@@ -14,14 +14,17 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class EvictCache extends Command { - static final String TYPE = "evict-cache"; + static final EventType TYPE = EventType.CACHE_EVICTION; private final String cacheName; private final String keyJson; - protected EvictCache(String cacheName, String keyJson) { - super(TYPE); + protected EvictCache(String cacheName, String keyJson, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.cacheName = cacheName; this.keyJson = keyJson; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java index 8f81832..be6bbf4 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/FailsafeExecutorProvider.java
@@ -15,6 +15,7 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; import com.google.common.flogger.FluentLogger; import com.google.gerrit.server.git.WorkQueue; import com.google.inject.Inject; @@ -23,7 +24,7 @@ import dev.failsafe.FailsafeExecutor; import dev.failsafe.RetryPolicy; -public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Boolean>> { +public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Result>> { private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final Configuration cfg; private final WorkQueue workQueue; @@ -35,9 +36,9 @@ } @Override - public FailsafeExecutor<Boolean> get() { - RetryPolicy<Boolean> retryPolicy = - RetryPolicy.<Boolean>builder() + public FailsafeExecutor<Result> get() { + RetryPolicy<Result> retryPolicy = + RetryPolicy.<Result>builder() .withMaxAttempts(cfg.jgroups().maxTries()) .withDelay(cfg.jgroups().retryInterval()) .onRetry(e -> log.atFine().log("Retrying event %s", e)) @@ -45,7 +46,7 @@ e -> log.atWarning().log( "%d jgroups retries exceeded for event %s", cfg.jgroups().maxTries(), e)) - .handleResult(false) + .handleResultIf(r -> !r.result()) .build(); return Failsafe.with(retryPolicy) .with(workQueue.createQueue(cfg.jgroups().threadPoolSize(), "JGroupsForwarder"));
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java index 7d9bbc4..55cadda 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexAccount.java
@@ -14,13 +14,16 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class IndexAccount extends Command { - static final String TYPE = "index-account"; + static final EventType TYPE = EventType.INDEX_ACCOUNT_UPDATE; private final int id; - public IndexAccount(int id) { - super(TYPE); + public IndexAccount(int id, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.id = id; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java index 501a722..075bfd7 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexChange.java
@@ -14,15 +14,18 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.google.common.base.Strings; +import java.time.Instant; public abstract class IndexChange extends Command { private final String projectName; private final int id; private final boolean batchMode; - protected IndexChange(String type, String projectName, int id, boolean batchMode) { - super(type); + protected IndexChange( + EventType type, String projectName, int id, boolean batchMode, Instant eventCreatedOn) { + super(type, eventCreatedOn); this.projectName = projectName; this.id = id; this.batchMode = batchMode; @@ -37,26 +40,30 @@ } public static class Update extends IndexChange { - static final String TYPE = "update-change"; + static final EventType TYPE = EventType.INDEX_CHANGE_UPDATE; - public Update(String projectName, int id) { - this(projectName, id, false); + public Update(String projectName, int id, Instant eventCreatedOn) { + super(TYPE, projectName, id, false, eventCreatedOn); } + } - public Update(String projectName, int id, boolean batchMode) { - super(TYPE, projectName, id, batchMode); + public static class BatchUpdate extends IndexChange { + static final EventType TYPE = EventType.INDEX_CHANGE_UPDATE_BATCH; + + public BatchUpdate(String projectName, int id, Instant eventCreatedOn) { + super(TYPE, projectName, id, true, eventCreatedOn); } } public static class Delete extends IndexChange { - static final String TYPE = "delete-change"; + static final EventType TYPE = EventType.INDEX_CHANGE_DELETION; - public Delete(int id) { - this("", id); + public Delete(int id, Instant eventCreatedOn) { + this("", id, eventCreatedOn); } - public Delete(String projectName, int id) { - super(TYPE, projectName, id, false); + public Delete(String projectName, int id, Instant eventCreatedOn) { + super(TYPE, projectName, id, false, eventCreatedOn); } } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java index b5c9f78..9f49060 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexGroup.java
@@ -14,13 +14,16 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class IndexGroup extends Command { - static final String TYPE = "index-group"; + static final EventType TYPE = EventType.INDEX_GROUP_UPDATE; private final String uuid; - protected IndexGroup(String uuid) { - super(TYPE); + protected IndexGroup(String uuid, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.uuid = uuid; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java index 4d8214d..40e400d 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/IndexProject.java
@@ -14,13 +14,16 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class IndexProject extends Command { - static final String TYPE = "index-project"; + static final EventType TYPE = EventType.INDEX_PROJECT_UPDATE; private String projectName; - protected IndexProject(String projectName) { - super(TYPE); + protected IndexProject(String projectName, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.projectName = projectName; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java index 9f1f179..0bd8c5d 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarder.java
@@ -17,6 +17,7 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Project; @@ -25,6 +26,8 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import dev.failsafe.FailsafeExecutor; +import java.time.Duration; +import java.time.Instant; import java.util.List; import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; @@ -43,89 +46,106 @@ private final MessageDispatcher dispatcher; private final JGroups jgroupsConfig; private final Gson gson; - private final FailsafeExecutor<Boolean> executor; + private final FailsafeExecutor<Result> executor; + private final ForwarderMetricsRegistry metricsRegistry; @Inject JGroupsForwarder( MessageDispatcher dispatcher, Configuration cfg, @JGroupsGson Gson gson, - @JGroupsForwarderExecutor FailsafeExecutor<Boolean> executor) { + @JGroupsForwarderExecutor FailsafeExecutor<Result> executor, + ForwarderMetricsRegistry metricsRegistry) { this.dispatcher = dispatcher; this.jgroupsConfig = cfg.jgroups(); this.gson = gson; this.executor = executor; + + this.metricsRegistry = metricsRegistry; + this.executor.onComplete( + ev -> { + this.metricsRegistry.get(ev.getResult().type()).recordRetries(ev.getAttemptCount()); + }); } @Override - public CompletableFuture<Boolean> indexAccount(int accountId, IndexEvent indexEvent) { - return execute(new IndexAccount(accountId)); + public CompletableFuture<Result> indexAccount(int accountId, IndexEvent indexEvent) { + return execute(new IndexAccount(accountId, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> indexChange( + public CompletableFuture<Result> indexChange( String projectName, int changeId, IndexEvent indexEvent) { - return execute(new IndexChange.Update(projectName, changeId)); + return execute(new IndexChange.Update(projectName, changeId, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> batchIndexChange( + public CompletableFuture<Result> batchIndexChange( String projectName, int changeId, IndexEvent indexEvent) { - return execute(new IndexChange.Update(projectName, changeId, true)); + return execute(new IndexChange.BatchUpdate(projectName, changeId, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> deleteChangeFromIndex(int changeId, IndexEvent indexEvent) { - return execute(new IndexChange.Delete(changeId)); + public CompletableFuture<Result> deleteChangeFromIndex(int changeId, IndexEvent indexEvent) { + return execute(new IndexChange.Delete(changeId, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> indexGroup(String uuid, IndexEvent indexEvent) { - return execute(new IndexGroup(uuid)); + public CompletableFuture<Result> indexGroup(String uuid, IndexEvent indexEvent) { + return execute(new IndexGroup(uuid, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent indexEvent) { - return execute(new IndexProject(projectName)); + public CompletableFuture<Result> indexProject(String projectName, IndexEvent indexEvent) { + return execute(new IndexProject(projectName, indexEvent.eventCreatedOn)); } @Override - public CompletableFuture<Boolean> send(Event event) { - return execute(new PostEvent(event)); + public CompletableFuture<Result> send(Event event) { + return execute(new PostEvent(event, Instant.ofEpochSecond(event.eventCreatedOn))); } @Override - public CompletableFuture<Boolean> evict(String cacheName, Object key) { - return execute(new EvictCache(cacheName, gson.toJson(key))); + public CompletableFuture<Result> evict(String cacheName, Object key) { + return execute(new EvictCache(cacheName, gson.toJson(key), Instant.now())); } @Override - public CompletableFuture<Boolean> addToProjectList(String projectName) { - return execute(new AddToProjectList(projectName)); + public CompletableFuture<Result> addToProjectList(String projectName) { + return execute(new AddToProjectList(projectName, Instant.now())); } @Override - public CompletableFuture<Boolean> removeFromProjectList(String projectName) { - return execute(new RemoveFromProjectList(projectName)); + public CompletableFuture<Result> removeFromProjectList(String projectName) { + return execute(new RemoveFromProjectList(projectName, Instant.now())); } @Override - public CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName) { - return execute(new DeleteAllProjectChangesFromIndex(projectName)); + public CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName) { + return execute(new DeleteAllProjectChangesFromIndex(projectName, Instant.now())); } - private CompletableFuture<Boolean> execute(Command cmd) { - return executor.getAsync(() -> executeOnce(cmd)); + private CompletableFuture<Result> execute(Command cmd) { + return executor + .getAsync(() -> executeOnce(cmd)) + .thenApplyAsync( + result -> { + metricsRegistry.get(cmd.type).recordResult(result.result()); + metricsRegistry + .get(cmd.type) + .recordLatency(Duration.between(cmd.eventCreatedOn, Instant.now()).toMillis()); + return result; + }); } - private boolean executeOnce(Command cmd) { + private Result executeOnce(Command cmd) { String json = gson.toJson(cmd); try { logJGroupsInfo(); if (dispatcher.getChannel().getView().size() < 2) { log.atFine().log("Less than two members in cluster, not sending %s", json); - return false; + return new Result(cmd.type, false); } log.atFine().log("Sending %s", json); @@ -135,7 +155,7 @@ log.atFine().log("Received response list length = %s", list.size()); if (list.isEmpty()) { - return false; + return new Result(cmd.type, false); } for (Entry<Address, Rsp<Object>> e : list.entrySet()) { @@ -144,14 +164,14 @@ log.atWarning().log( "Received a non TRUE response from receiver %s: %s", e.getKey(), e.getValue().getValue()); - return false; + return new Result(cmd.type, false); } } log.atFine().log("Successfully sent message %s", json); - return true; + return new Result(cmd.type, true); } catch (Exception e) { log.atWarning().withCause(e).log("Forwarding %s failed", json); - return false; + return new Result(cmd.type, false); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java index 58e7aeb..0895088 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderModule.java
@@ -15,6 +15,8 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; +import com.ericsson.gerrit.plugins.highavailability.forwarder.InstantTypeAdapter; import com.ericsson.gerrit.plugins.highavailability.peers.jgroups.JChannelProviderModule; import com.google.gerrit.lifecycle.LifecycleModule; import com.google.gerrit.server.events.EventGson; @@ -24,6 +26,7 @@ import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import dev.failsafe.FailsafeExecutor; +import java.time.Instant; import org.jgroups.blocks.MessageDispatcher; import org.jgroups.blocks.RequestHandler; @@ -37,7 +40,7 @@ install(new JChannelProviderModule()); listener().to(OnStartStop.class); - bind(new TypeLiteral<FailsafeExecutor<Boolean>>() {}) + bind(new TypeLiteral<FailsafeExecutor<Result>>() {}) .annotatedWith(JGroupsForwarderExecutor.class) .toProvider(FailsafeExecutorProvider.class) .in(Scopes.SINGLETON); @@ -50,6 +53,7 @@ return eventGson .newBuilder() .registerTypeAdapter(Command.class, new CommandDeserializer()) + .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) .create(); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java index 73df3eb..489e070 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessor.java
@@ -23,6 +23,8 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.common.flogger.FluentLogger; import com.google.gerrit.entities.Account; import com.google.gerrit.server.events.Event; @@ -30,6 +32,7 @@ import com.google.inject.Inject; import com.google.inject.Singleton; import java.io.IOException; +import java.time.Instant; import java.util.Optional; import org.jgroups.Message; import org.jgroups.blocks.RequestHandler; @@ -45,6 +48,7 @@ private final ForwardedCacheEvictionHandler cacheEvictionHandler; private final ForwardedEventHandler eventHandler; private final ForwardedProjectListUpdateHandler projectListUpdateHandler; + private final ProcessorMetricsRegistry metricRegistry; @Inject MessageProcessor( @@ -54,7 +58,8 @@ ForwardedIndexAccountHandler indexAccountHandler, ForwardedCacheEvictionHandler cacheEvictionHandler, ForwardedEventHandler eventHandler, - ForwardedProjectListUpdateHandler projectListUpdateHandler) { + ForwardedProjectListUpdateHandler projectListUpdateHandler, + ProcessorMetricsRegistry metricRegistry) { this.gson = gson; this.indexChangeHandler = indexChangeHandler; this.indexBatchChangeHandler = indexBatchChangeHandler; @@ -62,11 +67,15 @@ this.cacheEvictionHandler = cacheEvictionHandler; this.eventHandler = eventHandler; this.projectListUpdateHandler = projectListUpdateHandler; + this.metricRegistry = metricRegistry; } @Override public Object handle(Message msg) { Command cmd = getCommand(msg); + ProcessorMetrics metrics = metricRegistry.get(cmd.type); + Instant startTime = Instant.now(); + boolean success = false; Context.setForwardedEvent(true); try { @@ -83,7 +92,7 @@ } catch (Exception e) { log.atSevere().withCause(e).log( "Change index %s on change %s failed", op.name().toLowerCase(), indexChange.getId()); - return false; + throw e; } } else if (cmd instanceof IndexAccount) { @@ -95,7 +104,7 @@ } catch (IOException e) { log.atSevere().withCause(e).log( "Account index update on account %s failed", indexAccount.getId()); - return false; + throw e; } } else if (cmd instanceof EvictCache) { @@ -117,17 +126,18 @@ String projectName = ((RemoveFromProjectList) cmd).getProjectName(); projectListUpdateHandler.update(projectName, true); } - - return true; + success = true; } catch (Exception e) { - return false; + success = false; } finally { Context.unsetForwardedEvent(); } + metrics.record(cmd.eventCreatedOn, startTime, success); + return success; } private Operation getOperation(IndexChange cmd) { - if (cmd instanceof IndexChange.Update) { + if (cmd instanceof IndexChange.Update || cmd instanceof IndexChange.BatchUpdate) { return Operation.INDEX; } else if (cmd instanceof IndexChange.Delete) { return Operation.DELETE;
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java index 49627a7..3f50b0a 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/PostEvent.java
@@ -14,15 +14,17 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.google.gerrit.server.events.Event; +import java.time.Instant; public class PostEvent extends Command { - static final String TYPE = "post-event"; + static final EventType TYPE = EventType.EVENT_SENT; private final Event event; - protected PostEvent(Event event) { - super(TYPE); + protected PostEvent(Event event, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.event = event; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java index 8eb0a3d..1a43ac8 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/RemoveFromProjectList.java
@@ -14,13 +14,16 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import java.time.Instant; + public class RemoveFromProjectList extends Command { - static final String TYPE = "remove-from-project-list"; + static final EventType TYPE = EventType.PROJECT_LIST_DELETION; private final String projectName; - public RemoveFromProjectList(String projectName) { - super(TYPE); + public RemoveFromProjectList(String projectName, Instant eventCreatedOn) { + super(TYPE, eventCreatedOn); this.projectName = projectName; }
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 index a567f61..108c37c 100644 --- 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
@@ -18,11 +18,14 @@ import static javax.servlet.http.HttpServletResponse.SC_METHOD_NOT_ALLOWED; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.common.base.Charsets; -import com.google.gerrit.server.events.EventGson; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.extensions.restapi.NotImplementedException; import com.google.gson.Gson; import java.io.IOException; import java.io.InputStreamReader; @@ -56,45 +59,52 @@ AbstractIndexRestApiServlet( ForwardedIndexingHandler<T> forwardedIndexingHandler, IndexName indexName, - boolean allowDelete, - @EventGson Gson gson) { + Gson gson, + ProcessorMetricsRegistry metricsRegistry, + EventType postEventType, + @Nullable EventType deleteEventType) { + super(metricsRegistry, postEventType, deleteEventType); this.forwardedIndexingHandler = forwardedIndexingHandler; this.indexName = indexName; - this.allowDelete = allowDelete; this.gson = gson; - } - - AbstractIndexRestApiServlet( - ForwardedIndexingHandler<T> forwardedIndexingHandler, IndexName indexName) { - this(forwardedIndexingHandler, indexName, false, new Gson()); + this.allowDelete = deleteEventType != null; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) { - process(req, rsp, Operation.INDEX); + protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) { + return process(req, rsp, Operation.INDEX); } @Override - protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) { + protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) { if (!allowDelete) { sendError( rsp, SC_METHOD_NOT_ALLOWED, String.format("cannot delete %s from index", indexName)); - } else { - process(req, rsp, Operation.DELETE); + throw new NotImplementedException("Deletions not allowed for " + indexName); } + return process(req, rsp, Operation.DELETE); } - private void process(HttpServletRequest req, HttpServletResponse rsp, Operation operation) { - setHeaders(rsp); + /** + * Process the request by parsing the ID from the URL and invoking the indexing handler. + * + * @param req the HTTP request + * @param rsp the HTTP response + * @param operation the indexing operation to perform (INDEX or DELETE) + * @return true if the operation was successful, false otherwise + */ + private boolean process(HttpServletRequest req, HttpServletResponse rsp, Operation operation) { String path = req.getRequestURI(); T id = parse(path.substring(path.lastIndexOf('/') + 1)); try { forwardedIndexingHandler.index(id, operation, parseBody(req)); rsp.setStatus(SC_NO_CONTENT); + return true; } catch (IOException e) { sendError(rsp, SC_CONFLICT, e.getMessage()); log.atSevere().withCause(e).log("Unable to update %s index", indexName); + return false; } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java index a76d8ea..6aa0461 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/AbstractRestApiServlet.java
@@ -16,20 +16,88 @@ import static java.nio.charset.StandardCharsets.UTF_8; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; +import com.google.common.base.Strings; import com.google.common.flogger.FluentLogger; +import com.google.gerrit.common.Nullable; +import com.google.gerrit.extensions.restapi.NotImplementedException; import java.io.IOException; +import java.time.Instant; import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public abstract class AbstractRestApiServlet extends HttpServlet { private static final long serialVersionUID = 1L; protected static final FluentLogger log = FluentLogger.forEnclosingClass(); + private final ProcessorMetrics postMetrics; + private final ProcessorMetrics deleteMetrics; + + public AbstractRestApiServlet( + ProcessorMetricsRegistry metricsRegistry, + EventType postEventType, + @Nullable EventType deleteEventType) { + super(); + this.postMetrics = metricsRegistry.get(postEventType); + if (deleteEventType != null) { + this.deleteMetrics = metricsRegistry.get(deleteEventType); + } else { + this.deleteMetrics = null; + } + } + protected static void setHeaders(HttpServletResponse rsp) { rsp.setContentType("text/plain"); rsp.setCharacterEncoding(UTF_8.name()); } + @Override + public final void doPost(HttpServletRequest req, HttpServletResponse rsp) { + setHeaders(rsp); + Instant start = Instant.now(); + + boolean success = processPostRequest(req, rsp); + + postMetrics.record(getEventCreatedOnFromHeader(req), start, success); + } + + @Override + public final void doDelete(HttpServletRequest req, HttpServletResponse rsp) { + setHeaders(rsp); + Instant start = Instant.now(); + + try { + boolean success = processDeleteRequest(req, rsp); + deleteMetrics.record(getEventCreatedOnFromHeader(req), start, success); + } catch (NotImplementedException e) { + return; + } + } + + private Instant getEventCreatedOnFromHeader(HttpServletRequest req) { + String header = req.getHeader(HttpSession.HEADER_EVENT_CREATED_ON); + if (!Strings.isNullOrEmpty(header)) { + try { + return Instant.ofEpochMilli(Long.valueOf(header)); + } catch (NumberFormatException e) { + log.atWarning().withCause(e).log( + "Invalid value for header %s: %s", HttpSession.HEADER_EVENT_CREATED_ON, header); + } + } + return null; + } + + protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) { + throw new NotImplementedException("POST requests not implemented"); + } + + protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) { + throw new NotImplementedException("DELETE requests not implemented"); + } + protected void sendError(HttpServletResponse rsp, int statusCode, String message) { try { rsp.sendError(statusCode, message); @@ -37,4 +105,19 @@ log.atSevere().withCause(e).log("Failed to send error messsage"); } } + + protected static void updateMetrics( + ProcessorMetrics metrics, HttpServletRequest req, Instant startTime, boolean success) { + String eventCreatedOn = req.getHeader(HttpSession.HEADER_EVENT_CREATED_ON); + Instant now = Instant.now(); + long totalDuration; + if (Strings.isNullOrEmpty(eventCreatedOn)) { + totalDuration = 0L; + } else { + totalDuration = now.toEpochMilli() - Long.valueOf(eventCreatedOn); + } + metrics.recordResult(success); + metrics.recordProcessingTime(now.toEpochMilli() - startTime.toEpochMilli()); + metrics.recordTotalTime(totalDuration); + } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java index b1822ff..f3eb71e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParser.java
@@ -15,13 +15,11 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import com.ericsson.gerrit.plugins.highavailability.cache.Constants; +import com.google.common.base.CharMatcher; import com.google.common.base.Strings; -import com.google.common.base.Supplier; -import com.google.common.base.Suppliers; -import com.google.gerrit.entities.Account; -import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.Project; -import com.google.gerrit.server.events.EventGson; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.server.cache.CacheDef; import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.inject.Inject; @@ -30,38 +28,44 @@ @Singleton public class CacheKeyJsonParser { private final Gson gson; + private final DynamicMap<CacheDef<?, ?>> cachesMap; @Inject - public CacheKeyJsonParser(@EventGson Gson gson) { + public CacheKeyJsonParser(@RestGson Gson gson, DynamicMap<CacheDef<?, ?>> cachesMap) { this.gson = gson; + this.cachesMap = cachesMap; } public Object fromJson(String cacheName, String jsonString) { JsonElement json = gson.fromJson(Strings.nullToEmpty(jsonString), JsonElement.class); - Supplier<JsonElement> id = Suppliers.memoize(() -> json.getAsJsonObject().get("id")); - Supplier<JsonElement> uuid = Suppliers.memoize(() -> json.getAsJsonObject().get("uuid")); - - // Need to add a case for 'adv_bases' switch (cacheName) { - case Constants.ACCOUNTS: - case Constants.TOKENS: - case Constants.GROUPS_BYMEMBER: - return id.get() == null ? null : Account.id(id.get().getAsInt()); - case Constants.GROUPS: - return id.get() == null ? null : AccountGroup.id(id.get().getAsInt()); - case Constants.GROUPS_BYINCLUDE: - case Constants.GROUPS_MEMBERS: - return uuid.get() == null ? null : AccountGroup.uuid(uuid.get().getAsString()); case Constants.PROJECT_LIST: return gson.fromJson(json, Object.class); case Constants.PROJECTS: - return Project.nameKey(json.getAsString()); + return Project.nameKey(CharMatcher.is('\"').trimFrom(json.getAsString())); default: try { - return gson.fromJson(json, String.class); + return gson.fromJson(json, getCacheKeyType(cacheName)); } catch (Exception e) { return gson.fromJson(json, Object.class); } } } + + private Class<?> getCacheKeyType(String cacheName) { + int dot = cacheName.indexOf('.'); + String pluginName = Constants.GERRIT; + String pluginCacheName = cacheName; + if (dot > 0) { + pluginName = cacheName.substring(0, dot); + pluginCacheName = cacheName.substring(dot + 1); + } + + CacheDef<?, ?> cacheDef = cachesMap.get(pluginName, pluginCacheName); + if (cacheDef == null) { + throw new IllegalStateException("Unable to find definition for cache '" + cacheName + "'"); + } + + return cacheDef.keyType().getRawType(); + } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java index 86d64d2..926776b 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServlet.java
@@ -19,7 +19,9 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheEntry; import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheNotFoundException; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedCacheEvictionHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.common.base.Splitter; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -39,13 +41,15 @@ @Inject CacheRestApiServlet( ForwardedCacheEvictionHandler forwardedCacheEvictionHandler, - CacheKeyJsonParser cacheKeyParser) { + CacheKeyJsonParser cacheKeyParser, + ProcessorMetricsRegistry metricRegistry) { + super(metricRegistry, EventType.CACHE_EVICTION, null); this.forwardedCacheEvictionHandler = forwardedCacheEvictionHandler; this.cacheKeyParser = cacheKeyParser; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) { + protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) { setHeaders(rsp); try { List<String> params = Splitter.on('/').splitToList(req.getPathInfo()); @@ -54,6 +58,7 @@ forwardedCacheEvictionHandler.evict( CacheEntry.from(cacheName, cacheKeyParser.fromJson(cacheName, json))); rsp.setStatus(SC_NO_CONTENT); + return true; } catch (CacheNotFoundException e) { log.atSevere().log("Failed to process eviction request: %s", e.getMessage()); sendError(rsp, SC_BAD_REQUEST, e.getMessage()); @@ -61,5 +66,6 @@ log.atSevere().withCause(e).log("Failed to process eviction request"); sendError(rsp, SC_BAD_REQUEST, e.getMessage()); } + return false; } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java index e2e3302..422e3df 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServlet.java
@@ -19,11 +19,12 @@ import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; import static javax.servlet.http.HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedEventHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.common.io.CharStreams; import com.google.common.net.MediaType; import com.google.gerrit.server.events.Event; -import com.google.gerrit.server.events.EventGson; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -39,24 +40,29 @@ private final Gson gson; @Inject - EventRestApiServlet(ForwardedEventHandler forwardedEventHandler, @EventGson Gson gson) { + EventRestApiServlet( + ForwardedEventHandler forwardedEventHandler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super(metricRegistry, EventType.EVENT_SENT, null); this.forwardedEventHandler = forwardedEventHandler; this.gson = gson; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) { - setHeaders(rsp); + protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) { try { if (!MediaType.parse(req.getContentType()).is(JSON_UTF_8)) { sendError(rsp, SC_UNSUPPORTED_MEDIA_TYPE, "Expecting " + JSON_UTF_8 + " content type"); - return; + return false; } Event event = getEventFromRequest(req); rsp.setStatus(SC_NO_CONTENT); forwardedEventHandler.dispatch(event); + return true; } catch (IOException e) { sendError(rsp, SC_BAD_REQUEST, e.getMessage()); + return false; } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java index 8134220..6168880 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/FailsafeExecutorProvider.java
@@ -15,18 +15,18 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; import com.google.common.flogger.FluentLogger; import com.google.inject.Inject; import com.google.inject.Provider; import com.google.inject.Singleton; import dev.failsafe.Failsafe; import dev.failsafe.FailsafeExecutor; -import dev.failsafe.Fallback; import dev.failsafe.RetryPolicy; import java.util.concurrent.Executors; @Singleton -public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Boolean>> { +public class FailsafeExecutorProvider implements Provider<FailsafeExecutor<Result>> { private static final FluentLogger log = FluentLogger.forEnclosingClass(); private final Configuration cfg; @@ -36,10 +36,9 @@ } @Override - public FailsafeExecutor<Boolean> get() { - Fallback<Boolean> fallbackToFalse = Fallback.<Boolean>of(() -> false); - RetryPolicy<Boolean> retryPolicy = - RetryPolicy.<Boolean>builder() + public FailsafeExecutor<Result> get() { + RetryPolicy<Result> retryPolicy = + RetryPolicy.<Result>builder() .withMaxAttempts(cfg.http().maxTries()) .withDelay(cfg.http().retryInterval()) .onRetry(e -> log.atFine().log("Retrying event %s", e)) @@ -47,15 +46,13 @@ e -> log.atWarning().log( "%d http retries exceeded for event %s", cfg.http().maxTries(), e)) - .handleResult(false) - .abortIf( - (r, e) -> - e instanceof ForwardingException && !((ForwardingException) e).isRecoverable()) + .handleResultIf(r -> !r.result()) + .abortIf((r, e) -> !r.result() && !r.isRecoverable()) .build(); // TODO: the executor shall be created by workQueue.createQueue(...) // However, this currently doesn't work because WorkQueue.Executor doesn't support wrapping of // Callable i.e. it throws an exception on decorateTask(Callable) - return Failsafe.with(fallbackToFalse, retryPolicy) + return Failsafe.with(retryPolicy) .with(Executors.newScheduledThreadPool(cfg.http().threadPoolSize())); } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java index 6d801f6..a7a8580 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSession.java
@@ -16,12 +16,12 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult; import com.google.common.net.MediaType; -import com.google.gerrit.server.events.EventGson; import com.google.gson.Gson; import com.google.inject.Inject; import java.io.IOException; import java.net.URI; import java.nio.charset.StandardCharsets; +import java.time.Instant; import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpEntityEnclosingRequestBase; import org.apache.http.client.methods.HttpPost; @@ -29,40 +29,44 @@ import org.apache.http.impl.client.CloseableHttpClient; class HttpSession { + public static final String HEADER_EVENT_CREATED_ON = "Event-Created-On"; + private final CloseableHttpClient httpClient; private final Gson gson; @Inject - HttpSession(CloseableHttpClient httpClient, @EventGson Gson gson) { + HttpSession(CloseableHttpClient httpClient, @RestGson Gson gson) { this.httpClient = httpClient; this.gson = gson; } - HttpResult post(String uri) throws IOException { - return post(uri, null); + HttpResult post(String uri, Instant createdOn) throws IOException { + return post(uri, null, createdOn); } - HttpResult post(String uri, Object content) throws IOException { + HttpResult post(String uri, Object content, Instant createdOn) throws IOException { HttpPost post = new HttpPost(uri); - setContent(post, content); + setContent(post, content, createdOn); return httpClient.execute(post, new HttpResponseHandler()); } - HttpResult delete(String uri) throws IOException { - return delete(uri, null); + HttpResult delete(String uri, Instant createdOn) throws IOException { + return delete(uri, null, createdOn); } - HttpResult delete(String uri, Object content) throws IOException { + HttpResult delete(String uri, Object content, Instant createdOn) throws IOException { HttpDeleteWithBody delete = new HttpDeleteWithBody(uri); - setContent(delete, content); + setContent(delete, content, createdOn); return httpClient.execute(delete, new HttpResponseHandler()); } - private void setContent(HttpEntityEnclosingRequestBase request, Object content) { + private void setContent( + HttpEntityEnclosingRequestBase request, Object content, Instant createdOn) { if (content != null) { request.addHeader("Content-Type", MediaType.JSON_UTF_8.toString()); request.setEntity(new StringEntity(jsonEncode(content), StandardCharsets.UTF_8)); } + request.addHeader(HEADER_EVENT_CREATED_ON, String.valueOf(createdOn.toEpochMilli())); } private String jsonEncode(Object content) {
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 fa38cc3..1adfbbe 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,8 +14,11 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexAccountHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.Account; +import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -24,8 +27,11 @@ private static final long serialVersionUID = -1L; @Inject - IndexAccountRestApiServlet(ForwardedIndexAccountHandler handler) { - super(handler, IndexName.ACCOUNT); + IndexAccountRestApiServlet( + ForwardedIndexAccountHandler handler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super(handler, IndexName.ACCOUNT, gson, metricRegistry, EventType.INDEX_ACCOUNT_UPDATE, null); } @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java index bebfae9..7c593be 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexBatchChangeRestApiServlet.java
@@ -14,9 +14,10 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexBatchChangeHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.extensions.restapi.Url; -import com.google.gerrit.server.events.EventGson; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -26,8 +27,12 @@ private static final long serialVersionUID = -1L; @Inject - IndexBatchChangeRestApiServlet(ForwardedIndexBatchChangeHandler handler, @EventGson Gson gson) { - super(handler, IndexName.CHANGE, true, gson); + IndexBatchChangeRestApiServlet( + ForwardedIndexBatchChangeHandler handler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super( + handler, IndexName.CHANGE, gson, metricRegistry, EventType.INDEX_CHANGE_UPDATE_BATCH, null); } @Override
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 71e10fb..f63ceeb 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,9 +14,10 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.extensions.restapi.Url; -import com.google.gerrit.server.events.EventGson; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -26,8 +27,17 @@ private static final long serialVersionUID = -1L; @Inject - IndexChangeRestApiServlet(ForwardedIndexChangeHandler handler, @EventGson Gson gson) { - super(handler, IndexName.CHANGE, true, gson); + IndexChangeRestApiServlet( + ForwardedIndexChangeHandler handler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super( + handler, + IndexName.CHANGE, + gson, + metricRegistry, + EventType.INDEX_CHANGE_UPDATE, + EventType.INDEX_CHANGE_DELETION); } @Override
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 index 35c526f..6328cd7 100644 --- 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
@@ -14,8 +14,11 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.AccountGroup; +import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -24,8 +27,11 @@ private static final long serialVersionUID = -1L; @Inject - IndexGroupRestApiServlet(ForwardedIndexGroupHandler handler) { - super(handler, IndexName.GROUP); + IndexGroupRestApiServlet( + ForwardedIndexGroupHandler handler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super(handler, IndexName.GROUP, gson, metricRegistry, EventType.INDEX_GROUP_UPDATE, null); } @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java index 31df2bb..bf3a3f0 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServlet.java
@@ -14,9 +14,12 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexProjectHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.restapi.Url; +import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -25,8 +28,11 @@ private static final long serialVersionUID = -1L; @Inject - IndexProjectRestApiServlet(ForwardedIndexProjectHandler handler) { - super(handler, IndexName.PROJECT); + IndexProjectRestApiServlet( + ForwardedIndexProjectHandler handler, + @RestGson Gson gson, + ProcessorMetricsRegistry metricRegistry) { + super(handler, IndexName.PROJECT, gson, metricRegistry, EventType.INDEX_PROJECT_UPDATE, null); } @Override
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java index 7a31ea0..fb88edb 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListApiServlet.java
@@ -17,7 +17,9 @@ import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.extensions.restapi.Url; import com.google.inject.Inject; import com.google.inject.Singleton; @@ -32,30 +34,34 @@ private final ForwardedProjectListUpdateHandler forwardedProjectListUpdateHandler; @Inject - ProjectListApiServlet(ForwardedProjectListUpdateHandler forwardedProjectListUpdateHandler) { + ProjectListApiServlet( + ForwardedProjectListUpdateHandler forwardedProjectListUpdateHandler, + ProcessorMetricsRegistry metricRegistry) { + super(metricRegistry, EventType.PROJECT_LIST_ADDITION, EventType.PROJECT_LIST_DELETION); this.forwardedProjectListUpdateHandler = forwardedProjectListUpdateHandler; } @Override - protected void doPost(HttpServletRequest req, HttpServletResponse rsp) { - process(req, rsp, false); + protected boolean processPostRequest(HttpServletRequest req, HttpServletResponse rsp) { + return process(req, rsp, false); } @Override - protected void doDelete(HttpServletRequest req, HttpServletResponse rsp) { - process(req, rsp, true); + protected boolean processDeleteRequest(HttpServletRequest req, HttpServletResponse rsp) { + return process(req, rsp, true); } - private void process(HttpServletRequest req, HttpServletResponse rsp, boolean delete) { - setHeaders(rsp); + private boolean process(HttpServletRequest req, HttpServletResponse rsp, boolean delete) { String requestURI = req.getRequestURI(); String projectName = requestURI.substring(requestURI.lastIndexOf('/') + 1); try { forwardedProjectListUpdateHandler.update(Url.decode(projectName), delete); rsp.setStatus(SC_NO_CONTENT); + return true; } catch (IOException e) { log.atSevere().withCause(e).log("Unable to update project list"); sendError(rsp, SC_BAD_REQUEST, e.getMessage()); + return false; } } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java index 4ebf175..244831e 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/QueryChangesUpdatedSinceServlet.java
@@ -32,11 +32,12 @@ import java.io.PrintWriter; import java.util.ArrayList; import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @Singleton -public class QueryChangesUpdatedSinceServlet extends AbstractRestApiServlet { +public class QueryChangesUpdatedSinceServlet extends HttpServlet { private static final long serialVersionUID = 1L; Gson gson = new Gson();
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 d53cd82..ea783a6 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
@@ -16,7 +16,9 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.cache.Constants; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult; import com.ericsson.gerrit.plugins.highavailability.peers.PeerInfo; @@ -27,12 +29,13 @@ import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.restapi.Url; import com.google.gerrit.server.events.Event; -import com.google.gerrit.server.events.EventGson; import com.google.gson.Gson; import com.google.inject.Inject; import com.google.inject.Provider; import dev.failsafe.FailsafeExecutor; import java.io.IOException; +import java.time.Duration; +import java.time.Instant; import java.util.Set; import java.util.concurrent.CompletableFuture; import javax.net.ssl.SSLException; @@ -54,7 +57,8 @@ private final Configuration cfg; private final Provider<Set<PeerInfo>> peerInfoProvider; private final Gson gson; - private FailsafeExecutor<Boolean> executor; + private FailsafeExecutor<Result> executor; + private final ForwarderMetricsRegistry metricsRegistry; @Inject RestForwarder( @@ -62,52 +66,81 @@ @PluginName String pluginName, Configuration cfg, Provider<Set<PeerInfo>> peerInfoProvider, - @EventGson Gson gson, - @RestForwarderExecutor FailsafeExecutor<Boolean> executor) { + @RestGson Gson gson, + @RestForwarderExecutor FailsafeExecutor<Result> executor, + ForwarderMetricsRegistry metricsRegistry) { this.httpSession = httpClient; this.pluginRelativePath = Joiner.on("/").join("plugins", pluginName); this.cfg = cfg; this.peerInfoProvider = peerInfoProvider; this.gson = gson; this.executor = executor; + this.metricsRegistry = metricsRegistry; + this.executor.onComplete( + ev -> { + this.metricsRegistry.get(ev.getResult().type()).recordRetries(ev.getAttemptCount()); + }); } @Override - public CompletableFuture<Boolean> indexAccount(final int accountId, IndexEvent event) { - return execute(RequestMethod.POST, "index account", "index/account", accountId, event); - } - - @Override - public CompletableFuture<Boolean> indexChange( - String projectName, int changeId, IndexEvent event) { + public CompletableFuture<Result> indexAccount(final int accountId, IndexEvent event) { return execute( RequestMethod.POST, + EventType.INDEX_ACCOUNT_UPDATE, + "index account", + "index/account", + accountId, + event, + event.eventCreatedOn); + } + + @Override + public CompletableFuture<Result> indexChange(String projectName, int changeId, IndexEvent event) { + return execute( + RequestMethod.POST, + EventType.INDEX_CHANGE_UPDATE, "index change", "index/change", buildIndexEndpoint(projectName, changeId), - event); + event, + event.eventCreatedOn); } @Override - public CompletableFuture<Boolean> batchIndexChange( + public CompletableFuture<Result> batchIndexChange( String projectName, int changeId, IndexEvent event) { return execute( RequestMethod.POST, + EventType.INDEX_CHANGE_UPDATE_BATCH, "index change", "index/change/batch", buildIndexEndpoint(projectName, changeId), - event); + event, + event.eventCreatedOn); } @Override - public CompletableFuture<Boolean> deleteChangeFromIndex(final int changeId, IndexEvent event) { + public CompletableFuture<Result> deleteChangeFromIndex(final int changeId, IndexEvent event) { return execute( - RequestMethod.DELETE, "delete change", "index/change", buildIndexEndpoint(changeId), event); + RequestMethod.DELETE, + EventType.INDEX_CHANGE_DELETION, + "delete change", + "index/change", + buildIndexEndpoint(changeId), + event, + event.eventCreatedOn); } @Override - public CompletableFuture<Boolean> indexGroup(final String uuid, IndexEvent event) { - return execute(RequestMethod.POST, "index group", "index/group", uuid, event); + public CompletableFuture<Result> indexGroup(final String uuid, IndexEvent event) { + return execute( + RequestMethod.POST, + EventType.INDEX_GROUP_UPDATE, + "index group", + "index/group", + uuid, + event, + event.eventCreatedOn); } private String buildIndexEndpoint(int changeId) { @@ -126,99 +159,153 @@ } @Override - public CompletableFuture<Boolean> indexProject(String projectName, IndexEvent event) { - return execute( - RequestMethod.POST, "index project", "index/project", Url.encode(projectName), event); - } - - @Override - public CompletableFuture<Boolean> send(final Event event) { - return execute(RequestMethod.POST, "send event", "event", event.type, event); - } - - @Override - public CompletableFuture<Boolean> evict(final String cacheName, final Object key) { - String json = gson.toJson(key); - return execute(RequestMethod.POST, "invalidate cache " + cacheName, "cache", cacheName, json); - } - - @Override - public CompletableFuture<Boolean> addToProjectList(String projectName) { + public CompletableFuture<Result> indexProject(String projectName, IndexEvent event) { return execute( RequestMethod.POST, + EventType.INDEX_PROJECT_UPDATE, + "index project", + "index/project", + Url.encode(projectName), + event, + event.eventCreatedOn); + } + + @Override + public CompletableFuture<Result> send(final Event event) { + return execute( + RequestMethod.POST, + EventType.EVENT_SENT, + "send event", + "event", + event.type, + event, + Instant.ofEpochSecond(event.eventCreatedOn)); + } + + @Override + public CompletableFuture<Result> evict(final String cacheName, final Object key) { + String json = gson.toJson(key); + return execute( + RequestMethod.POST, + EventType.CACHE_EVICTION, + "invalidate cache " + cacheName, + "cache", + cacheName, + json, + Instant.now()); + } + + @Override + public CompletableFuture<Result> addToProjectList(String projectName) { + return execute( + RequestMethod.POST, + EventType.PROJECT_LIST_ADDITION, "Update project_list, add ", buildProjectListEndpoint(), - Url.encode(projectName)); + Url.encode(projectName), + Instant.now()); } @Override - public CompletableFuture<Boolean> removeFromProjectList(String projectName) { + public CompletableFuture<Result> removeFromProjectList(String projectName) { return execute( RequestMethod.DELETE, + EventType.PROJECT_LIST_DELETION, "Update project_list, remove ", buildProjectListEndpoint(), - Url.encode(projectName)); + Url.encode(projectName), + Instant.now()); } @Override - public CompletableFuture<Boolean> deleteAllChangesForProject(Project.NameKey projectName) { + public CompletableFuture<Result> deleteAllChangesForProject(Project.NameKey projectName) { return execute( RequestMethod.DELETE, + EventType.INDEX_CHANGE_DELETION_ALL_OF_PROJECT, "Delete all project changes from index", "index/change", - buildAllChangesForProjectEndpoint(projectName.get())); + buildAllChangesForProjectEndpoint(projectName.get()), + Instant.now()); } private static String buildProjectListEndpoint() { return Joiner.on("/").join("cache", Constants.PROJECT_LIST); } - private CompletableFuture<Boolean> execute( - RequestMethod method, String action, String endpoint, Object id) { - return execute(method, action, endpoint, id, null); + private CompletableFuture<Result> execute( + RequestMethod method, + EventType eventType, + String action, + String endpoint, + Object id, + Instant requestStart) { + return execute(method, eventType, action, endpoint, id, null, requestStart); } - private CompletableFuture<Boolean> execute( - RequestMethod method, String action, String endpoint, Object id, Object payload) { + private CompletableFuture<Result> execute( + RequestMethod method, + EventType eventType, + String action, + String endpoint, + Object id, + Object payload, + Instant requestStart) { + log.atFine().log("Scheduling forwarding of: %s %s %s", action, id, payload); return peerInfoProvider.get().stream() - .map(peer -> createRequest(method, peer, action, endpoint, id, payload)) + .map( + peer -> + createRequest(method, eventType, peer, action, endpoint, id, payload, requestStart)) .map(r -> executor.getAsync(() -> r.execute())) .reduce( - CompletableFuture.completedFuture(true), - (a, b) -> a.thenCombine(b, (left, right) -> left && right)); + CompletableFuture.completedFuture(new Result(eventType, true)), + (a, b) -> + a.thenCombine( + b, (left, right) -> new Result(eventType, left.result() && right.result()))) + .thenApplyAsync( + result -> { + metricsRegistry.get(eventType).recordResult(result.result()); + metricsRegistry + .get(eventType) + .recordLatency(Duration.between(requestStart, Instant.now()).toMillis()); + return result; + }); } private Request createRequest( RequestMethod method, + EventType eventType, PeerInfo peer, String action, String endpoint, Object id, - Object payload) { + Object payload, + Instant createdOn) { String destination = peer.getDirectUrl(); - return new Request(action, id, destination) { + return new Request(eventType, action, id, destination) { @Override HttpResult send() throws IOException { String request = Joiner.on("/").join(destination, pluginRelativePath, endpoint, id); switch (method) { case POST: - return httpSession.post(request, payload); + return httpSession.post(request, payload, createdOn); case DELETE: default: - return httpSession.delete(request); + return httpSession.delete(request, createdOn); } } }; } protected abstract class Request { + private final EventType eventType; private final String action; private final Object key; private final String destination; private int execCnt; - Request(String action, Object key, String destination) { + Request(EventType eventType, String action, Object key, String destination) { + this.eventType = eventType; this.action = action; this.key = key; this.destination = destination; @@ -229,13 +316,13 @@ return String.format("%s:%s => %s (try #%d)", action, key, destination, execCnt); } - boolean execute() throws ForwardingException { + Result execute() { log.atFine().log("Executing %s %s towards %s", action, key, destination); try { execCnt++; tryOnce(); log.atFine().log("%s %s towards %s OK", action, key, destination); - return true; + return new Result(eventType, true); } catch (ForwardingException e) { int maxTries = cfg.http().maxTries(); log.atFine().withCause(e).log( @@ -244,10 +331,10 @@ log.atSevere().withCause(e).log( "%s %s towards %s failed with unrecoverable error; giving up", action, key, destination); - throw e; + return new Result(eventType, false, false); } } - return false; + return new Result(eventType, false); } void tryOnce() throws ForwardingException {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java index 39c9cc0..712e7f4 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestForwarderModule.java
@@ -15,10 +15,17 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; +import com.ericsson.gerrit.plugins.highavailability.forwarder.InstantTypeAdapter; +import com.google.gerrit.server.events.EventGson; +import com.google.gson.Gson; import com.google.inject.AbstractModule; +import com.google.inject.Provides; import com.google.inject.Scopes; +import com.google.inject.Singleton; import com.google.inject.TypeLiteral; import dev.failsafe.FailsafeExecutor; +import java.time.Instant; import org.apache.http.impl.client.CloseableHttpClient; public class RestForwarderModule extends AbstractModule { @@ -29,9 +36,19 @@ bind(HttpSession.class); bind(Forwarder.class).to(RestForwarder.class); - bind(new TypeLiteral<FailsafeExecutor<Boolean>>() {}) + bind(new TypeLiteral<FailsafeExecutor<Result>>() {}) .annotatedWith(RestForwarderExecutor.class) .toProvider(FailsafeExecutorProvider.class) .in(Scopes.SINGLETON); } + + @Provides + @Singleton + @RestGson + static Gson buildRestGson(@EventGson Gson eventGson) { + return eventGson + .newBuilder() + .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) + .create(); + } }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestGson.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestGson.java new file mode 100644 index 0000000..e55ddd9 --- /dev/null +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/RestGson.java
@@ -0,0 +1,24 @@ +// Copyright (C) 2026 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.lang.annotation.RetentionPolicy.RUNTIME; + +import com.google.inject.BindingAnnotation; +import java.lang.annotation.Retention; + +@BindingAnnotation +@Retention(RUNTIME) +public @interface RestGson {}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java index 0ed6160..f702d87 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeChecker.java
@@ -17,6 +17,7 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.gerrit.server.notedb.ChangeNotes; import java.io.IOException; +import java.time.Instant; import java.util.Optional; /** Encapsulates the logic of verifying the up-to-date status of a change. */ @@ -51,8 +52,8 @@ * * <p>Compute the up-to-date Change time-stamp when it is invoked for the very first time. * - * @return the Change timestamp epoch in seconds + * @return the Change timestamp instant * @throws IOException if an I/O error occurred while reading the local Change */ - Optional<Long> getComputedChangeTs() throws IOException; + Optional<Instant> getComputedChangeTs() throws IOException; }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java index 6fe0cd7..e4de705 100644 --- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java +++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImpl.java
@@ -26,6 +26,7 @@ import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; import java.io.IOException; +import java.time.Instant; import java.util.Objects; import java.util.Optional; import org.eclipse.jgit.lib.ObjectId; @@ -38,7 +39,7 @@ private final OneOffRequestContext oneOffReqCtx; private final String changeId; private final ChangeFinder changeFinder; - private Optional<Long> computedChangeTs = Optional.empty(); + private Optional<Instant> computedChangeTs = Optional.empty(); private Optional<ChangeNotes> changeNotes = Optional.empty(); public interface Factory { @@ -59,12 +60,12 @@ @Override public Optional<IndexEvent> newIndexEvent() throws IOException { - Optional<Long> changeTs = getComputedChangeTs(); + Optional<Instant> changeTs = getComputedChangeTs(); if (!changeTs.isPresent()) { return Optional.empty(); } - long ts = changeTs.get(); + Instant ts = changeTs.get(); IndexEvent event = new IndexEvent(); event.eventCreatedOn = ts; @@ -99,12 +100,9 @@ if (indexEventOption.isPresent()) { try (Repository repo = gitRepoMgr.openRepository(changeNotes.get().getProjectName())) { IndexEvent indexEvent = indexEventOption.get(); - return (computedChangeTs.get() > indexEvent.eventCreatedOn) - || (computedChangeTs.get() == indexEvent.eventCreatedOn) - && (Objects.isNull(indexEvent.targetSha) - || repositoryHas(repo, indexEvent.targetSha)) - && (Objects.isNull(indexEvent.metaSha) - || repositoryHas(repo, indexEvent.metaSha)); + return computedChangeTs.get().compareTo(indexEvent.eventCreatedOn) >= 0 + && (Objects.isNull(indexEvent.targetSha) || repositoryHas(repo, indexEvent.targetSha)) + && (Objects.isNull(indexEvent.metaSha) || repositoryHas(repo, indexEvent.metaSha)); } } return true; @@ -116,7 +114,7 @@ } @Override - public Optional<Long> getComputedChangeTs() { + public Optional<Instant> getComputedChangeTs() { if (!computedChangeTs.isPresent()) { computedChangeTs = computeLastChangeTs(); } @@ -166,7 +164,7 @@ } } - private Optional<Long> computeLastChangeTs() { + private Optional<Instant> computeLastChangeTs() { return getChangeNotes().map(this::getTsFromChange); } @@ -180,8 +178,8 @@ return ref.getTarget().getObjectId().getName(); } - private long getTsFromChange(ChangeNotes notes) { + private Instant getTsFromChange(ChangeNotes notes) { Change change = notes.getChange(); - return change.getLastUpdatedOn().toEpochMilli() / 1000; + return change.getLastUpdatedOn(); } }
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md index 615c1ac..5ee8e6c 100644 --- a/src/main/resources/Documentation/build.md +++ b/src/main/resources/Documentation/build.md
@@ -1,48 +1,95 @@ Build ===== -This plugin can be built with Bazel in the Gerrit tree. +This plugin can be built with Bazel in the Gerrit source 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. +Clone or link this plugin into the `plugins` directory of the Gerrit +source tree. Then link the plugin's module fragment into Gerrit's +`plugins` directory, replacing the placeholder file. This fragment +exposes the plugin's Bazel module and its external dependencies to +the Gerrit root module when building in-tree. ``` cd gerrit/plugins - rm external_plugin_deps.bzl - ln -s @PLUGIN@/external_plugin_deps.bzl . + rm external_plugin_deps.MODULE.bazel + ln -s @PLUGIN@/external_plugin_deps.MODULE.bazel . ``` -From Gerrit source tree issue the command: +From the Gerrit source tree run: ``` bazel build plugins/@PLUGIN@ ``` -The output is created in +The output is created in: ``` bazel-bin/plugins/@PLUGIN@/@PLUGIN@.jar ``` -To execute the tests run either one of: +To execute the tests run either of: ``` + bazel test plugins/@PLUGIN@/... bazel test --test_tag_filters=@PLUGIN@ //... bazel test plugins/@PLUGIN@:@PLUGIN@_tests ``` -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 +This project can also be imported into the Eclipse IDE. + +Add the plugin name to the `CUSTOM_PLUGINS` and +`CUSTOM_PLUGINS_TEST_DEPS` sets in Gerrit core in `tools/bzl/plugins.bzl`, and execute: ``` ./tools/eclipse/project.py ``` -How to build the Gerrit Plugin API is described in the [Gerrit -documentation](../../../Documentation/dev-bazel.html#_extension_and_plugin_api_jar_files). +### Packaged runtime JAR allowlist test + +This plugin tracks the set of third-party runtime JARs that are bundled into the plugin JAR. +A deterministic, version-agnostic manifest is generated from the plugin’s runtime classpath and +compared against the checked-in allowlist: + +`high-availability_third_party_runtime_jars.allowlist.txt` + +This acts as a guardrail to detect unintended changes to the packaged runtime dependency set. + +To run the check in Gerrit tree: + +```bash + bazel test plugins/@PLUGIN@:check_high-availability_third_party_runtime_jars +``` + +#### Updating the allowlist + +If the test fails because the packaged third-party JAR set changed, the plugin’s bundled runtime +dependencies have changed. + +If the change is expected and has been reviewed, refresh the allowlist: + +```bash + bazel build plugins/@PLUGIN@:check_high-availability_third_party_runtime_jars_manifest + cp bazel-bin/plugins/@PLUGIN@/check_high-availability_third_party_runtime_jars_manifest.txt \ + plugins/@PLUGIN@/high-availability_third_party_runtime_jars.allowlist.txt +``` + +Commit the updated allowlist along with the dependency change. + +### Gerrit-tree-only plugin checks + +This plugin contains additional guardrail tests that are meaningful only +when it is built inside the Gerrit source tree (e.g. checks comparing the +plugin’s packaged runtime jars against Gerrit’s own runtime classpath). + +Then execute: + +```bash + bazel test plugins/@PLUGIN@:high-availability_no_overlap_with_gerrit +``` + +How to build the Gerrit Plugin API is described in the +[Gerrit documentation](../../../Documentation/dev-bazel.html#_extension_and_plugin_api_jar_files). [Back to @PLUGIN@ documentation index][index]
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile index c86722e..3db728b 100644 --- a/src/test/docker/gerrit/Dockerfile +++ b/src/test/docker/gerrit/Dockerfile
@@ -11,7 +11,7 @@ nfs-utils \ && yum -y clean all -ENV GERRIT_BRANCH stable-3.13 +ENV GERRIT_BRANCH stable-3.14 # Add gerrit user RUN adduser -p -m --uid 1000 gerrit --home-dir /home/gerrit
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java index 2f5c5b7..63e6f46 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/CommandDeserializerTest.java
@@ -17,8 +17,10 @@ import static com.google.common.truth.Truth.assertThat; import com.ericsson.gerrit.plugins.highavailability.cache.Constants; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.CacheKeyJsonParser; import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gerrit.server.events.Event; import com.google.gerrit.server.events.EventGsonProvider; import com.google.gerrit.server.events.ProjectCreatedEvent; @@ -35,12 +37,14 @@ public void setUp() { Gson eventGson = new EventGsonProvider().get(); this.gson = new JGroupsForwarderModule().buildJGroupsGson(eventGson); - this.cacheKeyParser = new CacheKeyJsonParser(eventGson); + this.cacheKeyParser = new CacheKeyJsonParser(eventGson, DynamicMap.emptyMap()); } @Test public void indexAccount() { - Command cmd = gson.fromJson("{type: 'index-account', id: 100}", Command.class); + Command cmd = + gson.fromJson( + String.format("{type: '%s', id: 100}", EventType.INDEX_ACCOUNT_UPDATE), Command.class); assertThat(cmd).isInstanceOf(IndexAccount.class); IndexAccount index = (IndexAccount) cmd; assertThat(index.getId()).isEqualTo(100); @@ -49,7 +53,10 @@ @Test public void updateChangeCommand() { Command cmd = - gson.fromJson("{type: 'update-change', projectName: 'foo', id: 100}", Command.class); + gson.fromJson( + String.format( + "{type: '%s', projectName: 'foo', id: 100}", EventType.INDEX_CHANGE_UPDATE), + Command.class); assertThat(cmd).isInstanceOf(IndexChange.Update.class); IndexChange.Update update = (IndexChange.Update) cmd; assertThat(update.getId()).isEqualTo("foo~100"); @@ -60,22 +67,30 @@ public void batchUpdateChangeCommand() { Command cmd = gson.fromJson( - "{type: 'update-change', projectName: 'foo', id: 100, batchMode: 'true'}", + String.format( + "{type: '%s', projectName: 'foo', id: 100, batchMode: 'true'}", + EventType.INDEX_CHANGE_UPDATE_BATCH), Command.class); - assertThat(cmd).isInstanceOf(IndexChange.Update.class); - IndexChange.Update update = (IndexChange.Update) cmd; + assertThat(cmd).isInstanceOf(IndexChange.BatchUpdate.class); + IndexChange.BatchUpdate update = (IndexChange.BatchUpdate) cmd; assertThat(update.getId()).isEqualTo("foo~100"); assertThat(update.isBatch()).isTrue(); } @Test public void deleteChangeCommand() { - Command cmd = gson.fromJson("{type: 'delete-change', id: 100}", Command.class); + Command cmd = + gson.fromJson( + String.format("{type: '%s', id: 100}", EventType.INDEX_CHANGE_DELETION), Command.class); assertThat(cmd).isInstanceOf(IndexChange.Delete.class); IndexChange.Delete delete = (IndexChange.Delete) cmd; assertThat(delete.getId()).isEqualTo("~100"); - cmd = gson.fromJson("{type: 'delete-change', projectName: 'foo', id: 100}", Command.class); + cmd = + gson.fromJson( + String.format( + "{type: '%s', projectName: 'foo', id: 100}", EventType.INDEX_CHANGE_DELETION), + Command.class); assertThat(cmd).isInstanceOf(IndexChange.Delete.class); delete = (IndexChange.Delete) cmd; assertThat(delete.getId()).isEqualTo("foo~100"); @@ -83,7 +98,10 @@ @Test public void indexGroup() { - Command cmd = gson.fromJson("{type: 'index-group', uuid: 'foo'}", Command.class); + Command cmd = + gson.fromJson( + String.format("{type: '%s', uuid: 'foo'}", EventType.INDEX_GROUP_UPDATE), + Command.class); assertThat(cmd).isInstanceOf(IndexGroup.class); IndexGroup index = (IndexGroup) cmd; assertThat(index.getUuid()).isEqualTo("foo"); @@ -91,7 +109,10 @@ @Test public void indexProject() { - Command cmd = gson.fromJson("{type: 'index-project', projectName: 'foo'}", Command.class); + Command cmd = + gson.fromJson( + String.format("{type: '%s', projectName: 'foo'}", EventType.INDEX_PROJECT_UPDATE), + Command.class); assertThat(cmd).isInstanceOf(IndexProject.class); IndexProject index = (IndexProject) cmd; assertThat(index.getProjectName()).isEqualTo("foo"); @@ -101,8 +122,10 @@ public void postEvent() { Command cmd = gson.fromJson( - "{event: {projectName : 'foo', headName : 'refs/heads/master', type :" - + " 'project-created', eventCreatedOn:1505898779}, type : 'post-event'}", + String.format( + "{event: {projectName : 'foo', headName : 'refs/heads/master', type :" + + " 'project-created', eventCreatedOn:1505898779}, type : '%s'}", + EventType.EVENT_SENT), Command.class); assertThat(cmd).isInstanceOf(PostEvent.class); Event e = ((PostEvent) cmd).getEvent(); @@ -118,7 +141,7 @@ gson.fromJson( String.format( "{type: '%s', cacheName: '%s', keyJson: '%s'}", - EvictCache.TYPE, Constants.PROJECTS, keyJson), + EventType.CACHE_EVICTION, Constants.PROJECTS, keyJson), EvictCache.class); assertThat(cmd).isInstanceOf(EvictCache.class); EvictCache evict = (EvictCache) cmd; @@ -130,7 +153,10 @@ @Test public void addToProjectList() { - Command cmd = gson.fromJson("{type: 'add-to-project-list', projectName: 'foo'}", Command.class); + Command cmd = + gson.fromJson( + String.format("{type: '%s', projectName: 'foo'}", EventType.PROJECT_LIST_ADDITION), + Command.class); assertThat(cmd).isInstanceOf(AddToProjectList.class); AddToProjectList addToProjectList = (AddToProjectList) cmd; assertThat(addToProjectList.getProjectName()).isEqualTo("foo"); @@ -139,7 +165,9 @@ @Test public void removeFromProjectList() { Command cmd = - gson.fromJson("{type: 'remove-from-project-list', projectName: 'foo'}", Command.class); + gson.fromJson( + String.format("{type: '%s', projectName: 'foo'}", EventType.PROJECT_LIST_DELETION), + Command.class); assertThat(cmd).isInstanceOf(RemoveFromProjectList.class); RemoveFromProjectList removeFromProjectList = (RemoveFromProjectList) cmd; assertThat(removeFromProjectList.getProjectName()).isEqualTo("foo");
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java index 082b5d1..e7542b4 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/JGroupsForwarderTest.java
@@ -23,6 +23,10 @@ import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.Configuration; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry; +import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.gerrit.server.events.EventGsonProvider; import com.google.gerrit.server.git.WorkQueue; import com.google.gson.Gson; @@ -30,7 +34,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledThreadPoolExecutor; import org.jgroups.Address; import org.jgroups.blocks.MessageDispatcher; import org.jgroups.util.Rsp; @@ -38,7 +42,10 @@ import org.jgroups.util.UUID; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +@RunWith(org.mockito.junit.MockitoJUnitRunner.class) public class JGroupsForwarderTest { private static final int MAX_TRIES = 3; @@ -52,6 +59,9 @@ private MessageDispatcher dispatcher; private JGroupsForwarder forwarder; + @Mock ForwarderMetricsRegistry metricsRegistry; + @Mock ForwarderMetrics metrics; + @Before public void setUp() throws Exception { Gson eventGson = new EventGsonProvider().get(); @@ -67,10 +77,17 @@ WorkQueue workQueue = mock(WorkQueue.class); when(workQueue.createQueue(THREAD_POOLS_SIZE, "JGroupsForwarder")) - .thenReturn(Executors.newScheduledThreadPool(THREAD_POOLS_SIZE)); + .thenReturn(new ScheduledThreadPoolExecutor(THREAD_POOLS_SIZE)); + + when(metricsRegistry.get(any())).thenReturn(metrics); + forwarder = new JGroupsForwarder( - dispatcher, cfg, gson, new FailsafeExecutorProvider(cfg, workQueue).get()); + dispatcher, + cfg, + gson, + new FailsafeExecutorProvider(cfg, workQueue).get(), + metricsRegistry); } @Test @@ -78,8 +95,8 @@ RspList<Object> OK = new RspList<>(Map.of(A1, RSP_OK, A2, RSP_OK)); when(dispatcher.castMessage(any(), any(), any())).thenReturn(OK); - CompletableFuture<Boolean> result = forwarder.indexAccount(100, null); - assertThat(result.get()).isTrue(); + CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent()); + assertThat(result.get().result()).isTrue(); verify(dispatcher, times(1)).castMessage(any(), any(), any()); } @@ -90,8 +107,8 @@ RspList<Object> FAIL = new RspList<>(Map.of(A1, RSP_OK, A2, RSP_FAIL)); when(dispatcher.castMessage(any(), any(), any())).thenReturn(FAIL, OK); - CompletableFuture<Boolean> result = forwarder.indexAccount(100, null); - assertThat(result.get()).isTrue(); + CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent()); + assertThat(result.get().result()).isTrue(); verify(dispatcher, times(2)).castMessage(any(), any(), any()); } @@ -102,8 +119,8 @@ // return FAIL x MAX_TRIES when(dispatcher.castMessage(any(), any(), any())).thenReturn(FAIL, FAIL, FAIL); - CompletableFuture<Boolean> result = forwarder.indexAccount(100, null); - assertThat(result.get()).isFalse(); + CompletableFuture<Result> result = forwarder.indexAccount(100, new IndexEvent()); + assertThat(result.get().result()).isFalse(); verify(dispatcher, times(MAX_TRIES)).castMessage(any(), any(), any()); } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java index c3114de..91d291c 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/jgroups/MessageProcessorTest.java
@@ -15,10 +15,12 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.jgroups; import static com.google.common.truth.Truth.assertThat; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoInteractions; +import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheEntry; import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheNotFoundException; @@ -29,6 +31,8 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.Account; import com.google.gerrit.entities.Change; import com.google.gerrit.server.events.Event; @@ -37,14 +41,18 @@ import com.google.gerrit.server.permissions.PermissionBackendException; import com.google.gson.Gson; import java.io.IOException; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Optional; import org.jgroups.ObjectMessage; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +@RunWith(org.mockito.junit.MockitoJUnitRunner.class) public class MessageProcessorTest { private MessageProcessor processor; @@ -57,10 +65,14 @@ private ForwardedEventHandler eventHandler; private ForwardedProjectListUpdateHandler projectListUpdateHandler; + @Mock ProcessorMetrics processorMetrics; + @Mock ProcessorMetricsRegistry metricsRegistry; + private List<Object> allHandlers = new ArrayList<>(); @Before public void setUp() { + when(metricsRegistry.get(any())).thenReturn(processorMetrics); Gson eventGson = new EventGsonProvider().get(); gson = new JGroupsForwarderModule().buildJGroupsGson(eventGson); @@ -79,7 +91,8 @@ indexAccountHandler, cacheEvictionHandler, eventHandler, - projectListUpdateHandler); + projectListUpdateHandler, + metricsRegistry); } private <T> T createHandlerMock(Class<T> handlerClass) { @@ -92,7 +105,7 @@ public void indexAccount() throws IOException { int ACCOUNT_ID = 100; - IndexAccount cmd = new IndexAccount(ACCOUNT_ID); + IndexAccount cmd = new IndexAccount(ACCOUNT_ID, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(indexAccountHandler, times(1)) .index(Account.id(ACCOUNT_ID), Operation.INDEX, Optional.empty()); @@ -104,7 +117,7 @@ String PROJECT = "foo"; int CHANGE_ID = 100; - IndexChange.Update cmd = new IndexChange.Update(PROJECT, CHANGE_ID); + IndexChange.Update cmd = new IndexChange.Update(PROJECT, CHANGE_ID, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(indexChangeHandler, times(1)) .index(PROJECT + "~" + Change.id(CHANGE_ID), Operation.INDEX, Optional.empty()); @@ -116,7 +129,7 @@ String PROJECT = "foo"; int CHANGE_ID = 100; - IndexChange.Update cmd = new IndexChange.Update(PROJECT, CHANGE_ID, true); + IndexChange.BatchUpdate cmd = new IndexChange.BatchUpdate(PROJECT, CHANGE_ID, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(indexBatchChangeHandler, times(1)) .index(PROJECT + "~" + Change.id(CHANGE_ID), Operation.INDEX, Optional.empty()); @@ -128,7 +141,7 @@ String PROJECT = "foo"; int CHANGE_ID = 100; - IndexChange.Delete cmd = new IndexChange.Delete(PROJECT, CHANGE_ID); + IndexChange.Delete cmd = new IndexChange.Delete(PROJECT, CHANGE_ID, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(indexChangeHandler, times(1)) .index(PROJECT + "~" + Change.id(CHANGE_ID), Operation.DELETE, Optional.empty()); @@ -140,7 +153,7 @@ String CACHE = "foo"; String KEY_JSON = gson.toJson(100); - EvictCache cmd = new EvictCache(CACHE, KEY_JSON); + EvictCache cmd = new EvictCache(CACHE, KEY_JSON, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); CacheEntry e = CacheEntry.from(CACHE, KEY_JSON); verify(cacheEvictionHandler, times(1)).evict(e); @@ -154,7 +167,7 @@ EventTypes.register(TestEvent.TYPE, TestEvent.class); TestEvent event = new TestEvent(FOO, BAR); - PostEvent cmd = new PostEvent(event); + PostEvent cmd = new PostEvent(event, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); ArgumentCaptor<Event> captor = ArgumentCaptor.forClass(Event.class); verify(eventHandler, times(1)).dispatch(captor.capture()); @@ -169,7 +182,7 @@ public void addToProjectList() throws IOException { String PROJECT = "foo"; - AddToProjectList cmd = new AddToProjectList(PROJECT); + AddToProjectList cmd = new AddToProjectList(PROJECT, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(projectListUpdateHandler, times(1)).update(PROJECT, false); verifyOtherHandlersNotUsed(projectListUpdateHandler); @@ -179,7 +192,7 @@ public void removeFromProjectList() throws IOException { String PROJECT = "foo"; - RemoveFromProjectList cmd = new RemoveFromProjectList(PROJECT); + RemoveFromProjectList cmd = new RemoveFromProjectList(PROJECT, Instant.now()); assertThat(processor.handle(new ObjectMessage(null, gson.toJson(cmd)))).isEqualTo(true); verify(projectListUpdateHandler, times(1)).update(PROJECT, true); verifyOtherHandlersNotUsed(projectListUpdateHandler);
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java index d2088741..c08fef0 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheKeyJsonParserTest.java
@@ -17,17 +17,110 @@ import static com.google.common.truth.Truth.assertThat; import com.ericsson.gerrit.plugins.highavailability.cache.Constants; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.Weigher; import com.google.gerrit.entities.Account; import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.registration.DynamicMap; +import com.google.gerrit.extensions.registration.PrivateInternals_DynamicMapImpl; +import com.google.gerrit.extensions.registration.RegistrationHandle; +import com.google.gerrit.server.cache.CacheDef; import com.google.gerrit.server.events.EventGsonProvider; import com.google.gson.Gson; +import com.google.inject.TypeLiteral; +import com.google.inject.util.Providers; +import java.time.Duration; +import org.junit.Before; import org.junit.Test; public class CacheKeyJsonParserTest { private static final Object EMPTY_JSON = "{}"; - private final Gson gson = new EventGsonProvider().get(); - private final CacheKeyJsonParser objectUnderTest = new CacheKeyJsonParser(gson); + private final Gson gson = RestForwarderModule.buildRestGson(new EventGsonProvider().get()); + private CacheKeyJsonParser objectUnderTest; + + private PrivateInternals_DynamicMapImpl<CacheDef<?, ?>> cacheDefMap; + + @Before + public void setUp() throws Exception { + cacheDefMap = + (PrivateInternals_DynamicMapImpl<CacheDef<?, ?>>) DynamicMap.<CacheDef<?, ?>>emptyMap(); + + defineCache(Constants.GROUPS_BYMEMBER, Account.Id.class); + defineCache(Constants.ACCOUNTS, Account.Id.class); + defineCache(Constants.TOKENS, Account.Id.class); + defineCache(Constants.GROUPS, AccountGroup.Id.class); + defineCache(Constants.GROUPS_BYINCLUDE, AccountGroup.UUID.class); + defineCache(Constants.GROUPS_MEMBERS, AccountGroup.UUID.class); + + objectUnderTest = new CacheKeyJsonParser(gson, cacheDefMap); + } + + private void defineCache(String cacheName, Class<?> keyClass) { + RegistrationHandle unused = + cacheDefMap.put( + Constants.GERRIT, cacheName, Providers.of(new TestCacheDef<>(cacheName, keyClass))); + } + + static class TestCacheDef<K> implements CacheDef<K, Object> { + private final Class<K> keyClass; + private final String name; + + TestCacheDef(String name, Class<K> keyClass) { + this.name = name; + this.keyClass = keyClass; + } + + @Override + public String name() { + return name; + } + + @Override + public String configKey() { + return ""; + } + + @Override + public TypeLiteral<K> keyType() { + return TypeLiteral.get(keyClass); + } + + @Override + public TypeLiteral<Object> valueType() { + return null; + } + + @Override + public long maximumWeight() { + return 0; + } + + @Override + public Duration expireAfterWrite() { + return null; + } + + @Override + public Duration expireFromMemoryAfterAccess() { + return null; + } + + @Override + public Duration refreshAfterWrite() { + return null; + } + + @Override + public Weigher<K, Object> weigher() { + return null; + } + + @Override + public CacheLoader<K, Object> loader() { + return null; + } + } @Test public void accountIDParse() {
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java index 58c0aef..88a325e 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/CacheRestApiServletTest.java
@@ -24,6 +24,9 @@ import com.ericsson.gerrit.plugins.highavailability.cache.Constants; import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheNotFoundException; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedCacheEvictionHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; +import com.google.gerrit.extensions.registration.DynamicMap; import com.google.gson.Gson; import java.io.BufferedReader; import java.io.IOException; @@ -41,13 +44,18 @@ @Mock private HttpServletResponse responseMock; @Mock private BufferedReader readerMock; @Mock private ForwardedCacheEvictionHandler forwardedCacheEvictionHandlerMock; + @Mock private ProcessorMetricsRegistry metricsRegistry; + @Mock ProcessorMetrics metrics; private CacheRestApiServlet servlet; @Before public void setUp() { + when(metricsRegistry.get(any())).thenReturn(metrics); servlet = new CacheRestApiServlet( - forwardedCacheEvictionHandlerMock, new CacheKeyJsonParser(new Gson())); + forwardedCacheEvictionHandlerMock, + new CacheKeyJsonParser(new Gson(), DynamicMap.emptyMap()), + metricsRegistry); } @Test
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java index 7c23e29..c099c9c 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/EventRestApiServletTest.java
@@ -24,6 +24,8 @@ import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedEventHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.common.net.MediaType; import com.google.gerrit.entities.Project; import com.google.gerrit.server.events.EventDispatcher; @@ -52,8 +54,10 @@ @Mock private ForwardedEventHandler forwardedEventHandlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private EventRestApiServlet eventRestApiServlet; - private Gson gson = new EventGsonProvider().get(); + private Gson gson = RestForwarderModule.buildRestGson(new EventGsonProvider().get()); @BeforeClass public static void setup() { @@ -62,7 +66,9 @@ @Before public void createEventsRestApiServlet() throws Exception { - eventRestApiServlet = new EventRestApiServlet(forwardedEventHandlerMock, gson); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + eventRestApiServlet = + new EventRestApiServlet(forwardedEventHandlerMock, gson, metricsRegistryMock); when(requestMock.getContentType()).thenReturn(MediaType.JSON_UTF_8.toString()); } @@ -93,7 +99,7 @@ .when(dispatcher) .postEvent(any(RefReplicationDoneEvent.class)); ForwardedEventHandler forwardedEventHandler = new ForwardedEventHandler(dispatcher); - eventRestApiServlet = new EventRestApiServlet(forwardedEventHandler, gson); + eventRestApiServlet = new EventRestApiServlet(forwardedEventHandler, gson, metricsRegistryMock); eventRestApiServlet.doPost(requestMock, responseMock); verify(responseMock).setStatus(SC_NO_CONTENT); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java index 5a0833f..1e5cc3d 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ForwardedCacheEvictionHandlerIT.java
@@ -16,16 +16,15 @@ import static com.google.common.truth.Truth.assertThat; -import com.ericsson.gerrit.plugins.highavailability.forwarder.CacheEntry; -import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedCacheEvictionHandler; import com.google.common.cache.RemovalNotification; import com.google.common.collect.Maps; import com.google.common.collect.Sets; -import com.google.gerrit.acceptance.*; +import com.google.gerrit.acceptance.LightweightPluginDaemonTest; +import com.google.gerrit.acceptance.TestPlugin; import com.google.gerrit.extensions.registration.DynamicSet; import com.google.gerrit.extensions.registration.RegistrationHandle; import com.google.gerrit.server.cache.CacheRemovalListener; -import com.google.gerrit.server.events.EventGson; +import com.google.gerrit.server.events.EventGsonProvider; import com.google.gerrit.server.project.ProjectCacheImpl; import com.google.gson.Gson; import com.google.inject.Inject; @@ -39,8 +38,6 @@ import org.junit.Before; import org.junit.Test; -@NoHttpd -@UseSsh @TestPlugin( name = "high-availability", sysModule = "com.ericsson.gerrit.plugins.highavailability.Module", @@ -52,9 +49,7 @@ @Inject private DynamicSet<CacheRemovalListener> cacheRemovalListeners; - @Inject @EventGson private Gson gson; - @Inject private ForwardedCacheEvictionHandler objectUnderTest; - @Inject private CacheKeyJsonParser gsonParser; + private Gson gson = RestForwarderModule.buildRestGson(new EventGsonProvider().get()); private CacheEvictionsTracker<?, ?> evictionsCacheTracker; private RegistrationHandle cacheEvictionRegistrationHandle; @@ -106,8 +101,10 @@ @Test public void shouldEvictProjectCache() throws Exception { - Object parsedKey = gsonParser.fromJson(ProjectCacheImpl.CACHE_NAME, gson.toJson(project)); - objectUnderTest.evict(CacheEntry.from(ProjectCacheImpl.CACHE_NAME, parsedKey)); + adminRestSession + .post( + "/plugins/high-availability/cache/" + ProjectCacheImpl.CACHE_NAME, gson.toJson(project)) + .assertNoContent(); evictionsCacheTracker.waitForExpectedEvictions(); assertThat(evictionsCacheTracker.trackedEvictionsFor(ProjectCacheImpl.CACHE_NAME))
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java index 4d874dd..a0bcf10 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/HttpSessionTest.java
@@ -31,6 +31,7 @@ import com.google.gson.Gson; import java.net.SocketTimeoutException; import java.time.Duration; +import java.time.Instant; import org.junit.Before; import org.junit.Rule; import org.junit.Test; @@ -80,7 +81,7 @@ wireMockRule.givenThat( post(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(NO_CONTENT))); - assertThat(httpSession.post(uri).isSuccessful()).isTrue(); + assertThat(httpSession.post(uri, Instant.now()).isSuccessful()).isTrue(); } @Test @@ -89,7 +90,7 @@ post(urlEqualTo(ENDPOINT)) .withRequestBody(equalTo(BODY)) .willReturn(aResponse().withStatus(NO_CONTENT))); - assertThat(httpSession.post(uri, BODY).isSuccessful()).isTrue(); + assertThat(httpSession.post(uri, BODY, Instant.now()).isSuccessful()).isTrue(); } @Test @@ -97,7 +98,7 @@ wireMockRule.givenThat( delete(urlEqualTo(ENDPOINT)).willReturn(aResponse().withStatus(NO_CONTENT))); - assertThat(httpSession.delete(uri).isSuccessful()).isTrue(); + assertThat(httpSession.delete(uri, Instant.now()).isSuccessful()).isTrue(); } @Test @@ -107,7 +108,7 @@ post(urlEqualTo(ENDPOINT)) .willReturn(aResponse().withStatus(UNAUTHORIZED).withBody(expected))); - HttpResult result = httpSession.post(uri); + HttpResult result = httpSession.post(uri, Instant.now()); assertThat(result.isSuccessful()).isFalse(); assertThat(result.getMessage()).isEqualTo(expected); } @@ -119,7 +120,7 @@ post(urlEqualTo(ENDPOINT)) .willReturn(aResponse().withStatus(NOT_FOUND).withBody(expected))); - HttpResult result = httpSession.post(uri); + HttpResult result = httpSession.post(uri, Instant.now()); assertThat(result.isSuccessful()).isFalse(); assertThat(result.getMessage()).isEqualTo(expected); } @@ -130,7 +131,7 @@ post(urlEqualTo(ENDPOINT)) .willReturn(aResponse().withStatus(ERROR).withBody(ERROR_MESSAGE))); - HttpResult result = httpSession.post(uri); + HttpResult result = httpSession.post(uri, Instant.now()); assertThat(result.isSuccessful()).isFalse(); assertThat(result.getMessage()).isEqualTo(ERROR_MESSAGE); } @@ -161,7 +162,7 @@ .whenScenarioStateIs(THIRD_TRY) .willReturn(aResponse().withFixedDelay((int) TIMEOUT.toMillis()))); - httpSession.post(uri); + httpSession.post(uri, Instant.now()); } @Test @@ -170,6 +171,6 @@ post(urlEqualTo(ENDPOINT)) .willReturn(aResponse().withFault(Fault.MALFORMED_RESPONSE_CHUNK))); - assertThat(httpSession.post(uri).isSuccessful()).isFalse(); + assertThat(httpSession.post(uri, Instant.now()).isSuccessful()).isFalse(); } }
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 83102a9..49ce5f8 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
@@ -26,7 +26,10 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexAccountHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.Account; +import com.google.gson.Gson; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -44,13 +47,16 @@ @Mock private ForwardedIndexAccountHandler handlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private Account.Id id; private IndexAccountRestApiServlet servlet; @Before public void setUpMocks() { - servlet = new IndexAccountRestApiServlet(handlerMock); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + servlet = new IndexAccountRestApiServlet(handlerMock, new Gson(), metricsRegistryMock); id = Account.id(ACCOUNT_NUMBER); when(requestMock.getRequestURI()) .thenReturn("http://gerrit.com/index/account/" + ACCOUNT_NUMBER);
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 3da35d4..a19fda6 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
@@ -25,6 +25,8 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexChangeHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gson.Gson; import java.io.IOException; import javax.servlet.http.HttpServletRequest; @@ -46,12 +48,15 @@ @Mock private ForwardedIndexChangeHandler handlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private IndexChangeRestApiServlet servlet; @Before public void setUpMocks() { - servlet = new IndexChangeRestApiServlet(handlerMock, new Gson()); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + servlet = new IndexChangeRestApiServlet(handlerMock, new Gson(), metricsRegistryMock); when(requestMock.getRequestURI()) .thenReturn("http://gerrit.com/index/change/" + PROJECT_NAME_URL_ENC + "~" + CHANGE_NUMBER); }
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 index fb5788d..0e87bf7 100644 --- 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
@@ -26,7 +26,10 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexGroupHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.AccountGroup; +import com.google.gson.Gson; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -44,13 +47,16 @@ @Mock private ForwardedIndexGroupHandler handlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private AccountGroup.UUID uuid; private IndexGroupRestApiServlet servlet; @Before public void setUpMocks() { - servlet = new IndexGroupRestApiServlet(handlerMock); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + servlet = new IndexGroupRestApiServlet(handlerMock, new Gson(), metricsRegistryMock); uuid = AccountGroup.uuid(UUID); when(requestMock.getRequestURI()).thenReturn("http://gerrit.com/index/group/" + UUID); }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java index da6e70d..d74a43f 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/IndexProjectRestApiServletTest.java
@@ -26,8 +26,11 @@ import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexProjectHandler; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedIndexingHandler.Operation; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.restapi.Url; +import com.google.gson.Gson; import java.io.IOException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -45,13 +48,16 @@ @Mock private ForwardedIndexProjectHandler handlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private Project.NameKey nameKey; private IndexProjectRestApiServlet servlet; @Before public void setUpMocks() { - servlet = new IndexProjectRestApiServlet(handlerMock); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + servlet = new IndexProjectRestApiServlet(handlerMock, new Gson(), metricsRegistryMock); nameKey = Project.nameKey(PROJECT_NAME); when(requestMock.getRequestURI()) .thenReturn("http://gerrit.com/index/project/" + Url.encode(nameKey.get()));
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletTest.java index 38ab488..fd0813e 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/forwarder/rest/ProjectListRestApiServletTest.java
@@ -15,11 +15,14 @@ package com.ericsson.gerrit.plugins.highavailability.forwarder.rest; import static javax.servlet.http.HttpServletResponse.SC_NO_CONTENT; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwardedProjectListUpdateHandler; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ProcessorMetricsRegistry; import com.google.gerrit.extensions.restapi.Url; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -36,12 +39,15 @@ @Mock private ForwardedProjectListUpdateHandler handlerMock; @Mock private HttpServletRequest requestMock; @Mock private HttpServletResponse responseMock; + @Mock private ProcessorMetricsRegistry metricsRegistryMock; + @Mock private ProcessorMetrics metrics; private ProjectListApiServlet servlet; @Before public void setUpMocks() { - servlet = new ProjectListApiServlet(handlerMock); + when(metricsRegistryMock.get(any())).thenReturn(metrics); + servlet = new ProjectListApiServlet(handlerMock, metricsRegistryMock); when(requestMock.getRequestURI()) .thenReturn( "http://hostname/plugins/high-availability/cache/project_list/"
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 4b98fe1..78d21ef 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
@@ -19,12 +19,13 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.cache.Constants; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetrics; +import com.ericsson.gerrit.plugins.highavailability.forwarder.ForwarderMetricsRegistry; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.ericsson.gerrit.plugins.highavailability.forwarder.TestEvent; import com.ericsson.gerrit.plugins.highavailability.forwarder.rest.HttpResponseHandler.HttpResult; @@ -35,19 +36,20 @@ import com.google.gerrit.entities.AccountGroup; import com.google.gerrit.entities.Project; import com.google.gerrit.server.events.Event; -import com.google.gerrit.server.git.WorkQueue; import com.google.gson.Gson; import com.google.inject.Provider; import java.io.IOException; import java.time.Duration; import java.util.Set; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import javax.net.ssl.SSLException; import org.junit.Before; import org.junit.Test; +import org.junit.runner.RunWith; import org.mockito.Answers; +import org.mockito.Mock; +@RunWith(org.mockito.junit.MockitoJUnitRunner.class) public class RestForwarderTest { private static final String URL = "http://fake.com"; private static final String PLUGIN_NAME = "high-availability"; @@ -111,6 +113,9 @@ private Configuration configMock; Provider<Set<PeerInfo>> peersMock; + @Mock ForwarderMetricsRegistry metricsRegistry; + @Mock ForwarderMetrics metrics; + @SuppressWarnings("unchecked") @Before public void setUp() { @@ -121,9 +126,7 @@ when(configMock.http().threadPoolSize()).thenReturn(2); peersMock = mock(Provider.class); when(peersMock.get()).thenReturn(ImmutableSet.of(new PeerInfo(URL))); - WorkQueue workQueue = mock(WorkQueue.class); - when(workQueue.createQueue(configMock.http().threadPoolSize(), "RestForwarderScheduler")) - .thenReturn(Executors.newScheduledThreadPool(2)); + when(metricsRegistry.get(any())).thenReturn(metrics); forwarder = new RestForwarder( httpSessionMock, @@ -131,188 +134,228 @@ configMock, peersMock, gson, // TODO: Create provider - new FailsafeExecutorProvider(configMock).get()); + new FailsafeExecutorProvider(configMock).get(), + metricsRegistry); } @Test public void testIndexAccountOK() throws Exception { - when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any(), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); assertThat( forwarder .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testIndexAccountFailed() throws Exception { - when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any(), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); assertThat( forwarder .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexAccountThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_ACCOUNT_ENDPOINT), any()); + when(httpSessionMock.post(eq(INDEX_ACCOUNT_ENDPOINT), any(), any())) + .thenThrow(IOException.class); assertThat( forwarder .indexAccount(ACCOUNT_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexGroupOK() throws Exception { - when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder + .indexGroup(UUID, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testIndexGroupFailed() throws Exception { - when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder + .indexGroup(UUID, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexGroupThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_GROUP_ENDPOINT), any()); - assertThat(forwarder.indexGroup(UUID, new IndexEvent()).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + when(httpSessionMock.post(eq(INDEX_GROUP_ENDPOINT), any(), any())).thenThrow(IOException.class); + assertThat( + forwarder + .indexGroup(UUID, new IndexEvent()) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexChangeOK() throws Exception { - when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any(), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); assertThat( forwarder .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testIndexChangeFailed() throws Exception { - when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any(), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); assertThat( forwarder .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexChangeThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_CHANGE_ENDPOINT), any()); + when(httpSessionMock.post(eq(INDEX_CHANGE_ENDPOINT), any(), any())) + .thenThrow(IOException.class); assertThat( forwarder .indexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testIndexBatchChangeOK() throws Exception { - when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + assertThat( + forwarder + .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get() + .result()) .isTrue(); } @Test public void testIndexBatchChangeFailed() throws Exception { - when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any())) + when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + assertThat( + forwarder + .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get() + .result()) .isFalse(); } @Test public void testIndexBatchChangeThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any()); - assertThat(forwarder.batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()).get()) + when(httpSessionMock.post(eq(INDEX_BATCH_CHANGE_ENDPOINT), any(), any())) + .thenThrow(IOException.class); + assertThat( + forwarder + .batchIndexChange(PROJECT_NAME, CHANGE_NUMBER, new IndexEvent()) + .get() + .result()) .isFalse(); } @Test public void testChangeDeletedFromIndexOK() throws Exception { - when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT))) + when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); assertThat( forwarder .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testAllChangesDeletedFromIndexOK() throws Exception { - when(httpSessionMock.delete(eq(DELETE_ALL_CHANGES_ENDPOINT))) + when(httpSessionMock.delete(eq(DELETE_ALL_CHANGES_ENDPOINT), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); assertThat( forwarder .deleteAllChangesForProject(Project.nameKey(PROJECT_NAME)) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testChangeDeletedFromIndexFailed() throws Exception { - when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT))) + when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); assertThat( forwarder .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testChangeDeletedFromThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).delete(eq(DELETE_CHANGE_ENDPOINT)); + when(httpSessionMock.delete(eq(DELETE_CHANGE_ENDPOINT), any())).thenThrow(IOException.class); assertThat( forwarder .deleteChangeFromIndex(CHANGE_NUMBER, new IndexEvent()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testEventSentOK() throws Exception { - when(httpSessionMock.post(EVENT_ENDPOINT, event)) + when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isTrue(); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()).isTrue(); } @Test public void testEventSentFailed() throws Exception { - when(httpSessionMock.post(EVENT_ENDPOINT, event)).thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse(); + when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any())) + .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()).isFalse(); } @Test public void testEventSentThrowsException() throws Exception { - doThrow(new IOException()).when(httpSessionMock).post(EVENT_ENDPOINT, event); - assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)).isFalse(); + when(httpSessionMock.post(eq(EVENT_ENDPOINT), eq(event), any())).thenThrow(IOException.class); + assertThat(forwarder.send(event).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()).isFalse(); } @Test public void testEvictProjectOK() throws Exception { String key = PROJECT_NAME; String keyJson = gson.toJson(key); - when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson)) + when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isTrue(); } @@ -320,9 +363,10 @@ public void testEvictAccountsOK() throws Exception { Account.Id key = Account.id(123); String keyJson = gson.toJson(key); - when(httpSessionMock.post(buildCacheEndpoint(Constants.ACCOUNTS), keyJson)) + when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.ACCOUNTS)), eq(keyJson), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder.evict(Constants.ACCOUNTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isTrue(); } @@ -331,8 +375,10 @@ AccountGroup.Id key = AccountGroup.id(123); String keyJson = gson.toJson(key); String endpoint = buildCacheEndpoint(Constants.GROUPS); - when(httpSessionMock.post(endpoint, keyJson)).thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + when(httpSessionMock.post(eq(endpoint), eq(keyJson), any())) + .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); + assertThat( + forwarder.evict(Constants.GROUPS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isTrue(); } @@ -340,10 +386,14 @@ public void testEvictGroupsByIncludeOK() throws Exception { AccountGroup.UUID key = AccountGroup.uuid("90b3042d9094a37985f3f9281391dbbe9a5addad"); String keyJson = gson.toJson(key); - when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE), keyJson)) + when(httpSessionMock.post( + eq(buildCacheEndpoint(Constants.GROUPS_BYINCLUDE)), eq(keyJson), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); assertThat( - forwarder.evict(Constants.GROUPS_BYINCLUDE, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + forwarder + .evict(Constants.GROUPS_BYINCLUDE, key) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @@ -351,9 +401,13 @@ public void testEvictGroupsMembersOK() throws Exception { AccountGroup.UUID key = AccountGroup.uuid("90b3042d9094a37985f3f9281391dbbe9a5addad"); String keyJson = gson.toJson(key); - when(httpSessionMock.post(buildCacheEndpoint(Constants.GROUPS_MEMBERS), keyJson)) + when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.GROUPS_MEMBERS)), eq(keyJson), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.GROUPS_MEMBERS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder + .evict(Constants.GROUPS_MEMBERS, key) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @@ -361,9 +415,10 @@ public void testEvictCacheFailed() throws Exception { String key = PROJECT_NAME; String keyJson = gson.toJson(key); - when(httpSessionMock.post(buildCacheEndpoint(Constants.PROJECTS), keyJson)) + when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isFalse(); } @@ -371,10 +426,10 @@ public void testEvictCacheThrowsException() throws Exception { String key = PROJECT_NAME; String keyJson = gson.toJson(key); - doThrow(new IOException()) - .when(httpSessionMock) - .post(buildCacheEndpoint(Constants.PROJECTS), keyJson); - assertThat(forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + when(httpSessionMock.post(eq(buildCacheEndpoint(Constants.PROJECTS)), eq(keyJson), any())) + .thenThrow(IOException.class); + assertThat( + forwarder.evict(Constants.PROJECTS, key).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isFalse(); } @@ -385,56 +440,69 @@ @Test public void testAddToProjectListOK() throws Exception { String projectName = PROJECT_TO_ADD; - when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null)) + when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isTrue(); } @Test public void testAddToProjectListFailed() throws Exception { String projectName = PROJECT_TO_ADD; - when(httpSessionMock.post(buildProjectListCacheEndpoint(projectName), null)) + when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isFalse(); } @Test public void testAddToProjectListThrowsException() throws Exception { String projectName = PROJECT_TO_ADD; - doThrow(new IOException()) - .when(httpSessionMock) - .post(buildProjectListCacheEndpoint(projectName), null); - assertThat(forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + when(httpSessionMock.post(eq(buildProjectListCacheEndpoint(projectName)), any(), any())) + .thenThrow(IOException.class); + assertThat( + forwarder.addToProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS).result()) .isFalse(); } @Test public void testRemoveFromProjectListOK() throws Exception { String projectName = PROJECT_TO_DELETE; - when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName))) + when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any())) .thenReturn(new HttpResult(SUCCESSFUL, EMPTY_MSG)); - assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder + .removeFromProjectList(projectName) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testRemoveToProjectListFailed() throws Exception { String projectName = PROJECT_TO_DELETE; - when(httpSessionMock.delete(buildProjectListCacheEndpoint(projectName))) + when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any())) .thenReturn(new HttpResult(FAILED, EMPTY_MSG)); - assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + assertThat( + forwarder + .removeFromProjectList(projectName) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testRemoveToProjectListThrowsException() throws Exception { String projectName = PROJECT_TO_DELETE; - doThrow(new IOException()) - .when(httpSessionMock) - .delete((buildProjectListCacheEndpoint(projectName))); - assertThat(forwarder.removeFromProjectList(projectName).get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + when(httpSessionMock.delete(eq(buildProjectListCacheEndpoint(projectName)), any())) + .thenThrow(IOException.class); + assertThat( + forwarder + .removeFromProjectList(projectName) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @@ -444,7 +512,7 @@ @Test public void testRetryOnErrorThenSuccess() throws Exception { - when(httpSessionMock.post(anyString(), anyString())) + when(httpSessionMock.post(anyString(), anyString(), any())) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(true, SUCCESS)); @@ -452,13 +520,14 @@ assertThat( forwarder .evict(Constants.PROJECT_LIST, new Object()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testRetryOnIoExceptionThenSuccess() throws Exception { - when(httpSessionMock.post(anyString(), anyString())) + when(httpSessionMock.post(anyString(), anyString(), any())) .thenThrow(new IOException()) .thenThrow(new IOException()) .thenReturn(new HttpResult(true, SUCCESS)); @@ -466,26 +535,28 @@ assertThat( forwarder .evict(Constants.PROJECT_LIST, new Object()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isTrue(); } @Test public void testNoRetryAfterNonRecoverableException() throws Exception { - when(httpSessionMock.post(anyString(), anyString())) + when(httpSessionMock.post(anyString(), anyString(), any())) .thenThrow(new SSLException("Non Recoverable")) .thenReturn(new HttpResult(true, SUCCESS)); assertThat( forwarder .evict(Constants.PROJECT_LIST, new Object()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } @Test public void testFailureAfterMaxTries() throws Exception { - when(httpSessionMock.post(anyString(), anyString())) + when(httpSessionMock.post(anyString(), anyString(), any())) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)) .thenReturn(new HttpResult(false, ERROR)); @@ -493,7 +564,8 @@ assertThat( forwarder .evict(Constants.PROJECT_LIST, new Object()) - .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS)) + .get(TEST_TIMEOUT, TEST_TIMEOUT_UNITS) + .result()) .isFalse(); } }
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java index dfea1df..460d56b 100644 --- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java +++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/index/ChangeCheckerImplTest.java
@@ -24,7 +24,6 @@ import com.google.gerrit.server.notedb.ChangeNotes; import com.google.gerrit.server.util.OneOffRequestContext; import java.io.IOException; -import java.sql.Timestamp; import java.time.Instant; import java.util.Optional; import org.junit.Before; @@ -45,7 +44,7 @@ private final Instant testLastUpdatedOn = Instant.now(); private final String changeId = "1"; Optional<IndexEvent> event = Optional.empty(); - private Optional<Long> computedChangeTs = Optional.empty(); + private Optional<Instant> computedChangeTs = Optional.empty(); private ChangeCheckerImpl changeChecker; @Before @@ -61,8 +60,7 @@ @Test public void testGetComputedChangeTs() { - long testTime = Timestamp.from(testLastUpdatedOn).getTime(); - computedChangeTs = Optional.of(testTime / 1000); + computedChangeTs = Optional.of(testLastUpdatedOn); when(changeChecker.getChangeNotes()).thenReturn(Optional.of(testChangeNotes)); when(testChangeNotes.getChange()).thenReturn(testChange); when(testChange.getLastUpdatedOn()).thenReturn(testLastUpdatedOn);
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 a8aa4a9..a986b90 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,7 +24,9 @@ import com.ericsson.gerrit.plugins.highavailability.Configuration; import com.ericsson.gerrit.plugins.highavailability.forwarder.Context; +import com.ericsson.gerrit.plugins.highavailability.forwarder.EventType; import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder; +import com.ericsson.gerrit.plugins.highavailability.forwarder.Forwarder.Result; import com.ericsson.gerrit.plugins.highavailability.forwarder.IndexEvent; import com.google.gerrit.entities.Account; import com.google.gerrit.entities.AccountGroup; @@ -75,12 +77,17 @@ when(changeCheckerMock.newIndexEvent()).thenReturn(Optional.of(new IndexEvent())); when(forwarder.indexAccount(eq(ACCOUNT_ID), any())) - .thenReturn(CompletableFuture.completedFuture(true)); + .thenReturn( + CompletableFuture.completedFuture(new Result(EventType.INDEX_ACCOUNT_UPDATE, true))); when(forwarder.deleteChangeFromIndex(eq(CHANGE_ID), any())) - .thenReturn(CompletableFuture.completedFuture(true)); - when(forwarder.indexGroup(eq(UUID), any())).thenReturn(CompletableFuture.completedFuture(true)); + .thenReturn( + CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_DELETION, true))); + when(forwarder.indexGroup(eq(UUID), any())) + .thenReturn( + CompletableFuture.completedFuture(new Result(EventType.INDEX_GROUP_UPDATE, true))); when(forwarder.indexChange(eq(PROJECT_NAME), eq(CHANGE_ID), any())) - .thenReturn(CompletableFuture.completedFuture(true)); + .thenReturn( + CompletableFuture.completedFuture(new Result(EventType.INDEX_CHANGE_UPDATE, true))); setUpIndexEventHandler(currCtx); }