/*
* R Service Bus
*
* Copyright (c) Copyright of Open Analytics NV, 2010-2015
*
* ===========================================================================
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package eu.openanalytics.rsb.component;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.lang.management.RuntimeMXBean;
import java.net.URI;
import java.rmi.ConnectException;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import javax.servlet.ServletContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.time.DurationFormatUtils;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import de.walware.rj.servi.RServi;
import eu.openanalytics.rsb.Constants;
import eu.openanalytics.rsb.Util;
import eu.openanalytics.rsb.rest.types.NodeInformation;
import eu.openanalytics.rsb.rservi.RServiInstanceProvider;
import eu.openanalytics.rsb.rservi.RServiInstanceProvider.PoolingStrategy;
/**
* Handles health check requests.
*
* @author "OpenAnalytics <rsb.development@openanalytics.eu>"
*/
@Component("systemResource")
@Path("/" + Constants.SYSTEM_PATH)
public class SystemHealthResource extends AbstractResource
{
private static final long CHECK_DELAY = 60000L;
@Resource
private RServiInstanceProvider rServiInstanceProvider;
private final AtomicBoolean nodeHealthy = new AtomicBoolean(true);
private long initializationTime;
// exposed for unit testing
void setRServiInstanceProvider(final RServiInstanceProvider rServiInstanceProvider)
{
this.rServiInstanceProvider = rServiInstanceProvider;
}
@PostConstruct
public void initialize()
{
initializationTime = System.currentTimeMillis();
}
@Scheduled(fixedDelay = CHECK_DELAY)
public void verifyNodeHealth()
{
// start delay unless health must be checked right from start
if ((!getConfiguration().isCheckHealthOnStart())
&& (System.currentTimeMillis() - initializationTime < CHECK_DELAY))
{
return;
}
try
{
verifyRServiConnectivity();
// LATER consider other tests
nodeHealthy.set(true);
}
catch (final Exception e)
{
getLogger().error("RSB is in bad health!", e);
nodeHealthy.set(false);
}
}
@GET
@Path("/health/check")
@Produces({Constants.TEXT_CONTENT_TYPE})
public Response check()
{
return nodeHealthy.get() ? Response.ok("OK").build() : Response.status(Status.INTERNAL_SERVER_ERROR)
.entity("ERROR")
.build();
}
@GET
@Path("/info")
@Produces({Constants.RSB_XML_CONTENT_TYPE, Constants.RSB_JSON_CONTENT_TYPE})
public NodeInformation getInfo(@Context final ServletContext sc)
{
final NodeInformation info = Util.REST_OBJECT_FACTORY.createNodeInformation();
info.setName(getConfiguration().getNodeName());
info.setHealthy(nodeHealthy.get());
info.setRsbVersion(getClass().getPackage().getImplementationVersion());
info.setServletContainerInfo(sc.getServerInfo());
final OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
info.setOsLoadAverage(operatingSystemMXBean.getSystemLoadAverage());
final Runtime runtime = Runtime.getRuntime();
info.setJvmMaxMemory(runtime.maxMemory());
info.setJvmFreeMemory(runtime.freeMemory());
final RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
final long uptimeMilliseconds = runtimeMXBean.getUptime();
info.setUptime(uptimeMilliseconds);
info.setUptimeText(DurationFormatUtils.formatDurationWords(uptimeMilliseconds, true, true));
return info;
}
private void verifyRServiConnectivity() throws Exception
{
// check default pool
final Set<URI> urisToCheck = new TreeSet<URI>();
urisToCheck.add(getConfiguration().getDefaultRserviPoolUri());
// and application specific pools
final Map<String, Set<URI>> applicationSpecificRserviPoolUris = getConfiguration().getApplicationSpecificRserviPoolUris();
if ((applicationSpecificRserviPoolUris != null) && (!applicationSpecificRserviPoolUris.isEmpty()))
{
for (final Set<URI> uris : applicationSpecificRserviPoolUris.values())
{
urisToCheck.addAll(uris);
}
}
// stopping at first failure
for (final URI uriToCheck : urisToCheck)
{
verifyRServiConnectivity(uriToCheck);
}
}
private void verifyRServiConnectivity(final URI rServiUri) throws Exception
{
RServi rServi = null;
try
{
// never use pooled clients to check connectivity
rServi = rServiInstanceProvider.getRServiInstance(rServiUri.toString(),
Constants.RSERVI_CLIENT_ID, PoolingStrategy.NEVER);
}
catch (final Exception e)
{
throw new ConnectException("Failed to retrieve an RServi instance at URI: " + rServiUri, e);
}
try
{
Validate.isTrue(Util.isRResponding(rServi), "R is not responding at URI: " + rServiUri);
}
finally
{
rServi.close();
}
}
}