All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sakaiproject.cluster.impl.SakaiClusterService Maven / Gradle / Ivy

There is a newer version: 23.3
Show newest version
/**********************************************************************************
 * $URL$
 * $Id$
 ***********************************************************************************
 *
 * Copyright (c) 2004, 2005, 2006, 2007, 2008 Sakai Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *       http://www.opensource.org/licenses/ECL-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 **********************************************************************************/

package org.sakaiproject.cluster.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.cluster.api.ClusterNode;
import org.sakaiproject.cluster.api.ClusterService;
import org.sakaiproject.component.api.ServerConfigurationService;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.db.api.SqlReader;
import org.sakaiproject.db.api.SqlReaderFinishedException;
import org.sakaiproject.db.api.SqlService;
import org.sakaiproject.event.api.Event;
import org.sakaiproject.event.api.EventTrackingService;
import org.sakaiproject.event.api.UsageSessionService;
import org.sakaiproject.thread_local.api.ThreadLocalManager;

/**
 * 

* SakaiClusterService is a Sakai cluster service implementation. * This class is it just manages it's own row in the DB and events are used to pass notifications to a node. *

*/ public class SakaiClusterService implements ClusterService { /** Our log (commons). */ private static Log M_log = LogFactory.getLog(SakaiClusterService.class); /** The maintenance. */ protected Maintenance m_maintenance = null; /** Our status */ protected Status status = Status.UNKNOWN; /************************************************************************************************************************************************* * Dependencies and their setter methods ************************************************************************************************************************************************/ /** Dependency: ServerConfigurationService. */ protected ServerConfigurationService m_serverConfigurationService = null; /** * Dependency: ServerConfigurationService. * * @param service * The ServerConfigurationService. */ public void setServerConfigurationService(ServerConfigurationService service) { m_serverConfigurationService = service; } /** Dependency: EventTrackingService. */ protected EventTrackingService m_eventTrackingService = null; /** * Dependency: EventTrackingService. * * @param service * The EventTrackingService. */ public void setEventTrackingService(EventTrackingService service) { m_eventTrackingService = service; } /** Dependency: SqlService. */ protected SqlService m_sqlService = null; /** * Dependency: SqlService. * * @param service * The SqlService. */ public void setSqlService(SqlService service) { m_sqlService = service; } /** Dependency: UsageSessionService. */ protected UsageSessionService m_usageSessionService = null; /** * Dependency: UsageSessionService. * * @param service * The UsageSessionService. */ public void setUsageSessionService(UsageSessionService service) { m_usageSessionService = service; } /** Configuration: how often to register that we are alive with the cluster table (seconds). */ protected long m_refresh = 60; /** * Configuration: set the refresh value * * @param value * The refresh value. */ public void setRefresh(String value) { try { m_refresh = Long.parseLong(value); } catch (Exception ignore) { } } /** Configuration: how long we give an app server to respond before it is considered lost (seconds). */ protected long m_expired = 600; /** * Configuration: set the expired value * * @param value * The expired value. */ public void setExpired(String value) { try { m_expired = Long.parseLong(value); } catch (Exception ignore) { } } /** Configuration: to run the ddl on init or not. */ protected boolean m_autoDdl = false; /** * Configuration: to run the ddl on init or not. * * @param value * the auto ddl value. */ public void setAutoDdl(String value) { m_autoDdl = Boolean.valueOf(value).booleanValue(); } /** Dependency: the current manager. */ protected ThreadLocalManager m_threadLocalManager = null; /** * Dependency - set the current manager. * * @param value * The current manager. */ public void setThreadLocalManager(ThreadLocalManager manager) { m_threadLocalManager = manager; } /** Configuration: percent of maintenance passes to run the full de-ghosting / cleanup activities. */ protected int m_ghostingPercent = 100; /** * Configuration: set the percent of maintenance passes to run the full de-ghosting / cleanup activities * * @param value * The percent of maintenance passes to run the full de-ghosting / cleanup activities. */ public void setGhostingPercent(String value) { try { m_ghostingPercent = Integer.parseInt(value); } catch (Exception ignore) { } } public void setDatabaseBeans(Map databaseBeans) { this.databaseBeans = databaseBeans; } /** contains a map of the database dependent handlers. */ protected Map databaseBeans; /** the handler we are using. */ protected ClusterServiceSql clusterServiceSql; public ClusterServiceSql getClusterServiceSql() { return clusterServiceSql; } /** * sets which bean containing database dependent code should be used depending on the database vendor. */ public void setClusterServiceSql(String vendor) { this.clusterServiceSql = (databaseBeans.containsKey(vendor) ? databaseBeans.get(vendor) : databaseBeans.get("default")); } /************************************************************************************************************************************************* * Init and Destroy ************************************************************************************************************************************************/ /** * Final initialization, once all dependencies are set. */ public void init() { changeStatus(Status.STARTING); m_eventTrackingService.addObserver(new ClusterEventObserver()); setClusterServiceSql(m_sqlService.getVendor()); try { // if we are auto-creating our schema, check and create if (m_autoDdl) { m_sqlService.ddl(this.getClass().getClassLoader(), "sakai_cluster"); } // start the maintenance thread m_maintenance = new Maintenance(); m_maintenance.start(); M_log.info("init: refresh: " + m_refresh + " expired: " + m_expired + " ghostingPercent: " + m_ghostingPercent); } catch (Exception t) { M_log.warn("init(): ", t); } } /** * Returns to uninitialized state. */ public void destroy() { changeStatus(Status.STOPPING); m_maintenance.stop(); m_maintenance = null; M_log.info("destroy()"); } /************************************************************************************************************************************************* * ClusterService implementation ************************************************************************************************************************************************/ @Override public Status getStatus() { return status; } @SuppressWarnings("unchecked") @Override public List getServers() { String statement = clusterServiceSql.getListServersSql(); List servers = m_sqlService.dbRead(statement); return servers; } @Override public Map getServerStatus() { String statement = clusterServiceSql.getListServerStatusSql(); final Map servers = new HashMap<>(); m_sqlService.dbRead(statement, null, new SqlReader() { @Override public Object readSqlResultRecord(ResultSet result) throws SqlReaderFinishedException { try { String serverInstanceId = result.getString("SERVER_ID_INSTANCE"); String serverId = result.getString("SERVER_ID"); Date updateTime = result.getTimestamp("UPDATE_TIME"); Status status = parseStatus(result.getString("STATUS")); ClusterNode node = new ClusterNodeImpl(serverId, status, updateTime); servers.put(serverInstanceId, node); } catch (SQLException e) { M_log.warn("Failed to read result.", e); } return null; } }); // Always override DB status with memory version. ClusterNode dbStatus = servers.put(m_serverConfigurationService.getServerIdInstance(), new ClusterNodeImpl(m_serverConfigurationService.getServerId(), status, new Date())); if (dbStatus == null) { M_log.warn("Failed to find ourselves in the cluster: "+ m_serverConfigurationService.getServerIdInstance()); } else if (!status.equals(dbStatus.getStatus())) { M_log.warn("In memory status ("+ status+ ") different to DB ("+ dbStatus.getStatus()+ ")"); } return servers; } @Override public void markClosing(String serverId, boolean close) { if(!(getServers().contains(serverId))) { throw new IllegalArgumentException("Unknown server ID: "+ serverId); } String event = (close)? EVENT_CLOSE :EVENT_RUN; m_eventTrackingService.post(m_eventTrackingService.newEvent(event, serverId, true)); } /** * This changes the status of the current server. * @param status The new status. */ protected void changeStatus(Status status) { if (status != null && !(this.status.equals(status))) { M_log.info("Switching status from "+ this.status+ " to "+ status); this.status = status; if (m_maintenance != null) { m_maintenance.update(); } } } /************************************************************************************************************************************************* * Maintenance ************************************************************************************************************************************************/ protected class Maintenance implements Runnable { /** My thread running my timeout checker. */ protected Thread m_maintenanceChecker = null; /** Signal to the timeout checker to stop. */ protected boolean m_maintenanceCheckerStop = false; /** Out of sync update of status. */ protected boolean m_updateStatus = false; /** * Construct. */ public Maintenance() { } /** * Start the maintenance thread, registering this app server in the cluster table. */ public void start() { if (m_maintenanceChecker != null) return; // register in the cluster table String statement = clusterServiceSql.getInsertServerSql(); Object fields[] = new Object[3]; fields[0] = m_serverConfigurationService.getServerIdInstance(); fields[1] = Status.STARTING.toString(); fields[2] = m_serverConfigurationService.getServerId(); boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("start(): dbWrite failed"); } m_maintenanceChecker = new Thread(this, "SakaiClusterService.Maintenance"); m_maintenanceChecker.setDaemon(true); m_maintenanceCheckerStop = false; m_maintenanceChecker.start(); } /** * Stop the maintenance thread, removing this app server's registration from the cluster table. */ public void stop() { if (m_maintenanceChecker != null) { m_maintenanceCheckerStop = true; m_maintenanceChecker.interrupt(); try { // wait for it to die m_maintenanceChecker.join(); } catch (InterruptedException ignore) { } m_maintenanceChecker = null; } // close our entry from the database - delete the record String statement = clusterServiceSql.getDeleteServerSql(); Object fields[] = new Object[1]; fields[0] = m_serverConfigurationService.getServerIdInstance(); boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("stop(): dbWrite failed: " + statement); } } /** * Update the status in the DB. */ public void update() { if (m_maintenanceChecker == null) { return; } m_updateStatus = true; m_maintenanceChecker.interrupt(); } /** * Run the maintenance thread. Every REFRESH seconds, re-register this app server as alive in the cluster. Then check for any cluster entries * that are more than EXPIRED seconds old, indicating a failed app server, and remove that record, that server's sessions, * generating appropriate session events so the other app servers know what's going on. The "then" checks need not be done each * iteration - run them on 1 of n randomly choosen iterations. In a clustered environment, this also distributes the work over the cluster * better. */ public void run() { // wait till things are rolling ComponentManager.waitTillConfigured(); // Component manager is up so now we update our status. status = Status.RUNNING; if (M_log.isDebugEnabled()) M_log.debug("run()"); while (!m_maintenanceCheckerStop) { final String serverIdInstance = m_serverConfigurationService.getServerIdInstance(); try { updateOurStatus(serverIdInstance); ghostCleanup(serverIdInstance); } catch (Exception e) { M_log.warn("exception: ", e); } finally { // clear out any current access bindings m_threadLocalManager.clear(); } // cycle every REFRESH seconds if (!m_maintenanceCheckerStop) { try { long sleepTill = System.currentTimeMillis() + m_refresh * 1000L; long sleepFor = sleepTill - System.currentTimeMillis(); while (sleepFor > 0) { try { Thread.sleep(sleepFor); sleepFor = sleepTill - System.currentTimeMillis(); } catch (InterruptedException e) { if (m_updateStatus) { updateOurStatus(serverIdInstance); m_updateStatus = false; } if(!m_updateStatus || m_maintenanceCheckerStop) { throw e; } } } } catch (Exception ignore) { } } } if (M_log.isDebugEnabled()) M_log.debug("done"); } private void ghostCleanup(String serverIdInstance) { // pick a random number, 0..99, to see if we want to do the full ghosting / cleanup activities now int rand = (int) (Math.random() * 100.0); if (rand < m_ghostingPercent) { String statement; // get all expired open app servers not me statement = clusterServiceSql.getListExpiredServers(m_expired); // setup the fields to skip reading me! Object[] fields = new Object[1]; fields[0] = serverIdInstance; List instances = m_sqlService.dbRead(statement, fields, null); // close any severs found to be expired for (Iterator iInstances = instances.iterator(); iInstances.hasNext();) { String serverId = (String) iInstances.next(); // close the server - delete the record statement = clusterServiceSql.getDeleteServerSql(); fields[0] = serverId; boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("run(): dbWrite failed: " + statement); } M_log.warn("run(): ghost-busting server: " + serverId + " from : " + serverIdInstance); } // Close all sessions left over from deleted servers. int nbrClosed = m_usageSessionService.closeSessionsOnInvalidServers(getServers()); if ((nbrClosed > 0) && M_log.isInfoEnabled()) M_log.info("Closed " + nbrClosed + " orphaned usage session records"); // Delete any orphaned locks from closed or missing sessions. statement = clusterServiceSql.getOrphanedLockSessionsSql(); List sessions = m_sqlService.dbRead(statement); if (sessions.size() > 0) { if (M_log.isInfoEnabled()) M_log.info("Found " + sessions.size() + " closed or deleted sessions in lock table"); statement = clusterServiceSql.getDeleteLocksSql(); for (Iterator iSessions = sessions.iterator(); iSessions.hasNext();) { fields[0] = (String) iSessions.next(); boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("run(): dbWrite failed: " + statement); } } } } } private void updateOurStatus(String serverIdInstance) { if (M_log.isDebugEnabled()) M_log.debug("checking..."); // if we have been closed, reopen! String statement = clusterServiceSql.getReadServerSql(); Object[] fields = new Object[1]; fields[0] = serverIdInstance; List results = m_sqlService.dbRead(statement, fields, new StatusSqlReader()); if (results.isEmpty()) { M_log.warn("run(): server has been closed in cluster table, reopened: " + serverIdInstance); statement = clusterServiceSql.getInsertServerSql(); fields = new Object[3]; fields[0] = serverIdInstance; fields[1] = status; fields[2] = m_serverConfigurationService.getServerId(); boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("start(): dbWrite failed"); } } // update our alive and well status else { // register that this app server is alive and well statement = clusterServiceSql.getUpdateServerSql(); fields = new Object[3]; fields[0] = status; fields[1] = m_serverConfigurationService.getServerId(); fields[2] = serverIdInstance; boolean ok = m_sqlService.dbWrite(statement, fields); if (!ok) { M_log.warn("run(): dbWrite failed: " + statement); } } } /** * Reads a status row from the DB. */ private class StatusSqlReader implements SqlReader { @Override public Object readSqlResultRecord(ResultSet result) throws SqlReaderFinishedException { Status status = null; try { status = parseStatus(result.getString("STATUS")); } catch (SQLException sqlException) { M_log.warn("Failed to read STATUS.", sqlException); } return status; } } } private Status parseStatus(String statusString) { Status status = Status.UNKNOWN; if (statusString != null) { try { status = Status.valueOf(statusString); } catch (IllegalArgumentException iae) { M_log.debug("Failed to convert to a status: "+ statusString); } } return status; } /** * This watches for events asking us to change status. */ private class ClusterEventObserver implements Observer { @Override public void update(Observable o, Object arg) { if (arg instanceof Event) { Event event = ((Event) arg); // We don't actually if (m_serverConfigurationService.getServerIdInstance().equals(event.getResource())) { if (EVENT_CLOSE.equals(event.getEvent())) { changeStatus(Status.CLOSING); } else if (EVENT_RUN.equals(event.getEvent())) { changeStatus(Status.RUNNING); } } else { M_log.debug("Ignoring message for other node."); } } } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy