package org.kairosdb.rollup;
import com.google.inject.name.Named;
import org.kairosdb.core.KairosDBService;
import org.kairosdb.core.datastore.Duration;
import org.kairosdb.core.datastore.KairosDatastore;
import org.kairosdb.core.datastore.TimeUnit;
import org.kairosdb.core.exception.KairosDBException;
import org.kairosdb.core.scheduler.KairosDBScheduler;
import org.quartz.DateBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobKey;
import org.quartz.Trigger;
import org.quartz.impl.JobDetailImpl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.inject.Inject;
import java.util.List;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static org.quartz.CalendarIntervalScheduleBuilder.calendarIntervalSchedule;
import static org.quartz.TriggerBuilder.newTrigger;
public class RollUpManager implements KairosDBService, RollupTaskChangeListener
{
public static final Logger logger = LoggerFactory.getLogger(RollUpManager.class);
private static final String GROUP_ID = RollUpJob.class.getSimpleName();
private final KairosDBScheduler scheduler;
private final KairosDatastore dataStore;
@Inject
@Named("HOSTNAME")
private String hostName = "localhost";
@Inject
public RollUpManager(RollUpTasksStore taskStore,
KairosDBScheduler scheduler, KairosDatastore dataStore) throws RollUpException
{
checkNotNull(taskStore, "taskStore cannot be null");
this.scheduler = checkNotNull(scheduler, "scheduler cannot be null");
this.dataStore = checkNotNull(dataStore, "dataStore cannot be null");
// Load saved tasks
List<RollupTask> tasks = taskStore.read();
for (RollupTask task : tasks)
{
scheduleNewTask(task);
}
taskStore.addListener(this);
}
@Override
public void change(RollupTask task, Action action)
{
checkNotNull(task, "task cannot be null");
switch (action)
{
case ADDED:
scheduleNewTask(task);
break;
case CHANGED:
updateScheduledTask(task);
break;
case REMOVED:
removeScheduledTask(task);
break;
}
}
private void scheduleNewTask(RollupTask task)
{
try
{
logger.info("Scheduling rollup " + task.getName());
Trigger trigger = createTrigger(task);
JobDetailImpl jobDetail = createJobDetail(task, dataStore, hostName);
scheduler.schedule(jobDetail, trigger);
logger.info("Roll-up task " + jobDetail.getFullName() + " scheduled. Next execution time " + trigger.getNextFireTime());
}
catch (KairosDBException e)
{
logger.error("Failed to schedule new roll up task job " + task, e);
}
}
private void updateScheduledTask(RollupTask task)
{
try
{
scheduler.cancel(getJobKey(task));
}
catch (KairosDBException e)
{
logger.error("Could not cancel roll up task job " + task, e);
return;
}
try
{
logger.info("Updating schedule for rollup " + task.getName());
JobDetailImpl jobDetail = createJobDetail(task, dataStore, hostName);
Trigger trigger = createTrigger(task);
scheduler.schedule(jobDetail, trigger);
logger.info("Roll-up task " + jobDetail.getFullName() + " scheduled. Next execution time " + trigger.getNextFireTime());
}
catch (KairosDBException e)
{
logger.error("Could not schedule roll up task job " + task, e);
}
}
private void removeScheduledTask(RollupTask task)
{
try
{
JobKey jobKey = getJobKey(task);
logger.info("Cancelling rollup " + task.getName());
scheduler.cancel(jobKey);
}
catch (KairosDBException e)
{
logger.error("Could not cancel roll up task job " + task.getName(), e);
}
}
private static JobKey getJobKey(RollupTask task)
{
return new JobKey(task.getId() + "-" + task.getName(), RollUpJob.class.getSimpleName());
}
static JobDetailImpl createJobDetail(RollupTask task, KairosDatastore dataStore, String hostName)
{
JobDetailImpl jobDetail = new JobDetailImpl();
jobDetail.setJobClass(RollUpJob.class);
jobDetail.setKey(getJobKey(task));
JobDataMap map = new JobDataMap();
map.put("task", task);
map.put("datastore", dataStore);
map.put("hostName", hostName);
jobDetail.setJobDataMap(map);
return jobDetail;
}
@SuppressWarnings("ConstantConditions")
static Trigger createTrigger(RollupTask task)
{
Duration executionInterval = task.getExecutionInterval();
return newTrigger()
.withIdentity(task.getId(), GROUP_ID)
.startAt(DateBuilder.futureDate((int) executionInterval.getValue(), toIntervalUnit(executionInterval.getUnit())))
.withSchedule(calendarIntervalSchedule()
.withInterval((int) executionInterval.getValue(), toIntervalUnit(executionInterval.getUnit())))
.build();
}
private static DateBuilder.IntervalUnit toIntervalUnit(TimeUnit unit)
{
switch (unit)
{
case MILLISECONDS:
return DateBuilder.IntervalUnit.MILLISECOND;
case SECONDS:
return DateBuilder.IntervalUnit.SECOND;
case MINUTES:
return DateBuilder.IntervalUnit.MINUTE;
case HOURS:
return DateBuilder.IntervalUnit.HOUR;
case DAYS:
return DateBuilder.IntervalUnit.DAY;
case WEEKS:
return DateBuilder.IntervalUnit.WEEK;
case MONTHS:
return DateBuilder.IntervalUnit.MONTH;
case YEARS:
return DateBuilder.IntervalUnit.YEAR;
default:
checkState(false, "Invalid time unit" + unit);
return null;
}
}
@Override
public void start() throws KairosDBException
{
}
@Override
public void stop()
{
}
}