allow rerun of healthchecks

This commit is contained in:
Sebastian Sdorra
2014-01-25 15:00:04 +01:00
parent 9e9351fe86
commit 08dede40fe
8 changed files with 226 additions and 82 deletions

View File

@@ -31,9 +31,13 @@
package sonia.scm.repository; package sonia.scm.repository;
//~--- non-JDK imports --------------------------------------------------------
import sonia.scm.plugin.ExtensionPoint; import sonia.scm.plugin.ExtensionPoint;
/** /**
* Repository health check. Executes a check to verify the health
* state of a repository.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 1.36 * @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); public HealthCheckResult check(Repository repository);
} }

View File

@@ -42,6 +42,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlRootElement; import javax.xml.bind.annotation.XmlRootElement;
/** /**
* Single failure of a {@link HealthCheck}.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 1.36 * @since 1.36
@@ -52,17 +53,18 @@ public final class HealthCheckFailure
{ {
/** /**
* Constructs ... * Constructs a new {@link HealthCheckFailure}.
* This constructor is only for JAXB.
* *
*/ */
HealthCheckFailure() {} HealthCheckFailure() {}
/** /**
* Constructs ... * Constructs a new {@link HealthCheckFailure}.
* *
* @param id * @param id id of the failure
* @param summary * @param summary summary of the failure
* @param description * @param description description of the failure
*/ */
public HealthCheckFailure(String id, String summary, String description) public HealthCheckFailure(String id, String summary, String description)
{ {
@@ -72,10 +74,10 @@ public final class HealthCheckFailure
/** /**
* Constructs ... * Constructs ...
* *
* @param id * @param id id of the failure
* @param summary * @param summary summary of the failure
* @param url * @param url url of the failure
* @param description * @param description description of the failure
*/ */
public HealthCheckFailure(String id, String summary, String url, public HealthCheckFailure(String id, String summary, String url,
String description) String description)
@@ -89,12 +91,7 @@ public final class HealthCheckFailure
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/** /**
* Method description * {@inheritDoc}
*
*
* @param obj
*
* @return
*/ */
@Override @Override
public boolean equals(Object obj) public boolean equals(Object obj)
@@ -120,10 +117,7 @@ public final class HealthCheckFailure
} }
/** /**
* Method description * {@inheritDoc}
*
*
* @return
*/ */
@Override @Override
public int hashCode() public int hashCode()
@@ -132,10 +126,7 @@ public final class HealthCheckFailure
} }
/** /**
* Method description * {@inheritDoc}
*
*
* @return
*/ */
@Override @Override
public String toString() public String toString()
@@ -153,10 +144,9 @@ public final class HealthCheckFailure
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**
* Method description * Returns the description of this failure.
* *
* * @return description of this failure
* @return
*/ */
public String getDescription() public String getDescription()
{ {
@@ -164,10 +154,9 @@ public final class HealthCheckFailure
} }
/** /**
* Method description * Returns the id of this failure.
* *
* * @return id of this failure
* @return
*/ */
public String getId() public String getId()
{ {
@@ -175,10 +164,9 @@ public final class HealthCheckFailure
} }
/** /**
* Method description * Returns the summary of the failure.
* *
* * @return summary of the failure
* @return
*/ */
public String getSummary() public String getSummary()
{ {
@@ -186,10 +174,9 @@ public final class HealthCheckFailure
} }
/** /**
* Method description * Return the url of the failure.
* *
* * @return url of the failure
* @return
*/ */
public String getUrl() public String getUrl()
{ {
@@ -198,15 +185,15 @@ public final class HealthCheckFailure
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** description of failure */
private String description; private String description;
/** Field description */ /** id of failure */
private String id; private String id;
/** Field description */ /** summary of failure */
private String summary; private String summary;
/** Field description */ /** url of failure */
private String url; private String url;
} }

View File

@@ -33,6 +33,7 @@ package sonia.scm.repository;
//~--- non-JDK imports -------------------------------------------------------- //~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -40,6 +41,7 @@ import com.google.common.collect.ImmutableSet;
import java.util.Set; import java.util.Set;
/** /**
* Result of {@link HealthCheck}.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
* @since 1.36 * @since 1.36
@@ -47,7 +49,7 @@ import java.util.Set;
public final class HealthCheckResult public final class HealthCheckResult
{ {
/** Field description */ /** healthy result */
private static final HealthCheckResult HEALTHY = private static final HealthCheckResult HEALTHY =
new HealthCheckResult(ImmutableSet.<HealthCheckFailure>of()); new HealthCheckResult(ImmutableSet.<HealthCheckFailure>of());
@@ -67,10 +69,9 @@ public final class HealthCheckResult
//~--- methods -------------------------------------------------------------- //~--- methods --------------------------------------------------------------
/** /**
* Method description * Returns a {@link HealthCheckResult} for a healthy repository.
* *
* * @return {@link HealthCheckResult} for a healthy repository
* @return
*/ */
public static HealthCheckResult healthy() 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( public static HealthCheckResult unhealthy(
Iterable<HealthCheckFailure> failures) Iterable<HealthCheckFailure> failures)
@@ -92,13 +93,13 @@ public final class HealthCheckResult
} }
/** /**
* Method description * Returns a {@link HealthCheckResult} for a unhealthy repository.
* *
* *
* @param failure * @param failure failure of {@link HealthCheck}
* @param otherFailures * @param otherFailures failures of failed {@link HealthCheck}s
* *
* @return * @return {@link HealthCheckResult} for a unhealthy repository
*/ */
public static HealthCheckResult unhealthy(HealthCheckFailure failure, public static HealthCheckResult unhealthy(HealthCheckFailure failure,
HealthCheckFailure... otherFailures) 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) public HealthCheckResult merge(HealthCheckResult otherResult)
{ {
@@ -144,13 +176,22 @@ public final class HealthCheckResult
return merged; return merged;
} }
/**
* {@inheritDoc}
*/
@Override
public String toString()
{
return Objects.toStringHelper(this).add("failures", failures).toString();
}
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
/** /**
* Method description * Returns a {@link Set} of {@link HealthCheckFailure}s. The set is empty if
* the repository is healthy.
* *
* * @return {@link Set} of {@link HealthCheckFailure}s
* @return
*/ */
public Set<HealthCheckFailure> getFailures() public Set<HealthCheckFailure> getFailures()
{ {
@@ -158,10 +199,9 @@ public final class HealthCheckResult
} }
/** /**
* Method description * Returns {@code true} if the result is healthy.
* *
* * @return {@code true} if the result is healthy
* @return
*/ */
public boolean isHealthy() public boolean isHealthy()
{ {
@@ -169,10 +209,9 @@ public final class HealthCheckResult
} }
/** /**
* Method description * Returns {@code true} if the result is unhealthy
* *
* * @return {@code true} if the result is unhealthy.
* @return
*/ */
public boolean isUnhealthy() public boolean isUnhealthy()
{ {
@@ -181,6 +220,6 @@ public final class HealthCheckResult
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /** set of failures */
private final Set<HealthCheckFailure> failures; private final Set<HealthCheckFailure> failures;
} }

View File

@@ -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 * @since 1.36
*/ */
@SuppressWarnings("unchecked") @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 * @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 * @since 1.36
*/ */

View File

@@ -54,6 +54,7 @@ import sonia.scm.repository.Branches;
import sonia.scm.repository.BrowserResult; import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.Changeset; import sonia.scm.repository.Changeset;
import sonia.scm.repository.ChangesetPagingResult; import sonia.scm.repository.ChangesetPagingResult;
import sonia.scm.repository.HealthChecker;
import sonia.scm.repository.Permission; import sonia.scm.repository.Permission;
import sonia.scm.repository.PermissionType; import sonia.scm.repository.PermissionType;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
@@ -131,16 +132,18 @@ public class RepositoryResource
* @param configuration * @param configuration
* @param repositoryManager * @param repositoryManager
* @param servicefactory * @param servicefactory
* @param healthChecker
*/ */
@Inject @Inject
public RepositoryResource(ScmConfiguration configuration, public RepositoryResource(ScmConfiguration configuration,
RepositoryManager repositoryManager, RepositoryManager repositoryManager,
RepositoryServiceFactory servicefactory) RepositoryServiceFactory servicefactory, HealthChecker healthChecker)
{ {
super(repositoryManager); super(repositoryManager);
this.configuration = configuration; this.configuration = configuration;
this.repositoryManager = repositoryManager; this.repositoryManager = repositoryManager;
this.servicefactory = servicefactory; this.servicefactory = servicefactory;
this.healthChecker = healthChecker;
setDisableCache(false); setDisableCache(false);
} }
@@ -213,7 +216,7 @@ public class RepositoryResource
} }
catch (ScmSecurityException ex) catch (ScmSecurityException ex)
{ {
logger.warn("delete not allowd", ex); logger.warn("delete not allowed", ex);
response = Response.status(Response.Status.FORBIDDEN).build(); response = Response.status(Response.Status.FORBIDDEN).build();
} }
catch (Exception ex) catch (Exception ex)
@@ -231,6 +234,50 @@ public class RepositoryResource
return response; return response;
} }
/**
* Re run repository health checks.<br />
* Status codes:
* <ul>
* <li>201 re run success</li>
* <li>403 forbidden, the current user has no owner privileges</li>
* <li>404 could not find repository</li>
* <li>500 internal server error</li>
* </ul>
*
* @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.<br /> * Modifies the given repository.<br />
* This method requires owner privileges.<br /> * This method requires owner privileges.<br />
@@ -1138,6 +1185,9 @@ public class RepositoryResource
/** Field description */ /** Field description */
private final ScmConfiguration configuration; private final ScmConfiguration configuration;
/** Field description */
private final HealthChecker healthChecker;
/** Field description */ /** Field description */
private final RepositoryManager repositoryManager; private final RepositoryManager repositoryManager;

View File

@@ -81,6 +81,33 @@ public final class HealthChecker
//~--- methods -------------------------------------------------------------- //~--- 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 * Method description
* *

View File

@@ -456,6 +456,8 @@ Sonia.repository.Grid = Ext.extend(Sonia.rest.Grid, {
if (admin && item.healthCheckFailures && item.healthCheckFailures.length > 0){ if (admin && item.healthCheckFailures && item.healthCheckFailures.length > 0){
panels.push({ panels.push({
xtype: 'repositoryHealthCheckFailurePanel', xtype: 'repositoryHealthCheckFailurePanel',
grid: this,
repository: item,
healthCheckFailures: item.healthCheckFailures healthCheckFailures: item.healthCheckFailures
}); });
} }

View File

@@ -35,17 +35,24 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, {
title: 'Health check', title: 'Health check',
linkTemplate: '<a target="_blank" href="{0}">{0}</a>', linkTemplate: '<a target="_blank" href="{0}">{0}</a>',
errorTitleText: 'Error',
errorDescriptionText: 'Could not execute health check.',
initComponent: function(){ initComponent: function(){
var items = []; var items = [];
if ( this.healthCheckFailures && this.healthCheckFailures.length > 0 ){ if ( this.healthCheckFailures && this.healthCheckFailures.length > 0 ){
for (var i=0; i<this.healthCheckFailures.length; i++){ for (var i=0; i<this.healthCheckFailures.length; i++){
if (i>0){
this.appendSpacer(items);
}
this.appendHealthCheckFailures(items,this.healthCheckFailures[i]); 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 = { var config = {
title: this.title, title: this.title,
@@ -67,12 +74,34 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, {
Sonia.repository.HealthCheckFailure.superclass.initComponent.apply(this, arguments); Sonia.repository.HealthCheckFailure.superclass.initComponent.apply(this, arguments);
}, },
appendSpacer: function(items){ rerunHealthChecks: function(){
items.push({ var url = restUrl + 'repositories/' + this.repository.id + '/healthcheck.json';
xtype: 'box', var el = this.el;
height: 10, var tid = setTimeout( function(){el.mask('Loading ...');}, 100);
colspan: 2
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){ appendHealthCheckFailures: function(items, hcf){
@@ -101,6 +130,11 @@ Sonia.repository.HealthCheckFailure = Ext.extend(Ext.Panel, {
text: hcf.description text: hcf.description
}); });
} }
items.push({
xtype: 'box',
height: 10,
colspan: 2
});
} }
}); });