Merge branch 'stable-3.6' into stable-3.7

* stable-3.6:
  Remove `@NoHttpd` from IT tests that interact with Gerrit Rest API

Change-Id: Ia25c8da867a3e68251e2f1b7cef6fd7efad3a4d3
diff --git a/BUILD b/BUILD
index 7f32bc1..80d202b 100644
--- a/BUILD
+++ b/BUILD
@@ -21,6 +21,7 @@
     resources = glob(["src/main/resources/**/*"]),
     deps = [
       "@jgroups//jar",
+      "@jgroups-kubernetes//jar",
       "@global-refdb//jar:neverlink",
     ],
 )
diff --git a/external_plugin_deps.bzl b/external_plugin_deps.bzl
index eaa632e..09f34de 100644
--- a/external_plugin_deps.bzl
+++ b/external_plugin_deps.bzl
@@ -9,12 +9,18 @@
 
     maven_jar(
         name = "jgroups",
-        artifact = "org.jgroups:jgroups:3.6.15.Final",
-        sha1 = "755afcfc6c8a8ea1e15ef0073417c0b6e8c6d6e4",
+        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.6.3.1",
-        sha1 = "0f5229856d6a17e9c2382c8a404f43851f1f0287",
+        artifact = "com.gerritforge:global-refdb:3.7.4",
+        sha1 = "a5f3fcdbc04b7e98c52ecd50d2a56424e60b0575",
     )
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
index e0d5a50..fd5f2cb 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Configuration.java
@@ -58,6 +58,9 @@
   // common parameter to peerInfo section
   static final String PEER_INFO_SECTION = "peerInfo";
 
+  // common parameter to jgroups section
+  static final String JGROUPS_SECTION = "jgroups";
+
   // common parameters to cache and index sections
   static final String THREAD_POOL_SIZE_KEY = "threadPoolSize";
   static final String BATCH_THREAD_POOL_SIZE_KEY = "batchThreadPoolSize";
@@ -70,6 +73,7 @@
   private final AutoReindex autoReindex;
   private final PeerInfo peerInfo;
   private final JGroups jgroups;
+  private final JGroupsKubernetes jgroupsKubernetes;
   private final Http http;
   private final Cache cache;
   private final Event event;
@@ -106,6 +110,7 @@
         throw new IllegalArgumentException("Not supported strategy: " + peerInfo.strategy);
     }
     jgroups = new JGroups(site, cfg);
+    jgroupsKubernetes = new JGroupsKubernetes(cfg);
     http = new Http(cfg);
     cache = new Cache(cfg);
     event = new Event(cfg);
@@ -152,6 +157,10 @@
     return jgroups;
   }
 
+  public JGroupsKubernetes jgroupsKubernetes() {
+    return jgroupsKubernetes;
+  }
+
   public Http http() {
     return http;
   }
@@ -316,9 +325,9 @@
   }
 
   public static class JGroups {
-    static final String JGROUPS_SECTION = "jgroups";
     static final String SKIP_INTERFACE_KEY = "skipInterface";
     static final String CLUSTER_NAME_KEY = "clusterName";
+    static final String KUBERNETES_KEY = "kubernetes";
     static final String PROTOCOL_STACK_KEY = "protocolStack";
     static final ImmutableList<String> DEFAULT_SKIP_INTERFACE_LIST =
         ImmutableList.of("lo*", "utun*", "awdl*");
@@ -326,6 +335,7 @@
 
     private final ImmutableList<String> skipInterface;
     private final String clusterName;
+    private final boolean useKubernetes;
     private final Optional<Path> protocolStack;
 
     private JGroups(SitePaths site, Config cfg) {
@@ -334,6 +344,7 @@
       log.atFine().log("Skip interface(s): %s", skipInterface);
       clusterName = getString(cfg, JGROUPS_SECTION, null, CLUSTER_NAME_KEY, DEFAULT_CLUSTER_NAME);
       log.atFine().log("Cluster name: %s", clusterName);
+      useKubernetes = cfg.getBoolean(JGROUPS_SECTION, KUBERNETES_KEY, false);
       protocolStack = getProtocolStack(cfg, site);
       log.atFine().log(
           "Protocol stack config %s",
@@ -362,6 +373,32 @@
     public String clusterName() {
       return clusterName;
     }
+
+    public boolean useKubernetes() {
+      return useKubernetes;
+    }
+  }
+
+  public static class JGroupsKubernetes {
+    static final String KUBERNETES_SUBSECTION = "kubernetes";
+    static final String NAMESPACE_KEY = "namespace";
+    static final String LABEL_KEY = "label";
+
+    private final String namespace;
+    private final List<String> labels;
+
+    public JGroupsKubernetes(Config cfg) {
+      namespace = cfg.getString(JGROUPS_SECTION, KUBERNETES_SUBSECTION, NAMESPACE_KEY);
+      labels = Arrays.asList(cfg.getStringList(JGROUPS_SECTION, KUBERNETES_SUBSECTION, LABEL_KEY));
+    }
+
+    public String namespace() {
+      return namespace;
+    }
+
+    public List<String> labels() {
+      return labels;
+    }
   }
 
   public static class Http {
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/EnvModule.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/EnvModule.java
new file mode 100644
index 0000000..d503f09
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/EnvModule.java
@@ -0,0 +1,36 @@
+// Copyright (C) 2023 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;
+
+import com.google.common.base.CharMatcher;
+import com.google.gerrit.common.Nullable;
+import com.google.inject.AbstractModule;
+import com.google.inject.name.Names;
+
+public class EnvModule extends AbstractModule {
+  public static final String MY_URL_ENV_VAR = "GERRIT_URL";
+
+  @Override
+  protected void configure() {
+    bind(String.class)
+        .annotatedWith(Names.named(MY_URL_ENV_VAR))
+        .toInstance(urlWithTrimmedTrailingSlash(System.getenv(MY_URL_ENV_VAR)));
+  }
+
+  @Nullable
+  private static String urlWithTrimmedTrailingSlash(@Nullable String in) {
+    return in == null ? "" : CharMatcher.is('/').trimTrailingFrom(in);
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
index ddc294b..688e567 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/Module.java
@@ -39,6 +39,7 @@
 
   @Override
   protected void configure() {
+    install(new EnvModule());
     install(new ForwarderModule());
     install(new RestForwarderModule());
 
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
index 0e3fdc0..68efbec 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsPeerInfoProvider.java
@@ -30,7 +30,8 @@
 import org.jgroups.Address;
 import org.jgroups.JChannel;
 import org.jgroups.Message;
-import org.jgroups.ReceiverAdapter;
+import org.jgroups.ObjectMessage;
+import org.jgroups.Receiver;
 import org.jgroups.View;
 
 /**
@@ -44,8 +45,8 @@
  * cluster.
  */
 @Singleton
-public class JGroupsPeerInfoProvider extends ReceiverAdapter
-    implements Provider<Set<PeerInfo>>, LifecycleListener {
+public class JGroupsPeerInfoProvider
+    implements Receiver, Provider<Set<PeerInfo>>, LifecycleListener {
   private static final FluentLogger log = FluentLogger.forEnclosingClass();
   private static final String JGROUPS_LOG_FACTORY_PROPERTY = "jgroups.logging.log_factory_class";
 
@@ -56,6 +57,7 @@
   }
 
   private final Configuration.JGroups jgroupsConfig;
+  private final Configuration.JGroupsKubernetes jgroupsKubernetesConfig;
   private final InetAddressFinder finder;
   private final String myUrl;
 
@@ -67,6 +69,7 @@
   JGroupsPeerInfoProvider(
       Configuration pluginConfiguration, InetAddressFinder finder, MyUrlProvider myUrlProvider) {
     this.jgroupsConfig = pluginConfiguration.jgroups();
+    this.jgroupsKubernetesConfig = pluginConfiguration.jgroupsKubernetes();
     this.finder = finder;
     this.myUrl = myUrlProvider.get();
   }
@@ -105,7 +108,7 @@
     }
     if (view.size() > 1) {
       try {
-        channel.send(new Message(null, myUrl));
+        channel.send(new ObjectMessage(null, myUrl));
       } catch (Exception e) {
         // channel communication caused an error. Can't do much about it.
         log.atSevere().withCause(e).log(
@@ -149,6 +152,16 @@
       if (protocolStack.isPresent()) {
         return new JChannel(protocolStack.get().toString());
       }
+      if (jgroupsConfig.useKubernetes()) {
+        if (jgroupsKubernetesConfig.namespace() != null) {
+          System.setProperty("KUBERNETES_NAMESPACE", jgroupsKubernetesConfig.namespace());
+        }
+        if (!jgroupsKubernetesConfig.labels().isEmpty()) {
+          System.setProperty(
+              "KUBERNETES_LABELS", String.join(",", jgroupsKubernetesConfig.labels()));
+        }
+        return new JChannel(getClass().getResource("kubernetes.xml").toString());
+      }
       return new JChannel();
     } catch (Exception e) {
       log.atSevere().withCause(e).log(
diff --git a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
index a257eb3..d972e08 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProvider.java
@@ -14,6 +14,8 @@
 
 package com.ericsson.gerrit.plugins.highavailability.peers.jgroups;
 
+import static com.ericsson.gerrit.plugins.highavailability.EnvModule.MY_URL_ENV_VAR;
+
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
 import com.google.common.base.CharMatcher;
 import com.google.common.flogger.FluentLogger;
@@ -22,6 +24,7 @@
 import com.google.inject.Provider;
 import com.google.inject.ProvisionException;
 import com.google.inject.Singleton;
+import com.google.inject.name.Named;
 import java.net.InetAddress;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
@@ -40,8 +43,12 @@
   private final String myUrl;
 
   @Inject
-  MyUrlProvider(@GerritServerConfig Config srvConfig, Configuration pluginConfiguration) {
+  MyUrlProvider(
+      @GerritServerConfig Config srvConfig,
+      Configuration pluginConfiguration,
+      @Named(MY_URL_ENV_VAR) String myUrlEnvVar) {
     String url = pluginConfiguration.peerInfoJGroups().myUrl();
+    url = url == null ? myUrlEnvVar : url;
     if (url == null) {
       log.atInfo().log("myUrl not configured; attempting to determine from %s", LISTEN_URL);
       try {
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index d94a91e..2d9c10c 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -98,7 +98,11 @@
     configured, add as many url entries as necessary.
 
 ```peerInfo.jgroups.myUrl```
-:   The URL of this instance to be broadcast to other peers. If not specified, the
+:   The URL of this instance to be broadcast to other peers. Alternatively, this URL
+    can also be specified using the environment variable `GERRIT_URL`. This is useful
+    in environments like Kubernetes, where manual configuration of each Gerrit
+    instance is not possible.
+    If neither the configuration option nor the system property is specified, the
     URL is determined from the `httpd.listenUrl` in the `gerrit.config`.
     If `httpd.listenUrl` is configured with multiple values, is configured to work
     with a reverse proxy (i.e. uses `proxy-http` or `proxy-https` scheme), or is
@@ -127,6 +131,22 @@
     its configuration file syntax please refer to JGroups documentation.
     See [JGroups - Advanced topics](http://jgroups.org/manual-3.x/html/user-advanced.html).
 
+```jgroups.kubernetes```
+:   If true, a protocol stack optimized for Kubernetes will be used. Peers will be discovered
+    by querying the Kubernetes API server for pods. The functionality is provided by the
+    [jgroups-kubernetes extension](https://github.com/jgroups-extras/jgroups-kubernetes).
+    To enable Gerrit to use the Kubernetes API, the pods require a ServiceAccount with
+    permissions to list pods ([example](https://github.com/jgroups-extras/jgroups-kubernetes#demo)).
+    Further, Gerrit requires a valid TLS certificate in its keystore, since the Kubernetes
+    API server requires TLS. (Default: false)
+
+```jgroups.kubernetes.namespace```
+:   The namespace in which to query for pods. (Default: default)
+
+```jgroups.kubernetes.label```
+:   A label that will be used to select the pods in the format `label=value`. Can be set
+    multiple times.
+
 NOTE: To work properly in certain environments, JGroups needs the System property
 `java.net.preferIPv4Stack` to be set to `true`.
 See [JGroups - Trouble shooting](http://jgroups.org/tutorial/index.html#_trouble_shooting).
diff --git a/src/main/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/kubernetes.xml b/src/main/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/kubernetes.xml
new file mode 100644
index 0000000..6e89dbc
--- /dev/null
+++ b/src/main/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/kubernetes.xml
@@ -0,0 +1,49 @@
+<!--
+    Based on a configuration written by Bela Ban [1].
+
+    [1] https://koudingspawn.de/jgroups-on-kubernetes/
+-->
+<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+  xmlns="urn:org:jgroups"
+  xsi:schemaLocation="urn:org:jgroups http://www.jgroups.org/schema/jgroups.xsd"
+>
+  <TCP bind_addr="loopback,match-interface:eth0"
+    bind_port="7800"
+    external_addr="match-interface:eth0"
+    thread_pool.min_threads="0"
+    thread_pool.max_threads="200"
+    thread_pool.keep_alive_time="30000" />
+  <RED />
+
+  <org.jgroups.protocols.kubernetes.KUBE_PING
+    port_range="1"
+    namespace="${KUBERNETES_NAMESPACE:default}"
+    labels="${KUBERNETES_LABELS:app=gerrit}"
+  />
+
+  <MERGE3 max_interval="30000"
+    min_interval="10000" />
+  <FD_SOCK external_addr="${JGROUPS_EXTERNAL_ADDR}"
+    start_port="${FD_SOCK_PORT:9000}" />
+  <FD_ALL timeout="30000" interval="5000" />
+  <VERIFY_SUSPECT timeout="1500" />
+  <BARRIER />
+  <pbcast.NAKACK2 xmit_interval="500"
+    xmit_table_num_rows="100"
+    xmit_table_msgs_per_row="2000"
+    xmit_table_max_compaction_time="30000"
+    use_mcast_xmit="false"
+    discard_delivered_msgs="true" />
+  <UNICAST3
+    xmit_table_num_rows="100"
+    xmit_table_msgs_per_row="1000"
+    xmit_table_max_compaction_time="30000" />
+  <pbcast.STABLE desired_avg_gossip="50000" max_bytes="8m" />
+  <pbcast.GMS print_local_addr="true" print_physical_addrs="true" join_timeout="3000" />
+  <MFC max_credits="2M"
+    min_threshold="0.4" />
+  <FRAG2 frag_size="60K" />
+  <pbcast.STATE_TRANSFER />
+  <CENTRAL_LOCK />
+  <COUNTER />
+</config>
diff --git a/src/test/docker/gerrit/Dockerfile b/src/test/docker/gerrit/Dockerfile
index d305e21..4c4d092 100644
--- a/src/test/docker/gerrit/Dockerfile
+++ b/src/test/docker/gerrit/Dockerfile
@@ -1,6 +1,6 @@
-FROM gerritcodereview/gerrit:3.6.4
+FROM gerritcodereview/gerrit:3.7.1
 
-ENV GERRIT_BRANCH=stable-3.6
+ENV GERRIT_BRANCH=stable-3.7
 
 ENV GERRIT_CI_URL=https://archive-ci.gerritforge.com/job
 
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
index fb1b8d0..b6986db 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/ConfigurationTest.java
@@ -39,10 +39,10 @@
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Index.DEFAULT_SYNCHRONIZE_FORCED;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Index.INDEX_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Index.SYNCHRONIZE_FORCED_KEY;
+import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGROUPS_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.CLUSTER_NAME_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.DEFAULT_CLUSTER_NAME;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.DEFAULT_SKIP_INTERFACE_LIST;
-import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.JGROUPS_SECTION;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.PROTOCOL_STACK_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.JGroups.SKIP_INTERFACE_KEY;
 import static com.ericsson.gerrit.plugins.highavailability.Configuration.Main.DEFAULT_SHARED_DIRECTORY;
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
new file mode 100644
index 0000000..0dd27e1
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/JGroupsKubernetesPeerInfoProviderTest.java
@@ -0,0 +1,131 @@
+// Copyright (C) 2023 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.peers.jgroups;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.getRequestedFor;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo;
+import static com.github.tomakehurst.wiremock.client.WireMock.verify;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.options;
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.mockito.Answers.RETURNS_DEEP_STUBS;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.github.tomakehurst.wiremock.junit.WireMockRule;
+import com.google.common.io.Resources;
+import com.google.gerrit.extensions.restapi.Url;
+import java.net.Inet4Address;
+import java.net.NetworkInterface;
+import java.util.List;
+import java.util.Optional;
+import org.apache.http.HttpStatus;
+import org.jgroups.Message;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
+import wiremock.com.fasterxml.jackson.databind.ObjectMapper;
+
+@RunWith(MockitoJUnitRunner.class)
+public class JGroupsKubernetesPeerInfoProviderTest {
+
+  @Mock(answer = RETURNS_DEEP_STUBS)
+  private Configuration pluginConfigurationMock;
+
+  @Mock private InetAddressFinder finder;
+  private JGroupsPeerInfoProvider jGroupsPeerInfoProvider;
+  private JGroupsPeerInfoProvider jGroupsPeerInfoProvider2;
+  @Mock private MyUrlProvider myUrlProviderTest;
+
+  @Rule public WireMockRule kubeApiMock = new WireMockRule(options().port(48443));
+
+  @Before
+  public void setUp() throws Exception {
+    System.setProperty("KUBERNETES_MASTER_PROTOCOL", "http");
+    System.setProperty("KUBERNETES_SERVICE_HOST", "localhost");
+    System.setProperty("KUBERNETES_SERVICE_PORT", "48443");
+    System.setProperty("java.net.preferIPv4Stack", "true");
+  }
+
+  @After
+  public void shutdown() {
+    System.clearProperty("KUBERNETES_MASTER_PROTOCOL");
+    System.clearProperty("KUBERNETES_SERVICE_HOST");
+    System.clearProperty("KUBERNETES_SERVICE_PORT");
+    System.clearProperty("java.net.preferIPv4Stack");
+  }
+
+  @Test
+  public void testPeerDiscoveryInKubernetesSuccessful() throws Exception {
+    String namespace = "gerrit";
+    List<String> labels = List.of("app=gerrit", "mode=primary");
+
+    when(pluginConfigurationMock.jgroups().useKubernetes()).thenReturn(true);
+    when(pluginConfigurationMock.jgroups().clusterName()).thenReturn("gerritCluster");
+    when(pluginConfigurationMock.jgroupsKubernetes().namespace()).thenReturn(namespace);
+    when(pluginConfigurationMock.jgroupsKubernetes().labels()).thenReturn(labels);
+
+    NetworkInterface eth0 = NetworkInterface.getByName("eth0");
+    if (eth0 != null) {
+      when(finder.findAddress()).thenReturn(eth0.inetAddresses().findFirst());
+    } else {
+      when(finder.findAddress()).thenReturn(Optional.of(Inet4Address.getByName("127.0.0.1")));
+    }
+    jGroupsPeerInfoProvider =
+        Mockito.spy(
+            new JGroupsPeerInfoProvider(pluginConfigurationMock, finder, myUrlProviderTest));
+    jGroupsPeerInfoProvider2 =
+        Mockito.spy(
+            new JGroupsPeerInfoProvider(pluginConfigurationMock, finder, myUrlProviderTest));
+
+    StringBuilder kubeApiUrlBuilder = new StringBuilder();
+    kubeApiUrlBuilder.append("/api/v1/namespaces/");
+    kubeApiUrlBuilder.append(namespace);
+    kubeApiUrlBuilder.append("/pods?labelSelector=");
+    kubeApiUrlBuilder.append(Url.encode(String.join(",", labels)));
+    String kubeApiUrl = kubeApiUrlBuilder.toString();
+
+    String respJson = Resources.toString(this.getClass().getResource("pod-list.json"), UTF_8);
+    respJson = respJson.replaceAll("\\$\\{IP\\}", finder.findAddress().get().getHostAddress());
+
+    kubeApiMock.stubFor(
+        get(urlEqualTo(kubeApiUrl))
+            .willReturn(
+                aResponse()
+                    .withJsonBody(new ObjectMapper().readTree(respJson))
+                    .withStatus(HttpStatus.SC_OK)));
+    jGroupsPeerInfoProvider.connect();
+    verify(getRequestedFor(urlEqualTo(kubeApiUrl)));
+    jGroupsPeerInfoProvider2.connect();
+
+    verify(jGroupsPeerInfoProvider, timeout(10000)).receive(any(Message.class));
+
+    assertThat(jGroupsPeerInfoProvider.get().isEmpty()).isFalse();
+    assertThat(jGroupsPeerInfoProvider.get().size()).isEqualTo(1);
+
+    assertThat(jGroupsPeerInfoProvider2.get().isEmpty()).isFalse();
+    assertThat(jGroupsPeerInfoProvider2.get().size()).isEqualTo(1);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProviderTest.java b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProviderTest.java
index c337947..1a27de0 100644
--- a/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProviderTest.java
+++ b/src/test/java/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/MyUrlProviderTest.java
@@ -21,6 +21,7 @@
 import static org.mockito.Mockito.when;
 
 import com.ericsson.gerrit.plugins.highavailability.Configuration;
+import com.google.gerrit.common.Nullable;
 import com.google.inject.ProvisionException;
 import org.eclipse.jgit.lib.Config;
 import org.junit.Before;
@@ -48,7 +49,11 @@
   }
 
   private MyUrlProvider getMyUrlProvider() {
-    return new MyUrlProvider(gerritServerConfig, configurationMock);
+    return getMyUrlProvider(null);
+  }
+
+  private MyUrlProvider getMyUrlProvider(@Nullable String myUrlEnvVar) {
+    return new MyUrlProvider(gerritServerConfig, configurationMock, myUrlEnvVar);
   }
 
   @Test
@@ -66,6 +71,12 @@
   }
 
   @Test
+  public void testGetJGroupsMyUrlFromEnvVariable() throws Exception {
+    String hostName = "https://foo:8080";
+    assertThat(getMyUrlProvider(hostName).get()).isEqualTo(hostName);
+  }
+
+  @Test
   public void testGetJGroupsMyUrlFromListenUrlWhenNoListenUrlSpecified() throws Exception {
     ProvisionException thrown = assertThrows(ProvisionException.class, () -> getMyUrlProvider());
     assertThat(thrown).hasMessageThat().contains("exactly 1 value configured; found 0");
@@ -97,4 +108,10 @@
     when(configurationMock.peerInfoJGroups().myUrl()).thenReturn("http://somehost");
     assertThat(getMyUrlProvider().get()).isEqualTo("http://somehost");
   }
+
+  @Test
+  public void testGetJGroupsMyUrlOverridesEnvVariable() throws Exception {
+    when(configurationMock.peerInfoJGroups().myUrl()).thenReturn("http://somehost");
+    assertThat(getMyUrlProvider("https://foo:8080").get()).isEqualTo("http://somehost");
+  }
 }
diff --git a/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json
new file mode 100644
index 0000000..4be7c10
--- /dev/null
+++ b/src/test/resources/com/ericsson/gerrit/plugins/highavailability/peers/jgroups/pod-list.json
@@ -0,0 +1,109 @@
+{
+  "apiVersion": "v1",
+  "items": [
+    {
+      "apiVersion": "v1",
+      "kind": "Pod",
+      "metadata": {
+        "labels": {
+          "app": "gerrit",
+          "mode": "primary",
+          "controller-revision-hash": "gerrit-primary-1234"
+        },
+        "name": "gerrit-primary-0",
+        "namespace": "gerrit"
+      },
+      "spec": {
+        "containers": [
+          {
+            "image": "gerrit:test",
+            "name": "gerrit"
+          }
+        ],
+        "hostname": "gerrit-primary-0"
+      },
+      "status": {
+        "conditions": [
+          {
+            "status": "True",
+            "type": "Ready"
+          }
+        ],
+        "containerStatuses": [
+          {
+            "image": "gerrit:test",
+            "name": "gerrit",
+            "ready": true,
+            "started": true,
+            "state": {
+              "running": {
+                "startedAt": "2023-08-23T17:26:19Z"
+              }
+            }
+          }
+        ],
+        "phase": "Running",
+        "podIP": "${IP}",
+        "podIPs": [
+          {
+            "ip": "${IP}"
+          }
+        ]
+      }
+    },
+    {
+      "apiVersion": "v1",
+      "kind": "Pod",
+      "metadata": {
+        "labels": {
+          "app": "gerrit",
+          "mode": "primary",
+          "controller-revision-hash": "gerrit-primary-1234"
+        },
+        "name": "gerrit-primary-1",
+        "namespace": "gerrit"
+      },
+      "spec": {
+        "containers": [
+          {
+            "image": "gerrit:test",
+            "name": "gerrit"
+          }
+        ],
+        "hostname": "gerrit-primary-1"
+      },
+      "status": {
+        "conditions": [
+          {
+            "status": "True",
+            "type": "Ready"
+          }
+        ],
+        "containerStatuses": [
+          {
+            "image": "gerrit:test",
+            "name": "gerrit",
+            "ready": true,
+            "started": true,
+            "state": {
+              "running": {
+                "startedAt": "2023-08-23T17:26:19Z"
+              }
+            }
+          }
+        ],
+        "phase": "Running",
+        "podIP": "${IP}",
+        "podIPs": [
+          {
+            "ip": "${IP}"
+          }
+        ]
+      }
+    }
+  ],
+  "kind": "List",
+  "metadata": {
+    "resourceVersion": ""
+  }
+}