Package org.platformlayer.ops.schedule

Source Code of org.platformlayer.ops.schedule.SchedulerImpl$SchedulerTask

package org.platformlayer.ops.schedule;

import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.TimerTask;

import org.platformlayer.HttpPlatformLayerClient;
import org.platformlayer.PlatformLayerClient;
import org.platformlayer.PlatformLayerClientException;
import org.platformlayer.PlatformLayerEndpointInfo;
import org.platformlayer.RepositoryException;
import org.platformlayer.auth.Authenticator;
import org.platformlayer.auth.DirectAuthenticationToken;
import org.platformlayer.auth.DirectAuthenticator;
import org.platformlayer.core.model.JobSchedule;
import org.platformlayer.core.model.PlatformLayerKey;
import org.platformlayer.http.HttpStrategy;
import org.platformlayer.ids.ProjectId;
import org.platformlayer.ops.OpsException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.fathomdb.TimeSpan;
import com.fathomdb.crypto.CryptoKey;
import com.fathomdb.crypto.FathomdbCrypto;
import com.fathomdb.utils.Hex;
import com.google.common.base.Splitter;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.inject.Inject;
import com.google.inject.Singleton;

@Singleton
public class SchedulerImpl implements Scheduler {
  static final Logger log = LoggerFactory.getLogger(SchedulerImpl.class);

  private static final TimeSpan MIN_DELAY = new TimeSpan("1s");

  private static final TimeSpan FORCE_RECALCULATE_INTERVAL = new TimeSpan("5m");

  @Inject
  ThreadServices threadServices;

  @Inject
  SchedulerRepository repository;

  @Inject
  HttpStrategy httpStrategy;

  final Map<String, SchedulerTask> tasks = Maps.newHashMap();

  boolean ready;

  @Override
  public void start() throws OpsException {
    ensureStarted();
  }

  class SchedulerTask {
    final String key;
    final JobScheduleCalculator schedule;
    final Runnable runnable;

    TimerTask scheduledTimerTask;
    Date scheduledExecution;

    volatile boolean cancelled = false;

    JobExecution previousExecution;

    public SchedulerTask(String key, JobScheduleCalculator schedule, Runnable runnable) {
      this.key = key;
      this.schedule = schedule;
      this.runnable = runnable;
    }

    private void runTask() {
      boolean jobSuccess = false;
      Throwable jobException = null;

      Date startTimestamp = new Date();
      Date endTimestamp;

      try {
        runnable.run();
        jobSuccess = true;
      } catch (Throwable t) {
        jobException = t;
      } finally {
        endTimestamp = new Date();
        JobExecution execution = new JobExecution(startTimestamp, endTimestamp, jobSuccess, jobException);
        previousExecution = execution;

        if (!cancelled) {
          try {
            scheduleTask(this, execution);
          } catch (Throwable t) {
            log.error("Error rescheduling job", t);
          }
        } else {
          log.debug("Task cancelled; won't reschedule");
        }

        try {
          repository.logExecution(key, execution, jobException);
        } catch (Throwable t) {
          log.error("Error logging job execution", t);
        }

      }
    }

    public synchronized void scheduleNextExecution(Date nextExecution) {
      if (cancelled) {
        log.debug("Task cancelled; won't schedule");
        return;
      }

      TimeSpan delay = TimeSpan.timeUntil(nextExecution);
      delay = TimeSpan.max(delay, MIN_DELAY);

      // We can't reissue tasks, so we have to create a new TimerTask every time
      // (as we want more complicated scheduling)
      TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
          try {
            runTask();
          } catch (Exception e) {
            log.error("Error in timer task", e);
          }
        }
      };

      log.debug("Scheduling task: " + this + " for " + nextExecution);

      threadServices.scheduleOneOff("SchedulerJob", timerTask, delay);

      this.scheduledTimerTask = timerTask;
      this.scheduledExecution = nextExecution;
    }

    public JobScheduleCalculator getSchedule() {
      return schedule;
    }

    @Override
    public String toString() {
      return "SchedulerTimerTask [task=" + runnable + " schedule=" + schedule + "]";
    }

    public synchronized JobExecution getPreviousExecution() {
      return previousExecution;
    }

    public synchronized void cancel() {
      cancelled = true;

      if (scheduledTimerTask != null) {
        if (!scheduledTimerTask.cancel()) {
          log.debug("Unable to cancel scheduled task - likely already started");
        }

        scheduledTimerTask = null;
      }
    }
  }

  void scheduleTask(final SchedulerTask task, final JobExecution jobExecution) {
    JobScheduleCalculator schedule = task.getSchedule();

    Date nextExecution = null;
    try {
      nextExecution = schedule.calculateNext(jobExecution);
    } catch (Exception e) {
      log.warn("Error while calculating next execution", e);
    }

    if (nextExecution == null) {
      // Give it a chance to change its mind and reschedule itself,
      // by asking it again in a few minutes

      // Note: this means that tasks cannot be cancelled at the moment
      // (we'll at least keep polling the calendar)
      TimerTask timerTask = new TimerTask() {
        @Override
        public void run() {
          try {
            scheduleTask(task, jobExecution);
          } catch (Exception e) {
            log.error("Error rescheduling timer task", e);
          }
        }
      };
      threadServices.scheduleOneOff("RecalculateNext", timerTask, FORCE_RECALCULATE_INTERVAL);
    } else {
      task.scheduleNextExecution(nextExecution);
    }
  }

  private Runnable toRunnable(SchedulerRecord record) {
    Task task = record.task;
    if (task instanceof ActionTask) {
      return toRunnable((ActionTask) task);
    } else {
      throw new UnsupportedOperationException();
    }
  }

  private static PlatformLayerEndpointInfo rehydrateEndpoint(final EndpointRecord in) {
    final Authenticator authenticator;
    final String platformlayerBaseUrl = in.url;
    final ProjectId projectId = new ProjectId(in.project);
    final List<String> trustKeys;

    if (Strings.isNullOrEmpty(in.trustKeys)) {
      trustKeys = Collections.emptyList();
    } else {
      trustKeys = Lists.newArrayList(Splitter.on(",").split(in.trustKeys));
    }
    {
      String token = in.token;
      CryptoKey secret = FathomdbCrypto.deserializeKey(Hex.fromHex(in.secret.plaintext()));
      DirectAuthenticationToken authenticationToken = new DirectAuthenticationToken(token, secret);
      authenticator = new DirectAuthenticator(authenticationToken);
    }

    PlatformLayerEndpointInfo out = new PlatformLayerEndpointInfo(authenticator, platformlayerBaseUrl, projectId,
        trustKeys);
    return out;
  }

  private Runnable toRunnable(final ActionTask task) {
    final PlatformLayerKey target = task.target;

    final PlatformLayerEndpointInfo endpoint = rehydrateEndpoint(task.endpoint);

    Runnable runnable = new Runnable() {
      @Override
      public void run() {
        try {
          PlatformLayerClient platformLayer = HttpPlatformLayerClient.build(httpStrategy,
              endpoint.getPlatformlayerBaseUrl(), endpoint.getAuthenticator(), endpoint.getProjectId(),
              endpoint.getTrustKeys());

          platformLayer.doAction(target, task.action);

          // TODO: Wait for task completion??
          // TODO: Link job id??
        } catch (PlatformLayerClientException e) {
          log.warn("Error running action", e);
        }
      }

      @Override
      public String toString() {
        return task.action + " on " + task.target;
      }
    };

    return runnable;
  }

  private JobScheduleCalculator parseSchedule(JobSchedule schedule, boolean tolerant) {
    TimeSpan interval = null;
    Date base = null;

    if (schedule == null) {
      if (tolerant) {
        log.warn("Expected schedule; was null");
      } else {
        throw new IllegalArgumentException("Schedule is required");
      }
    } else {
      if (!Strings.isNullOrEmpty(schedule.interval)) {
        try {
          interval = TimeSpan.parse(schedule.interval);
        } catch (IllegalArgumentException e) {
          if (tolerant) {
            log.warn("Ignoring error parsing interval: " + schedule.interval, e);
          } else {
            throw new IllegalArgumentException("Invalid interval: " + schedule.interval, e);
          }
        }
      }

      if (schedule.base != null) {
        base = schedule.base;
      }
    }

    if (interval == null) {
      if (tolerant) {
        log.warn("Interval not provided; assuming default");
        interval = TimeSpan.ONE_HOUR;
      } else {
        throw new IllegalArgumentException("Interval is required");
      }
    }

    JobScheduleCalculator scheduleCalculator = new SimpleJobScheduleCalculator(interval, base);
    return scheduleCalculator;
  }

  synchronized void ensureStarted() throws OpsException {
    if (ready) {
      return;
    }

    try {
      for (SchedulerRecord record : repository.findAll()) {
        // TODO: Tolerate exceptions?
        scheduleRecord(record, true);
      }
    } catch (RepositoryException e) {
      throw new OpsException("Error initializing scheduled tasks", e);
    }

    ready = true;
  }

  @Override
  public void putJob(SchedulerRecord record) throws OpsException {
    ensureStarted();

    scheduleRecord(record, false);

    try {
      repository.put(record);
    } catch (RepositoryException e) {
      throw new OpsException("Error persisting record", e);
    }
  }

  private void scheduleRecord(SchedulerRecord record, boolean tolerant) {
    String key = record.key;

    JobScheduleCalculator schedule = parseSchedule(record.schedule, tolerant);

    // TODO: More lightweight synchronization?
    synchronized (tasks) {
      SchedulerTask task = tasks.get(key);
      JobExecution previousExecution = null;
      if (task != null) {
        previousExecution = task.getPreviousExecution();

        // The action may have changed, so we delete
        task.cancel();
        task = null;
      }

      Runnable runnable = toRunnable(record);

      task = new SchedulerTask(key, schedule, runnable);
      scheduleTask(task, previousExecution);

      tasks.put(key, task);
    }
  }
}
TOP

Related Classes of org.platformlayer.ops.schedule.SchedulerImpl$SchedulerTask

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.