/* * 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.process.puppet; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import jp.primecloud.auto.common.component.FreeMarkerGenerator; import jp.primecloud.auto.common.component.PasswordEncryptor; import jp.primecloud.auto.common.constant.PCCConstant; import jp.primecloud.auto.common.log.LoggingUtils; import jp.primecloud.auto.common.status.InstanceCoodinateStatus; import jp.primecloud.auto.entity.crud.Farm; import jp.primecloud.auto.entity.crud.Image; import jp.primecloud.auto.entity.crud.Instance; import jp.primecloud.auto.entity.crud.PccSystemInfo; import jp.primecloud.auto.entity.crud.Platform; import jp.primecloud.auto.entity.crud.PlatformAws; import jp.primecloud.auto.entity.crud.PuppetInstance; import jp.primecloud.auto.entity.crud.User; import jp.primecloud.auto.exception.AutoException; import jp.primecloud.auto.exception.MultiCauseException; import jp.primecloud.auto.log.EventLogger; import jp.primecloud.auto.process.InstancesProcessContext; import jp.primecloud.auto.process.ProcessLogger; import jp.primecloud.auto.puppet.PuppetClient; import jp.primecloud.auto.service.ServiceSupport; import jp.primecloud.auto.util.MessageUtils; /** * <p> * puppetrunを実行するための情報収集と制御を行う * </p> * */ public class PuppetNodesProcess extends ServiceSupport { protected File manifestDir; protected FreeMarkerGenerator freeMarkerGenerator; protected PuppetClient puppetClient; protected ExecutorService executorService; protected ProcessLogger processLogger; protected EventLogger eventLogger; public void configureNodes(InstancesProcessContext context) { // 処理対象のインスタンスがない場合はスキップ if ((context.getStartInstanceNos() == null || context.getStartInstanceNos().isEmpty()) && (context.getStopInstanceNos() == null || context.getStopInstanceNos().isEmpty())) { return; } configureInstances(context); } protected void configureInstances(final InstancesProcessContext context) { List<Long> startInstanceNos = context.getStartInstanceNos(); if (startInstanceNos == null) { startInstanceNos = new ArrayList<Long>(); } List<Long> stopInstanceNos = context.getStopInstanceNos(); if (stopInstanceNos == null) { stopInstanceNos = new ArrayList<Long>(); } List<Long> targetInstanceNos = new ArrayList<Long>(); targetInstanceNos.addAll(startInstanceNos); targetInstanceNos.addAll(stopInstanceNos); // Puppet設定対象のものを取得 List<PuppetInstance> puppetInstances = puppetInstanceDao.readInInstanceNos(targetInstanceNos); List<Long> puppetInstanceNos = new ArrayList<Long>(); for (PuppetInstance puppetInstance : puppetInstances) { puppetInstanceNos.add(puppetInstance.getInstanceNo()); } // ステータスを変更 List<Instance> instances = instanceDao.readInInstanceNos(targetInstanceNos); for (Instance instance : instances) { if (puppetInstanceNos.contains(instance.getInstanceNo())) { InstanceCoodinateStatus status; if (startInstanceNos.contains(instance.getInstanceNo())) { status = InstanceCoodinateStatus.COODINATING; } else { status = InstanceCoodinateStatus.UN_COODINATING; } //instance.setStatus(InstanceStatus.CONFIGURING.toString()); instance.setCoodinateStatus(status.toString()); instanceDao.update(instance); } } // Puppet設定対象のものが無ければ終了 if (puppetInstanceNos.isEmpty()) { return; } // マニフェスト用情報モデルの作成 final Map<String, Object> rootMap = createInstancesMap(context); // 並列実行 List<Callable<Void>> callables = new ArrayList<Callable<Void>>(); final Map<String, Object> loggingContext = LoggingUtils.getContext(); for (final Long instanceNo : puppetInstanceNos) { final boolean start = startInstanceNos.contains(instanceNo) ? true : false; Callable<Void> callable = new Callable<Void>() { @Override public Void call() throws Exception { LoggingUtils.setContext(loggingContext); try { doConfigureInstance(instanceNo, context, start, rootMap); } catch (Exception e) { log.error(e.getMessage(), e); // イベントログ出力 eventLogger.error("SystemError", new Object[] { e.getMessage() }); throw e; } finally { LoggingUtils.removeContext(); } return null; } }; callables.add(callable); } try { List<Future<Void>> futures = executorService.invokeAll(callables); // 並列実行で例外発生時の処理 List<Throwable> throwables = new ArrayList<Throwable>(); for (Future<Void> future : futures) { try { future.get(); } catch (ExecutionException e) { throwables.add(e.getCause()); } catch (InterruptedException ignore) { } } // 例外を処理する if (throwables.size() > 0) { throw new MultiCauseException(throwables.toArray(new Throwable[throwables.size()])); } } catch (InterruptedException e) { } } protected Map<String, Object> createInstancesMap(InstancesProcessContext context) { Map<String, Object> map = new HashMap<String, Object>(); // Farm Farm farm = farmDao.read(context.getFarmNo()); map.put("farm", farm); // User User user = userDao.read(farm.getUserNo()); PccSystemInfo pccSystemInfo = pccSystemInfoDao.read(); PasswordEncryptor encryptor = new PasswordEncryptor(); user.setPassword(encryptor.decrypt(user.getPassword(), pccSystemInfo.getSecretKey())); map.put("user", user); // Instances List<Instance> startInstances; if (context.getStartInstanceNos() == null || context.getStartInstanceNos().isEmpty()) { startInstances = new ArrayList<Instance>(); } else { startInstances = instanceDao.readInInstanceNos(context.getStartInstanceNos()); } map.put("startInstances", startInstances); List<Instance> stopInstances; if (context.getStopInstanceNos() == null || context.getStopInstanceNos().isEmpty()) { stopInstances = new ArrayList<Instance>(); } else { stopInstances = instanceDao.readInInstanceNos(context.getStopInstanceNos()); } map.put("stopInstances", stopInstances); return map; } private void doConfigureInstance(Long instanceNo, InstancesProcessContext context, boolean start, Map<String, Object> rootMap) { Instance instance = instanceDao.read(instanceNo); // ログ用情報を格納 LoggingUtils.setInstanceNo(instanceNo); LoggingUtils.setInstanceName(instance.getInstanceName()); LoggingUtils.setInstanceType(processLogger.getInstanceType(instanceNo, instance.getPlatformNo())); LoggingUtils.setPlatformNo(instance.getPlatformNo()); // マニフェストファイルのリストア restoreManifest(instanceNo); try { configureInstance(instanceNo, context, start, rootMap); } catch (RuntimeException e) { if (start) { // ステータス更新 instance = instanceDao.read(instanceNo); //instance.setStatus(InstanceStatus.WARNING.toString()); instance.setCoodinateStatus(InstanceCoodinateStatus.WARNING.toString()); instanceDao.update(instance); throw e; } else { // 処理に失敗した場合、警告ログを出力する log.warn(e.getMessage()); } } finally { if (start) { // マニフェストファイルのバックアップ backupManifest(instanceNo); } else { // コンポーネントのマニフェスト削除 deleteManifest(instanceNo); } } // ステータスを変更 InstanceCoodinateStatus status = start ? InstanceCoodinateStatus.COODINATED : InstanceCoodinateStatus.UN_COODINATED; instance = instanceDao.read(instanceNo); //instance.setStatus(InstanceStatus.RUNNING.toString()); instance.setCoodinateStatus(status.toString()); instanceDao.update(instance); } protected void configureInstance(Long instanceNo, InstancesProcessContext context, boolean start, Map<String, Object> rootMap) { Instance instance = instanceDao.read(instanceNo); // マニフェスト用情報モデルにインスタンス情報固有を追加 rootMap = createInstanceMap(instanceNo, context, start, rootMap); // マニフェストの出力 File manifestName = new File(manifestDir, instance.getFqdn() + ".base_coordinate.pp"); // 既にあるマニフェストファイルのダイジェストを求める String digest = getFileDigest(manifestName, "UTF-8"); // 協調停止時でマニフェストが存在しない場合、スキップする if (digest == null && !start) { return; } // マニフェストの生成 String templateName = "base_coordinate.ftl"; generateManifest(templateName, rootMap, manifestName, "UTF-8"); // 協調設定がされてない場合以外で、マニフェストが変更されていない場合はスキップする InstanceCoodinateStatus status = InstanceCoodinateStatus.fromStatus(instance.getCoodinateStatus()); if (status != InstanceCoodinateStatus.UN_COODINATED) { // 既にあるマニフェストファイルのダイジェストと、生成したマニフェストファイルのダイジェストと比較する if (digest != null) { String newDigest = getFileDigest(manifestName, "UTF-8"); if (digest.equals(newDigest)) { // マニフェストファイルに変更がない場合 if (log.isDebugEnabled()) { log.debug(MessageUtils.format("Not changed manifest.(file={0})", manifestName.getName())); } return; } } } // Puppetクライアントの設定更新指示 runPuppet(instance); } protected void runPuppet(Instance instance) { Image image = imageDao.read(instance.getImageNo()); // Puppetクライアントの設定更新処理を実行 try { processLogger.debug(null, instance, "PuppetManifestApply", new String[] { instance.getFqdn(), "base_coordinate" }); puppetClient.runClient(instance.getFqdn()); if (StringUtils.startsWithIgnoreCase(image.getOs(), PCCConstant.OS_NAME_WIN)) { // TODO 協調設定が反映されない不具合対応 // 1回の「puppet run」だと協調設定が空振りする事があるので、同じマニフェストの内容で2回実行する。 // Linux系OSに関しては、puppetの「postrun_command」で対応可能なので実行は1回のみ log.debug(MessageUtils.format("run the puppet process(base_coordinate) twice for windows instance. (fqdn={0})", instance.getFqdn())); puppetClient.runClient(instance.getFqdn()); } } catch (RuntimeException e) { processLogger.debug(null, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), "base_coordinate" }); // マニフェスト適用に失敗した場合、警告ログ出力した後にリトライする String code = (e instanceof AutoException) ? AutoException.class.cast(e).getCode() : null; if ("EPUPPET-000003".equals(code) || "EPUPPET-000007".equals(code)) { log.warn(e.getMessage()); processLogger.debug(null, instance, "PuppetManifestApply", new String[] { instance.getFqdn(), "base_coordinate" }); try { puppetClient.runClient(instance.getFqdn()); } catch (RuntimeException e2) { processLogger.debug(null, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), "base_coordinate" }); throw e2; } } else { throw e; } } processLogger.debug(null, instance, "PuppetManifestApplyFinish", new String[] { instance.getFqdn(), "base_coordinate" }); } @SuppressWarnings("unchecked") protected Map<String, Object> createInstanceMap(Long instanceNo, InstancesProcessContext context, boolean start, Map<String, Object> rootMap) { Map<String, Object> map = new HashMap<String, Object>(rootMap); // start map.put("start", start); // Instance Instance instance = instanceDao.read(instanceNo); map.put("instance", instance); // Platform Platform platform = platformDao.read(instance.getPlatformNo()); map.put("platform", platform); // アクセスIP List<Instance> startInstances = (List<Instance>) rootMap.get("startInstances"); Map<String, String> accessIps = new HashMap<String, String>(); for (Instance startInstance : startInstances) { // 基本はpublicIpでアクセスする String accessIp = startInstance.getPublicIp(); if (instance.getPlatformNo().equals(startInstance.getPlatformNo())) { // 同一のプラットフォームの場合 // TODO CLOUD BRANCHING if (PCCConstant.PLATFORM_TYPE_AWS.equals(platform.getPlatformType())) { PlatformAws platformAws = platformAwsDao.read(startInstance.getPlatformNo()); if (platformAws.getVpc() == false) { // VPCを使用しない場合はprivateIpでアクセスする accessIp = startInstance.getPrivateIp(); } } else if (PCCConstant.PLATFORM_TYPE_CLOUDSTACK.equals(platform.getPlatformType())) { // Cloudstackプラットフォームの場合はpublicIpでアクセスする accessIp = startInstance.getPublicIp(); } else if (PCCConstant.PLATFORM_TYPE_VMWARE.equals(platform.getPlatformType())) { // VMwareプラットフォームの場合はprivateIpでアクセスする accessIp = startInstance.getPrivateIp(); } else if (PCCConstant.PLATFORM_TYPE_NIFTY.equals(platform.getPlatformType())) { // ニフティクラウドプラットフォームの場合はprivateIpでアクセスする accessIp = startInstance.getPrivateIp(); } else if (PCCConstant.PLATFORM_TYPE_VCLOUD.equals(platform.getPlatformType())) { // VCloudプラットフォームの場合はprivateIpでアクセスする accessIp = startInstance.getPrivateIp(); } else if (PCCConstant.PLATFORM_TYPE_AZURE.equals(platform.getPlatformType())) { // Azureプラットフォームの場合はpublicIpでアクセスする accessIp = startInstance.getPublicIp(); } else if (PCCConstant.PLATFORM_TYPE_OPENSTACK.equals(platform.getPlatformType())) { // Openstackプラットフォームの場合はpublicIpでアクセスする accessIp = startInstance.getPublicIp(); } } accessIps.put(startInstance.getInstanceNo().toString(), accessIp); } map.put("accessIps", accessIps); return map; } protected String getFileDigest(File file, String encoding) { if (!file.exists()) { return null; } try { String content = FileUtils.readFileToString(file, encoding); return DigestUtils.shaHex(content); } catch (Exception e) { throw new RuntimeException(e); } } protected void generateManifest(String templateName, Map<String, Object> rootMap, File file, String encoding) { String data = freeMarkerGenerator.generate(templateName, rootMap); // 改行コードの変換(PuppetはLFでないと読み込めないことへの対応) data = data.replaceAll("\r\n", "\n"); // 出力先ディレクトリの作成 if (!file.getParentFile().exists()) { file.getParentFile().mkdirs(); } // ファイル出力 try { FileUtils.writeStringToFile(file, data, encoding); } catch (IOException e) { throw new RuntimeException(e); } } protected void restoreManifest(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); File manifestFile = new File(manifestDir, instance.getFqdn() + ".base_coordinate.pp"); // マニフェストファイルのリストア File backupDir = new File(manifestDir, "backup"); File backupFile = new File(backupDir, manifestFile.getName()); if (!backupFile.exists()) { return; } try { if (manifestFile.exists()) { FileUtils.forceDelete(manifestFile); } FileUtils.moveFile(backupFile, manifestFile); } catch (IOException e) { // マニフェストファイルのリストア失敗時 log.warn(e.getMessage()); } } protected void backupManifest(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); File manifestFile = new File(manifestDir, instance.getFqdn() + ".base_coordinate.pp"); if (!manifestFile.exists()) { return; } // マニフェストファイルのバックアップ File backupDir = new File(manifestDir, "backup"); File backupFile = new File(backupDir, manifestFile.getName()); try { if (!backupDir.exists()) { backupDir.mkdir(); } if (backupFile.exists()) { FileUtils.forceDelete(backupFile); } FileUtils.moveFile(manifestFile, backupFile); } catch (IOException e) { // マニフェストファイルのバックアップ失敗時 log.warn(e.getMessage()); } } protected void deleteManifest(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); // マニフェストファイルを削除する File file = new File(manifestDir, instance.getFqdn() + ".base_coordinate.pp"); if (!file.exists()) { return; } try { FileUtils.forceDelete(file); } catch (IOException e) { // マニフェストファイルの削除失敗時 log.warn(e.getMessage()); } } /** * manifestDirを設定します。 * * @param manifestDir manifestDir */ public void setManifestDir(File manifestDir) { this.manifestDir = manifestDir; } /** * freeMarkerGeneratorを設定します。 * * @param freeMarkerGenerator freeMarkerGenerator */ public void setFreeMarkerGenerator(FreeMarkerGenerator freeMarkerGenerator) { this.freeMarkerGenerator = freeMarkerGenerator; } /** * puppetClientを設定します。 * * @param puppetClient puppetClient */ public void setPuppetClient(PuppetClient puppetClient) { this.puppetClient = puppetClient; } /** * executorServiceを設定します。 * * @param executorService executorService */ public void setExecutorService(ExecutorService executorService) { this.executorService = executorService; } /** * eventLoggerを設定します。 * * @param eventLogger eventLogger */ public void setEventLogger(EventLogger eventLogger) { this.eventLogger = eventLogger; } /** * processLoggerを設定します。 * * @param processLogger processLogger */ public void setProcessLogger(ProcessLogger processLogger) { this.processLogger = processLogger; } }