| // 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.googlesource.gerrit.plugins.healthcheck.check; |
| |
| import static com.googlesource.gerrit.plugins.healthcheck.check.HealthCheckNames.CHANGES_INDEX; |
| |
| import com.google.common.util.concurrent.ListeningExecutorService; |
| import com.google.gerrit.index.IndexType; |
| import com.google.gerrit.metrics.MetricMaker; |
| import com.google.gerrit.server.config.GerritServerConfig; |
| import com.google.gerrit.server.config.SitePaths; |
| import com.google.gerrit.server.index.OnlineUpgradeListener; |
| import com.google.inject.Inject; |
| import com.google.inject.Singleton; |
| import com.googlesource.gerrit.plugins.healthcheck.HealthCheckConfig; |
| import java.io.File; |
| import java.io.IOException; |
| import java.nio.file.Path; |
| import java.util.Optional; |
| import java.util.concurrent.atomic.AtomicReference; |
| import org.eclipse.jgit.errors.ConfigInvalidException; |
| import org.eclipse.jgit.lib.Config; |
| import org.eclipse.jgit.storage.file.FileBasedConfig; |
| import org.eclipse.jgit.util.FS; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| @Singleton |
| public class ChangesIndexHealthCheck extends AbstractHealthCheck implements OnlineUpgradeListener { |
| private static final Logger log = LoggerFactory.getLogger(QueryChangesHealthCheck.class); |
| private static final String lockFilename = "write.lock"; |
| |
| private final SitePaths sitePaths; |
| private final boolean isLuceneIndex; |
| private final AtomicReference<Optional<ChangesIndexLockFiles>> changes; |
| |
| @Inject |
| ChangesIndexHealthCheck( |
| @GerritServerConfig Config cfg, |
| ListeningExecutorService executor, |
| HealthCheckConfig config, |
| MetricMaker metricMaker, |
| SitePaths sitePaths) { |
| super(executor, config, CHANGES_INDEX, metricMaker); |
| this.sitePaths = sitePaths; |
| this.isLuceneIndex = isIndexTypeLucene(cfg); |
| this.changes = new AtomicReference<>(getChangesIndexLockFiles(sitePaths.index_dir)); |
| } |
| |
| @Override |
| protected Result doCheck() throws Exception { |
| return isLuceneIndex |
| ? changes |
| .get() |
| .map(locks -> locks.valid() ? Result.PASSED : Result.FAILED) |
| .orElse(Result.FAILED) |
| : Result.DISABLED; |
| } |
| |
| @Override |
| public void onStart(String name, int oldVersion, int newVersion) {} |
| |
| @Override |
| public void onSuccess(String name, int oldVersion, int newVersion) { |
| if (!isLuceneIndex || !name.equals("changes") || oldVersion == newVersion) { |
| return; |
| } |
| |
| Optional<ChangesIndexLockFiles> newLockFiles = |
| Optional.of( |
| getChangesLockFiles(sitePaths.index_dir, String.format("changes_%04d", newVersion))); |
| if (!changes.compareAndSet(changes.get(), newLockFiles)) { |
| log.info( |
| "New version {} of changes index healthcheck lock files was set already by another thread", |
| newVersion); |
| } else { |
| log.info( |
| "Changes index healthcheck switched from index version {} to {}", oldVersion, newVersion); |
| } |
| } |
| |
| @Override |
| public void onFailure(String name, int oldVersion, int newVersion) {} |
| |
| private static boolean isIndexTypeLucene(Config cfg) { |
| IndexType indexType = new IndexType(cfg.getString("index", null, "type")); |
| boolean isLucene = indexType.isLucene(); |
| if (!isLucene) { |
| log.warn( |
| "Configured index type [{}] is not supported for index health check therefore it is disabled.", |
| indexType); |
| } |
| return isLucene; |
| } |
| |
| private Optional<ChangesIndexLockFiles> getChangesIndexLockFiles(Path indexDir) { |
| if (!isLuceneIndex) { |
| return Optional.empty(); |
| } |
| |
| FileBasedConfig cfg = |
| new FileBasedConfig(indexDir.resolve("gerrit_index.config").toFile(), FS.detect()); |
| try { |
| cfg.load(); |
| return getActiveIndexVersion(cfg, "changes") |
| .map(version -> getChangesLockFiles(indexDir, version)); |
| } catch (IOException | ConfigInvalidException e) { |
| log.error("Getting changes index version from configuration failed", e); |
| } |
| return Optional.empty(); |
| } |
| |
| private static Optional<String> getActiveIndexVersion(Config cfg, String indexPrefix) { |
| String section = "index"; |
| return cfg.getSubsections(section).stream() |
| .filter( |
| subsection -> |
| subsection.startsWith(indexPrefix) |
| && cfg.getBoolean(section, subsection, "ready", false)) |
| .findAny(); |
| } |
| |
| private ChangesIndexLockFiles getChangesLockFiles(Path indexDir, String indexVersion) { |
| Path versionDir = indexDir.resolve(indexVersion); |
| return new ChangesIndexLockFiles( |
| versionDir.resolve("open").resolve(lockFilename).toFile(), |
| versionDir.resolve("closed").resolve(lockFilename).toFile()); |
| } |
| |
| private static class ChangesIndexLockFiles { |
| private final File openLock; |
| private final File closedLock; |
| |
| private ChangesIndexLockFiles(File openLock, File closedLock) { |
| this.openLock = openLock; |
| this.closedLock = closedLock; |
| } |
| |
| private boolean valid() { |
| return validLock(openLock) && validLock(closedLock); |
| } |
| |
| private static boolean validLock(File writeLock) { |
| return writeLock.isFile() && writeLock.canWrite(); |
| } |
| } |
| } |