package org.zstack.test.multinodes;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.zstack.core.cloudbus.CloudBus;
import org.zstack.core.thread.AsyncThread;
import org.zstack.header.errorcode.SysErrors;
import org.zstack.header.exception.CloudRuntimeException;
import org.zstack.header.message.MessageReply;
import org.zstack.utils.Utils;
import org.zstack.utils.logging.CLogger;
import org.zstack.utils.path.PathUtil;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import static org.zstack.utils.CollectionDSL.e;
import static org.zstack.utils.CollectionDSL.map;
import static org.zstack.utils.StringDSL.s;
/**
*/
@Configurable(preConstruction = true, autowire = Autowire.BY_TYPE)
public class NodeRunner {
private static final CLogger logger = Utils.getLogger(NodeRunner.class);
private Process process;
@Autowired
private CloudBus bus;
private String serviceId;
private String deployConf;
private List<String> springConfigs;
private Integer port;
private Boolean deployDB;
private String propertyString;
private int uuid;
private boolean loadAll;
private String managementNodeId;
private volatile boolean isRunning = false;
public String getServiceId() {
return serviceId;
}
public void setServiceId(String serviceId) {
this.serviceId = serviceId;
}
public String getDeployConf() {
return deployConf;
}
public void setDeployConf(String deployConf) {
this.deployConf = deployConf;
}
public List<String> getSpringConfigs() {
return springConfigs;
}
public void setSpringConfigs(List<String> springConfigs) {
this.springConfigs = springConfigs;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public Boolean getDeployDB() {
return deployDB;
}
public void setDeployDB(Boolean deployDB) {
this.deployDB = deployDB;
}
public String getManagementNodeId() {
return managementNodeId;
}
private String createConfFromTemplate(String tmptName, String confFmt, Map<String, String> tokens) {
File tmpt = PathUtil.findFileOnClassPath(tmptName);
String conf = String.format(confFmt, serviceId);
try {
String tmptContent = FileUtils.readFileToString(tmpt);
tmptContent = s(tmptContent).formatByMap(tokens);
FileUtils.writeStringToFile(new File(PathUtil.join(tmpt.getParentFile().getAbsolutePath(), conf)), tmptContent);
return conf;
} catch (IOException e) {
throw new CloudRuntimeException(e);
}
}
@Override
public String toString() {
assert port != null;
assert serviceId != null;
String logname = String.format("management-%s.log", serviceId);
String logconf = createConfFromTemplate("log4j-tmpt.xml", "log4j2-%s", map(e("managementLogName", logname)));
String quartzJdbcConf = createConfFromTemplate("zstack-jdbc-quartz-tmpt.properties", "zstack-jdbc-quartz-%s.properties", map(e("instanceName", serviceId)));
StringBuilder sb = new StringBuilder();
sb.append(String.format("-Dport=%s ", port));
sb.append(String.format("-DserviceId=%s ", serviceId));
sb.append(String.format("-Dlog4j.configurationFile=%s ", logconf));
sb.append(String.format("-DquartzJdbc=%s ", quartzJdbcConf));
sb.append(String.format("-DquartzJdbc=%s ", quartzJdbcConf));
if (loadAll) {
sb.append(String.format("-DloadAll=true "));
}
if (deployDB != null) {
sb.append(String.format("-DdeployDB=%s ", deployDB));
}
if (deployConf != null) {
sb.append(String.format("-DdeployConfig=%s ", deployConf));
}
if (springConfigs != null) {
String sconf = StringUtils.join(springConfigs, ",");
sb.append(String.format("-DspringConfigs=%s ", sconf));
}
if (propertyString != null) {
sb.append(propertyString);
}
return sb.toString();
}
public boolean waitNodeStart(int timeout) {
isRunning = true;
assert serviceId != null;
MultiNodeTestMsg msg = new MultiNodeTestMsg();
msg.setServiceId(serviceId);
msg.setOpCode(msg.READY);
msg.setTimeout(TimeUnit.SECONDS.toMillis(1));
long endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(timeout);
do {
MessageReply r = bus.call(msg);
if (!r.isSuccess()) {
if (!r.getError().getCode().equals(SysErrors.NO_ROUTE_ERROR.toString()) &&
!r.getError().getCode().equals(SysErrors.TIMEOUT.toString())) {
throw new CloudRuntimeException(String.format("node[%s] fail to start, %s", serviceId, r.getError()));
}
} else {
MultiNodeTestReply mr = (MultiNodeTestReply) r;
managementNodeId = mr.getManagementNodeId();
break;
}
if (System.currentTimeMillis() > endTime) {
logger.debug(String.format("node[%s] fail to start, timeout after %s seconds", serviceId, timeout));
return false;
}
logger.debug(String.format("waiting for node[%s] start ...", serviceId));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
} while (true);
return true;
}
public boolean waitNodeExit(int timeout) {
if (!isRunning) {
return true;
}
assert serviceId != null;
MultiNodeTestMsg msg = new MultiNodeTestMsg();
msg.setServiceId(serviceId);
msg.setOpCode(msg.EXIT);
msg.setTimeout(TimeUnit.SECONDS.toMillis(60));
long endTime = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(timeout);
do {
MessageReply r = bus.call(msg);
if (!r.isSuccess()) {
if (r.getError().getCode().equals(SysErrors.NO_ROUTE_ERROR.toString())) {
break;
} else {
logger.warn(String.format("node[%s] fail to exit, %s", serviceId, r.getError()));
return false;
}
}
if (System.currentTimeMillis() > endTime) {
logger.debug(String.format("node[%s] fail to exit, timeout after %s seconds", serviceId, timeout));
return false;
}
logger.debug(String.format("waiting for node[%s] exit ...", serviceId));
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
} while (true);
logger.debug(String.format("node[%s] exits successfully", serviceId));
isRunning = false;
return true;
}
public boolean isLoadAll() {
return loadAll;
}
public void setLoadAll(boolean loadAll) {
this.loadAll = loadAll;
}
public int getUuid() {
return uuid;
}
public void setUuid(int uuid) {
this.uuid = uuid;
}
public String getPropertyString() {
return propertyString;
}
public void setPropertyString(String propertyString) {
this.propertyString = propertyString;
}
public NodeRunner() {
}
@AsyncThread
public void run() {
String cmd = String.format("mvn test -Dtest=%s %s", ManagementNodeTester.class.getSimpleName(), toString());
ProcessBuilder pb = new ProcessBuilder(Arrays.asList("/bin/bash", "-c", cmd));
try {
String baseDir = System.getProperty("user.dir");
pb.directory(new File(baseDir));
process = pb.start();
logger.debug(String.format("run node by command[%s]", cmd));
process.waitFor();
} catch (Exception e) {
throw new CloudRuntimeException(e);
} finally {
if (process != null) {
process.destroy();
}
}
}
public boolean waitFor(int timeout) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
process.waitFor();
} catch (InterruptedException e) {
logger.warn(e.getMessage(), e);
}
}
});
t.run();
try {
t.join(TimeUnit.SECONDS.toMillis(timeout));
if (t.isAlive()) {
logger.warn(String.format("node[%s] is still running after %s seconds for quit, kill it", getServiceId(), timeout));
t.interrupt();
return false;
} else {
return true;
}
} catch (InterruptedException e) {
throw new CloudRuntimeException(e);
}
}
public void kill() {
assert process != null;
Process p = process;
process = null;
while (true) {
p.destroy();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
try {
p.exitValue();
return;
} catch (IllegalThreadStateException ie) {
logger.debug(String.format("node process[%s] is still running, kill it again", getServiceId()));
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
}
}
}
}