[Operator] Add option to debug Gerrit

Change-Id: I871f3c95c9bd89a372c7bdb151c82b239c818cd0
diff --git a/Documentation/operator-api-reference.md b/Documentation/operator-api-reference.md
index 6277fec..7ec2e1e 100644
--- a/Documentation/operator-api-reference.md
+++ b/Documentation/operator-api-reference.md
@@ -30,21 +30,22 @@
    26. [GerritModule](#gerritmodule)
    27. [GerritPlugin](#gerritplugin)
    28. [GerritMode](#gerritmode)
-   29. [GerritSpec](#gerritspec)
-   30. [GerritStatus](#gerritstatus)
-   31. [IngressConfig](#ingressconfig)
-   32. [ReceiverTemplate](#receivertemplate)
-   33. [ReceiverTemplateSpec](#receivertemplatespec)
-   34. [ReceiverSpec](#receiverspec)
-   35. [ReceiverStatus](#receiverstatus)
-   36. [ReceiverProbe](#receiverprobe)
-   37. [ReceiverServiceConfig](#receiverserviceconfig)
-   38. [GitGarbageCollectionSpec](#gitgarbagecollectionspec)
-   39. [GitGarbageCollectionStatus](#gitgarbagecollectionstatus)
-   40. [GitGcState](#gitgcstate)
-   41. [GerritNetworkSpec](#gerritnetworkspec)
-   42. [NetworkMember](#networkmember)
-   43. [NetworkMemberWithSsh](#networkmemberwithssh)
+   29. [GerritDebugConfig](#gerritdebugconfig)
+   30. [GerritSpec](#gerritspec)
+   31. [GerritStatus](#gerritstatus)
+   32. [IngressConfig](#ingressconfig)
+   33. [ReceiverTemplate](#receivertemplate)
+   34. [ReceiverTemplateSpec](#receivertemplatespec)
+   35. [ReceiverSpec](#receiverspec)
+   36. [ReceiverStatus](#receiverstatus)
+   37. [ReceiverProbe](#receiverprobe)
+   38. [ReceiverServiceConfig](#receiverserviceconfig)
+   39. [GitGarbageCollectionSpec](#gitgarbagecollectionspec)
+   40. [GitGarbageCollectionStatus](#gitgarbagecollectionstatus)
+   41. [GitGcState](#gitgcstate)
+   42. [GerritNetworkSpec](#gerritnetworkspec)
+   43. [NetworkMember](#networkmember)
+   44. [NetworkMemberWithSsh](#networkmemberwithssh)
 
 ## General Remarks
 
@@ -59,7 +60,7 @@
 ---
 
 **Group**: gerritoperator.google.com \
-**Version**: v1alpha12 \
+**Version**: v1alpha13 \
 **Kind**: GerritCluster
 
 ---
@@ -76,7 +77,7 @@
 Example:
 
 ```yaml
-apiVersion: "gerritoperator.google.com/v1alpha12"
+apiVersion: "gerritoperator.google.com/v1alpha13"
 kind: GerritCluster
 metadata:
   name: gerrit
@@ -205,6 +206,10 @@
 
       mode: REPLICA
 
+      debug:
+        enabled: false
+        suspend: false
+
       site:
         size: 1Gi
 
@@ -323,7 +328,7 @@
 ---
 
 **Group**: gerritoperator.google.com \
-**Version**: v1alpha13 \
+**Version**: v1alpha14 \
 **Kind**: Gerrit
 
 ---
@@ -340,7 +345,7 @@
 Example:
 
 ```yaml
-apiVersion: "gerritoperator.google.com/v1alpha13"
+apiVersion: "gerritoperator.google.com/v1alpha14"
 kind: Gerrit
 metadata:
   name: gerrit
@@ -414,6 +419,10 @@
 
     mode: PRIMARY
 
+    debug:
+      enabled: false
+      suspend: false
+
     site:
       size: 1Gi
 
@@ -879,6 +888,7 @@
 | `configFiles` | `Map<String, String>` | Configuration files for Gerrit that will be mounted into the Gerrit site's etc-directory (gerrit.config is mandatory) |
 | `secretRef` | `String` | Name of secret containing configuration files, e.g. secure.config, that will be mounted into the Gerrit site's etc-directory (optional) |
 | `mode` | [`GerritMode`](#gerritmode) | In which mode Gerrit should be run. (default: PRIMARY) |
+| `debug` | [`GerritDebugConfig`](#gerritdebugconfig) | Enable the debug-mode for Gerrit |
 
 ## GerritProbe
 
@@ -924,6 +934,20 @@
 | `PRIMARY` | A primary Gerrit |
 | `REPLICA` | A Gerrit Replica, which only serves git fetch/clone requests |
 
+## GerritDebugConfig
+
+These options allow to debug Gerrit. It will enable debugging in all pods and
+expose the port 8000 in the container. Port-forwarding is required to connect the
+debugger.
+Note, that all pods will be restarted to enable the debugger. Also, if `suspend`
+is enabled, ensure that the lifecycle probes are configured accordingly to prevent
+pod restarts before Gerrit is ready.
+
+| Field | Type | Description |
+|---|---|---|
+| `enabled` | `boolean` | Whether to enable debugging. (default: `false`) |
+| `suspend` | `boolean` | Whether to suspend Gerrit on startup. (default: `false`) |
+
 ## GerritSpec
 
 **Extends:** [`GerritTemplateSpec`](#gerrittemplatespec)
diff --git a/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml b/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
index b7bee59..fee207f 100644
--- a/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
+++ b/helm-charts/gerrit-operator-crds/templates/gerritclusters.gerritoperator.google.com-v1.yml
@@ -13,7 +13,7 @@
     singular: gerritcluster
   scope: Namespaced
   versions:
-  - name: v1alpha12
+  - name: v1alpha13
     schema:
       openAPIV3Schema:
         properties:
@@ -831,6 +831,13 @@
                           - PRIMARY
                           - REPLICA
                           type: string
+                        debug:
+                          properties:
+                            enabled:
+                              type: boolean
+                            suspend:
+                              type: boolean
+                          type: object
                       type: object
                   type: object
                 type: array
diff --git a/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml b/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
index 65aa48f..8997336 100644
--- a/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
+++ b/helm-charts/gerrit-operator-crds/templates/gerrits.gerritoperator.google.com-v1.yml
@@ -13,7 +13,7 @@
     singular: gerrit
   scope: Namespaced
   versions:
-  - name: v1alpha13
+  - name: v1alpha14
     schema:
       openAPIV3Schema:
         properties:
@@ -746,6 +746,13 @@
                 - PRIMARY
                 - REPLICA
                 type: string
+              debug:
+                properties:
+                  enabled:
+                    type: boolean
+                  suspend:
+                    type: boolean
+                type: object
             type: object
           status:
             properties:
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
index eda5546..c08c93c 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/model/GerritCluster.java
@@ -42,7 +42,7 @@
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 @Group("gerritoperator.google.com")
-@Version("v1alpha12")
+@Version("v1alpha13")
 @ShortNames("gclus")
 public class GerritCluster extends CustomResource<GerritClusterSpec, GerritClusterStatus>
     implements Namespaced {
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/config/GerritConfigBuilder.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/config/GerritConfigBuilder.java
index a0d389a..5199bc6 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/config/GerritConfigBuilder.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/config/GerritConfigBuilder.java
@@ -44,6 +44,16 @@
     if (gerrit.getSpec().isHighlyAvailablePrimary()) {
       javaOptions.add("-Djava.net.preferIPv4Stack=true");
     }
+    if (gerrit.getSpec().getDebug().isEnabled()) {
+      javaOptions.add("-Xdebug");
+      String debugServerCfg = "-Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000";
+      if (gerrit.getSpec().getDebug().isSuspend()) {
+        debugServerCfg = debugServerCfg + ",suspend=y";
+      } else {
+        debugServerCfg = debugServerCfg + ",suspend=n";
+      }
+      javaOptions.add(debugServerCfg);
+    }
     addRequiredOption(new RequiredOption<Set<String>>("container", "javaOptions", javaOptions));
 
     addRequiredOption(new RequiredOption<String>("container", "user", "gerrit"));
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
index 8f1484a..d12b8f8 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/dependent/GerritStatefulSet.java
@@ -55,6 +55,7 @@
   public static final int HTTP_PORT = 8080;
   public static final int SSH_PORT = 29418;
   public static final int JGROUPS_PORT = 7800;
+  public static final int DEBUG_PORT = 8000;
 
   public GerritStatefulSet() {
     super(StatefulSet.class);
@@ -298,6 +299,10 @@
       containerPorts.add(new ContainerPort(JGROUPS_PORT, null, null, "jgroups", null));
     }
 
+    if (gerrit.getSpec().getDebug().isEnabled()) {
+      containerPorts.add(new ContainerPort(DEBUG_PORT, null, null, "debug", null));
+    }
+
     return containerPorts;
   }
 
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
index de844a8..2be0b12 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/Gerrit.java
@@ -24,7 +24,7 @@
 import org.apache.commons.lang3.builder.ToStringStyle;
 
 @Group("gerritoperator.google.com")
-@Version("v1alpha13")
+@Version("v1alpha14")
 @ShortNames("gcr")
 public class Gerrit extends CustomResource<GerritSpec, GerritStatus> implements Namespaced {
   private static final long serialVersionUID = 2L;
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritDebugConfig.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritDebugConfig.java
new file mode 100644
index 0000000..69d32ad
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritDebugConfig.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.google.gerrit.k8s.operator.gerrit.model;
+
+public class GerritDebugConfig {
+  private boolean enabled;
+  private boolean suspend;
+
+  public boolean isEnabled() {
+    return enabled;
+  }
+
+  public void setEnabled(boolean enabled) {
+    this.enabled = enabled;
+  }
+
+  public boolean isSuspend() {
+    return suspend;
+  }
+
+  public void setSuspend(boolean suspend) {
+    this.suspend = suspend;
+  }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
index 1963001..4ae5737 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gerrit/model/GerritTemplateSpec.java
@@ -52,6 +52,8 @@
   private String secretRef;
   private GerritMode mode = GerritMode.PRIMARY;
 
+  private GerritDebugConfig debug = new GerritDebugConfig();
+
   public GerritTemplateSpec() {}
 
   public GerritTemplateSpec(GerritTemplateSpec templateSpec) {
@@ -80,6 +82,8 @@
     this.configFiles = templateSpec.configFiles;
     this.secretRef = templateSpec.secretRef;
     this.mode = templateSpec.mode;
+
+    this.debug = templateSpec.debug;
   }
 
   public String getServiceAccount() {
@@ -235,6 +239,14 @@
     this.mode = mode;
   }
 
+  public GerritDebugConfig getDebug() {
+    return debug;
+  }
+
+  public void setDebug(GerritDebugConfig debug) {
+    this.debug = debug;
+  }
+
   public enum GerritMode {
     PRIMARY,
     REPLICA