mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-18 03:01:05 +01:00
Enable Health Checks (#1621)
In the release of version 2.0.0 of SCM-Manager, the health checks had been neglected. This makes them visible again in the frontend and adds the ability to trigger them. In addition there are two types of health checks: The "normal" ones, now called "light checks", that are run on startup, and more intense checks run only on request. As a change to version 1.x, health checks will no longer be persisted for repositories. Co-authored-by: Eduard Heimbuch <eduard.heimbuch@cloudogu.com>
This commit is contained in:
@@ -21,14 +21,25 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.HalRepresentation;
|
||||
import de.otto.edison.hal.Links;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter @Setter
|
||||
public class HealthCheckFailureDto {
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
@SuppressWarnings("java:S2160") // we do not need this for dto
|
||||
public class HealthCheckFailureDto extends HalRepresentation {
|
||||
public HealthCheckFailureDto(Links links) {
|
||||
super(links);
|
||||
}
|
||||
|
||||
private String id;
|
||||
private String description;
|
||||
private String summary;
|
||||
private String url;
|
||||
|
||||
@@ -59,6 +59,7 @@ public class RepositoryDto extends HalRepresentation implements CreateRepository
|
||||
private String type;
|
||||
private boolean archived;
|
||||
private boolean exporting;
|
||||
private boolean healthCheckRunning;
|
||||
|
||||
RepositoryDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
|
||||
@@ -30,6 +30,7 @@ import io.swagger.v3.oas.annotations.media.ExampleObject;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import io.swagger.v3.oas.annotations.parameters.RequestBody;
|
||||
import io.swagger.v3.oas.annotations.responses.ApiResponse;
|
||||
import sonia.scm.repository.HealthCheckService;
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryManager;
|
||||
@@ -61,17 +62,19 @@ public class RepositoryResource {
|
||||
private final RepositoryManager manager;
|
||||
private final SingleResourceManagerAdapter<Repository, RepositoryDto> adapter;
|
||||
private final RepositoryBasedResourceProvider resourceProvider;
|
||||
private final HealthCheckService healthCheckService;
|
||||
|
||||
@Inject
|
||||
public RepositoryResource(
|
||||
RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
|
||||
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
|
||||
RepositoryBasedResourceProvider resourceProvider) {
|
||||
RepositoryBasedResourceProvider resourceProvider, HealthCheckService healthCheckService) {
|
||||
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||
this.manager = manager;
|
||||
this.repositoryToDtoMapper = repositoryToDtoMapper;
|
||||
this.adapter = new SingleResourceManagerAdapter<>(manager, Repository.class);
|
||||
this.resourceProvider = resourceProvider;
|
||||
this.healthCheckService = healthCheckService;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,6 +274,25 @@ public class RepositoryResource {
|
||||
manager.unarchive(repository);
|
||||
}
|
||||
|
||||
@POST
|
||||
@Path("runHealthCheck")
|
||||
@Operation(summary = "Check health of repository", description = "Starts a full health check for the repository.", tags = "Repository")
|
||||
@ApiResponse(responseCode = "204", description = "check started")
|
||||
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
|
||||
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository:healthCheck\" privilege")
|
||||
@ApiResponse(
|
||||
responseCode = "404",
|
||||
description = "not found, no repository with the specified namespace and name available",
|
||||
content = @Content(
|
||||
mediaType = VndMediaType.ERROR_TYPE,
|
||||
schema = @Schema(implementation = ErrorDto.class)
|
||||
))
|
||||
@ApiResponse(responseCode = "500", description = "internal server error")
|
||||
public void runHealthCheck(@PathParam("namespace") String namespace, @PathParam("name") String name) {
|
||||
Repository repository = loadBy(namespace, name).get();
|
||||
healthCheckService.fullCheck(repository);
|
||||
}
|
||||
|
||||
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
|
||||
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
|
||||
changedRepository.setPermissions(existing.getPermissions());
|
||||
|
||||
@@ -32,10 +32,12 @@ import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.MappingTarget;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.config.ScmConfiguration;
|
||||
import sonia.scm.repository.DefaultRepositoryExportingCheck;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.HealthCheckFailure;
|
||||
import sonia.scm.repository.HealthCheckService;
|
||||
import sonia.scm.repository.NamespaceStrategy;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.RepositoryPermissions;
|
||||
@@ -68,9 +70,28 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@Inject
|
||||
private Set<NamespaceStrategy> strategies;
|
||||
@Inject
|
||||
private HealthCheckService healthCheckService;
|
||||
@Inject
|
||||
private SCMContextProvider contextProvider;
|
||||
|
||||
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
|
||||
|
||||
@ObjectFactory
|
||||
HealthCheckFailureDto createHealthCheckFailureDto(HealthCheckFailure failure) {
|
||||
String url = failure.getUrl(contextProvider.getDocumentationVersion());
|
||||
if (url == null) {
|
||||
return new HealthCheckFailureDto();
|
||||
} else {
|
||||
return new HealthCheckFailureDto(Links.linkingTo().single(link("documentation", url)).build());
|
||||
}
|
||||
}
|
||||
|
||||
@AfterMapping
|
||||
void updateHealthCheckUrlForCurrentVersion(HealthCheckFailure failure, @MappingTarget HealthCheckFailureDto dto) {
|
||||
dto.setUrl(failure.getUrl(contextProvider.getDocumentationVersion()));
|
||||
}
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
@Mapping(target = "exporting", ignore = true)
|
||||
@Override
|
||||
@@ -138,11 +159,16 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
|
||||
linksBuilder.single(link("changesets", resourceLinks.changeset().all(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("sources", resourceLinks.source().selfWithoutRevision(repository.getNamespace(), repository.getName())));
|
||||
linksBuilder.single(link("paths", resourceLinks.repository().paths(repository.getNamespace(), repository.getName())));
|
||||
if (RepositoryPermissions.healthCheck(repository).isPermitted() && !healthCheckService.checkRunning(repository)) {
|
||||
linksBuilder.single(link("runHealthCheck", resourceLinks.repository().runHealthCheck(repository.getNamespace(), repository.getName())));
|
||||
}
|
||||
|
||||
Embedded.Builder embeddedBuilder = embeddedBuilder();
|
||||
applyEnrichers(new EdisonHalAppender(linksBuilder, embeddedBuilder), repository);
|
||||
|
||||
return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
RepositoryDto repositoryDto = new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
|
||||
repositoryDto.setHealthCheckRunning(healthCheckService.checkRunning(repository));
|
||||
return repositoryDto;
|
||||
}
|
||||
|
||||
private boolean isRenameNamespacePossible() {
|
||||
|
||||
@@ -410,6 +410,10 @@ class ResourceLinks {
|
||||
String paths(String namespace, String name) {
|
||||
return repositoryPathsLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("paths").parameters().method("collect").parameters("_REVISION_").href().replace("_REVISION_", "{revision}");
|
||||
}
|
||||
|
||||
String runHealthCheck(String namespace, String name) {
|
||||
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("runHealthCheck").parameters().href();
|
||||
}
|
||||
}
|
||||
|
||||
RepositoryCollectionLinks repositoryCollection() {
|
||||
|
||||
@@ -66,11 +66,13 @@ import sonia.scm.net.ahc.XmlContentTransformer;
|
||||
import sonia.scm.plugin.DefaultPluginManager;
|
||||
import sonia.scm.plugin.PluginLoader;
|
||||
import sonia.scm.plugin.PluginManager;
|
||||
import sonia.scm.repository.DefaultHealthCheckService;
|
||||
import sonia.scm.repository.DefaultNamespaceManager;
|
||||
import sonia.scm.repository.DefaultRepositoryManager;
|
||||
import sonia.scm.repository.DefaultRepositoryProvider;
|
||||
import sonia.scm.repository.DefaultRepositoryRoleManager;
|
||||
import sonia.scm.repository.HealthCheckContextListener;
|
||||
import sonia.scm.repository.HealthCheckService;
|
||||
import sonia.scm.repository.NamespaceManager;
|
||||
import sonia.scm.repository.NamespaceStrategy;
|
||||
import sonia.scm.repository.NamespaceStrategyProvider;
|
||||
@@ -259,6 +261,8 @@ class ScmServletModule extends ServletModule {
|
||||
bind(RootURL.class).to(DefaultRootURL.class);
|
||||
|
||||
bind(PermissionProvider.class).to(RepositoryPermissionProvider.class);
|
||||
|
||||
bind(HealthCheckService.class).to(DefaultHealthCheckService.class);
|
||||
}
|
||||
|
||||
private <T> void bind(Class<T> clazz, Class<? extends T> defaultImplementation) {
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
public class DefaultHealthCheckService implements HealthCheckService {
|
||||
|
||||
private final HealthChecker healthChecker;
|
||||
|
||||
@Inject
|
||||
public DefaultHealthCheckService(HealthChecker healthChecker) {
|
||||
this.healthChecker = healthChecker;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullCheck(String id) {
|
||||
RepositoryPermissions.healthCheck(id).check();
|
||||
healthChecker.fullCheck(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fullCheck(Repository repository) {
|
||||
RepositoryPermissions.healthCheck(repository).check();
|
||||
healthChecker.fullCheck(repository);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkRunning(String repositoryId) {
|
||||
return healthChecker.checkRunning(repositoryId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean checkRunning(Repository repository) {
|
||||
return healthChecker.checkRunning(repository.getId());
|
||||
}
|
||||
}
|
||||
@@ -77,14 +77,16 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
private final Set<Type> types;
|
||||
private final Provider<NamespaceStrategy> namespaceStrategyProvider;
|
||||
private final ManagerDaoAdapter<Repository> managerDaoAdapter;
|
||||
private final RepositoryPostProcessor repositoryPostProcessor;
|
||||
|
||||
@Inject
|
||||
public DefaultRepositoryManager(SCMContextProvider contextProvider, KeyGenerator keyGenerator,
|
||||
RepositoryDAO repositoryDAO, Set<RepositoryHandler> handlerSet,
|
||||
Provider<NamespaceStrategy> namespaceStrategyProvider) {
|
||||
Provider<NamespaceStrategy> namespaceStrategyProvider, RepositoryPostProcessor repositoryPostProcessor) {
|
||||
this.keyGenerator = keyGenerator;
|
||||
this.repositoryDAO = repositoryDAO;
|
||||
this.namespaceStrategyProvider = namespaceStrategyProvider;
|
||||
this.repositoryPostProcessor = repositoryPostProcessor;
|
||||
|
||||
handlerMap = new HashMap<>();
|
||||
types = new HashSet<>();
|
||||
@@ -220,7 +222,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
Repository repository = repositoryDAO.get(id);
|
||||
|
||||
if (repository != null) {
|
||||
repository = repository.clone();
|
||||
repository = postProcess(repository);
|
||||
}
|
||||
|
||||
return repository;
|
||||
@@ -236,7 +238,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
|
||||
if (repository != null) {
|
||||
RepositoryPermissions.read(repository).check();
|
||||
repository = repository.clone();
|
||||
repository = postProcess(repository);
|
||||
}
|
||||
|
||||
return repository;
|
||||
@@ -289,7 +291,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
throw new NoChangesMadeException(repository);
|
||||
}
|
||||
|
||||
Repository changedRepository = originalRepository.clone();
|
||||
Repository changedRepository = postProcess(originalRepository);
|
||||
|
||||
changedRepository.setArchived(archived);
|
||||
|
||||
@@ -314,7 +316,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
if (handlerMap.containsKey(repository.getType())
|
||||
&& filter.test(repository)
|
||||
&& RepositoryPermissions.read().isPermitted(repository)) {
|
||||
Repository r = repository.clone();
|
||||
Repository r = postProcess(repository);
|
||||
|
||||
repositories.add(r);
|
||||
}
|
||||
@@ -342,7 +344,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
@Override
|
||||
public void append(Collection<Repository> collection, Repository item) {
|
||||
if (RepositoryPermissions.read().isPermitted(item)) {
|
||||
collection.add(item.clone());
|
||||
collection.add(postProcess(item));
|
||||
}
|
||||
}
|
||||
}, start, limit);
|
||||
@@ -427,4 +429,10 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
|
||||
|
||||
return handler;
|
||||
}
|
||||
|
||||
private Repository postProcess(Repository repository) {
|
||||
Repository clone = repository.clone();
|
||||
repositoryPostProcessor.postProcess(repository);
|
||||
return clone;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -121,7 +121,7 @@ public class HealthCheckContextListener implements ServletContextListener
|
||||
{
|
||||
|
||||
// excute health checks for all repsitories asynchronous
|
||||
SecurityUtils.getSubject().execute(healthChecker::checkAll);
|
||||
SecurityUtils.getSubject().execute(healthChecker::lightCheckAll);
|
||||
}
|
||||
|
||||
//~--- fields -------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
public interface HealthCheckService {
|
||||
|
||||
void fullCheck(String id);
|
||||
|
||||
void fullCheck(Repository repository);
|
||||
|
||||
boolean checkRunning(String repositoryId);
|
||||
|
||||
boolean checkRunning(Repository repository);
|
||||
}
|
||||
@@ -24,17 +24,27 @@
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.github.sdorra.ssp.PermissionActionCheck;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.inject.Inject;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
|
||||
import javax.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
import static java.util.Collections.synchronizedCollection;
|
||||
|
||||
public final class HealthChecker {
|
||||
@Singleton
|
||||
final class HealthChecker {
|
||||
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(HealthChecker.class);
|
||||
@@ -42,40 +52,68 @@ public final class HealthChecker {
|
||||
private final Set<HealthCheck> checks;
|
||||
|
||||
private final RepositoryManager repositoryManager;
|
||||
private final RepositoryServiceFactory repositoryServiceFactory;
|
||||
private final RepositoryPostProcessor repositoryPostProcessor;
|
||||
|
||||
private final Collection<String> checksRunning = synchronizedCollection(new HashSet<>());
|
||||
|
||||
private final ExecutorService healthCheckExecutor = Executors.newSingleThreadExecutor();
|
||||
|
||||
@Inject
|
||||
public HealthChecker(Set<HealthCheck> checks,
|
||||
RepositoryManager repositoryManager) {
|
||||
HealthChecker(Set<HealthCheck> checks,
|
||||
RepositoryManager repositoryManager,
|
||||
RepositoryServiceFactory repositoryServiceFactory,
|
||||
RepositoryPostProcessor repositoryPostProcessor) {
|
||||
this.checks = checks;
|
||||
this.repositoryManager = repositoryManager;
|
||||
this.repositoryServiceFactory = repositoryServiceFactory;
|
||||
this.repositoryPostProcessor = repositoryPostProcessor;
|
||||
}
|
||||
|
||||
public void check(String id){
|
||||
void lightCheck(String id) {
|
||||
RepositoryPermissions.healthCheck(id).check();
|
||||
|
||||
Repository repository = loadRepository(id);
|
||||
|
||||
doLightCheck(repository);
|
||||
}
|
||||
|
||||
void fullCheck(String id) {
|
||||
RepositoryPermissions.healthCheck(id).check();
|
||||
|
||||
Repository repository = loadRepository(id);
|
||||
|
||||
doFullCheck(repository);
|
||||
}
|
||||
|
||||
void lightCheck(Repository repository) {
|
||||
RepositoryPermissions.healthCheck(repository).check();
|
||||
|
||||
doLightCheck(repository);
|
||||
}
|
||||
|
||||
void fullCheck(Repository repository) {
|
||||
RepositoryPermissions.healthCheck(repository).check();
|
||||
|
||||
doFullCheck(repository);
|
||||
}
|
||||
|
||||
private Repository loadRepository(String id) {
|
||||
Repository repository = repositoryManager.get(id);
|
||||
|
||||
if (repository == null) {
|
||||
throw new NotFoundException(Repository.class, id);
|
||||
}
|
||||
|
||||
doCheck(repository);
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void check(Repository repository)
|
||||
{
|
||||
RepositoryPermissions.healthCheck(repository).check();
|
||||
|
||||
doCheck(repository);
|
||||
}
|
||||
|
||||
public void checkAll() {
|
||||
void lightCheckAll() {
|
||||
logger.debug("check health of all repositories");
|
||||
|
||||
for (Repository repository : repositoryManager.getAll()) {
|
||||
if (RepositoryPermissions.healthCheck().isPermitted(repository)) {
|
||||
try {
|
||||
check(repository);
|
||||
lightCheck(repository);
|
||||
} catch (NotFoundException ex) {
|
||||
logger.error("health check ends with exception", ex);
|
||||
}
|
||||
@@ -87,32 +125,89 @@ public final class HealthChecker {
|
||||
}
|
||||
}
|
||||
|
||||
private void doCheck(Repository repository){
|
||||
logger.info("start health check for repository {}", repository);
|
||||
private void doLightCheck(Repository repository) {
|
||||
withLockedRepository(repository, () -> {
|
||||
HealthCheckResult result = gatherLightChecks(repository);
|
||||
|
||||
if (result.isUnhealthy()) {
|
||||
logger.warn("repository {} is unhealthy: {}", repository,
|
||||
result);
|
||||
} else {
|
||||
logger.info("repository {} is healthy", repository);
|
||||
}
|
||||
|
||||
storeResult(repository, result);
|
||||
});
|
||||
}
|
||||
|
||||
private HealthCheckResult gatherLightChecks(Repository repository) {
|
||||
logger.info("start light health check for repository {}", repository);
|
||||
|
||||
HealthCheckResult result = HealthCheckResult.healthy();
|
||||
|
||||
for (HealthCheck check : checks) {
|
||||
logger.trace("execute health check {} for repository {}",
|
||||
logger.trace("execute light health check {} for repository {}",
|
||||
check.getClass(), repository);
|
||||
result = result.merge(check.check(repository));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
if (result.isUnhealthy()) {
|
||||
logger.warn("repository {} is unhealthy: {}", repository,
|
||||
result);
|
||||
} else {
|
||||
logger.info("repository {} is healthy", repository);
|
||||
private void doFullCheck(Repository repository) {
|
||||
withLockedRepository(repository, () ->
|
||||
runInExecutorAndWait(repository, () -> {
|
||||
HealthCheckResult lightCheckResult = gatherLightChecks(repository);
|
||||
HealthCheckResult fullCheckResult = gatherFullChecks(repository);
|
||||
HealthCheckResult result = lightCheckResult.merge(fullCheckResult);
|
||||
|
||||
storeResult(repository, result);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
private void withLockedRepository(Repository repository, Runnable runnable) {
|
||||
if (!checksRunning.add(repository.getId())) {
|
||||
logger.debug("check for repository {} is already running", repository);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(repository.isHealthy() && result.isHealthy())) {
|
||||
logger.trace("store health check results for repository {}",
|
||||
repository);
|
||||
repository.setHealthCheckFailures(
|
||||
ImmutableList.copyOf(result.getFailures()));
|
||||
repositoryManager.modify(repository);
|
||||
try {
|
||||
runnable.run();
|
||||
} finally {
|
||||
checksRunning.remove(repository.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void runInExecutorAndWait(Repository repository, Runnable runnable) {
|
||||
try {
|
||||
healthCheckExecutor.submit(runnable).get();
|
||||
} catch (ExecutionException e) {
|
||||
logger.warn("could not submit task for health check for repository {}", repository, e);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
}
|
||||
|
||||
private HealthCheckResult gatherFullChecks(Repository repository) {
|
||||
try (RepositoryService service = repositoryServiceFactory.create(repository)) {
|
||||
if (service.isSupported(Command.FULL_HEALTH_CHECK)) {
|
||||
return service.getFullCheckCommand().check();
|
||||
} else {
|
||||
return HealthCheckResult.healthy();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new InternalRepositoryException(repository, "error during full health check", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void storeResult(Repository repository, HealthCheckResult result) {
|
||||
if (!(repository.isHealthy() && result.isHealthy())) {
|
||||
logger.trace("store health check results for repository {}",
|
||||
repository);
|
||||
repositoryPostProcessor.setCheckResults(repository, result.getFailures());
|
||||
}
|
||||
}
|
||||
|
||||
public boolean checkRunning(String repositoryId) {
|
||||
return checksRunning.contains(repositoryId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.google.common.collect.ImmutableList.copyOf;
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
@Singleton
|
||||
class RepositoryPostProcessor {
|
||||
|
||||
private final ScmEventBus eventBus;
|
||||
|
||||
private final Map<String, List<HealthCheckFailure>> checkResults = new HashMap<>();
|
||||
|
||||
@Inject
|
||||
RepositoryPostProcessor(ScmEventBus eventBus) {
|
||||
this.eventBus = eventBus;
|
||||
}
|
||||
|
||||
void setCheckResults(Repository repository, Collection<HealthCheckFailure> failures) {
|
||||
List<HealthCheckFailure> oldFailures = getCheckResults(repository.getId());
|
||||
List<HealthCheckFailure> copyOfFailures = copyOf(failures);
|
||||
checkResults.put(repository.getId(), copyOfFailures);
|
||||
repository.setHealthCheckFailures(copyOfFailures);
|
||||
eventBus.post(new HealthCheckEvent(repository, oldFailures, copyOfFailures));
|
||||
}
|
||||
|
||||
void postProcess(Repository repository) {
|
||||
repository.setHealthCheckFailures(getCheckResults(repository.getId()));
|
||||
}
|
||||
|
||||
private List<HealthCheckFailure> getCheckResults(String repositoryId) {
|
||||
return checkResults.getOrDefault(repositoryId, emptyList());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user