Skip to content

Commit

Permalink
SONAR-9741 return nodes in response of api/system/health
Browse files Browse the repository at this point in the history
  • Loading branch information
sns-seb committed Sep 13, 2017
1 parent fa0fe58 commit 9250ab1
Show file tree
Hide file tree
Showing 17 changed files with 786 additions and 55 deletions.
@@ -0,0 +1,71 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.health;

import java.util.Objects;
import java.util.Set;
import org.sonar.cluster.health.NodeHealth;

import static com.google.common.collect.ImmutableSet.copyOf;
import static java.util.Objects.requireNonNull;

public class ClusterHealth {
private final Health health;
private final Set<NodeHealth> nodes;

public ClusterHealth(Health health, Set<NodeHealth> nodes) {
this.health = requireNonNull(health, "health can't be null");
this.nodes = copyOf(requireNonNull(nodes, "nodes can't be null"));
}

public Health getHealth() {
return health;
}

public Set<NodeHealth> getNodes() {
return nodes;
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
ClusterHealth that = (ClusterHealth) o;
return Objects.equals(health, that.health) &&
Objects.equals(nodes, that.nodes);
}

@Override
public int hashCode() {
return Objects.hash(health, nodes);
}

@Override
public String toString() {
return "ClusterHealth{" +
"health=" + health +
", nodes=" + nodes +
'}';
}
}
Expand Up @@ -31,7 +31,7 @@ public class Health {
/** /**
* The GREEN status without any cause as a constant, for convenience and optimisation. * The GREEN status without any cause as a constant, for convenience and optimisation.
*/ */
static final Health GREEN = newHealthCheckBuilder() public static final Health GREEN = newHealthCheckBuilder()
.setStatus(Status.GREEN) .setStatus(Status.GREEN)
.build(); .build();


Expand Down
Expand Up @@ -31,5 +31,5 @@ public interface HealthChecker {
* *
* @throws IllegalStateException if clustering is not enabled. * @throws IllegalStateException if clustering is not enabled.
*/ */
Health checkCluster(); ClusterHealth checkCluster();
} }
Expand Up @@ -70,14 +70,15 @@ public Health checkNode() {
} }


@Override @Override
public Health checkCluster() { public ClusterHealth checkCluster() {
checkState(!webServer.isStandalone(), "Clustering is not enabled"); checkState(!webServer.isStandalone(), "Clustering is not enabled");
checkState(sharedHealthState != null, "HealthState instance can't be null when clustering is enabled"); checkState(sharedHealthState != null, "HealthState instance can't be null when clustering is enabled");


Set<NodeHealth> nodeHealths = sharedHealthState.readAll(); Set<NodeHealth> nodeHealths = sharedHealthState.readAll();
return clusterHealthChecks.stream() Health health = clusterHealthChecks.stream()
.map(clusterHealthCheck -> clusterHealthCheck.check(nodeHealths)) .map(clusterHealthCheck -> clusterHealthCheck.check(nodeHealths))
.reduce(Health.GREEN, HealthReducer.INSTANCE); .reduce(Health.GREEN, HealthReducer.INSTANCE);
return new ClusterHealth(health, nodeHealths);
} }


private enum HealthReducer implements BinaryOperator<Health> { private enum HealthReducer implements BinaryOperator<Health> {
Expand Down
Expand Up @@ -19,47 +19,33 @@
*/ */
package org.sonar.server.platform.ws; package org.sonar.server.platform.ws;


import com.google.common.io.Resources;
import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService; import org.sonar.api.server.ws.WebService;
import org.sonar.server.health.Health; import org.sonar.server.platform.WebServer;
import org.sonar.server.health.HealthChecker;
import org.sonar.server.ws.WsUtils; import org.sonar.server.ws.WsUtils;
import org.sonarqube.ws.WsSystem;


public class HealthAction implements SystemWsAction { public class HealthAction implements SystemWsAction {
private final HealthChecker healthChecker; private final WebServer webServer;
private final HealthActionSupport support;


public HealthAction(HealthChecker healthChecker) { public HealthAction(WebServer webServer, HealthActionSupport support) {
this.healthChecker = healthChecker; this.webServer = webServer;
this.support = support;
} }


@Override @Override
public void define(WebService.NewController controller) { public void define(WebService.NewController controller) {
controller.createAction("health") support.define(controller, this);
.setDescription("Provide health status of the current SonarQube instance." +
"<p>status: the health status" +
" <ul>" +
" <li>GREEN: SonarQube is fully operational</li>" +
" <li>YELLOW: SonarQube is operational but something must be fixed to be fully operational</li>" +
" <li>RED: SonarQube is not operational</li>" +
" </ul>" +
"</p>")
.setSince("6.6")
.setResponseExample(Resources.getResource(this.getClass(), "example-health.json"))
.setHandler(this);
} }


@Override @Override
public void handle(Request request, Response response) throws Exception { public void handle(Request request, Response response) throws Exception {
Health check = healthChecker.checkNode(); if (webServer.isStandalone()) {
WsSystem.HealthResponse.Builder responseBuilder = WsSystem.HealthResponse.newBuilder() WsUtils.writeProtobuf(support.checkNodeHealth(), request, response);
.setHealth(WsSystem.Health.valueOf(check.getStatus().name())); } else {
WsSystem.Cause.Builder causeBuilder = WsSystem.Cause.newBuilder(); WsUtils.writeProtobuf(support.checkClusterHealth(), request, response);
check.getCauses().forEach(str -> responseBuilder.addCauses(causeBuilder.clear().setMessage(str).build())); }

WsUtils.writeProtobuf(responseBuilder.build(), request, response);
} }


} }
Expand Up @@ -41,6 +41,7 @@ protected void configureModule() {
add(EsStatusClusterCheck.class); add(EsStatusClusterCheck.class);


add(HealthCheckerImpl.class, add(HealthCheckerImpl.class,
HealthActionSupport.class,
HealthAction.class); HealthAction.class);
} }
} }
@@ -0,0 +1,113 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.platform.ws;

import com.google.common.io.Resources;
import java.util.Comparator;
import org.sonar.api.server.ws.WebService;
import org.sonar.cluster.health.NodeDetails;
import org.sonar.cluster.health.NodeHealth;
import org.sonar.server.health.ClusterHealth;
import org.sonar.server.health.Health;
import org.sonar.server.health.HealthChecker;
import org.sonarqube.ws.WsSystem;

import static java.lang.String.valueOf;
import static org.sonar.api.utils.DateUtils.formatDateTime;

public class HealthActionSupport {
private static final Comparator<NodeHealth> NODE_HEALTH_COMPARATOR = Comparator.<NodeHealth>comparingInt(s -> s.getDetails().getType().ordinal())
.thenComparing(a -> a.getDetails().getName())
.thenComparing(a -> a.getDetails().getHost())
.thenComparing(a -> a.getDetails().getPort());
private final HealthChecker healthChecker;

public HealthActionSupport(HealthChecker healthChecker) {
this.healthChecker = healthChecker;
}

void define(WebService.NewController controller, SystemWsAction handler) {
controller.createAction("health")
.setDescription("Provide health status of SonarQube." +
"<p>Require 'Administer System' permission or authentication with passcode</p>" +
"<p> " +
" <ul>" +
" <li>GREEN: SonarQube is fully operational</li>" +
" <li>YELLOW: SonarQube is usable, but it needs attention in order to be fully operational</li>" +
" <li>RED: SonarQube is not operational</li>" +
" </ul>" +
"</p>")
.setSince("6.6")
.setResponseExample(Resources.getResource(this.getClass(), "example-health.json"))
.setHandler(handler);
}

WsSystem.HealthResponse checkNodeHealth() {
Health check = healthChecker.checkNode();
WsSystem.HealthResponse.Builder responseBuilder = WsSystem.HealthResponse.newBuilder()
.setHealth(WsSystem.Health.valueOf(check.getStatus().name()));
WsSystem.Cause.Builder causeBuilder = WsSystem.Cause.newBuilder();
check.getCauses().forEach(str -> responseBuilder.addCauses(causeBuilder.clear().setMessage(str).build()));

return responseBuilder.build();
}

WsSystem.HealthResponse checkClusterHealth() {
ClusterHealth check = healthChecker.checkCluster();
return toResponse(check);
}

private static WsSystem.HealthResponse toResponse(ClusterHealth check) {
WsSystem.HealthResponse.Builder responseBuilder = WsSystem.HealthResponse.newBuilder();
WsSystem.Node.Builder nodeBuilder = WsSystem.Node.newBuilder();
WsSystem.Cause.Builder causeBuilder = WsSystem.Cause.newBuilder();

Health health = check.getHealth();
responseBuilder.setHealth(WsSystem.Health.valueOf(health.getStatus().name()));
health.getCauses().forEach(str -> responseBuilder.addCauses(toCause(str, causeBuilder)));

WsSystem.Nodes.Builder nodesBuilder = WsSystem.Nodes.newBuilder();
check.getNodes().stream()
.sorted(NODE_HEALTH_COMPARATOR)
.map(node -> toNode(node, nodeBuilder, causeBuilder))
.forEach(nodesBuilder::addNodes);
responseBuilder.setNodes(nodesBuilder.build());

return responseBuilder.build();
}

private static WsSystem.Node toNode(NodeHealth nodeHealth, WsSystem.Node.Builder nodeBuilder, WsSystem.Cause.Builder causeBuilder) {
nodeBuilder.clear();
nodeBuilder.setHealth(WsSystem.Health.valueOf(nodeHealth.getStatus().name()));
nodeHealth.getCauses().forEach(str -> nodeBuilder.addCauses(toCause(str, causeBuilder)));
NodeDetails details = nodeHealth.getDetails();
nodeBuilder
.setType(WsSystem.NodeType.valueOf(details.getType().name()))
.setName(details.getName())
.setHost(details.getHost())
.setPort(valueOf(details.getPort()))
.setStarted(formatDateTime(details.getStarted()));
return nodeBuilder.build();
}

private static WsSystem.Cause toCause(String str, WsSystem.Cause.Builder causeBuilder) {
return causeBuilder.clear().setMessage(str).build();
}
}
@@ -0,0 +1,43 @@
/*
* SonarQube
* Copyright (C) 2009-2017 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.server.platform.ws;

import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.ws.WsUtils;

public class SafeModeHealthAction implements SystemWsAction {
private final HealthActionSupport support;

public SafeModeHealthAction(HealthActionSupport support) {
this.support = support;
}

@Override
public void define(WebService.NewController controller) {
support.define(controller, this);
}

@Override
public void handle(Request request, Response response) throws Exception {
WsUtils.writeProtobuf(support.checkNodeHealth(), request, response);
}
}
Expand Up @@ -35,6 +35,7 @@ protected void configureModule() {
EsStatusNodeCheck.class, EsStatusNodeCheck.class,


HealthCheckerImpl.class, HealthCheckerImpl.class,
HealthAction.class); HealthActionSupport.class,
SafeModeHealthAction.class);
} }
} }

0 comments on commit 9250ab1

Please sign in to comment.