package io.airlift.airship.agent;
import com.google.common.base.Charsets;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableMap;
import com.google.common.io.Files;
import com.google.common.net.InetAddresses;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject;
import io.airlift.airship.shared.SlotLifecycleState;
import io.airlift.command.Command;
import io.airlift.command.CommandFailedException;
import io.airlift.http.server.HttpServerInfo;
import io.airlift.log.Logger;
import io.airlift.node.NodeInfo;
import io.airlift.units.Duration;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.net.URI;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import static io.airlift.airship.shared.HttpUriBuilder.uriBuilderFrom;
import static io.airlift.airship.shared.SlotLifecycleState.RUNNING;
import static io.airlift.airship.shared.SlotLifecycleState.STOPPED;
import static io.airlift.airship.shared.SlotLifecycleState.UNKNOWN;
public class LauncherLifecycleManager
implements LifecycleManager
{
private static final Logger log = Logger.get(LauncherLifecycleManager.class);
private final Executor executor;
private final InetAddress internalIp;
private final String externalAddress;
private final Duration launcherTimeout;
private final Duration stopTimeout;
private final String environment;
private final InetAddress bindIp;
private final URI serviceInventoryUri;
@Inject
public LauncherLifecycleManager(AgentConfig config, NodeInfo nodeInfo, HttpServerInfo httpServerInfo)
{
this(nodeInfo.getEnvironment(),
nodeInfo.getInternalIp(),
nodeInfo.getExternalAddress(),
nodeInfo.getBindIp(),
config.getLauncherTimeout(),
config.getLauncherStopTimeout(),
uriBuilderFrom(httpServerInfo.getHttpsUri() != null ? httpServerInfo.getHttpsUri() : httpServerInfo.getHttpUri()).appendPath("/v1/serviceInventory").build()
);
}
public LauncherLifecycleManager(String environment,
InetAddress internalIp,
String externalAddress,
InetAddress bindIp,
Duration launcherTimeout,
Duration launcherStopTimeout,
URI serviceInventoryUri)
{
this.launcherTimeout = launcherTimeout;
stopTimeout = launcherStopTimeout;
executor = Executors.newCachedThreadPool(new ThreadFactoryBuilder().setDaemon(true).setNameFormat("launcher-command-%s").build());
this.environment = environment;
this.internalIp = internalIp;
this.externalAddress = externalAddress;
this.bindIp = bindIp;
this.serviceInventoryUri = serviceInventoryUri;
}
@Override
public SlotLifecycleState status(Deployment deployment)
{
try {
int exitCode = createCommand("status", deployment, launcherTimeout)
.setSuccessfulExitCodes(0, 1, 2, 3)
.execute(executor);
if (exitCode == 0) {
return RUNNING;
}
else {
return STOPPED;
}
}
catch (CommandFailedException e) {
return UNKNOWN;
}
}
@Override
public SlotLifecycleState start(Deployment deployment)
{
updateNodeConfig(deployment);
Command command = createCommand("start", deployment, launcherTimeout);
try {
command.execute(executor);
return RUNNING;
}
catch (CommandFailedException e) {
log.error("ENVIRONMENT:\n %s", Joiner.on("\n ").withKeyValueSeparator("=").join(command.getEnvironment()));
throw new RuntimeException("start failed: " + e.getMessage());
}
}
@Override
public SlotLifecycleState restart(Deployment deployment)
{
updateNodeConfig(deployment);
try {
Command command = createCommand("restart", deployment, stopTimeout);
command.execute(executor);
return RUNNING;
}
catch (CommandFailedException e) {
throw new RuntimeException("restart failed: " + e.getMessage());
}
}
@Override
public SlotLifecycleState stop(Deployment deployment)
{
updateNodeConfig(deployment);
try {
createCommand("stop", deployment, stopTimeout).execute(executor);
return STOPPED;
}
catch (CommandFailedException e) {
throw new RuntimeException("stop failed: " + e.getMessage());
}
}
@Override
public SlotLifecycleState kill(Deployment deployment)
{
updateNodeConfig(deployment);
try {
createCommand("kill", deployment, launcherTimeout).execute(executor);
return STOPPED;
}
catch (CommandFailedException e) {
throw new RuntimeException("kill failed: " + e.getMessage());
}
}
private Command createCommand(String commandName, Deployment deployment, Duration timeLimit)
{
File launcherScript = new File(new File(deployment.getDeploymentDir(), "bin"), "launcher");
Command command = new Command(launcherScript.getAbsolutePath(), commandName)
.setDirectory(deployment.getDataDir())
.setTimeLimit(timeLimit)
.addEnvironment("HOME", deployment.getDataDir().getAbsolutePath());
return command;
}
@Override
public void updateNodeConfig(Deployment deployment)
{
ImmutableMap.Builder<String, String> map = ImmutableMap.builder();
map.put("node.environment", environment);
map.put("node.id", deployment.getNodeId().toString());
map.put("node.location", deployment.getLocation());
map.put("node.data-dir", deployment.getDataDir().getAbsolutePath());
map.put("node.binary-spec", deployment.getAssignment().getBinary());
map.put("node.config-spec", deployment.getAssignment().getConfig());
if (internalIp != null) {
map.put("node.ip", InetAddresses.toAddrString(internalIp));
}
if (externalAddress != null) {
map.put("node.external-address", externalAddress);
}
// add bind ip only if explicitly set on the agent
if (bindIp != null && InetAddresses.coerceToInteger(bindIp) != 0) {
map.put("node.bind-ip", bindIp.getHostAddress());
}
// add service inventory uri
map.put("service-inventory.uri", serviceInventoryUri.toString());
File nodeConfig = new File(deployment.getDeploymentDir(), "etc/node.properties");
nodeConfig.getParentFile().mkdirs();
try {
String data = Joiner.on("\n").withKeyValueSeparator("=").join(map.build()) + "\n";
Files.write(data, nodeConfig, Charsets.UTF_8);
}
catch (IOException e) {
nodeConfig.delete();
throw new RuntimeException("create node config failed: " + e.getMessage());
}
}
}