package org.zstack.core.salt; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; 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.Platform; import org.zstack.core.errorcode.ErrorFacade; import org.zstack.core.job.Job; import org.zstack.core.job.JobContext; import org.zstack.header.core.ReturnValueCompletion; import org.zstack.utils.Utils; import org.zstack.utils.data.StringTemplate; import org.zstack.utils.logging.CLogger; import org.zstack.utils.path.PathUtil; import org.zstack.utils.ssh.Ssh; import org.zstack.utils.ssh.SshException; import org.zstack.utils.ssh.SshResult; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.util.HashMap; import java.util.Map; /** */ @Configurable(preConstruction = true, autowire = Autowire.BY_TYPE) public class SaltSetupMinionJob implements Job { private static final CLogger logger = Utils.getLogger(SaltSetupMinionJob.class); @JobContext private String targetIp; @JobContext private String username; @JobContext private String privateKey; @JobContext private String password; @JobContext private int port; @JobContext private String saltBootstrapScriptPath; @JobContext private String saltMinionConfPath; @JobContext private String minionId; @JobContext private boolean cleanMasterKey; @Autowired private ErrorFacade errf; private static final String SALT_BOOTSTRAP = "salt/salt-bootstrap.sh"; private File rewriteMinionConfFile(String minionId) throws IOException { File minionConfTmpt = new File(saltMinionConfPath); Map<String, String> map = new HashMap<String, String>(); map.put("managementNodeIp", Platform.getManagementServerIp()); map.put("minionId", minionId); String srcConf = FileUtils.readFileToString(minionConfTmpt); String conf = StringTemplate.substitute(srcConf, map); File minionConf = File.createTempFile("zstack-salt", "minion"); FileUtils.write(minionConf, conf); return minionConf; } @Override public void run(ReturnValueCompletion<Object> completion) { File tmpt = null; Ssh ssh = null; try { ssh = new Ssh().setHostname(targetIp).setPassword(password).setPrivateKey(privateKey) .setUsername(username).setPort(port); SshResult ret = ssh.checkTool("scp").run(); if (ret.getReturnCode() != 0) { completion.fail(errf.stringToOperationError(String.format("scp is not found on system[%s], unable to setup salt", targetIp))); return; } ret = ssh.reset().checkTool("salt-minion").run(); boolean hasMinion = ret.getReturnCode() == 0; if (!hasMinion) { String dstPath = String.format("/tmp/%s.sh", Platform.getUuid()); String srcPath = PathUtil.findFileOnClassPath(SALT_BOOTSTRAP, true).getAbsolutePath(); logger.debug(String.format("salt-minion is not found on system[%s], about to install a new one", targetIp)); ret = ssh.reset().scp(srcPath, dstPath).command(String.format("sh %s ; ret=$?; rm -f %s; exit $ret", dstPath, dstPath)).run(); ret.raiseExceptionIfFailed(); logger.debug(String.format("successfully installed salt-minion on system[%s]", targetIp)); } else { logger.debug(String.format("salt-minion is found on system[%s], no need to install new one", targetIp)); } tmpt = rewriteMinionConfFile(minionId); String minionConfPath = PathUtil.join(SaltConstant.SALT_CONF_HOME, SaltConstant.MINION_CONF_NAME); boolean deployMinion = false; ret = ssh.reset().command(String.format("md5sum %s", minionConfPath)).run(); if (ret.getReturnCode() != 0) { deployMinion = true; logger.debug(String.format("cannot get md5 of minion configuration file, need to re-setup minion")); } else { String dstMd5 = ret.getStdout().split(" ")[0].trim(); FileInputStream fis = new FileInputStream(tmpt); String srcMd5 = DigestUtils.md5Hex(fis); deployMinion = !srcMd5.equals(dstMd5); if (deployMinion) { logger.debug(String.format("MD5 of minion configuration file changed[%s --> %s], need to re-setup minion", dstMd5, srcMd5)); } else { logger.debug(String.format("MD5 of minion configuration file not changed, no needs to re-setup minion")); } } ssh.reset(); if (deployMinion) { if (cleanMasterKey) { ssh.command("rm -f /etc/salt/pki/minion/minion_master.pub"); } ret = ssh.scp(tmpt.getAbsolutePath(), minionConfPath) .command("service salt-minion restart").run(); } else { ret = ssh.command("service salt-minion status | grep -- 'running' || service salt-minion start").run(); } ret.raiseExceptionIfFailed(); logger.debug(String.format("successfully setup salt-minion on target system[%s]", targetIp)); completion.success(minionId); } catch (SshException e) { String err = String.format("failed to setup minion on target system[%s], because %s", targetIp, e.getMessage()); logger.warn(err, e); completion.fail(errf.throwableToOperationError(e)); } catch (IOException ie) { String err = String.format("failed to setup minion on target system[%s], because %s", targetIp, ie.getMessage()); logger.warn(err, ie); completion.fail(errf.throwableToInternalError(ie)); } finally { if (tmpt != null) { tmpt.delete(); } if (ssh != null) { ssh.close(); } } } public String getTargetIp() { return targetIp; } public void setTargetIp(String targetIp) { this.targetIp = targetIp; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPrivateKey() { return privateKey; } public void setPrivateKey(String privateKey) { this.privateKey = privateKey; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public int getPort() { return port; } public void setPort(int port) { this.port = port; } public String getSaltBootstrapScriptPath() { return saltBootstrapScriptPath; } public void setSaltBootstrapScriptPath(String saltBootstrapScriptPath) { this.saltBootstrapScriptPath = saltBootstrapScriptPath; } public String getSaltMinionConfPath() { return saltMinionConfPath; } public void setSaltMinionConfPath(String saltMinionConfPath) { this.saltMinionConfPath = saltMinionConfPath; } public String getMinionId() { return minionId; } public void setMinionId(String minionId) { this.minionId = minionId; } public boolean isCleanMasterKey() { return cleanMasterKey; } public void setCleanMasterKey(boolean cleanMasterKey) { this.cleanMasterKey = cleanMasterKey; } }