Add storage for logs to GerritCluster resource

As in the helm charts, logs will be stored in a shared RWM volume.

Change-Id: If8245b3f83c8ccff8eabd4b809124d6ee788e822
diff --git a/operator/README.md b/operator/README.md
index 6eb95b4..9fd1612 100644
--- a/operator/README.md
+++ b/operator/README.md
@@ -77,6 +77,7 @@
     ## Name of a StorageClass allowing ReadWriteMany access. (default: shared-storage)
     readWriteMany: nfs-client
 
+  ## Storage for git repositories
   gitRepositoryStorage:
     ## Size of the volume (ReadWriteMany) used to store git repositories. (mandatory)
     size: 1Gi
@@ -90,6 +91,21 @@
       matchLabels:
         volume-type: ssd
         aws-availability-zone: us-east-1
+
+  ## Storage for logs
+  gerritLogsStorage:
+    ## Size of the volume (ReadWriteMany) used to store logs. (mandatory)
+    size: 1Gi
+
+    ## Name of a specific persistent volume to claim (optional)
+    volumeName: logs
+
+    ## Selector (https://kubernetes.io/docs/concepts/storage/persistent-volumes/#selector)
+    ## to select a specific persistent volume (optional)
+    selector:
+      matchLabels:
+        volume-type: ssd
+        aws-availability-zone: us-east-1
 ```
 
 ### GitGarbageCollection
@@ -126,7 +142,4 @@
     limits:
       cpu: 100m
       memory: 256Mi
-
-  ## Name of an existing PVC that will be used to store the logs (mandatory)
-  logPVC: logs-pvc
 ```
diff --git a/operator/k8s/cluster.sample.yaml b/operator/k8s/cluster.sample.yaml
index 0afc40d..52b7759 100644
--- a/operator/k8s/cluster.sample.yaml
+++ b/operator/k8s/cluster.sample.yaml
@@ -8,3 +8,5 @@
     readWriteMany: nfs-client
   gitRepositoryStorage:
     size: 1Gi
+  gerritLogsStorage:
+    size: 1Gi
diff --git a/operator/k8s/gitgc.sample.yaml b/operator/k8s/gitgc.sample.yaml
index 3c2ee00..480f5c6 100644
--- a/operator/k8s/gitgc.sample.yaml
+++ b/operator/k8s/gitgc.sample.yaml
@@ -5,7 +5,6 @@
 spec:
   image: k8sgerrit/git-gc
   schedule: "*/5 * * * *"
-  logPVC: logs-pvc
   cluster: gerrit
   resources:
     requests:
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
index 693602a..d2a8297 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritCluster.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.k8s.operator.cluster;
 
+import static com.google.gerrit.k8s.operator.cluster.GerritLogsPVC.LOGS_PVC_NAME;
 import static com.google.gerrit.k8s.operator.cluster.GitRepositoriesPVC.REPOSITORY_PVC_NAME;
 
 import com.fasterxml.jackson.annotation.JsonIgnore;
@@ -38,6 +39,7 @@
 public class GerritCluster extends CustomResource<GerritClusterSpec, Status> implements Namespaced {
   private static final long serialVersionUID = 1L;
   private static final String GIT_REPOSITORIES_VOLUME_NAME = "git-repositories";
+  private static final String LOGS_VOLUME_NAME = "logs";
 
   public String toString() {
     return ToStringBuilder.reflectionToString(this, ToStringStyle.JSON_STYLE);
@@ -75,4 +77,22 @@
         .withMountPath("/var/gerrit/git")
         .build();
   }
+
+  @JsonIgnore
+  public Volume getLogsVolume() {
+    return new VolumeBuilder()
+        .withName(LOGS_VOLUME_NAME)
+        .withNewPersistentVolumeClaim()
+        .withClaimName(LOGS_PVC_NAME)
+        .endPersistentVolumeClaim()
+        .build();
+  }
+
+  @JsonIgnore
+  public VolumeMount getLogsVolumeMount() {
+    return new VolumeMountBuilder()
+        .withName(LOGS_VOLUME_NAME)
+        .withMountPath("/var/gerrit/logs")
+        .build();
+  }
 }
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
index f6e66f4..29d4d73 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterReconciler.java
@@ -23,6 +23,7 @@
 @ControllerConfiguration(
     dependents = {
       @Dependent(type = GitRepositoriesPVC.class),
+      @Dependent(type = GerritLogsPVC.class),
     })
 public class GerritClusterReconciler implements Reconciler<GerritCluster> {
   @Override
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
index e084753..2c0f8c0 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritClusterSpec.java
@@ -17,13 +17,14 @@
 public class GerritClusterSpec {
 
   private StorageClassConfig storageClasses;
-  private GitRepositoryStorage gitRepositoryStorage;
+  private SharedStorage gitRepositoryStorage;
+  private SharedStorage logsStorage;
 
   public StorageClassConfig getStorageClasses() {
     return storageClasses;
   }
 
-  public GitRepositoryStorage getGitRepositoryStorage() {
+  public SharedStorage getGitRepositoryStorage() {
     return gitRepositoryStorage;
   }
 
@@ -31,7 +32,15 @@
     this.storageClasses = storageClasses;
   }
 
-  public void setGitRepositoryStorage(GitRepositoryStorage gitRepositoryStorage) {
+  public void setGitRepositoryStorage(SharedStorage gitRepositoryStorage) {
     this.gitRepositoryStorage = gitRepositoryStorage;
   }
+
+  public SharedStorage getLogsStorage() {
+    return logsStorage;
+  }
+
+  public void setLogsStorage(SharedStorage logsStorage) {
+    this.logsStorage = logsStorage;
+  }
 }
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritLogsPVC.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritLogsPVC.java
new file mode 100644
index 0000000..bf4dc94
--- /dev/null
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GerritLogsPVC.java
@@ -0,0 +1,58 @@
+// Copyright (C) 2022 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.cluster;
+
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaim;
+import io.fabric8.kubernetes.api.model.PersistentVolumeClaimBuilder;
+import io.javaoperatorsdk.operator.api.reconciler.Context;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.CRUDKubernetesDependentResource;
+import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
+import java.util.Map;
+
+@KubernetesDependent(labelSelector = "app.kubernetes.io/component=gerrit-logs-storage")
+public class GerritLogsPVC
+    extends CRUDKubernetesDependentResource<PersistentVolumeClaim, GerritCluster> {
+
+  public static final String LOGS_PVC_NAME = "gerrit-logs-pvc";
+
+  public GerritLogsPVC() {
+    super(PersistentVolumeClaim.class);
+  }
+
+  @Override
+  protected PersistentVolumeClaim desired(
+      GerritCluster gerritCluster, Context<GerritCluster> context) {
+    PersistentVolumeClaim gerritLogsPvc =
+        new PersistentVolumeClaimBuilder()
+            .withNewMetadata()
+            .withName(LOGS_PVC_NAME)
+            .withNamespace(gerritCluster.getMetadata().getNamespace())
+            .withLabels(
+                gerritCluster.getLabels("gerrit-logs-storage", this.getClass().getSimpleName()))
+            .endMetadata()
+            .withNewSpec()
+            .withAccessModes("ReadWriteMany")
+            .withNewResources()
+            .withRequests(Map.of("storage", gerritCluster.getSpec().getLogsStorage().getSize()))
+            .endResources()
+            .withStorageClassName(gerritCluster.getSpec().getStorageClasses().getReadWriteMany())
+            .withSelector(gerritCluster.getSpec().getLogsStorage().getSelector())
+            .withVolumeName(gerritCluster.getSpec().getLogsStorage().getVolumeName())
+            .endSpec()
+            .build();
+
+    return gerritLogsPvc;
+  }
+}
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoriesPVC.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoriesPVC.java
index 30c7582..bde8797 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoriesPVC.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoriesPVC.java
@@ -21,7 +21,7 @@
 import io.javaoperatorsdk.operator.processing.dependent.kubernetes.KubernetesDependent;
 import java.util.Map;
 
-@KubernetesDependent
+@KubernetesDependent(labelSelector = "app.kubernetes.io/component=git-repositories-storage")
 public class GitRepositoriesPVC
     extends CRUDKubernetesDependentResource<PersistentVolumeClaim, GerritCluster> {
 
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoryStorage.java b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/SharedStorage.java
similarity index 97%
rename from operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoryStorage.java
rename to operator/src/main/java/com/google/gerrit/k8s/operator/cluster/SharedStorage.java
index 57c1fc6..5f8dba1 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/GitRepositoryStorage.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/cluster/SharedStorage.java
@@ -17,7 +17,7 @@
 import io.fabric8.kubernetes.api.model.LabelSelector;
 import io.fabric8.kubernetes.api.model.Quantity;
 
-public class GitRepositoryStorage {
+public class SharedStorage {
 
   private Quantity size;
   private String volumeName;
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
index 4c473dc..ae68a83 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionCronJob.java
@@ -20,10 +20,6 @@
 import io.fabric8.kubernetes.api.model.ContainerBuilder;
 import io.fabric8.kubernetes.api.model.EnvVar;
 import io.fabric8.kubernetes.api.model.EnvVarBuilder;
-import io.fabric8.kubernetes.api.model.Volume;
-import io.fabric8.kubernetes.api.model.VolumeBuilder;
-import io.fabric8.kubernetes.api.model.VolumeMount;
-import io.fabric8.kubernetes.api.model.VolumeMountBuilder;
 import io.fabric8.kubernetes.api.model.batch.v1.CronJob;
 import io.fabric8.kubernetes.api.model.batch.v1.CronJobBuilder;
 import io.fabric8.kubernetes.api.model.batch.v1.JobTemplateSpec;
@@ -39,8 +35,6 @@
     extends CRUDKubernetesDependentResource<CronJob, GitGarbageCollection> {
   private static final FluentLogger logger = FluentLogger.forEnclosingClass();
 
-  private static final String LOGS_VOLUME_NAME = "logs";
-
   public GitGarbageCollectionCronJob() {
     super(CronJob.class);
   }
@@ -64,16 +58,6 @@
     Map<String, String> gitGcLabels =
         gerritCluster.getLabels("GitGc", this.getClass().getSimpleName());
 
-    Volume gitRepositoriesVolume = gerritCluster.getGitRepositoriesVolume();
-
-    Volume logsVolume =
-        new VolumeBuilder()
-            .withName(LOGS_VOLUME_NAME)
-            .withNewPersistentVolumeClaim()
-            .withClaimName(gitGc.getSpec().getLogPVC())
-            .endPersistentVolumeClaim()
-            .build();
-
     JobTemplateSpec gitGcJobTemplate =
         new JobTemplateSpecBuilder()
             .withNewSpec()
@@ -93,7 +77,8 @@
             .withFsGroup(100L)
             .endSecurityContext()
             .addToContainers(buildGitGcContainer(gitGc, gerritCluster))
-            .withVolumes(List.of(gitRepositoriesVolume, logsVolume))
+            .withVolumes(
+                List.of(gerritCluster.getGitRepositoriesVolume(), gerritCluster.getLogsVolume()))
             .endSpec()
             .endTemplate()
             .endSpec()
@@ -123,13 +108,6 @@
   }
 
   private Container buildGitGcContainer(GitGarbageCollection gitGc, GerritCluster gerritCluster) {
-    VolumeMount logsVolumeMount =
-        new VolumeMountBuilder()
-            .withName(LOGS_VOLUME_NAME)
-            .withSubPathExpr("git-gc/$(POD_NAME)")
-            .withMountPath("/var/log/git")
-            .build();
-
     EnvVar podNameEnvVar =
         new EnvVarBuilder()
             .withName("POD_NAME")
@@ -147,7 +125,9 @@
             .withResources(gitGc.getSpec().getResources())
             .withEnv(podNameEnvVar)
             .withVolumeMounts(
-                List.of(gerritCluster.getGitRepositoriesVolumeMount(), logsVolumeMount));
+                List.of(
+                    gerritCluster.getGitRepositoriesVolumeMount(),
+                    gerritCluster.getLogsVolumeMount()));
 
     ArrayList<String> args = new ArrayList<>();
     for (String project : gitGc.getSpec().getProjects()) {
diff --git a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionSpec.java b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionSpec.java
index 80f46d4..86c8f11 100644
--- a/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionSpec.java
+++ b/operator/src/main/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionSpec.java
@@ -25,7 +25,6 @@
   private String schedule;
   private Set<String> projects;
   private ResourceRequirements resources;
-  private String logPVC;
 
   public GitGarbageCollectionSpec() {
     image = "k8s-gerrit/git-gc";
@@ -73,17 +72,9 @@
     return resources;
   }
 
-  public void setLogPVC(String logPVC) {
-    this.logPVC = logPVC;
-  }
-
-  public String getLogPVC() {
-    return logPVC;
-  }
-
   @Override
   public int hashCode() {
-    return Objects.hash(cluster, image, logPVC, projects, resources, schedule);
+    return Objects.hash(cluster, image, projects, resources, schedule);
   }
 
   @Override
@@ -92,7 +83,6 @@
       GitGarbageCollectionSpec other = (GitGarbageCollectionSpec) obj;
       return Objects.equals(cluster, other.cluster)
           && Objects.equals(image, other.image)
-          && Objects.equals(logPVC, other.logPVC)
           && Objects.equals(projects, other.projects)
           && Objects.equals(resources, other.resources)
           && Objects.equals(schedule, other.schedule);
diff --git a/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java b/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
index 2f85a3a..436bcf6 100644
--- a/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
+++ b/operator/src/test/java/com/google/gerrit/k8s/operator/cluster/GerritClusterE2E.java
@@ -43,30 +43,7 @@
 
   @Test
   void testGitRepositoriesPvcCreated() {
-    GerritCluster cluster = new GerritCluster();
-
-    cluster.setMetadata(
-        new ObjectMetaBuilder()
-            .withName("test-cluster")
-            .withNamespace(operator.getNamespace())
-            .build());
-
-    GitRepositoryStorage repoStorage = new GitRepositoryStorage();
-    repoStorage.setSize(Quantity.parse("1Gi"));
-
-    StorageClassConfig storageClassConfig = new StorageClassConfig();
-    storageClassConfig.setReadWriteMany(System.getProperty("rwmStorageClass", "nfs-client"));
-
-    GerritClusterSpec clusterSpec = new GerritClusterSpec();
-    clusterSpec.setGitRepositoryStorage(repoStorage);
-    clusterSpec.setStorageClasses(storageClassConfig);
-
-    cluster.setSpec(clusterSpec);
-
-    client
-        .resources(GerritCluster.class)
-        .inNamespace(operator.getNamespace())
-        .createOrReplace(cluster);
+    GerritCluster cluster = createGerritCluster();
 
     logger.atInfo().log("Waiting max 1 minutes for the git repositories pvc to be created.");
     await()
@@ -85,4 +62,59 @@
     logger.atInfo().log("Deleting test cluster object: %s", cluster);
     client.resource(cluster).delete();
   }
+
+  @Test
+  void testGerritLogsPvcCreated() {
+    GerritCluster cluster = createGerritCluster();
+
+    logger.atInfo().log("Waiting max 1 minutes for the gerrit logs pvc to be created.");
+    await()
+        .atMost(1, MINUTES)
+        .untilAsserted(
+            () -> {
+              PersistentVolumeClaim pvc =
+                  client
+                      .persistentVolumeClaims()
+                      .inNamespace(operator.getNamespace())
+                      .withName(GerritLogsPVC.LOGS_PVC_NAME)
+                      .get();
+              assertThat(pvc, is(notNullValue()));
+            });
+
+    logger.atInfo().log("Deleting test cluster object: %s", cluster);
+    client.resource(cluster).delete();
+  }
+
+  private GerritCluster createGerritCluster() {
+    GerritCluster cluster = new GerritCluster();
+
+    cluster.setMetadata(
+        new ObjectMetaBuilder()
+            .withName("test-cluster")
+            .withNamespace(operator.getNamespace())
+            .build());
+
+    SharedStorage repoStorage = new SharedStorage();
+    repoStorage.setSize(Quantity.parse("1Gi"));
+
+    SharedStorage logStorage = new SharedStorage();
+    logStorage.setSize(Quantity.parse("1Gi"));
+
+    StorageClassConfig storageClassConfig = new StorageClassConfig();
+    storageClassConfig.setReadWriteMany(System.getProperty("rwmStorageClass", "nfs-client"));
+
+    GerritClusterSpec clusterSpec = new GerritClusterSpec();
+    clusterSpec.setGitRepositoryStorage(repoStorage);
+    clusterSpec.setLogsStorage(logStorage);
+    clusterSpec.setStorageClasses(storageClassConfig);
+
+    cluster.setSpec(clusterSpec);
+
+    client
+        .resources(GerritCluster.class)
+        .inNamespace(operator.getNamespace())
+        .createOrReplace(cluster);
+
+    return cluster;
+  }
 }
diff --git a/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java b/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
index 5debf29..d4c51b0 100644
--- a/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
+++ b/operator/src/test/java/com/google/gerrit/k8s/operator/gitgc/GitGarbageCollectionE2E.java
@@ -28,7 +28,7 @@
 import com.google.gerrit.k8s.operator.cluster.GerritCluster;
 import com.google.gerrit.k8s.operator.cluster.GerritClusterReconciler;
 import com.google.gerrit.k8s.operator.cluster.GerritClusterSpec;
-import com.google.gerrit.k8s.operator.cluster.GitRepositoryStorage;
+import com.google.gerrit.k8s.operator.cluster.SharedStorage;
 import com.google.gerrit.k8s.operator.cluster.StorageClassConfig;
 import com.google.gerrit.k8s.operator.gitgc.GitGarbageCollectionStatus.GitGcState;
 import io.fabric8.kubernetes.api.model.ObjectMetaBuilder;
@@ -203,14 +203,18 @@
             .withNamespace(operator.getNamespace())
             .build());
 
-    GitRepositoryStorage repoStorage = new GitRepositoryStorage();
+    SharedStorage repoStorage = new SharedStorage();
     repoStorage.setSize(Quantity.parse("1Gi"));
 
+    SharedStorage logStorage = new SharedStorage();
+    logStorage.setSize(Quantity.parse("1Gi"));
+
     StorageClassConfig storageClassConfig = new StorageClassConfig();
     storageClassConfig.setReadWriteMany(System.getProperty("rwmStorageClass", "nfs-client"));
 
     GerritClusterSpec clusterSpec = new GerritClusterSpec();
     clusterSpec.setGitRepositoryStorage(repoStorage);
+    clusterSpec.setLogsStorage(logStorage);
     clusterSpec.setStorageClasses(storageClassConfig);
 
     cluster.setSpec(clusterSpec);
@@ -244,7 +248,6 @@
             .build());
     GitGarbageCollectionSpec spec = new GitGarbageCollectionSpec();
     spec.setSchedule(GITGC_SCHEDULE);
-    spec.setLogPVC("log-pvc");
     spec.setCluster(CLUSTER_NAME);
     gitGc.setSpec(spec);
 
@@ -260,7 +263,6 @@
         new ObjectMetaBuilder().withName(name).withNamespace(operator.getNamespace()).build());
     GitGarbageCollectionSpec spec = new GitGarbageCollectionSpec();
     spec.setSchedule(GITGC_SCHEDULE);
-    spec.setLogPVC("log-pvc");
     spec.setCluster(CLUSTER_NAME);
     spec.setProjects(projects);
     gitGc.setSpec(spec);