/* * @(#) MaintenanceTaskManager.java * Created Sep 23, 2011 by oleg * (C) ONE, SIA */ package org.apache.cassandra.maint; import java.util.ArrayList; import java.util.Calendar; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import org.apache.cassandra.concurrent.NamedThreadFactory; import org.apache.cassandra.dht.Range; import org.apache.cassandra.service.StorageService; import org.apache.log4j.Logger; /** * Controls execution of maintenance tasks inside maintenance window. * * @author Oleg Anastasyev<oa@hq.one.lv> * */ public class MaintenanceTaskManager implements Runnable { private static final Logger logger = Logger.getLogger(MaintenanceTaskManager.class); public static MaintenanceTaskManager instance; /** * start and end of maintenance window in millis since day start */ private long windowStartMillis, windowsEndMillis; private ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor(new NamedThreadFactory( "MAINTENANCE", Thread.MIN_PRIORITY)); /** * Configured tasks list */ private List<MaintenanceTask> tasks; /** * Configured final tasks list (run once after the maintenance window has be closed) */ private List<FinalMaintenanceTask> finalTasks; /** * During maintenance window is set to true. Right after maintenance window set to false.<br> * Used to detect first scheduled run right after maintenance window end. * */ boolean activeWindow = false; boolean stopped = false; public static void init(List<MaintenanceTask> tasks, String windowStart, String windowEnd) { if (instance != null) instance.stop(); instance = new MaintenanceTaskManager(tasks); // parse window times instance.setWindow(parseTime(windowStart), parseTime(windowEnd)); } public static boolean isConfigured() { return instance != null; } /** * @param windowStart time spec HH:MM * @return millis since day start */ public static long parseTime(String timeString) { String[] times = timeString.split(":"); int hour = Integer.parseInt(times[0]), minute = 0; assert hour >= 0 && hour < 24 : "Invalid hour in " + timeString; if (times.length > 1) minute = Integer.parseInt(times[1]); assert minute >= 0 && minute < 60 : "Invalid minute in " + timeString; return (hour * 60 + minute) * 60000; } /** * @param windowEnd * @param windowStart * */ private MaintenanceTaskManager(List<MaintenanceTask> tasks) { this.tasks = tasks; this.finalTasks = new ArrayList<FinalMaintenanceTask>(); for (MaintenanceTask maintenanceTask : tasks) { if (maintenanceTask instanceof FinalMaintenanceTask) { finalTasks.add((FinalMaintenanceTask) maintenanceTask); } } } /** * @return millis left to maint window end */ private long windowMillisLeft() { Calendar c = Calendar.getInstance(); long now = c.getTimeInMillis(); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); long startOfDay = c.getTimeInMillis(); if (now < startOfDay + windowStartMillis || now > startOfDay + windowsEndMillis) return 0; return startOfDay + windowsEndMillis - now; } private long windowStartedMillis() { Calendar c = Calendar.getInstance(); c.set(Calendar.HOUR_OF_DAY, 0); c.set(Calendar.MINUTE, 0); c.set(Calendar.SECOND, 0); c.set(Calendar.MILLISECOND, 0); return c.getTimeInMillis(); } public void setWindow(long startMillis, long endMillis) { assert startMillis < endMillis; this.windowStartMillis = startMillis; this.windowsEndMillis = endMillis; } /** * */ public int positionInRing() { List<Range> ranges = StorageService.instance.getAllRanges(); for (int i = 0; i < ranges.size(); i++) { if (StorageService.instance.getLocalPrimaryRange().equals(ranges.get(i))) return i; } return -1; } public void stop() { stopped = true; executor.shutdownNow(); } /** * */ public void start() { stopped = false; if (tasks.size() > 0) executor.scheduleWithFixedDelay(this, 1, 1, TimeUnit.MINUTES); logger.info(String.format("Maitenance started for time window %s - %s with tasks: %s", stringifyTimeOffset(windowStartMillis), stringifyTimeOffset(windowsEndMillis), tasks)); } private String stringifyTimeOffset(long timeOffset) { return String.format("%02d:%02d", timeOffset / 3600000, (timeOffset % 3600000) / 60000); } /* * (non-Javadoc) * * @see java.lang.Runnable#run() */ @Override public void run() { if (stopped) return; final long millisLeft = windowMillisLeft(); if (millisLeft == 0) { if (activeWindow) { try { for (FinalMaintenanceTask task : finalTasks) { logger.info("Starting final " + task); task.runFinally(); } } catch (Throwable e) { logger.error("Maintenance manager error .", e); } } activeWindow = false; return; } try { final int positionInRing = positionInRing(); if (positionInRing < 0) { logger.error("Cannot determine this endpoint's position in ring. Cannot run maintenance"); return; } final long windowStartedMillis = windowStartedMillis(); activeWindow = true; MaintenanceContext ctx = new MaintenanceContext() { @Override public long startedMillis() { return windowStartedMillis; } @Override public long millisLeft() { return millisLeft; } @Override public int ringPosition() { return positionInRing; } }; for (MaintenanceTask task : tasks) { Runnable r = task.maybeRun(ctx); if (r != null) { try { logger.info("Starting " + r); r.run(); } catch (Throwable e) { logger.error("Maintenance task " + r + "failed.", e); } return; } } } catch (Throwable e) { logger.error("Maintenance manager error .", e); } } }