diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java index ac4ea3b654..fae422f50f 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheck.java @@ -31,9 +31,13 @@ package sonia.scm.repository; +//~--- non-JDK imports -------------------------------------------------------- + import sonia.scm.plugin.ExtensionPoint; /** + * Repository health check. Executes a check to verify the health + * state of a repository. * * @author Sebastian Sdorra * @since 1.36 @@ -43,12 +47,12 @@ public interface HealthCheck { /** - * Method description + * Returns the result of the repository health check. * * - * @param repository + * @param repository repository to check * - * @return + * @return result of the health check */ public HealthCheckResult check(Repository repository); } diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java index a3b8ff1857..8bac16de8d 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheckFailure.java @@ -42,6 +42,7 @@ import javax.xml.bind.annotation.XmlAccessorType; import javax.xml.bind.annotation.XmlRootElement; /** + * Single failure of a {@link HealthCheck}. * * @author Sebastian Sdorra * @since 1.36 @@ -52,17 +53,18 @@ public final class HealthCheckFailure { /** - * Constructs ... + * Constructs a new {@link HealthCheckFailure}. + * This constructor is only for JAXB. * */ HealthCheckFailure() {} /** - * Constructs ... + * Constructs a new {@link HealthCheckFailure}. * - * @param id - * @param summary - * @param description + * @param id id of the failure + * @param summary summary of the failure + * @param description description of the failure */ public HealthCheckFailure(String id, String summary, String description) { @@ -72,10 +74,10 @@ public final class HealthCheckFailure /** * Constructs ... * - * @param id - * @param summary - * @param url - * @param description + * @param id id of the failure + * @param summary summary of the failure + * @param url url of the failure + * @param description description of the failure */ public HealthCheckFailure(String id, String summary, String url, String description) @@ -89,12 +91,7 @@ public final class HealthCheckFailure //~--- methods -------------------------------------------------------------- /** - * Method description - * - * - * @param obj - * - * @return + * {@inheritDoc} */ @Override public boolean equals(Object obj) @@ -120,10 +117,7 @@ public final class HealthCheckFailure } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public int hashCode() @@ -132,10 +126,7 @@ public final class HealthCheckFailure } /** - * Method description - * - * - * @return + * {@inheritDoc} */ @Override public String toString() @@ -153,10 +144,9 @@ public final class HealthCheckFailure //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns the description of this failure. * - * - * @return + * @return description of this failure */ public String getDescription() { @@ -164,10 +154,9 @@ public final class HealthCheckFailure } /** - * Method description + * Returns the id of this failure. * - * - * @return + * @return id of this failure */ public String getId() { @@ -175,10 +164,9 @@ public final class HealthCheckFailure } /** - * Method description + * Returns the summary of the failure. * - * - * @return + * @return summary of the failure */ public String getSummary() { @@ -186,10 +174,9 @@ public final class HealthCheckFailure } /** - * Method description + * Return the url of the failure. * - * - * @return + * @return url of the failure */ public String getUrl() { @@ -198,15 +185,15 @@ public final class HealthCheckFailure //~--- fields --------------------------------------------------------------- - /** Field description */ + /** description of failure */ private String description; - /** Field description */ + /** id of failure */ private String id; - /** Field description */ + /** summary of failure */ private String summary; - /** Field description */ + /** url of failure */ private String url; } diff --git a/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java b/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java index 8f070a645f..f5cb690533 100644 --- a/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/HealthCheckResult.java @@ -33,6 +33,7 @@ package sonia.scm.repository; //~--- non-JDK imports -------------------------------------------------------- +import com.google.common.base.Objects; import com.google.common.collect.ImmutableSet; //~--- JDK imports ------------------------------------------------------------ @@ -40,6 +41,7 @@ import com.google.common.collect.ImmutableSet; import java.util.Set; /** + * Result of {@link HealthCheck}. * * @author Sebastian Sdorra * @since 1.36 @@ -47,7 +49,7 @@ import java.util.Set; public final class HealthCheckResult { - /** Field description */ + /** healthy result */ private static final HealthCheckResult HEALTHY = new HealthCheckResult(ImmutableSet.of()); @@ -67,10 +69,9 @@ public final class HealthCheckResult //~--- methods -------------------------------------------------------------- /** - * Method description + * Returns a {@link HealthCheckResult} for a healthy repository. * - * - * @return + * @return {@link HealthCheckResult} for a healthy repository */ public static HealthCheckResult healthy() { @@ -78,12 +79,12 @@ public final class HealthCheckResult } /** - * Method description + * Returns a {@link HealthCheckResult} for a unhealthy repository. * * - * @param failures + * @param failures failures of failed {@link HealthCheck}s * - * @return + * @return {@link HealthCheckResult} for a unhealthy repository */ public static HealthCheckResult unhealthy( Iterable failures) @@ -92,13 +93,13 @@ public final class HealthCheckResult } /** - * Method description + * Returns a {@link HealthCheckResult} for a unhealthy repository. * * - * @param failure - * @param otherFailures + * @param failure failure of {@link HealthCheck} + * @param otherFailures failures of failed {@link HealthCheck}s * - * @return + * @return {@link HealthCheckResult} for a unhealthy repository */ public static HealthCheckResult unhealthy(HealthCheckFailure failure, HealthCheckFailure... otherFailures) @@ -114,12 +115,43 @@ public final class HealthCheckResult } /** - * Method description + * {@inheritDoc} + */ + @Override + public boolean equals(Object obj) + { + if (obj == null) + { + return false; + } + + if (getClass() != obj.getClass()) + { + return false; + } + + final HealthCheckResult other = (HealthCheckResult) obj; + + return Objects.equal(failures, other.failures); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() + { + return Objects.hashCode(failures); + } + + /** + * Merge this {@link HealthCheckResult} with another + * {@link HealthCheckResult}. * * - * @param otherResult + * @param otherResult result to merge with * - * @return + * @return merged {@link HealthCheckResult} */ public HealthCheckResult merge(HealthCheckResult otherResult) { @@ -144,13 +176,22 @@ public final class HealthCheckResult return merged; } + /** + * {@inheritDoc} + */ + @Override + public String toString() + { + return Objects.toStringHelper(this).add("failures", failures).toString(); + } + //~--- get methods ---------------------------------------------------------- /** - * Method description + * Returns a {@link Set} of {@link HealthCheckFailure}s. The set is empty if + * the repository is healthy. * - * - * @return + * @return {@link Set} of {@link HealthCheckFailure}s */ public Set getFailures() { @@ -158,10 +199,9 @@ public final class HealthCheckResult } /** - * Method description + * Returns {@code true} if the result is healthy. * - * - * @return + * @return {@code true} if the result is healthy */ public boolean isHealthy() { @@ -169,10 +209,9 @@ public final class HealthCheckResult } /** - * Method description + * Returns {@code true} if the result is unhealthy * - * - * @return + * @return {@code true} if the result is unhealthy. */ public boolean isUnhealthy() { @@ -181,6 +220,6 @@ public final class HealthCheckResult //~--- fields --------------------------------------------------------------- - /** Field description */ + /** set of failures */ private final Set failures; } diff --git a/scm-core/src/main/java/sonia/scm/repository/Repository.java b/scm-core/src/main/java/sonia/scm/repository/Repository.java index 6d438f4d9a..88601f4451 100644 --- a/scm-core/src/main/java/sonia/scm/repository/Repository.java +++ b/scm-core/src/main/java/sonia/scm/repository/Repository.java @@ -304,10 +304,11 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Returns a {@link List} of {@link HealthCheckFailure}s. The {@link List} + * is empty if the repository is healthy. * * - * @return + * @return {@link List} of {@link HealthCheckFailure}s * @since 1.36 */ @SuppressWarnings("unchecked") @@ -410,10 +411,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Returns {@code true} if the repository is healthy. * * - * @return + * @return {@code true} if the repository is healthy * * @since 1.36 */ @@ -581,10 +582,10 @@ public class Repository extends BasicPropertiesAware implements ModelObject } /** - * Method description + * Sets {@link HealthCheckFailure} for a unhealthy repository. * * - * @param healthCheckFailures + * @param healthCheckFailures list of {@link HealthCheckFailure}s * * @since 1.36 */ diff --git a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java index f3e828c5db..a6a494d5ca 100644 --- a/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java +++ b/scm-webapp/src/main/java/sonia/scm/api/rest/resources/RepositoryResource.java @@ -54,6 +54,7 @@ import sonia.scm.repository.Branches; import sonia.scm.repository.BrowserResult; import sonia.scm.repository.Changeset; import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.HealthChecker; import sonia.scm.repository.Permission; import sonia.scm.repository.PermissionType; import sonia.scm.repository.Repository; @@ -131,16 +132,18 @@ public class RepositoryResource * @param configuration * @param repositoryManager * @param servicefactory + * @param healthChecker */ @Inject public RepositoryResource(ScmConfiguration configuration, RepositoryManager repositoryManager, - RepositoryServiceFactory servicefactory) + RepositoryServiceFactory servicefactory, HealthChecker healthChecker) { super(repositoryManager); this.configuration = configuration; this.repositoryManager = repositoryManager; this.servicefactory = servicefactory; + this.healthChecker = healthChecker; setDisableCache(false); } @@ -213,7 +216,7 @@ public class RepositoryResource } catch (ScmSecurityException ex) { - logger.warn("delete not allowd", ex); + logger.warn("delete not allowed", ex); response = Response.status(Response.Status.FORBIDDEN).build(); } catch (Exception ex) @@ -231,6 +234,50 @@ public class RepositoryResource return response; } + /** + * Re run repository health checks.
+ * Status codes: + *
    + *
  • 201 re run success
  • + *
  • 403 forbidden, the current user has no owner privileges
  • + *
  • 404 could not find repository
  • + *
  • 500 internal server error
  • + *
+ * + * @param id id of the repository + * + * @return + */ + @POST + @Path("{id}/healthcheck") + public Response runHealthChecks(@PathParam("id") String id) + { + Response response; + + try + { + healthChecker.check(id); + response = Response.ok().build(); + } + catch (RepositoryNotFoundException ex) + { + logger.warn("could not find repository ".concat(id), ex); + response = Response.status(Status.NOT_FOUND).build(); + } + catch (RepositoryException ex) + { + logger.error("error occured during health check", ex); + response = Response.serverError().build(); + } + catch (IOException ex) + { + logger.error("error occured during health check", ex); + response = Response.serverError().build(); + } + + return response; + } + /** * Modifies the given repository.
* This method requires owner privileges.
@@ -1138,6 +1185,9 @@ public class RepositoryResource /** Field description */ private final ScmConfiguration configuration; + /** Field description */ + private final HealthChecker healthChecker; + /** Field description */ private final RepositoryManager repositoryManager; diff --git a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java index 7446fb0086..9d609ece75 100644 --- a/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java +++ b/scm-webapp/src/main/java/sonia/scm/repository/HealthChecker.java @@ -81,6 +81,33 @@ public final class HealthChecker //~--- methods -------------------------------------------------------------- + /** + * Method description + * + * + * @param id + * + * + * @throws IOException + * @throws RepositoryException + * @throws RepositoryNotFoundException + */ + public void check(String id) + throws RepositoryNotFoundException, RepositoryException, IOException + { + SecurityUtils.getSubject().checkRole(Role.ADMIN); + + Repository repository = repositoryManager.get(id); + + if (repository == null) + { + throw new RepositoryNotFoundException( + "could not find repository with id ".concat(id)); + } + + check(repository); + } + /** * Method description * diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js index 7e5dd6ebed..b6db7fb59f 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.grid.js @@ -456,6 +456,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, { if (admin && item.healthCheckFailures && item.healthCheckFailures.length > 0){ panels.push({ xtype: 'repositoryHealthCheckFailurePanel', + grid: this, + repository: item, healthCheckFailures: item.healthCheckFailures }); } diff --git a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js index bdc0a46ded..195d7d7509 100644 --- a/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js +++ b/scm-webapp/src/main/webapp/resources/js/repository/sonia.repository.healthcheckfailure.js @@ -35,17 +35,24 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { title: 'Health check', linkTemplate: '{0}', + errorTitleText: 'Error', + errorDescriptionText: 'Could not execute health check.', + initComponent: function(){ var items = []; if ( this.healthCheckFailures && this.healthCheckFailures.length > 0 ){ for (var i=0; i0){ - this.appendSpacer(items); - } this.appendHealthCheckFailures(items,this.healthCheckFailures[i]); } } + items.push({ + xtype: 'link', + style: 'font-weight: bold', + text: 'rerun health checks', + handler: this.rerunHealthChecks, + scope: this + }); var config = { title: this.title, @@ -67,12 +74,34 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { Sonia.repository.HealthCheckFailure.superclass.initComponent.apply(this, arguments); }, - appendSpacer: function(items){ - items.push({ - xtype: 'box', - height: 10, - colspan: 2 + rerunHealthChecks: function(){ + var url = restUrl + 'repositories/' + this.repository.id + '/healthcheck.json'; + var el = this.el; + var tid = setTimeout( function(){el.mask('Loading ...');}, 100); + + Ext.Ajax.request({ + url: url, + method: 'POST', + scope: this, + success: function(){ + clearTimeout(tid); + this.grid.reload(function(){ + this.grid.selectById(this.repository.id); + }, this); + el.unmask(); + }, + failure: function(result){ + clearTimeout(tid); + el.unmask(); + main.handleFailure( + result.status, + this.errorTitleText, + this.errorDescriptionText + ); + } }); + + this.grid.reload(); }, appendHealthCheckFailures: function(items, hcf){ @@ -101,6 +130,11 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, { text: hcf.description }); } + items.push({ + xtype: 'box', + height: 10, + colspan: 2 + }); } });