package org.zstack.storage.ceph.primary;
import org.springframework.beans.factory.annotation.Autowired;
import org.zstack.core.CoreGlobalProperty;
import org.zstack.core.ansible.AnsibleGlobalProperty;
import org.zstack.core.ansible.AnsibleRunner;
import org.zstack.core.ansible.SshFileMd5Checker;
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.thread.ChainTask;
import org.zstack.core.thread.SyncTaskChain;
import org.zstack.core.thread.ThreadFacade;
import org.zstack.core.workflow.FlowChainBuilder;
import org.zstack.core.workflow.ShareFlow;
import org.zstack.header.core.Completion;
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.rest.JsonAsyncRESTCallback;
import org.zstack.storage.ceph.CephGlobalProperty;
import org.zstack.storage.ceph.CephMonAO;
import org.zstack.storage.ceph.CephMonBase;
import org.zstack.storage.ceph.MonStatus;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;
import java.util.Map;
import static org.zstack.core.Platform.operr;
/**
* Created by frank on 7/28/2015.
*/
public class CephPrimaryStorageMonBase extends CephMonBase {
private static final CLogger logger = Utils.getLogger(CephPrimaryStorageMonBase.class);
@Autowired
private DatabaseFacade dbf;
@Autowired
private ThreadFacade thdf;
@Autowired
private ErrorFacade errf;
private String syncId;
public static final String ECHO_PATH = "/ceph/primarystorage/echo";
public static final String PING_PATH = "/ceph/primarystorage/ping";
public enum PingOperationFailure {
UnableToCreateFile,
MonAddrChanged,
}
public static class AgentCmd {
public String monUuid;
public String primaryStorageUuid;
}
public static class AgentRsp {
public boolean success;
public String error;
}
public static class PingCmd extends AgentCmd {
public String testImagePath;
public String monAddr;
}
public static class PingRsp extends AgentRsp {
public boolean operationFailure;
public PingOperationFailure failure;
}
public CephPrimaryStorageMonVO getSelf() {
return (CephPrimaryStorageMonVO) self;
}
public void changeStatus(MonStatus status) {
String uuid = self.getUuid();
self = dbf.reload(self);
if (self == null) {
throw new OperationFailureException(operr(
"cannot update status of the ceph primary storage mon[uuid:%s], it has been deleted." +
"This error can be ignored", uuid
));
}
if (self.getStatus() == status) {
return;
}
MonStatus oldStatus = self.getStatus();
self.setStatus(status);
self = dbf.updateAndRefresh(self);
logger.debug(String.format("Ceph primary storage mon[uuid:%s] changed status from %s to %s", self.getUuid(), oldStatus, status));
}
@Override
public void connect(final Completion completion) {
thdf.chainSubmit(new ChainTask(completion) {
@Override
public String getSyncSignature() {
return syncId;
}
@Override
public void run(final SyncTaskChain chain) {
doConnect(new Completion(completion, chain) {
@Override
public void success() {
completion.success();
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
chain.next();
}
});
}
@Override
public String getName() {
return String.format("connect-ceph-primary-storage-mon-%s", self.getUuid());
}
});
}
private void doConnect(final Completion completion) {
changeStatus(MonStatus.Connecting);
final FlowChain chain = FlowChainBuilder.newShareFlowChain();
chain.setName(String.format("connect-mon-%s-ceph-primary-storage-%s", self.getHostname(), getSelf().getPrimaryStorageUuid()));
chain.allowEmptyFlow();
chain.then(new ShareFlow() {
@Override
public void setup() {
if (!CoreGlobalProperty.UNIT_TEST_ON) {
flow(new NoRollbackFlow() {
String __name__ = "check-tools";
@Override
public void run(FlowTrigger trigger, Map data) {
checkTools();
trigger.next();
}
});
flow(new NoRollbackFlow() {
String __name__ = "deploy-agent";
@Override
public void run(final FlowTrigger trigger, Map data) {
SshFileMd5Checker checker = new SshFileMd5Checker();
checker.setTargetIp(getSelf().getHostname());
checker.setUsername(getSelf().getSshUsername());
checker.setPassword(getSelf().getSshPassword());
checker.addSrcDestPair(SshFileMd5Checker.ZSTACKLIB_SRC_PATH, String.format("/var/lib/zstack/cephp/package/%s", AnsibleGlobalProperty.ZSTACKLIB_PACKAGE_NAME));
checker.addSrcDestPair(PathUtil.findFileOnClassPath(String.format("ansible/cephp/%s", CephGlobalProperty.PRIMARY_STORAGE_PACKAGE_NAME), true).getAbsolutePath(),
String.format("/var/lib/zstack/cephp/package/%s", CephGlobalProperty.PRIMARY_STORAGE_PACKAGE_NAME));
AnsibleRunner runner = new AnsibleRunner();
runner.installChecker(checker);
runner.setPassword(getSelf().getSshPassword());
runner.setUsername(getSelf().getSshUsername());
runner.setSshPort(getSelf().getSshPort());
runner.setTargetIp(getSelf().getHostname());
runner.setAgentPort(CephGlobalProperty.PRIMARY_STORAGE_AGENT_PORT);
runner.setPlayBookName(CephGlobalProperty.PRIMARY_STORAGE_PLAYBOOK_NAME);
runner.putArgument("pkg_cephpagent", CephGlobalProperty.PRIMARY_STORAGE_PACKAGE_NAME);
runner.run(new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
}
flow(new NoRollbackFlow() {
String __name__ = "echo-agent";
@Override
public void run(final FlowTrigger trigger, Map data) {
restf.echo(String.format("http://%s:%s%s", getSelf().getHostname(),
CephGlobalProperty.PRIMARY_STORAGE_AGENT_PORT, ECHO_PATH), new Completion(trigger) {
@Override
public void success() {
trigger.next();
}
@Override
public void fail(ErrorCode errorCode) {
trigger.fail(errorCode);
}
});
}
});
done(new FlowDoneHandler(completion) {
@Override
public void handle(Map data) {
changeStatus(MonStatus.Connected);
completion.success();
}
});
error(new FlowErrorHandler(completion) {
@Override
public void handle(ErrorCode errCode, Map data) {
changeStatus(MonStatus.Disconnected);
completion.fail(errCode);
}
});
}
}).start();
}
private void httpCall(String path, AgentCmd cmd, final ReturnValueCompletion<AgentRsp> completion) {
httpCall(path, cmd, AgentRsp.class, completion);
}
private <T> void httpCall(String path, AgentCmd cmd, final Class<T> rspClass, final ReturnValueCompletion<T> completion) {
restf.asyncJsonPost(String.format("http://%s:%s%s", self.getHostname(), CephGlobalProperty.PRIMARY_STORAGE_AGENT_PORT, path),
cmd, new JsonAsyncRESTCallback<T>(completion) {
@Override
public void fail(ErrorCode err) {
completion.fail(err);
}
@Override
public void success(T ret) {
completion.success(ret);
}
@Override
public Class<T> getReturnClass() {
return rspClass;
}
});
}
@Override
public void ping(final ReturnValueCompletion<PingResult> completion) {
thdf.chainSubmit(new ChainTask(completion) {
@Override
public String getSyncSignature() {
return syncId;
}
@Override
public void run(final SyncTaskChain chain) {
doPing(new ReturnValueCompletion<PingResult>(completion) {
@Override
public void success(PingResult ret) {
completion.success(ret);
chain.next();
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
chain.next();
}
});
}
@Override
public String getName() {
return String.format("ping-ceph-primary-storage-%s", self.getUuid());
}
});
}
@Override
protected int getAgentPort() {
return CephGlobalProperty.PRIMARY_STORAGE_AGENT_PORT;
}
private void doPing(final ReturnValueCompletion<PingResult> completion) {
SimpleQuery<CephPrimaryStorageVO> q = dbf.createQuery(CephPrimaryStorageVO.class);
q.select(CephPrimaryStorageVO_.rootVolumePoolName);
q.add(CephPrimaryStorageVO_.uuid, Op.EQ, getSelf().getPrimaryStorageUuid());
String poolName = q.findValue();
PingCmd cmd = new PingCmd();
cmd.testImagePath = String.format("%s/%s-this-is-a-test-image-with-long-name", poolName, self.getUuid());
cmd.monUuid = getSelf().getUuid();
cmd.primaryStorageUuid = getSelf().getPrimaryStorageUuid();
cmd.monAddr = String.format("%s:%s", getSelf().getMonAddr(), getSelf().getMonPort());
httpCall(PING_PATH, cmd, PingRsp.class, new ReturnValueCompletion<PingRsp>(completion) {
@Override
public void success(PingRsp rsp) {
PingResult res = new PingResult();
if (rsp.success) {
res.success = true;
} else {
res.success = false;
res.error = rsp.error;
res.failure = rsp.failure.toString();
}
completion.success(res);
}
@Override
public void fail(ErrorCode errorCode) {
completion.fail(errorCode);
}
});
}
public CephPrimaryStorageMonBase(CephMonAO self) {
super(self);
syncId = String.format("ceph-primary-storage-mon-%s", self.getUuid());
}
}