package org.zstack.console;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.Platform;
import org.zstack.core.ansible.AnsibleFacade;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.db.SimpleQuery.Op;
import org.zstack.core.errorcode.ErrorFacade;
import org.zstack.core.notification.N;
import org.zstack.core.thread.AsyncThread;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.Component;
import org.zstack.header.console.*;
import org.zstack.header.core.AsyncLatch;
import org.zstack.header.core.Completion;
import org.zstack.header.core.NoErrorCompletion;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.core.workflow.*;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.errorcode.OperationFailureException;
import org.zstack.header.identity.SessionInventory;
import org.zstack.header.managementnode.ManagementNodeReadyExtensionPoint;
import org.zstack.header.rest.RESTFacade;
import org.zstack.header.vm.VmInstanceInventory;
import org.zstack.header.vm.VmInstanceVO;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import static org.zstack.core.Platform.operr;
import javax.persistence.TypedQuery;
import java.util.List;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* User: frank
* Time: 7:32 PM
* To change this template use File | Settings | File Templates.
*/
public abstract class AbstractConsoleProxyBackend implements ConsoleBackend, Component, ManagementNodeReadyExtensionPoint {
private static final CLogger logger = Utils.getLogger(AbstractConsoleProxyBackend.class);
@Autowired
protected DatabaseFacade dbf;
@Autowired
protected RESTFacade restf;
@Autowired
protected CloudBus bus;
@Autowired
protected AnsibleFacade asf;
@Autowired
protected ErrorFacade errf;
protected static final String ANSIBLE_PLAYBOOK_NAME = "consoleproxy.py";
protected abstract ConsoleProxy getConsoleProxy(VmInstanceInventory vm, ConsoleProxyVO vo);
protected abstract ConsoleProxy getConsoleProxy(SessionInventory session, VmInstanceInventory vm);
protected abstract void connectAgent();
protected abstract boolean isAgentConnected();
private void establishNewProxy(ConsoleProxy proxy, SessionInventory session, final VmInstanceInventory vm, final ReturnValueCompletion<ConsoleInventory> complete) {
proxy.establishProxy(session, vm, new ReturnValueCompletion<ConsoleProxyInventory>(complete) {
@Override
public void success(ConsoleProxyInventory ret) {
ConsoleProxyVO vo = new ConsoleProxyVO();
vo.setAgentIp(ret.getAgentIp());
vo.setProxyIdentity(ret.getProxyIdentity());
vo.setScheme(ret.getScheme());
vo.setProxyHostname(ret.getProxyHostname());
vo.setProxyPort(ret.getProxyPort());
vo.setTargetHostname(ret.getTargetHostname());
vo.setTargetPort(ret.getTargetPort());
vo.setToken(ret.getToken());
vo.setVmInstanceUuid(vm.getUuid());
vo.setUuid(Platform.getUuid());
vo.setAgentType(ret.getAgentType());
vo.setStatus(ConsoleProxyStatus.Active);
vo = dbf.persistAndRefresh(vo);
complete.success(ConsoleInventory.valueOf(vo));
}
@Override
public void fail(ErrorCode errorCode) {
complete.fail(errorCode);
}
});
}
@Override
public void grantConsoleAccess(final SessionInventory session, final VmInstanceInventory vm, final ReturnValueCompletion<ConsoleInventory> complete) {
if (!isAgentConnected()) {
complete.fail(operr(
"the console agent is not connected; it's mostly like the management node just starts, please wait for the console agent connected."
));
return;
}
SimpleQuery<ConsoleProxyVO> q = dbf.createQuery(ConsoleProxyVO.class);
q.add(ConsoleProxyVO_.vmInstanceUuid, SimpleQuery.Op.EQ, vm.getUuid());
q.add(ConsoleProxyVO_.status, SimpleQuery.Op.EQ, ConsoleProxyStatus.Active);
final ConsoleProxyVO vo = q.find();
if (vo == null) {
// new console proxy
ConsoleProxy proxy = getConsoleProxy(session, vm);
establishNewProxy(proxy, session, vm, complete);
return;
}
String hostIp = getHostIp(vm);
if (hostIp == null) {
throw new OperationFailureException(operr("cannot find host IP of the vm[uuid:%s], is the vm running???", vm.getUuid()));
}
if (vo.getTargetHostname().equals(hostIp)) {
// vm is on the same host
final ConsoleProxy proxy = getConsoleProxy(vm, vo);
dbf.remove(vo);
establishNewProxy(proxy, session, vm, complete);
} else {
// vm is on another host
FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("recreate-console-for-vm-%s", vm.getUuid()));
chain.then(new ShareFlow() {
ConsoleInventory ret;
@Override
public void setup() {
flow(new NoRollbackFlow() {
String __name__ = "delete-old-console";
@Override
public void run(final FlowTrigger trigger, Map data) {
deleteConsoleSession(vm, new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
flow(new NoRollbackFlow() {
String __name__ = "create-new-console";
@Override
public void run(final FlowTrigger trigger, Map data) {
ConsoleProxy proxy = getConsoleProxy(session, vm);
establishNewProxy(proxy, session, vm, new ReturnValueCompletion<ConsoleInventory>(trigger) {
@Override
public void success(ConsoleInventory returnValue) {
ret = returnValue;
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
done(new FlowDoneHandler(complete) {
@Override
public void handle(Map data) {
complete.success(ret);
}
});
error(new FlowErrorHandler(complete) {
@Override
public void handle(ErrorCode errCode, Map data) {
complete.fail(errCode);
}
});
}
}).start();
}
}
@Transactional(readOnly = true)
protected String getHostIp(VmInstanceInventory vm) {
String sql = "select h.managementIp from HostVO h, VmInstanceVO vm where h.uuid = vm.hostUuid and vm.uuid = :uuid";
TypedQuery<String> q = dbf.getEntityManager().createQuery(sql, String.class);
q.setParameter("uuid", vm.getUuid());
List<String> ret = q.getResultList();
return ret.isEmpty() ? null : ret.get(0);
}
@Override
public void deleteConsoleSession(SessionInventory session, final NoErrorCompletion completion) {
SimpleQuery<ConsoleProxyVO> q = dbf.createQuery(ConsoleProxyVO.class);
q.add(ConsoleProxyVO_.token, Op.LIKE, session.getUuid() + "%");
List<ConsoleProxyVO> vos = q.list();
if (vos.isEmpty()) {
completion.done();
return;
}
final AsyncLatch latch = new AsyncLatch(vos.size(), new NoErrorCompletion(completion) {
@Override
public void done() {
completion.done();
}
});
for (final ConsoleProxyVO vo : vos) {
final VmInstanceVO vm = dbf.findByUuid(vo.getVmInstanceUuid(), VmInstanceVO.class);
if (vm == null) {
latch.ack();
continue;
}
VmInstanceInventory vminv = VmInstanceInventory.valueOf(vm);
ConsoleProxy proxy = getConsoleProxy(vminv, vo);
proxy.deleteProxy(vminv, new Completion(latch) {
@Override
public void success() {
dbf.remove(vo);
logger.debug(String.format("deleted a console proxy[vmUuid:%s, host IP: %s, host port: %s, proxy IP: %s, proxy port: %s",
vm.getUuid(), vo.getTargetHostname(), vo.getTargetPort(), vo.getProxyHostname(), vo.getProxyPort()));
latch.ack();
}
@Override
public void fail(ErrorCode errorCode) {
N.New(VmInstanceVO.class, vm.getUuid()).warn_("failed to delete a console proxy[vmUuid:%s, host IP: %s, host port: %s, proxy IP: %s, proxy port: %s, %s",
vm.getUuid(), vo.getTargetHostname(), vo.getTargetPort(), vo.getProxyHostname(), vo.getProxyPort(), errorCode.toString());
logger.warn(String.format("failed to delete a console proxy[vmUuid:%s, host IP: %s, host port: %s, proxy IP: %s, proxy port: %s, %s",
vm.getUuid(), vo.getTargetHostname(), vo.getTargetPort(), vo.getProxyHostname(), vo.getProxyPort(), errorCode.toString()));
latch.ack();
}
});
}
}
@Override
public void deleteConsoleSession(final VmInstanceInventory vm, final Completion completion) {
SimpleQuery<ConsoleProxyVO> q = dbf.createQuery(ConsoleProxyVO.class);
q.add(ConsoleProxyVO_.vmInstanceUuid, SimpleQuery.Op.EQ, vm.getUuid());
q.add(ConsoleProxyVO_.status, SimpleQuery.Op.EQ, ConsoleProxyStatus.Active);
final ConsoleProxyVO vo = q.find();
if (vo != null) {
ConsoleProxy proxy = getConsoleProxy(vm, vo);
proxy.deleteProxy(vm, new Completion(completion) {
@Override
public void success() {
dbf.remove(vo);
logger.debug(String.format("deleted a console proxy[vmUuid:%s, host IP: %s, host port: %s, proxy IP: %s, proxy port: %s",
vm.getUuid(), vo.getTargetHostname(), vo.getTargetPort(), vo.getProxyHostname(), vo.getProxyPort()));
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
} else {
completion.success();
}
}
private void deploySaltState() {
if (CoreGlobalProperty.UNIT_TEST_ON) {
return;
}
asf.deployModule("ansible/consoleproxy", ANSIBLE_PLAYBOOK_NAME);
}
@Override
public boolean start() {
deploySaltState();
return true;
}
@Override
public boolean stop() {
return true;
}
@Override
@AsyncThread
public void managementNodeReady() {
if (CoreGlobalProperty.UNIT_TEST_ON) {
return;
}
connectAgent();
}
}