/* ############################################################################### # # # Copyright (C) 2011-2016 OpenMEAP, Inc. # # Credits to Jonathan Schang & Rob Thacher # # # # Released under the LGPLv3 # # # # OpenMEAP 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. # # # # OpenMEAP 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 OpenMEAP. If not, see <http://www.gnu.org/licenses/>. # # # ############################################################################### */ package com.openmeap.cluster; import java.net.ConnectException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Vector; import org.apache.commons.lang.exception.ExceptionUtils; import com.openmeap.thirdparty.org.json.me.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.openmeap.cluster.dto.ClusterNodeRequest; import com.openmeap.constants.FormConstants; import com.openmeap.http.HttpRequestExecuter; import com.openmeap.http.HttpResponse; import com.openmeap.json.JSONObjectBuilder; import com.openmeap.model.ModelManager; import com.openmeap.model.dto.ClusterNode; import com.openmeap.model.dto.GlobalSettings; import com.openmeap.services.dto.Result; import com.openmeap.util.AuthTokenProvider; import com.openmeap.util.Utils; public class ClusterNodeHealthCheckThread implements Runnable { private Logger logger = LoggerFactory.getLogger(ClusterNodeHealthCheckThread.class); private HttpRequestExecuter httpRequestExecuter; private ModelManager modelManager; private GlobalSettings settings; private Integer restartInterval = 30000; private Integer checkInterval = 2000; private Vector<Exception> lastCheckExceptions; @Override public void run() { while(true) { try { _run(); } catch(Exception e) { logger.error("Health check thread threw an exception. Trying to restart in 30 seconds.",e); try { Thread.sleep(restartInterval); } catch (InterruptedException e1) { logger.error("Nap interrupted!",e1); } } } } private void _run() { settings = modelManager.getGlobalSettings(); JSONObjectBuilder builder = new JSONObjectBuilder(); ClusterNodeRequest request = new ClusterNodeRequest(); request.setSubject(ClusterNodeRequest.HEALTH_CHECK); lastCheckExceptions = new Vector<Exception>(); while(true) { synchronized(this) { lastCheckExceptions.clear(); if(settings.getClusterNodes()!=null) { for(ClusterNode clusterNode : settings.getClusterNodes()) { try { request.setClusterNode(clusterNode); HttpResponse response = null; try { response = httpRequestExecuter.postContent( clusterNode.getServiceWebUrlPrefix()+"/service-management/?action="+ClusterNodeRequest.HEALTH_CHECK+"&auth="+AuthTokenProvider.newAuthToken(settings.getServiceManagementAuthSalt()), builder.toJSON(request).toString(3), FormConstants.CONT_TYPE_JSON); } catch(Exception e) { logger.error(clusterNode.getServiceWebUrlPrefix()+" health check returned exception",e); Throwable t = ExceptionUtils.getRootCause(e); ClusterNode.Status err = null; if( t instanceof ConnectException ) { err = ClusterNode.Status.CONNECT_ERROR; } else { err = ClusterNode.Status.ERROR; } synchronized(clusterNode) { clusterNode.setLastStatus(err); clusterNode.setLastStatusMessage(t.getMessage()); clusterNode.setLastStatusCheck(new Date()); } if(response!=null && response.getResponseBody()!=null) { Utils.consumeInputStream(response.getResponseBody()); response.getResponseBody().close(); } continue; } if( response!=null && response.getStatusCode()==200 ) { String json = Utils.readInputStream(response.getResponseBody(), FormConstants.CHAR_ENC_DEFAULT); JSONObject jsonObj = new JSONObject(json); Result result = (Result) builder.fromJSON(jsonObj, new Result()); response.getResponseBody().close(); synchronized(clusterNode) { clusterNode.setLastStatus( result.getStatus()==Result.Status.SUCCESS ? ClusterNode.Status.GOOD : ClusterNode.Status.ERROR); clusterNode.setLastStatusMessage(result.getMessage()); clusterNode.setLastStatusCheck(new Date()); } } else { synchronized(clusterNode) { clusterNode.setLastStatus(ClusterNode.Status.ERROR); String msg = "Service node "+clusterNode.getServiceWebUrlPrefix()+" returned a non-200 status code "+response.getStatusCode() +" "+Utils.readInputStream(response.getResponseBody(), FormConstants.CHAR_ENC_DEFAULT); logger.error(msg); clusterNode.setLastStatusMessage(msg); response.getResponseBody().close(); clusterNode.setLastStatusCheck(new Date()); } } } catch(Exception e) { logger.error("Exception performing health check",e); lastCheckExceptions.add(e); } } } } synchronized(lastCheckExceptions) { lastCheckExceptions.notifyAll(); } try { Thread.sleep(checkInterval); } catch (InterruptedException e) { logger.error("Nap interrupted!",e); } } } public synchronized void refreshSettings() { modelManager.getModelService().clearPersistenceContext(); settings = modelManager.getGlobalSettings(); } public List<Exception> checkNowAndWait() throws InterruptedException { synchronized(lastCheckExceptions) { lastCheckExceptions.wait(); return new ArrayList<Exception>(lastCheckExceptions); } } public GlobalSettings getSettings() { return settings; } public void setHttpRequestExecuter(HttpRequestExecuter httpRequestExecuter) { this.httpRequestExecuter = httpRequestExecuter; } public void setModelManager(ModelManager modelManager) { this.modelManager = modelManager; } public void setCheckInterval(Integer checkInterval) { this.checkInterval = checkInterval; } public void setRestartInterval(Integer restartInterval) { this.restartInterval = restartInterval; } }