package org.ophion.snitch;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.services.ec2.AmazonEC2Client;
import com.amazonaws.services.ec2.model.DescribeInstancesResult;
import com.amazonaws.services.ec2.model.Instance;
import com.amazonaws.services.ec2.model.Reservation;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.commons.configuration.Configuration;
import org.joda.time.DateTime;
import org.joda.time.Minutes;
import org.ophion.snitch.util.AWSUtils;
import org.ophion.snitch.util.EventSink;
import org.ophion.snitch.util.Log;
import java.text.ParseException;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Singleton
public class EventManager {
private Configuration config;
private static final Log LOG = new Log();
private static final int DEFAULT_POLL_PERIOD = 5;
private AWSCredentials creds;
// events
public EventSink onInstanceEvent = new EventSink();
public EventSink onStart = new EventSink();
@Inject
public EventManager(Configuration config, AWSCredentials creds) {
this.config = config;
this.creds = creds;
LOG.fine("ctor");
}
public void start() throws InterruptedException {
int pollPeriod = config.getInt("server.poll_period");
if (pollPeriod < 1) {
pollPeriod = DEFAULT_POLL_PERIOD;
}
LOG.info("starting - poll period %d minute(s)", pollPeriod);
final int PERIOD = pollPeriod;
onStart.dispatch(this, null);
Executors.newSingleThreadScheduledExecutor().scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
Date startDT = new Date();
LOG.fine("start time: " + startDT);
try {
long sm = System.currentTimeMillis();
AmazonEC2Client EC2 = new AmazonEC2Client(creds);
DescribeInstancesResult describeInstancesRequest = EC2.describeInstances();
List<Reservation> reservations = describeInstancesRequest.getReservations();
Set<Instance> instances = new HashSet<Instance>();
for (Reservation reservation : reservations) {
instances.addAll(reservation.getInstances());
}
boolean changed;
Date eventDate = null;
for (Instance instance : instances) {
changed = false;
LOG.fine("processing instance %s state: %s", instance.getInstanceId(), instance.getState());
//LOG.fine(instance.toString());
// first we see when the machine was booted
if (happenedSinceLastRun(instance.getLaunchTime(), startDT, PERIOD)) {
changed = true;
eventDate = instance.getLaunchTime();
}
if (instance.getStateReason() != null) {
// trying to parse the transition date
try {
eventDate = AWSUtils.getStateTransitionDate(instance.getStateTransitionReason());
LOG.fine("looks like instance %s changed state on %s", instance.getInstanceId(), eventDate);
if (happenedSinceLastRun(eventDate, startDT, PERIOD)) {
changed = true;
} else {
LOG.fine("state change happened too long ago, " + eventDate + " ignoring");
}
} catch (ParseException e) {
LOG.severe("could not parse transition timestamp: %s ", instance.getStateReason(), e);
}
}
if (changed) {
LOG.info("event instance %s (%s) state: %s on %s", instance.getInstanceId(), instance.getPrivateDnsName(), instance.getState().getName(), eventDate);
onInstanceEvent.dispatchAsync(this, instance);
}
}
LOG.info("processed %d instance(s) in %d msec", instances.size(), System.currentTimeMillis() - sm);
} catch (AmazonServiceException ase) {
System.out.println("Caught Exception: " + ase.getMessage());
System.out.println("Response Status Code: " + ase.getStatusCode());
System.out.println("Error Code: " + ase.getErrorCode());
System.out.println("Request ID: " + ase.getRequestId());
}
}
}, 0, pollPeriod, TimeUnit.MINUTES);
Thread.currentThread().join();
}
private boolean happenedSinceLastRun(Date event, Date whenRunStarted, int runInMinutes) {
return Math.abs(Minutes.minutesBetween(new DateTime(whenRunStarted), new DateTime(event)).getMinutes()) <= runInMinutes;
}
}