package org.zstack.core.puppet; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.DirectoryWalker; import org.apache.commons.io.FileUtils; import org.springframework.beans.factory.annotation.Autowired; import org.zstack.core.Platform; import org.zstack.core.cloudbus.CloudBus; import org.zstack.core.config.GlobalConfigFacade; import org.zstack.core.job.JobQueueFacade; import org.zstack.core.puppet.PuppetConstant.PuppetGlobalConfig; import org.zstack.core.thread.SyncTask; import org.zstack.core.thread.ThreadFacade; import org.zstack.header.AbstractService; import org.zstack.header.exception.CloudRuntimeException; import org.zstack.header.message.Message; import org.zstack.utils.Utils; import org.zstack.utils.logging.CLogger; import org.zstack.utils.path.PathUtil; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.net.InetAddress; import java.net.URL; import java.net.UnknownHostException; import java.util.*; public class PuppetFacadeImpl extends AbstractService implements PuppetFacade { private static final CLogger logger = Utils.getLogger(PuppetFacadeImpl.class); private String puppetModulesHome = "/etc/puppet/modules"; private String puppetNodesHome = "/etc/puppet/manifests/nodes"; private String puppetSitepp = "/etc/puppet/manifests/site.pp"; private String puppetAutoSign = "/etc/puppet/autosign.conf"; private String puppetMasterCertName; private String COMMON_MODULE_PATH = String.format("%s/puppet/commonModules", Platform.COMPONENT_CLASSPATH_HOME); private String hostname; @Autowired private ThreadFacade thdf; @Autowired private CloudBus bus; @Autowired protected JobQueueFacade jobf; @Autowired protected GlobalConfigFacade gcf; ////////////////////////////// For unit test ////////////////////////////////// private boolean createPuppetFolder = true; /////////////////////////////////////////////////////////////////////////////// @SuppressWarnings("rawtypes") public class ModuleWalker extends DirectoryWalker { @Override protected void handleFile(File file, int depth, Collection results) { results.add(file); } public void doWalk(File start, Collection result) throws IOException { walk(start, result); } } private boolean isNeedToDeploy(String moduleName, String modulePath) throws IOException { String destModulePath = PathUtil.join(puppetModulesHome, moduleName); File dest = new File(destModulePath); if (!dest.exists()) { logger.debug(String.format("%s is not existing, need to deploy puppet module[%s]", destModulePath, moduleName)); return true; } List<File> destFiles = new ArrayList<File>(20); ModuleWalker walker = new ModuleWalker(); walker.doWalk(dest, destFiles); List<File> srcFiles = new ArrayList<File>(20); walker = new ModuleWalker(); File src = new File(modulePath); walker.doWalk(src, srcFiles); if (srcFiles.size() != destFiles.size()) { logger.debug(String.format("%s has %s files, %s has %s files, need to deploy puppet module[%s]", destModulePath, destFiles.size(), modulePath, srcFiles.size(), moduleName)); return true; } Map<String, String> srcMd5sum = new HashMap<String, String>(srcFiles.size()); for (File f : srcFiles) { FileInputStream fis = new FileInputStream(f); String md5 = DigestUtils.md5Hex(fis); srcMd5sum.put(f.getName(), md5); } Map<String, String> destMd5sum = new HashMap<String, String>(destFiles.size()); for (File f : destFiles) { FileInputStream fis = new FileInputStream(f); String md5 = DigestUtils.md5Hex(fis); destMd5sum.put(f.getName(), md5); } for (Map.Entry<String, String> srcEntry : srcMd5sum.entrySet()) { String name = srcEntry.getKey(); String destMd5 = destMd5sum.get(name); if (destMd5 == null) { logger.debug(String.format("%s is not existing in %s, need to deploy puppet module[%s]", name, destModulePath, moduleName)); return true; } if (!destMd5.equals(srcEntry.getValue())) { logger.debug(String.format("%s's md5 changed[{%s} in %s, {%s} in %s], need to deploy puppet module[%s]", name, destMd5, destModulePath, srcEntry.getValue(), modulePath, moduleName)); return true; } } logger.debug(String.format("no file changed in puppet module[%s], no need to deploy", moduleName)); return false; } public void setPuppetModulesHome(String puppetModulesHome) { this.puppetModulesHome = puppetModulesHome; } public void setPuppetMasterCertName(String puppetMasterCertName) { this.puppetMasterCertName = puppetMasterCertName; } private void deployModule(String modulePath) { File src = new File(modulePath); if (!src.isDirectory()) { throw new PuppetException(String.format("Cannot find puppet module[%s], it's either not existing or not a directory", modulePath)); } String moduleName = src.getName(); if (moduleName == null) { throw new PuppetException(String.format("Cannot get puppet module name from path[%s]", modulePath)); } try { if (!isNeedToDeploy(moduleName, modulePath)) { return; } String destModulePath = PathUtil.join(puppetModulesHome, moduleName); File dest = new File(destModulePath); if (dest.exists()) { FileUtils.forceDelete(dest); FileUtils.forceMkdir(dest); } FileUtils.copyDirectory(src, dest); } catch (Exception e) { String err = String.format("Unable to deploy puppet module[%s] from %s", moduleName, modulePath); throw new PuppetException(err, e); } } @Override public void deployModule(String nodeFileName, String nodeExpression, String modulePath) { deployModule(modulePath); File nodeFile = new File(PathUtil.join(puppetNodesHome, nodeFileName)); try { FileUtils.writeStringToFile(nodeFile, nodeExpression); } catch (IOException e) { String err = String.format("Unable to deploy puppet module from %s", modulePath); throw new PuppetException(err, e); } } public void pokePuppetAgent(final PuppetPokeAgentMsg msg) throws UnknownHostException { final String targetIp = InetAddress.getByName(msg.getHostname()).getHostAddress(); thdf.syncSubmit(new SyncTask<Void>() { @Override public String getName() { return "poke-puppet-agent"; } @Override public Void call() throws Exception { /* PuppetPokeAgentReply reply = new PuppetPokeAgentReply(); try { int port = 22; if (msg.getSshPort() != null) { port = msg.getSshPort(); } // get management server ip from SSH_CLIENT env variable after ssh log into target system, then mapping this ip to hostname 'puppet' in /etc/hosts String cmd = String.mediaType("ip=`env | grep SSH_CLIENT | cut -d '=' -f 2 | cut -d ' ' -f 1`; sed -i \"/%s/d\" /etc/hosts; echo \"$ip %s\" >> /etc/hosts", hostname, hostname); Ssh.run(msg.getHostname(), port, msg.getUsername(), msg.getPassword(), cmd); cmd = String.mediaType("puppet agent --certname %s --no-daemonize --onetime --waitforcert 60 --server %s --verbose --detailed-exitcodes", msg.getNodeName(), hostname); SshResult ret = Ssh.run2(msg.getHostname(), msg.getUsername(), msg.getPassword(), port, cmd); StringBuilder sb = new StringBuilder(ret.getCommandToExecute()).append("\n"); sb.append(String.mediaType("return code: %s", ret.getReturnCode())); sb.append(String.mediaType("stdout: %s\n", ret.getStdout())); sb.append(String.mediaType("stderr: %s\n", ret.getStderr())); sb.append(String.mediaType("exitErrorMessage: %s\n", ret.getExitErrorMessage())); if (ret.getReturnCode() == 4 || ret.getReturnCode() == 6 || ret.getReturnCode() == 1) { //ErrorCodeFacade.setErrorToMessageReply(PuppetErrorCodes.FAILS_TO_RUN_PUPPET_AGENT.toString(), sb.toString(), reply); } else { logger.debug(sb.toString()); } } catch (Exception e) { logger.warn(e.getMessage(), e); //ErrorCodeFacade.setErrorToMessageReply(PuppetErrorCodes.FAILS_TO_RUN_PUPPET_AGENT.toString(), e.getMessage(), reply); } bus.reply(msg, reply); */ return null; } @Override public String getSyncSignature() { return targetIp; } @Override public int getSyncLevel() { return 1; } }); } public void setPuppetNodesHome(String puppetNodesHome) { this.puppetNodesHome = puppetNodesHome; } private void deployCommonModules() { URL commonModulesUrl = this.getClass().getClassLoader().getResource(COMMON_MODULE_PATH); if (commonModulesUrl == null) { logger.warn(String.format("Cannot find %s in classpath, it should be at least an empty directory", COMMON_MODULE_PATH)); return; } File modules = new File(commonModulesUrl.getPath()); for (File f : modules.listFiles()) { if (f.isDirectory()) { deployModule(f.getAbsolutePath()); } } } private void preparePuppet() { File nodeHome = new File(puppetNodesHome); if (!nodeHome.exists()) { nodeHome.mkdirs(); } try { File sitepp = new File(puppetSitepp); if (!sitepp.exists()) { FileUtils.writeStringToFile(sitepp, "import 'nodes/*.pp'"); } File autoSign = new File(puppetAutoSign); if (!autoSign.exists()) { FileUtils.writeStringToFile(autoSign, "*"); } } catch (Exception e) { throw new CloudRuntimeException(e); } } @Override public boolean start() { try { hostname = java.net.InetAddress.getLocalHost().getHostName(); logger.debug(String.format("get hostname as %s", hostname)); } catch (Exception e) { throw new CloudRuntimeException("unable to get hostname of management server", e); } if (createPuppetFolder) { preparePuppet(); } bus.registerService(this); deployCommonModules(); return true; } @Override public boolean stop() { bus.unregisterService(this); return true; } @Override public void handleMessage(Message msg) { try { if (msg instanceof PuppetPokeAgentMsg) { handle((PuppetPokeAgentMsg) msg); } else { bus.dealWithUnknownMessage(msg); } } catch (Exception e) { bus.logExceptionWithMessageDump(msg, e); bus.replyErrorByMessageType(msg, e); } } private void handle(PuppetPokeAgentMsg msg) throws UnknownHostException { if (true) { pokePuppetAgent(msg); } else { PokePuppetAgentJob job = new PokePuppetAgentJob(msg, puppetMasterCertName); jobf.execute("puppet-" + msg.getHostname(), Platform.getManagementServerId(), job); } } @Override public String getId() { return bus.makeLocalServiceId(PuppetConstant.SERVICE_ID); } public void setCreatePuppetFolder(boolean createPuppetFolder) { this.createPuppetFolder = createPuppetFolder; } }