| #!/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 abc |
| import enum |
| import os.path |
| import shutil |
| import subprocess |
| import sys |
| |
| import requests |
| |
| from ..constants import MNT_PATH, SITE_PATH |
| from ..helpers import git, log |
| |
| LOG = log.get_logger("reindex") |
| INDEX_CONFIG_PATH = f"{SITE_PATH}/index/gerrit_index.config" |
| INDEXES_PRIMARY = set(["accounts", "changes", "groups", "projects"]) |
| INDEXES_REPLICA = set(["groups"]) |
| |
| |
| class IndexType(enum.Enum): |
| LUCENE = enum.auto() |
| ELASTICSEARCH = enum.auto() |
| |
| |
| class GerritAbstractReindexer(abc.ABC): |
| def __init__(self, gerrit_config, initializer_config): |
| self.init_config = initializer_config |
| |
| self.gerrit_config = gerrit_config |
| self.is_replica = self.gerrit_config.get_boolean("container.replica", False) |
| |
| self.configured_indices = self._parse_gerrit_index_config() |
| |
| @abc.abstractmethod |
| def _get_indices(self): |
| pass |
| |
| @abc.abstractmethod |
| def _prepare(self): |
| pass |
| |
| def _parse_gerrit_index_config(self): |
| indices = {} |
| if os.path.exists(INDEX_CONFIG_PATH): |
| config = git.GitConfigParser(INDEX_CONFIG_PATH) |
| options = config.list() |
| for opt in options: |
| name, version = opt["subsection"].rsplit("_", 1) |
| ready = opt["value"].lower() == "true" |
| if name in indices: |
| indices[name] = { |
| "read": version if ready else indices[name]["read"], |
| "latest_write": max(version, indices[name]["latest_write"]), |
| } |
| else: |
| indices[name] = { |
| "read": version if ready else None, |
| "latest_write": version, |
| } |
| return indices |
| |
| def _get_not_ready_indices(self): |
| not_ready_indices = [] |
| for index, index_attrs in self.configured_indices.items(): |
| if not index_attrs["read"]: |
| LOG.info("Index %s not ready.", index) |
| not_ready_indices.append(index) |
| index_set = INDEXES_REPLICA if self.is_replica else INDEXES_PRIMARY |
| not_ready_indices.extend(index_set.difference(self.configured_indices.keys())) |
| return not_ready_indices |
| |
| def _get_missing_indices(self): |
| indices = self._get_indices() |
| |
| missing_indices = [] |
| index_set = INDEXES_REPLICA if self.is_replica else INDEXES_PRIMARY |
| if not indices: |
| return index_set |
| |
| for index in index_set: |
| if index not in indices: |
| missing_indices.append(index) |
| return missing_indices |
| |
| def reindex(self, indices=None): |
| LOG.info("Starting to reindex.") |
| command = f"java -jar /var/war/gerrit.war reindex -d {SITE_PATH}" |
| |
| if indices: |
| command += " ".join([f" --index {i}" for i in indices]) |
| |
| reindex_process = subprocess.run( |
| command.split(), stdout=subprocess.PIPE, check=True |
| ) |
| |
| if reindex_process.returncode > 0: |
| LOG.error( |
| "An error occurred, when reindexing Gerrit indices. Exit code: %d", |
| reindex_process.returncode, |
| ) |
| sys.exit(1) |
| |
| LOG.info("Finished reindexing.") |
| |
| def start(self, is_forced): |
| self._prepare() |
| self.configured_indices = self._parse_gerrit_index_config() |
| |
| 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 |
| |
| missing_indices = self._get_missing_indices() |
| if missing_indices: |
| LOG.info("Missing at least one index. Reindexing all.") |
| self.reindex(missing_indices) |
| return |
| |
| not_ready_indices = self._get_not_ready_indices() |
| if not_ready_indices: |
| self.reindex(not_ready_indices) |
| return |
| |
| LOG.info("Skipping reindexing.") |
| |
| |
| class GerritLuceneReindexer(GerritAbstractReindexer): |
| def _prepare(self): |
| pass |
| |
| def _get_indices(self): |
| file_list = os.listdir(os.path.join(SITE_PATH, "index")) |
| file_list.remove("gerrit_index.config") |
| lucene_indices = {} |
| for index in file_list: |
| try: |
| (name, version) = index.split("_") |
| if name in lucene_indices: |
| lucene_indices[name] = max(version, lucene_indices[name]) |
| else: |
| lucene_indices[name] = version |
| except ValueError: |
| LOG.debug("Ignoring invalid file in index-directory: %s", index) |
| return lucene_indices |
| |
| |
| class GerritElasticSearchReindexer(GerritAbstractReindexer): |
| def _get_elasticsearch_config(self): |
| es_config = {} |
| gerrit_config = git.GitConfigParser( |
| os.path.join(SITE_PATH, "etc", "gerrit.config") |
| ) |
| es_config["prefix"] = gerrit_config.get( |
| "elasticsearch.prefix", default="" |
| ).lower() |
| es_config["server"] = gerrit_config.get( |
| "elasticsearch.server", default="" |
| ).lower() |
| return es_config |
| |
| def _get_indices(self): |
| es_config = self._get_elasticsearch_config() |
| url = f"{es_config['server']}/{es_config['prefix']}*" |
| try: |
| response = requests.get(url) |
| except requests.exceptions.SSLError: |
| response = requests.get(url, verify=self.init_config.ca_cert_path) |
| |
| es_indices = {} |
| for index, _ in response.json().items(): |
| try: |
| index = index.replace(es_config["prefix"], "", 1) |
| (name, version) = index.split("_") |
| es_indices[name] = version |
| except ValueError: |
| LOG.debug("Found unknown index: %s", index) |
| |
| return es_indices |
| |
| def _prepare(self): |
| index_mnt_path = f"{MNT_PATH}/index" |
| index_site_path = f"{SITE_PATH}/index" |
| if not os.path.exists(index_mnt_path): |
| LOG.warning("No mounted index directory found in %s", index_mnt_path) |
| return |
| if os.path.exists(index_site_path): |
| if os.path.islink(index_site_path): |
| os.unlink(index_site_path) |
| elif os.path.isdir(index_site_path): |
| shutil.rmtree(index_site_path) |
| LOG.info(os.path.exists(index_site_path)) |
| os.symlink(index_mnt_path, index_site_path, target_is_directory=True) |
| |
| |
| def get_reindexer(gerrit_config, initializer_config): |
| if get_index_type(gerrit_config) == IndexType.ELASTICSEARCH: |
| return GerritElasticSearchReindexer(gerrit_config, initializer_config) |
| |
| return GerritLuceneReindexer(gerrit_config, initializer_config) |
| |
| |
| def get_index_type(gerrit_config): |
| indexModule = gerrit_config.get("gerrit.installIndexModule") |
| if indexModule and indexModule.startswith("com.google.gerrit.elasticsearch"): |
| return IndexType.ELASTICSEARCH |
| |
| return IndexType.LUCENE |