package org.zstack.console;
import org.apache.commons.io.FileUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.Platform;
import org.zstack.core.ansible.AnsibleConstant;
import org.zstack.core.ansible.AnsibleGlobalProperty;
import org.zstack.core.ansible.AnsibleRunner;
import org.zstack.core.ansible.SshFileMd5Checker;
import org.zstack.core.cloudbus.CloudBusListCallBack;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.defer.Defer;
import org.zstack.core.defer.Deferred;
import org.zstack.core.thread.AsyncThread;
import org.zstack.core.thread.ChainTask;
import org.zstack.core.thread.SyncTaskChain;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.header.console.*;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NopeCompletion;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.identity.SessionInventory;
import org.zstack.header.managementnode.ManagementNodeVO;
import org.zstack.header.managementnode.ManagementNodeVO_;
import org.zstack.header.message.APIMessage;
import org.zstack.header.message.Message;
import org.zstack.header.message.MessageReply;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.utils.CollectionUtils;
import org.zstack.utils.ShellUtils;
import org.zstack.utils.URLBuilder;
import org.zstack.utils.Utils;
import org.zstack.utils.function.Function;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;
import static org.zstack.core.Platform.argerr;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* User: frank
* Time: 9:02 PM
* To change this template use File | Settings | File Templates.
*/
public class ManagementServerConsoleProxyBackend extends AbstractConsoleProxyBackend {
private static final CLogger logger = Utils.getLogger(ManagementServerConsoleProxyBackend.class);
private int agentPort = 7758;
private String agentPackageName = ConsoleGlobalProperty.AGENT_PACKAGE_NAME;
private boolean connected = false;
public static ConsoleProxyAgentType type = new ConsoleProxyAgentType(ConsoleConstants.MANAGEMENT_SERVER_CONSOLE_PROXY_TYPE);
@Autowired
private ThreadFacade thdf;
@Autowired
private ConsoleProxyAgentTracker tracker;
protected ConsoleProxy getConsoleProxy(VmInstanceInventory vm, ConsoleProxyVO vo) {
return new ConsoleProxyBase(vo, getAgentPort());
}
@Override
protected ConsoleProxy getConsoleProxy(SessionInventory session, VmInstanceInventory vm) {
String mgmtIp = CoreGlobalProperty.UNIT_TEST_ON ? "127.0.0.1" : Platform.getManagementServerIp();
ConsoleProxyInventory inv = new ConsoleProxyInventory();
inv.setScheme("http");
inv.setProxyHostname(mgmtIp);
inv.setAgentIp("127.0.0.1");
inv.setAgentType(getConsoleBackendType());
inv.setToken(session.getUuid() + "_" + vm.getUuid());
inv.setVmInstanceUuid(vm.getUuid());
return new ConsoleProxyBase(inv, getAgentPort());
}
private void setupPublicKey() throws IOException {
File pubKeyFile = PathUtil.findFileOnClassPath(AnsibleConstant.RSA_PUBLIC_KEY);
String script = PathUtil.findFileOnClassPath(AnsibleConstant.IMPORT_PUBLIC_KEY_SCRIPT_PATH, true).getAbsolutePath();
ShellUtils.run(String.format("sh %s '%s'", script, pubKeyFile.getAbsolutePath()));
}
protected void doConnectAgent(final Completion completion) {
thdf.chainSubmit(new ChainTask(completion) {
@Override
public String getSyncSignature() {
return String.format("deploy-console-agent-%s", Platform.getManagementServerId());
}
@Override
@Deferred
public void run(final SyncTaskChain chain) {
ConsoleProxyAgentVO vo = dbf.findByUuid(Platform.getManagementServerId(), ConsoleProxyAgentVO.class);
if (vo == null) {
vo = new ConsoleProxyAgentVO();
vo.setManagementIp(Platform.getManagementServerIp());
vo.setUuid(Platform.getManagementServerId());
vo.setState(ConsoleProxyAgentState.Enabled);
vo.setStatus(ConsoleProxyAgentStatus.Connecting);
vo.setDescription(String.format("Console proxy agent running on the management node[uuid:%s]", Platform.getManagementServerId()));
vo.setType(ConsoleConstants.MANAGEMENT_SERVER_CONSOLE_PROXY_TYPE);
vo = dbf.persistAndRefresh(vo);
}
final ConsoleProxyAgentVO finalVo = vo;
Defer.guard(new Runnable() {
@Override
public void run() {
finalVo.setStatus(ConsoleProxyAgentStatus.Disconnected);
dbf.update(finalVo);
}
});
try {
ShellUtils.run("rm -rf /var/lib/zstack/consoleProxy/*");
setupPublicKey();
File privKeyFile = PathUtil.findFileOnClassPath("ansible/rsaKeys/id_rsa");
String privKey = FileUtils.readFileToString(privKeyFile);
String srcPath = PathUtil.findFileOnClassPath(String.format("ansible/consoleproxy/%s", agentPackageName), true).getAbsolutePath();
String destPath = String.format("/var/lib/zstack/console/package/%s", agentPackageName);
SshFileMd5Checker checker = new SshFileMd5Checker();
checker.setTargetIp("127.0.0.1");
checker.setUsername("root");
checker.setPrivateKey(privKey);
checker.addSrcDestPair(SshFileMd5Checker.ZSTACKLIB_SRC_PATH, String.format("/var/lib/zstack/console/package/%s", AnsibleGlobalProperty.ZSTACKLIB_PACKAGE_NAME));
checker.addSrcDestPair(srcPath, destPath);
AnsibleRunner runner = new AnsibleRunner();
runner.setRunOnLocal(true);
runner.setLocalPublicKey(true);
runner.installChecker(checker);
runner.setUsername("root");
runner.setPrivateKey(privKey);
runner.setAgentPort(7758);
runner.setTargetIp(Platform.getManagementServerIp());
runner.setPlayBookName(ANSIBLE_PLAYBOOK_NAME);
runner.putArgument("pkg_consoleproxy", agentPackageName);
runner.run(new Completion(completion, chain) {
@Override
public void success() {
finalVo.setStatus(ConsoleProxyAgentStatus.Connected);
dbf.update(finalVo);
connected = true;
logger.debug("successfully deploy console proxy agent by ansible");
completion.success();
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
finalVo.setStatus(ConsoleProxyAgentStatus.Disconnected);
dbf.update(finalVo);
connected = false;
logger.warn(String.format("failed to deploy console proxy agent by ansible, %s", errorCode));
completion.fail(errorCode);
chain.next();
}
});
} catch (IOException e) {
throw new CloudRuntimeException(e);
}
}
@Override
public String getName() {
return getSyncSignature();
}
});
}
@Override
@AsyncThread
protected void connectAgent() {
doConnectAgent(new NopeCompletion());
}
@Override
protected boolean isAgentConnected() {
return connected;
}
@Override
public String getConsoleBackendType() {
return ConsoleConstants.MANAGEMENT_SERVER_CONSOLE_PROXY_BACKEND;
}
@Override
public String returnServiceIdForConsoleAgentMsg(ConsoleProxyAgentMessage msg, String agentUuid) {
return bus.makeServiceIdByManagementNodeId(ConsoleConstants.SERVICE_ID, Platform.getManagementServerId());
}
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void handleLocalMessage(Message msg) {
if (msg instanceof ReconnectConsoleProxyMsg) {
handle((ReconnectConsoleProxyMsg) msg);
} else if (msg instanceof PingConsoleProxyAgentMsg) {
handle((PingConsoleProxyAgentMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(PingConsoleProxyAgentMsg msg) {
ConsoleProxyCommands.PingCmd cmd = new ConsoleProxyCommands.PingCmd();
String url = URLBuilder.buildHttpUrl("127.0.0.1", agentPort, ConsoleConstants.CONSOLE_PROXY_PING_PATH);
ConsoleProxyAgentVO vo = dbf.findByUuid(Platform.getManagementServerId(), ConsoleProxyAgentVO.class);
boolean success;
boolean reconnect = false;
try {
restf.syncJsonPost(url, cmd, ConsoleProxyCommands.PingRsp.class);
success = true;
if (vo != null) {
reconnect = vo.getStatus() == ConsoleProxyAgentStatus.Disconnected;
if (vo.getStatus() != ConsoleProxyAgentStatus.Connected) {
vo.setStatus(ConsoleProxyAgentStatus.Connected);
dbf.update(vo);
}
} else {
reconnect = true;
}
} catch (Exception e) {
logger.warn(String.format("cannot ping console proxy agent, %s", e.getMessage()), e);
if (vo != null) {
vo.setStatus(ConsoleProxyAgentStatus.Disconnected);
dbf.update(vo);
}
success = false;
}
PingConsoleProxyAgentReply reply = new PingConsoleProxyAgentReply();
reply.setConnected(success);
reply.setDoReconnect(reconnect);
bus.reply(msg, reply);
}
private void handle(final ReconnectConsoleProxyMsg msg) {
final ReconnectConsoleProxyReply reply = new ReconnectConsoleProxyReply();
doConnectAgent(new Completion(msg) {
@Override
public void success() {
bus.reply(msg, reply);
}
@Override
public void fail(ErrorCode errorCode) {
reply.setError(errorCode);
bus.reply(msg, reply);
}
});
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APIReconnectConsoleProxyAgentMsg) {
handle((APIReconnectConsoleProxyAgentMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(final APIReconnectConsoleProxyAgentMsg msg) {
final APIReconnectConsoleProxyAgentEvent evt = new APIReconnectConsoleProxyAgentEvent(msg.getId());
SimpleQuery<ManagementNodeVO> q = dbf.createQuery(ManagementNodeVO.class);
q.select(ManagementNodeVO_.uuid);
if (msg.getAgentUuids() != null) {
q.add(ManagementNodeVO_.uuid, SimpleQuery.Op.IN, msg.getAgentUuids());
}
final List<String> mgmtNodeUuids = q.listValue();
final Map<String, Object> errors = new HashMap<String, Object>();
if (msg.getAgentUuids() != null) {
for (String uuid : msg.getAgentUuids()) {
if (!mgmtNodeUuids.contains(uuid)) {
errors.put(uuid, argerr("invalid management node UUID[%s]", uuid));
}
}
}
if (mgmtNodeUuids.isEmpty()) {
evt.setInventory(errors);
bus.publish(evt);
return;
}
final List<ReconnectConsoleProxyMsg> rmsgs = CollectionUtils.transformToList(mgmtNodeUuids, new Function<ReconnectConsoleProxyMsg, String>() {
@Override
public ReconnectConsoleProxyMsg call(String arg) {
ReconnectConsoleProxyMsg rmsg = new ReconnectConsoleProxyMsg();
rmsg.setAgentUuid(arg);
bus.makeServiceIdByManagementNodeId(rmsg, ConsoleConstants.SERVICE_ID, arg);
return rmsg;
}
});
bus.send(rmsgs, new CloudBusListCallBack(msg) {
@Override
public void run(List<MessageReply> replies) {
for (MessageReply r : replies) {
String mgmgUuid = mgmtNodeUuids.get(replies.indexOf(r));
if (r.isSuccess()) {
errors.put(mgmgUuid, true);
} else {
errors.put(mgmgUuid, r.getError());
}
}
evt.setInventory(errors);
bus.publish(evt);
}
});
}
@Override
public boolean start() {
tracker.track(Platform.getManagementServerId());
return super.start();
}
public int getAgentPort() {
return agentPort;
}
public void setAgentPort(int agentPort) {
this.agentPort = agentPort;
}
}