/**
* DeployMan # Thomas Uhrig (Stuttgart, 2014) # www.tuhrig.de
*/
package de.tuhrig.deployman.health;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.apache.commons.lang.StringUtils;
import com.amazonaws.services.ec2.model.Instance;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.jcraft.jsch.JSchException;
import com.sun.jersey.api.client.ClientHandlerException;
import de.tuhrig.deployman.aws.Ec2;
import de.tuhrig.deployman.launch.Launcher;
import de.tuhrig.deployman.launch.formation.Container;
import de.tuhrig.deployman.ssh.SshClient;
import static de.tuhrig.deployman.DeployMan.*;
/**
* A MachineStatus contains the "status" of a EC2 machine. Therefore it can be created with the ID
* of an EC2 machine. The status of a machine is determined by its log.
*
* @author tuhrig
*/
public class MachineStatus {
/**
* The current log file from the machine.
*/
private String deploymentLog;
/**
* The deployment steps (called states) of the machine. Each deployed container has various steps
* (download, load, start).
*/
private List<Status> states = new ArrayList<>();
/**
* Some general meta information about the machine.
*/
private MetaInfo metaInfo;
/**
* EC2 instance.
*/
private Instance machine;
/**
* Information about the running Docker instance. E.g. which containers are running and which
* images are present.
*/
private DockerInfo dockerInfo;
/**
* The cloud init script which created the instance.
*/
private String cloudInitScript;
private static final Integer SESSION_CACHE_SIZE = 100;
private static final Integer SESSION_CACHE_TIME = 15;
private static final Cache<String, MachineStatus> machines = CacheBuilder.newBuilder()
.maximumSize(SESSION_CACHE_SIZE).expireAfterWrite(SESSION_CACHE_TIME, TimeUnit.MINUTES)
.build();
public static MachineStatus getMachineStatus(String instanceId) {
MachineStatus machineStatus = machines.getIfPresent(instanceId);
if (machineStatus != null) {
String currentLog = getCompleteDeploymentLog(instanceId);
machineStatus.setDeploymentLog(currentLog);
machineStatus.updateLogDependingInformation();
machineStatus.updateDockerInfo(instanceId);
return machineStatus;
}
return new MachineStatus(instanceId);
}
private MachineStatus(String instanceId) {
updateDeploymentLog(instanceId);
updateCloudInitScript(instanceId);
updateMachineInformation(instanceId);
updateDockerInfo(instanceId);
// the log is null if the machine didn't booted yet because
// we cannot get the log until the machine is up (right?)
if (this.deploymentLog != null)
updateLogDependingInformation();
// We add this object to the machine cache. The creation
// of a new machine object is very expensive (e.g. because
// of the SSH connection), so we reuse it.
machines.put(this.machine.getInstanceId(), this);
}
private void updateDeploymentLog(String instanceId) {
String currentLog = getCompleteDeploymentLog(instanceId);
setDeploymentLog(currentLog);
}
private void updateCloudInitScript(String instanceId) {
String cloudInit = new SshClient().getCloutInitScript(instanceId);
this.setCloudInitScript(cloudInit);
}
private void updateMachineInformation(String instanceId) {
this.machine = new Ec2().getEC2InstanceById(instanceId);
}
private void updateDockerInfo(String instanceId) {
try {
this.dockerInfo = new DockerInfo(instanceId);
} catch (ClientHandlerException e) {
console.exception(e,
"Cannot get Docker info. Is the machine already up and Docker installed?"); //$NON-NLS-1$
}
}
private void updateLogDependingInformation() {
setMetaInfo(this.deploymentLog);
this.states = new ArrayList<>();
addStatus(new Status("Booting", getStatusOfBooting()));
addStatus(new Status("Initialization", getStatusOfInitialization()));
int pulls = 1;
int tars = 1;
int total = 1;
for (Container setup : this.metaInfo.getFormation().getMachine().getContainers()) {
if (setup.hasImage()) {
addStatus(new Status("Pull " + setup.getImage(), getStatusOfImageDownload(pulls)));
addStatus(new Status("Download " + setup.getConfig(), getStatusOfConfigDownload(pulls)));
addStatus(new Status("Start " + setup.getImage(), getStatusOfImageStart(total)));
pulls++;
} else {
addStatus(new Status("Download " + setup.getTarball(), getStatusOfTarballDownload(tars)));
addStatus(new Status("Download " + setup.getConfig(), getStatusOfConfigDownload(total)));
addStatus(new Status("Load " + setup.getTarball(), getStatusOfImageLoad(tars)));
addStatus(new Status("Start " + setup.getTarball(), getStatusOfImageStart(total)));
tars++;
}
total++;
}
}
private void addStatus(Status status) {
this.states.add(status);
}
private void setDeploymentLog(String log) {
this.deploymentLog = log;
}
private void setMetaInfo(String log) {
this.metaInfo = new MetaInfo(log);
}
/**
* Returns the complete deployment log of an instance. The log is requested via SSH.
*/
public static String getCompleteDeploymentLog(String instanceId) {
try {
SshClient ssh = new SshClient();
return ssh.runCommand(instanceId, "cat " + Launcher.DEPLOYMENT_LOG_FILE); //$NON-NLS-1$
} catch (JSchException | IOException e) {
console.write("Cannot get deployment log"); //$NON-NLS-1$
// we don't print the stack trace, since during the
// bootprocess this exception is expected (because
// the machine is not reachable yet)
e.printStackTrace();
}
return null;
}
public List<Status> getSetupStates() {
return this.states;
}
private DeploymentStatus getStatusOfBooting() {
if (this.deploymentLog != null)
return DeploymentStatus.DONE;
return DeploymentStatus.PENDING;
}
private DeploymentStatus getStatusOfInitialization() {
return getStatusOfInitialization(this.deploymentLog);
}
private DeploymentStatus getStatusOfImageDownload(int number) {
return getStatusOfImageDownload(this.deploymentLog, number);
}
private DeploymentStatus getStatusOfTarballDownload(int number) {
return getStatusOfTarballDownload(this.deploymentLog, number);
}
private DeploymentStatus getStatusOfConfigDownload(int number) {
return getStatusOfConfigDownload(this.deploymentLog, number);
}
private DeploymentStatus getStatusOfImageLoad(int number) {
return getStatusOfImageLoad(this.deploymentLog, number);
}
private DeploymentStatus getStatusOfImageStart(int number) {
return getStatusOfImageStart(this.deploymentLog, number);
}
private DeploymentStatus getStatus(String log, String doneMessage, String progressMessage) {
if (log.contains(doneMessage))
return DeploymentStatus.DONE;
if (log.contains(progressMessage))
return DeploymentStatus.INPROGRESS;
return DeploymentStatus.PENDING;
}
private DeploymentStatus getStatus(String log, String doneMessage, String progressMessage,
int number) {
int occuranceDone = StringUtils.countMatches(log, doneMessage);
int occuranceInProgress = StringUtils.countMatches(log, progressMessage);
if (occuranceDone >= number)
return DeploymentStatus.DONE;
if (occuranceInProgress >= number)
return DeploymentStatus.INPROGRESS;
return DeploymentStatus.PENDING;
}
private DeploymentStatus getStatusOfImageDownload(String log, int number) {
String doneString = "downloading image"; //$NON-NLS-1$
String progressString = "downloaded image"; //$NON-NLS-1$
return getStatus(log, doneString, progressString, number);
}
private DeploymentStatus getStatusOfTarballDownload(String log, int number) {
String doneString = "copied image"; //$NON-NLS-1$
String progressString = "copying image"; //$NON-NLS-1$
return getStatus(log, doneString, progressString, number);
}
private DeploymentStatus getStatusOfImageLoad(String log, int number) {
String doneString = "loaded Docker image"; //$NON-NLS-1$
String progressString = "loading Docker image"; //$NON-NLS-1$
return getStatus(log, doneString, progressString, number);
}
private DeploymentStatus getStatusOfInitialization(String log) {
String doneString = "installed AWS CLI"; //$NON-NLS-1$
String progressString = "installing Docker..."; //$NON-NLS-1$
return getStatus(log, doneString, progressString);
}
private DeploymentStatus getStatusOfConfigDownload(String log, int number) {
String doneMessage = "copied config"; //$NON-NLS-1$
String progressMessage = "copying config"; //$NON-NLS-1$
return getStatus(log, doneMessage, progressMessage, number);
}
private DeploymentStatus getStatusOfImageStart(String log, int number) {
String doneMessage = "started command"; //$NON-NLS-1$
String progressMessage = "starting command"; //$NON-NLS-1$
return getStatus(log, doneMessage, progressMessage, number);
}
public Instance getMachine() {
return this.machine;
}
public void setMachine(Instance machine) {
this.machine = machine;
}
public DockerInfo getDockerInfo() {
return this.dockerInfo;
}
public void setDockerInfo(DockerInfo dockerInfo) {
this.dockerInfo = dockerInfo;
}
public String getCloudInitScript() {
return cloudInitScript;
}
public void setCloudInitScript(String cloudInitScript) {
this.cloudInitScript = cloudInitScript;
}
}