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.cloudbus.CloudBus;
import org.zstack.core.cloudbus.MessageSafe;
import org.zstack.core.componentloader.PluginRegistry;
import org.zstack.core.db.DatabaseFacade;
import org.zstack.core.db.SimpleQuery;
import org.zstack.core.thread.ChainTask;
import org.zstack.core.thread.SyncTaskChain;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.header.AbstractService;
import org.zstack.header.console.*;
import org.zstack.header.core.Completion;
import org.zstack.header.core.FutureCompletion;
import org.zstack.header.core.NoErrorCompletion;
import org.zstack.header.core.ReturnValueCompletion;
import org.zstack.header.errorcode.ErrorCode;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.host.HypervisorType;
import org.zstack.header.identity.SessionInventory;
import org.zstack.header.identity.SessionLogoutExtensionPoint;
import org.zstack.header.managementnode.ManagementNodeChangeListener;
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.vm.*;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import javax.persistence.Query;
import java.util.HashMap;
import java.util.Map;
/**
* Created with IntelliJ IDEA.
* User: frank
* Time: 11:51 PM
* To change this template use File | Settings | File Templates.
*/
public class ConsoleManagerImpl extends AbstractService implements ConsoleManager, VmInstanceMigrateExtensionPoint, ManagementNodeChangeListener,
VmReleaseResourceExtensionPoint, SessionLogoutExtensionPoint {
private static CLogger logger = Utils.getLogger(ConsoleManagerImpl.class);
@Autowired
private CloudBus bus;
@Autowired
private DatabaseFacade dbf;
@Autowired
private PluginRegistry pluginRgty;
@Autowired
private ThreadFacade thdf;
private Map<String, ConsoleBackend> consoleBackends = new HashMap<String, ConsoleBackend>();
private Map<String, ConsoleHypervisorBackend> consoleHypervisorBackends = new HashMap<String, ConsoleHypervisorBackend>();
private String useBackend;
@Override
@MessageSafe
public void handleMessage(Message msg) {
if (msg instanceof ConsoleProxyAgentMessage) {
passThrough(msg);
} else if (msg instanceof APIMessage) {
handleApiMessage((APIMessage) msg);
} else {
handleLocalMessage(msg);
}
}
private void passThrough(Message msg) {
getBackend().handleMessage(msg);
}
private void handleLocalMessage(Message msg) {
bus.dealWithUnknownMessage(msg);
}
private void handleApiMessage(APIMessage msg) {
if (msg instanceof APIRequestConsoleAccessMsg) {
handle((APIRequestConsoleAccessMsg) msg);
} else {
bus.dealWithUnknownMessage(msg);
}
}
private void handle(final APIRequestConsoleAccessMsg msg) {
final APIRequestConsoleAccessEvent evt = new APIRequestConsoleAccessEvent(msg.getId());
thdf.chainSubmit(new ChainTask(msg) {
@Override
public String getSyncSignature() {
return String.format("request-console-for-vm-%s", msg.getVmInstanceUuid());
}
@Override
public void run(final SyncTaskChain chain) {
VmInstanceVO vmvo = dbf.findByUuid(msg.getVmInstanceUuid(), VmInstanceVO.class);
ConsoleBackend bkd = getBackend();
bkd.grantConsoleAccess(msg.getSession(), VmInstanceInventory.valueOf(vmvo), new ReturnValueCompletion<ConsoleInventory>(chain) {
@Override
public void success(ConsoleInventory returnValue) {
if (!"0.0.0.0".equals(CoreGlobalProperty.CONSOLE_PROXY_OVERRIDDEN_IP) &&
!"".equals(CoreGlobalProperty.CONSOLE_PROXY_OVERRIDDEN_IP)) {
returnValue.setHostname(CoreGlobalProperty.CONSOLE_PROXY_OVERRIDDEN_IP);
} else {
returnValue.setHostname(CoreGlobalProperty.UNIT_TEST_ON ? "127.0.0.1" : Platform.getManagementServerIp());
}
evt.setInventory(returnValue);
bus.publish(evt);
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
evt.setError(errorCode);
evt.setSuccess(false);
bus.publish(evt);
chain.next();
}
});
}
@Override
public String getName() {
return getSyncSignature();
}
});
}
@Override
public String getId() {
return bus.makeLocalServiceId(ConsoleConstants.SERVICE_ID);
}
private void populateExtensions() {
for (ConsoleBackend bkd : pluginRgty.getExtensionList(ConsoleBackend.class)) {
ConsoleBackend old = consoleBackends.get(bkd.getConsoleBackendType());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate ConsoleBackend[%s, %s] for type[%s]",
bkd.getClass().getName(), old.getClass().getName(), old.getConsoleBackendType()));
}
consoleBackends.put(bkd.getConsoleBackendType(), bkd);
}
for (ConsoleHypervisorBackend bkd : pluginRgty.getExtensionList(ConsoleHypervisorBackend.class)) {
ConsoleHypervisorBackend old = consoleHypervisorBackends.get(bkd.getConsoleBackendHypervisorType().toString());
if (old != null) {
throw new CloudRuntimeException(String.format("duplicate ConsoleHypervisorBackend[%s, %s] for type[%s]",
bkd.getClass().getName(), old.getClass().getName(), bkd.getConsoleBackendHypervisorType()));
}
consoleHypervisorBackends.put(bkd.getConsoleBackendHypervisorType().toString(), bkd);
}
}
@Override
public boolean start() {
populateExtensions();
clean();
return true;
}
@Transactional
public void clean() {
String sql = "delete from ConsoleProxyVO";
Query q = dbf.getEntityManager().createQuery(sql);
q.executeUpdate();
}
@Override
public boolean stop() {
return true;
}
private ConsoleBackend getBackend() {
ConsoleBackend bkd = consoleBackends.get(useBackend);
if (bkd == null) {
throw new CloudRuntimeException(String.format("no plugin registered ConsoleBackend[type:%s]", useBackend));
}
return bkd;
}
public void setUseBackend(String useBackend) {
this.useBackend = useBackend;
}
@Override
public ConsoleHypervisorBackend getHypervisorConsoleBackend(HypervisorType type) {
ConsoleHypervisorBackend bkd = consoleHypervisorBackends.get(type.toString());
if (bkd == null) {
throw new CloudRuntimeException(String.format("cannot find ConsoleHypervisorBackend[type:%s]", type.toString()));
}
return bkd;
}
@Override
public ConsoleBackend getConsoleBackend() {
return getBackend();
}
@Override
public void preMigrateVm(VmInstanceInventory inv, String destHostUuid) {
}
@Override
public void beforeMigrateVm(VmInstanceInventory inv, String destHostUuid) {
}
@Override
public void afterMigrateVm(VmInstanceInventory inv, String srcHostUuid) {
ConsoleBackend bkd = getBackend();
FutureCompletion completion = new FutureCompletion(null);
bkd.deleteConsoleSession(inv, completion);
try {
synchronized (completion) {
completion.wait(1500);
}
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
@Override
public void failedToMigrateVm(VmInstanceInventory inv, String destHostUuid, ErrorCode reason) {
}
@Override
public void releaseVmResource(VmInstanceSpec spec, final Completion completion) {
ConsoleBackend bkd = getBackend();
bkd.deleteConsoleSession(spec.getVmInventory(), new Completion(completion) {
@Override
public void success() {
completion.success();
}
@Override
public void fail(ErrorCode errorCode) {
//TODO
logger.warn(errorCode.toString());
completion.success();
}
});
}
@Override
public void sessionLogout(final SessionInventory session) {
ConsoleBackend bkd = getBackend();
bkd.deleteConsoleSession(session, new NoErrorCompletion() {
@Override
public void done() {
logger.debug(String.format("deleted all console proxy opened by the session[uuid:%s]", session.getUuid()));
}
});
}
@Transactional
private void deleteConsoleProxyByManagementNode(ManagementNodeVO managementNode) {
String managementHostName = managementNode.getHostName();
String sql = "delete from ConsoleProxyVO q where q.proxyHostname = :managementHostName";
Query q = dbf.getEntityManager().createQuery(sql);
q.setParameter("managementHostName", managementHostName);
q.executeUpdate();
}
public void cleanupNode(String nodeId) {
logger.debug(String.format("Management node[uuid:%s] left, will clean the record in ConsoleProxyVO", nodeId));
SimpleQuery<ManagementNodeVO> query = dbf.createQuery(ManagementNodeVO.class);
query.add(ManagementNodeVO_.uuid, SimpleQuery.Op.EQ, nodeId);
ManagementNodeVO managementNode = query.find();
if (managementNode == null) {
logger.debug("Cannot find management node: " + nodeId + ", it may have been deleted");
return;
}
deleteConsoleProxyByManagementNode(managementNode);
}
@Override
public void nodeLeft(String nodeId) {
cleanupNode(nodeId);
}
@Override
public void iAmDead(String nodeId) {
cleanupNode(nodeId);
}
@Override
public void nodeJoin(String nodeId) {
}
@Override
public void iJoin(String nodeId) {
}
}