Merge changes Ia5a5f5da,I65e74f6b

* changes:
  Document that online reindexing is currently not supported
  Do not reindex Lucene indices on every start of the gerrit pod
diff --git a/container-images/gerrit-init/tools/gerrit_reindex.py b/container-images/gerrit-init/tools/gerrit_reindex.py
new file mode 100755
index 0000000..8f3e6b0
--- /dev/null
+++ b/container-images/gerrit-init/tools/gerrit_reindex.py
@@ -0,0 +1,152 @@
+#!/usr/bin/python3
+
+# Copyright (C) 2019 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.
+
+import argparse
+import os.path
+import subprocess
+import sys
+
+from git_config_parser import GitConfigParser
+from log import get_logger
+
+LOG = get_logger("reindex")
+
+
+class GerritReindexer:
+    def __init__(self, gerrit_site_path):
+        self.gerrit_site_path = gerrit_site_path
+        self.index_config_path = "%s/index/gerrit_index.config" % self.gerrit_site_path
+
+        self.index_type = self._get_index_type()
+        self.configured_indices = self._parse_gerrit_index_config()
+
+    def _get_index_type(self):
+        gerrit_config = GitConfigParser(
+            os.path.join(self.gerrit_site_path, "etc", "gerrit.config")
+        )
+        return gerrit_config.get("index.type", "lucene").lower()
+
+    def _parse_gerrit_index_config(self):
+        indices = dict()
+        if os.path.exists(self.index_config_path):
+            config = GitConfigParser(self.index_config_path)
+            options = config.list()
+            for opt in options:
+                name, version = opt["subsection"].rsplit("_", 1)
+                # TODO (Thomas): Properly handle multiple versions of the same index,
+                # which may be present due to online-reindexing in progress.
+                indices[name] = {
+                    "version": int(version),
+                    "ready": opt["value"].lower() == "true",
+                }
+        return indices
+
+    def _get_unready_indices(self):
+        unready_indices = []
+        for index, index_attrs in self.configured_indices.items():
+            if not index_attrs["ready"]:
+                LOG.info("Index %s not ready.", index)
+                unready_indices.append(index)
+        return unready_indices
+
+    def _get_lucene_indices(self):
+        file_list = os.listdir(os.path.join(self.gerrit_site_path, "index"))
+        file_list.remove("gerrit_index.config")
+        lucene_indices = dict()
+        for index in file_list:
+            try:
+                (name, version) = index.split("_")
+                lucene_indices[name] = int(version)
+            except ValueError:
+                LOG.debug("Ignoring invalid file in index-directory: %s", index)
+        return lucene_indices
+
+    def _check_lucene_index_versions(self):
+        lucene_indices = self._get_lucene_indices()
+        if not lucene_indices:
+            return False
+        for index, index_attrs in self.configured_indices.items():
+            if index_attrs["version"] is not lucene_indices[index]:
+                return False
+        return True
+
+    def reindex(self, indices=None):
+        LOG.info("Starting to reindex.")
+        command = "java -jar /var/war/gerrit.war reindex -d %s" % self.gerrit_site_path
+
+        if indices:
+            command += " ".join([" --index %s" % i for i in indices])
+
+        reindex_process = subprocess.run(command.split(), stdout=subprocess.PIPE)
+
+        if reindex_process.returncode > 0:
+            LOG.error(
+                "An error occured, when reindexing Gerrit indices. Exit code: %d",
+                reindex_process.returncode,
+            )
+            sys.exit(1)
+
+        LOG.info("Finished reindexing.")
+
+    def start(self, is_forced):
+        if is_forced:
+            self.reindex()
+            return
+
+        if not self.configured_indices:
+            LOG.info("gerrit_index.config does not exist. Creating all indices.")
+            self.reindex()
+            return
+
+        unready_indices = self._get_unready_indices()
+        if unready_indices:
+            self.reindex(unready_indices)
+
+        if self.index_type == "lucene":
+            if not self._check_lucene_index_versions():
+                LOG.info("Not all indices are up-to-date.")
+                self.reindex()
+                return
+        else:
+            self.reindex()
+            return
+
+        LOG.info("Skipping reindexing.")
+
+
+# pylint: disable=C0103
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument(
+        "-s",
+        "--site",
+        help="Path to Gerrit site",
+        dest="site",
+        action="store",
+        default="/var/gerrit",
+        required=True,
+    )
+    parser.add_argument(
+        "-f",
+        "--force",
+        help="Reindex even if indices are ready.",
+        dest="force",
+        action="store_true",
+    )
+    args = parser.parse_args()
+
+    reindexer = GerritReindexer(args.site)
+    reindexer.start(args.force)
diff --git a/container-images/gerrit-init/tools/git_config_parser.py b/container-images/gerrit-init/tools/git_config_parser.py
index fa5d609..c5cea95 100644
--- a/container-images/gerrit-init/tools/git_config_parser.py
+++ b/container-images/gerrit-init/tools/git_config_parser.py
@@ -29,11 +29,31 @@
         command = "git config -f %s --get %s" % (self.path, key)
         return self._execute_shell_command_and_get_output_lines(command)
 
+    def list(self):
+        command = "git config -f %s --list" % (self.path)
+        options = self._execute_shell_command_and_get_output_lines(command)
+        option_list = list()
+        for opt in options:
+            parsed_opt = dict()
+            full_key, value = opt.split("=", 1)
+            parsed_opt["value"] = value
+            full_key = full_key.split(".")
+            parsed_opt["section"] = full_key[0]
+            if len(full_key) == 2:
+                parsed_opt["subsection"] = None
+                parsed_opt["key"] = full_key[1]
+            elif len(full_key) == 3:
+                parsed_opt["subsection"] = full_key[1]
+                parsed_opt["key"] = full_key[2]
+            option_list.append(parsed_opt)
+
+        return option_list
+
     def get(self, key, default=None):
         """
-    Returns value of given key in the configuration file. If the key appears
-    multiple times, the last value is returned.
-    """
+        Returns value of given key in the configuration file. If the key appears
+        multiple times, the last value is returned.
+        """
         try:
             return self._get_value(key)[-1]
         except subprocess.CalledProcessError:
diff --git a/container-images/gerrit/tools/start b/container-images/gerrit/tools/start
index 05b76ba..812e036 100755
--- a/container-images/gerrit/tools/start
+++ b/container-images/gerrit/tools/start
@@ -12,9 +12,6 @@
 # from secrets/configmaps in Kubernetes make the containing directory read-only.
 symlink_config_to_site
 
-java -jar /var/gerrit/bin/gerrit.war reindex \
-    -d /var/gerrit
-
 JAVA_OPTIONS=$(git config --file /var/gerrit/etc/gerrit.config --get-all container.javaOptions)
 java ${JAVA_OPTIONS} -jar /var/gerrit/bin/gerrit.war daemon \
   -d /var/gerrit \
diff --git a/helm-charts/gerrit/README.md b/helm-charts/gerrit/README.md
index 0cb0264..8220917 100644
--- a/helm-charts/gerrit/README.md
+++ b/helm-charts/gerrit/README.md
@@ -212,6 +212,13 @@
 
     The canonical web URL has to be set to the Ingress host.
 
+- `index.onlineUpgrade`
+
+    Online reindexing is currently **NOT** supported. An offline reindexing will
+    be enforced upon Gerrit updates. Online reindexing might under some circum-
+    stances interfere with the Gerrit pod startup procedure and thus has to be
+    deactivated.
+
 - `httpd.listenURL`
 
     This has to be set to `proxy-http://*:8080/` or `proxy-https://*:8080`,
diff --git a/helm-charts/gerrit/templates/gerrit.stateful-set.yaml b/helm-charts/gerrit/templates/gerrit.stateful-set.yaml
index c3c04a6..bdef9c3 100644
--- a/helm-charts/gerrit/templates/gerrit.stateful-set.yaml
+++ b/helm-charts/gerrit/templates/gerrit.stateful-set.yaml
@@ -76,6 +76,15 @@
             -s /var/gerrit
 
           symlink_config_to_site
+
+          {{ if not .Values.gerrit.persistence.enabled -}}
+            FLAGS="$FLAGS --force"
+          {{- end }}
+
+          # TODO (Thomas): Do not enforce offline-reindexing, when online-reindexing
+          # is configured.
+          /var/tools/gerrit_reindex.py $FLAGS \
+            -s /var/gerrit
         volumeMounts:
         - name: gerrit-site
           mountPath: "/var/gerrit"
diff --git a/helm-charts/gerrit/values.yaml b/helm-charts/gerrit/values.yaml
index f033fbf..c9add68 100644
--- a/helm-charts/gerrit/values.yaml
+++ b/helm-charts/gerrit/values.yaml
@@ -169,6 +169,7 @@
           disableReverseDnsLookup = true
         [index]
           type = LUCENE
+          onlineUpgrade = false # FIXED
         [auth]
           type = DEVELOPMENT_BECOME_ANY_ACCOUNT
         [httpd]
diff --git a/tests/container-images/gerrit-init/test_container_integration_gerrit_init_reindexing.py b/tests/container-images/gerrit-init/test_container_integration_gerrit_init_reindexing.py
new file mode 100644
index 0000000..749a328
--- /dev/null
+++ b/tests/container-images/gerrit-init/test_container_integration_gerrit_init_reindexing.py
@@ -0,0 +1,137 @@
+# pylint: disable=E1101
+
+# Copyright (C) 2018 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.
+
+import os
+
+import pytest
+
+
+@pytest.fixture(scope="function")
+def temp_site(tmp_path_factory):
+    return tmp_path_factory.mktemp("gerrit-index-test")
+
+
+@pytest.fixture(scope="function")
+def container_run_endless(request, docker_client, gerrit_init_image, temp_site):
+    container_run = docker_client.containers.run(
+        image=gerrit_init_image.id,
+        entrypoint="/bin/ash",
+        command=["-c", "tail -f /dev/null"],
+        volumes={str(temp_site): {"bind": "/var/gerrit", "mode": "rw"}},
+        user="gerrit",
+        detach=True,
+        auto_remove=True,
+    )
+
+    def stop_container():
+        container_run.stop(timeout=1)
+
+    request.addfinalizer(stop_container)
+
+    return container_run
+
+
+@pytest.mark.incremental
+class TestGerritReindex:
+    def _get_indices(self, container):
+        _, indices = container.exec_run(
+            "git config -f /var/gerrit/index/gerrit_index.config "
+            + "--name-only "
+            + "--get-regexp index"
+        )
+        indices = indices.decode().strip().splitlines()
+        return [index.split(".")[1] for index in indices]
+
+    def test_gerrit_init_skips_reindexing_on_fresh_site(
+        self, temp_site, container_run_endless
+    ):
+        assert not os.path.exists(
+            os.path.join(temp_site, "index", "gerrit_index.config")
+        )
+        exit_code, _ = container_run_endless.exec_run(
+            "/var/tools/gerrit_init.py -s /var/gerrit -c /var/config/default.config.yaml"
+        )
+        assert exit_code == 0
+        expected_files = ["gerrit_index.config"] + self._get_indices(
+            container_run_endless
+        )
+        for expected_file in expected_files:
+            assert os.path.exists(os.path.join(temp_site, "index", expected_file))
+
+        timestamp_index_dir = os.path.getctime(os.path.join(temp_site, "index"))
+
+        exit_code, _ = container_run_endless.exec_run(
+            "/var/tools/gerrit_reindex.py -s /var/gerrit"
+        )
+        assert exit_code == 0
+        assert timestamp_index_dir == os.path.getctime(os.path.join(temp_site, "index"))
+
+    def test_gerrit_init_fixes_missing_index_config(
+        self, container_run_endless, temp_site
+    ):
+        container_run_endless.exec_run(
+            "/var/tools/gerrit_init.py -s /var/gerrit -c /var/config/default.config.yaml"
+        )
+        os.remove(os.path.join(temp_site, "index", "gerrit_index.config"))
+
+        exit_code, _ = container_run_endless.exec_run(
+            "/var/tools/gerrit_reindex.py -s /var/gerrit"
+        )
+        assert exit_code == 0
+
+        exit_code, _ = container_run_endless.exec_run("/var/gerrit/bin/gerrit.sh start")
+        assert exit_code == 0
+
+    def test_gerrit_init_fixes_unready_indices(self, container_run_endless):
+        container_run_endless.exec_run(
+            "/var/tools/gerrit_init.py -s /var/gerrit -c /var/config/default.config.yaml"
+        )
+
+        indices = self._get_indices(container_run_endless)
+        assert indices
+        container_run_endless.exec_run(
+            "git config -f /var/gerrit/index/gerrit_index.config %s false" % indices[0]
+        )
+
+        exit_code, _ = container_run_endless.exec_run(
+            "/var/tools/gerrit_reindex.py -s /var/gerrit"
+        )
+        assert exit_code == 0
+
+        exit_code, _ = container_run_endless.exec_run("/var/gerrit/bin/gerrit.sh start")
+        assert exit_code == 0
+
+    def test_gerrit_init_fixes_outdated_indices(self, container_run_endless, temp_site):
+        container_run_endless.exec_run(
+            "/var/tools/gerrit_init.py -s /var/gerrit -c /var/config/default.config.yaml"
+        )
+
+        index = self._get_indices(container_run_endless)[0]
+        (name, version) = index.split("_")
+        os.rename(
+            os.path.join(temp_site, "index", index),
+            os.path.join(
+                temp_site, "index", "{name}_{0:04d}".format(int(version) - 1, name=name)
+            ),
+        )
+
+        exit_code, _ = container_run_endless.exec_run(
+            "/var/tools/gerrit_reindex.py -s /var/gerrit"
+        )
+        assert exit_code == 0
+
+        exit_code, _ = container_run_endless.exec_run("/var/gerrit/bin/gerrit.sh start")
+        assert exit_code == 0
diff --git a/tests/container-images/gerrit-init/test_container_structure_gerrit_init.py b/tests/container-images/gerrit-init/test_container_structure_gerrit_init.py
index ab94fc8..1098c82 100755
--- a/tests/container-images/gerrit-init/test_container_structure_gerrit_init.py
+++ b/tests/container-images/gerrit-init/test_container_structure_gerrit_init.py
@@ -29,6 +29,7 @@
     params=[
         "/var/tools/download_plugins.py",
         "/var/tools/gerrit_init.py",
+        "/var/tools/gerrit_reindex.py",
         "/var/tools/git_config_parser.py",
         "/var/tools/init_config.py",
     ],