/* * Copyright 2014 by SCSK Corporation. * * This file is part of PrimeCloud Controller(TM). * * PrimeCloud Controller(TM) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 2 of the License, or * (at your option) any later version. * * PrimeCloud Controller(TM) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PrimeCloud Controller(TM). If not, see <http://www.gnu.org/licenses/>. */ package jp.primecloud.auto.puppet; import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.concurrent.Semaphore; import jp.primecloud.auto.exception.AutoException; import jp.primecloud.auto.puppet.report.MetricsResource; import jp.primecloud.auto.puppet.report.ReportAnalyzer; import jp.primecloud.auto.puppet.report.ReportLoader; import jp.primecloud.auto.util.CommandUtils; import jp.primecloud.auto.util.CommandUtils.CommandResult; import org.apache.commons.lang.builder.ReflectionToStringBuilder; import org.apache.commons.lang.builder.ToStringStyle; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * <p> * TODO: クラスコメントを記述 * </p> * */ public class PuppetClient { private static final Log log = LogFactory.getLog(PuppetClient.class); protected String siteFile = "/etc/puppet/manifests/site.pp"; protected Integer configTimeout; protected Integer delayTime = 10; protected File manifestDir; protected ReportLoader reportLoader; protected ReportAnalyzer reportAnalyzer; protected Semaphore runSemaphore = null; protected String puppetrunPath = "/usr/sbin/puppetrun"; protected String puppetcaPath = "/usr/sbin/puppetca"; public PuppetClient() { discover(); } public void discover() { List<File> searchDirs = new ArrayList<File>(); searchDirs.add(new File("/usr/sbin/")); searchDirs.add(new File("/usr/bin/")); // puppetrun for (File dir : searchDirs) { File file = new File(dir, "puppetrun"); if (file.exists()) { puppetrunPath = file.getAbsolutePath(); break; } } // puppetca for (File dir : searchDirs) { File file = new File(dir, "puppetca"); if (file.exists()) { puppetcaPath = file.getAbsolutePath(); break; } } } public void runClient(String fqdn) { // siteFileにtouchする touchFile(siteFile); // ノードのマニフェストにtouchする File manifestFile = new File(manifestDir, fqdn + ".pp"); touchFile(manifestFile.getAbsolutePath()); // キャッシュ対応ため一定時間待機する if (delayTime != null) { try { Thread.sleep(delayTime.intValue() * 1000); } catch (InterruptedException ignore) { } } // puppetrun実行前のレポートファイルの変更時刻を取得 String beforeReportFile = reportLoader.getLatestReportFile(fqdn); Long beforeLastModified = null; if (beforeReportFile != null) { beforeLastModified = reportLoader.getLastModified(fqdn, beforeReportFile); } List<String> commands = new ArrayList<String>(); commands.add("/usr/bin/sudo"); commands.add(puppetrunPath); commands.add("--host"); commands.add(fqdn); commands.add("--foreground"); // Puppetを同期的に実行する commands.add("--ping"); // puppetrunのタイムアウト時間の設定 if (configTimeout != null) { commands.add("--configtimeout=" + configTimeout.toString()); } log.debug(commands); CommandResult result = executeRun(commands); int retryCount = 0; while (result.getExitValue() != 0 && retryCount < 6) { // リトライを行うかどうかのチェック boolean retry = false; for (String stdout : result.getStdouts()) { log.warn(stdout); if (stdout.contains("Certificates were not trusted: ")) { retry = true; break; } else if (stdout.contains("Connection reset by peer")) { retry = true; break; } else if (stdout.contains("Could not contact")) { retry = true; break; } } if (!retry) { // リトライしない場合 break; } // 警告ログとして出力 log.warn(ReflectionToStringBuilder.toString(result, ToStringStyle.SHORT_PREFIX_STYLE)); // 一定時間待機後に再実行 try { Thread.sleep(5 * 1000); } catch (InterruptedException ignore) { } result = executeRun(commands); retryCount++; } if (result.getExitValue() != 0) { // puppetrunの実行に失敗 AutoException exception; if (result.getStdouts().size() > 0 && result.getStdouts().get(0).contains("Could not contact")) { // 対象のホストにpingが届かない場合(ホストの異常終了時など) exception = new AutoException("EPUPPET-000002", fqdn); } else if (result.getStdouts().size() > 0 && result.getStdouts().get(0).contains("Could not connect")) { // パペットマスターの滞留により排他制御に不具合が生じたい場合、少し待って再実行 try { Thread.sleep(100000); } catch (InterruptedException ignore) { } //マニュフェスト不正 exception = new AutoException("EPUPPET-000007", fqdn); } else if (result.getStdouts().size() > 0 && result.getStdouts().get(0).contains("returned unknown answer")) { // パペットマスターの滞留により排他制御に不具合が生じたい場合、少し待って再実行(強調設定版) try { Thread.sleep(100000); } catch (InterruptedException ignore) { } //マニュフェスト不正 exception = new AutoException("EPUPPET-000007", fqdn); } else { exception = new AutoException("EPUPPET-000001", fqdn); } exception.addDetailInfo("result=" + ReflectionToStringBuilder.toString(result, ToStringStyle.SHORT_PREFIX_STYLE)); throw exception; } // レポートファイルのチェック String reportFile = null; for (int i = 0; i < 30; i++) { try { Thread.sleep(1 * 1000); } catch (InterruptedException ignore) { } String tmpReportFile = reportLoader.getLatestReportFile(fqdn); if (tmpReportFile != null) { if (beforeLastModified == null) { reportFile = tmpReportFile; break; } else { Long lastModified = reportLoader.getLastModified(fqdn, tmpReportFile); if (lastModified.longValue() != beforeLastModified.longValue()) { reportFile = tmpReportFile; break; } } } } if (reportFile != null) { // レポートファイルが出力された場合 Map<String, Object> report = reportLoader.loadReport(fqdn, reportFile); String status = reportAnalyzer.getStatus(report); if (status.equals("failed")) { // レポートファイルにマニフェスト適用失敗が記録されている場合(マニフェストコンパイルエラー) throw new AutoException("EPUPPET-000003", fqdn, reportFile); } List<MetricsResource> metricsResources = reportAnalyzer.getMetricsResources(report); for (MetricsResource metricsResource : metricsResources) { if (metricsResource.getName().startsWith("Failed") && metricsResource.getCount() > 0) { // レポートファイルにマニフェスト適用失敗が記録されている場合 throw new AutoException("EPUPPET-000003", fqdn, reportFile); } } } else { // レポートファイルが出力されていない場合 throw new AutoException("EPUPPET-000007", fqdn); } } protected CommandResult executeRun(List<String> commands) { if (runSemaphore == null) { return execute(commands); } //同時開始しないようにずらす(安全策) PuppetScheduler ps = PuppetScheduler.getInstance(); ps.doStop(); // パーミットを取得 try { runSemaphore.acquire(); } catch (InterruptedException e) { throw new RuntimeException(e); } try { return execute(commands); } finally { // パーミットを解放 runSemaphore.release(); } } public List<String> listClients() { List<String> commands = new ArrayList<String>(); commands.add("/usr/bin/sudo"); commands.add(puppetcaPath); commands.add("-la"); CommandResult result = execute(commands); List<String> clients = new ArrayList<String>(); for (String stdout : result.getStdouts()) { if (stdout.startsWith("+ ")) { String host = stdout.substring(2); int index = host.indexOf(" "); if (index != -1) { host = host.substring(0, index); } clients.add(host); } } return clients; } public void clearCa(String fqdn) { List<String> commands = new ArrayList<String>(); commands.add("/usr/bin/sudo"); commands.add(puppetcaPath); commands.add("-c"); commands.add(fqdn); execute(commands); // 実行結果のチェックはしない } protected void touchFile(String file) { List<String> commands = new ArrayList<String>(); commands.add("/bin/touch"); commands.add(file); CommandResult result = execute(commands); if (result.getExitValue() != 0) { // ファイルのtouchに失敗 AutoException exception = new AutoException("EPUPPET-000004", file); exception.addDetailInfo("result=" + ReflectionToStringBuilder.toString(result, ToStringStyle.SHORT_PREFIX_STYLE)); throw exception; } } protected CommandResult execute(List<String> commands) { if (log.isDebugEnabled()) { log.debug(commands); } // タイムアウトしないようにする long timeout = Long.MAX_VALUE; CommandResult result = CommandUtils.execute(commands, timeout); if (log.isDebugEnabled()) { log.debug(ReflectionToStringBuilder.toString(result)); } return result; } /** * siteFileを設定します。 * * @param siteFile siteFile */ public void setSiteFile(String siteFile) { this.siteFile = siteFile; } /** * configTimeoutを設定します。 * * @param configTimeout configTimeout */ public void setConfigTimeout(Integer configTimeout) { this.configTimeout = configTimeout; } /** * delayTimeを設定します。 * * @param delayTime delayTime */ public void setDelayTime(Integer delayTime) { this.delayTime = delayTime; } /** * manifestDirを設定します。 * * @param manifestDir manifestDir */ public void setManifestDir(File manifestDir) { this.manifestDir = manifestDir; } /** * reportLoaderを設定します。 * * @param reportLoader reportLoader */ public void setReportLoader(ReportLoader reportLoader) { this.reportLoader = reportLoader; } /** * reportAnalyzerを設定します。 * * @param reportAnalyzer reportAnalyzer */ public void setReportAnalyzer(ReportAnalyzer reportAnalyzer) { this.reportAnalyzer = reportAnalyzer; } /** * runPermitsを設定します。 * * @param runPermits runPermits */ public void setRunPermits(Integer runPermits) { this.runSemaphore = runPermits == null ? null : new Semaphore(runPermits.intValue()); } /** * puppetrunPathを設定します。 * * @param puppetrunPath puppetrunPath */ public void setPuppetrunPath(String puppetrunPath) { this.puppetrunPath = puppetrunPath; } /** * puppetcaPathを設定します。 * * @param puppetcaPath puppetcaPath */ public void setPuppetcaPath(String puppetcaPath) { this.puppetcaPath = puppetcaPath; } }