blob: 326746996add532d2df834df2a7b83a5163a21ce [file] [log] [blame]
// 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();
}
}
}