/* * 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.FilenameFilter; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import jp.primecloud.auto.common.component.FreeMarkerGenerator; import jp.primecloud.auto.common.component.PasswordEncryptor; import jp.primecloud.auto.common.component.PasswordGenerator; import jp.primecloud.auto.common.constant.PCCConstant; import jp.primecloud.auto.config.Config; import jp.primecloud.auto.entity.crud.AwsVolume; import jp.primecloud.auto.entity.crud.AzureDisk; import jp.primecloud.auto.entity.crud.CloudstackVolume; import jp.primecloud.auto.entity.crud.Component; import jp.primecloud.auto.entity.crud.ComponentInstance; import jp.primecloud.auto.entity.crud.ComponentType; import jp.primecloud.auto.entity.crud.Farm; import jp.primecloud.auto.entity.crud.Instance; import jp.primecloud.auto.entity.crud.OpenstackVolume; import jp.primecloud.auto.entity.crud.PccSystemInfo; import jp.primecloud.auto.entity.crud.Platform; import jp.primecloud.auto.entity.crud.PuppetInstance; import jp.primecloud.auto.entity.crud.User; import jp.primecloud.auto.entity.crud.VcloudDisk; import jp.primecloud.auto.entity.crud.VmwareDisk; import jp.primecloud.auto.exception.AutoException; import jp.primecloud.auto.process.ProcessLogger; import jp.primecloud.auto.puppet.PuppetClient; import jp.primecloud.auto.service.ServiceSupport; import jp.primecloud.auto.util.MessageUtils; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.apache.commons.lang.BooleanUtils; import org.apache.commons.lang.StringUtils; /** * <p> * TODO: クラスコメントを記述 * </p> * */ public class PuppetNodeProcess extends ServiceSupport { protected File manifestDir; protected FreeMarkerGenerator freeMarkerGenerator; protected PuppetClient puppetClient; protected PasswordGenerator passwordGenerator = new PasswordGenerator(); protected ProcessLogger processLogger; public void startNode(Long instanceNo) { PuppetInstance puppetInstance = puppetInstanceDao.read(instanceNo); if (puppetInstance == null) { // Puppet処理対象でない throw new AutoException("EPROCESS-000301", instanceNo); } Instance instance = instanceDao.read(instanceNo); if (log.isInfoEnabled()) { log.info(MessageUtils.getMessage("IPROCESS-100201", instanceNo, instance.getInstanceName())); } // イベントログ出力 processLogger.debug(null, instance, "InstanceBaseStart", null); // PuppetMaster用のパスワード発行 createPassword(instanceNo); // Puppetクライアントの起動チェック checkClient(instanceNo); // マニフェスト用情報モデルの作成 Map<String, Object> rootMap = createNodeMap(instanceNo, true); // ノード用マニフェストの生成 createNodeManifest(instanceNo, true, rootMap); // マニフェストのリストア restoreManifest(instanceNo); try { // マニフェスト生成と実行 configureInstance(instanceNo, true, rootMap); } finally { // マニフェストファイルのバックアップ backupManifest(instanceNo); } // イベントログ出力 processLogger.debug(null, instance, "InstanceBaseStartFinish", null); if (log.isInfoEnabled()) { log.info(MessageUtils.getMessage("IPROCESS-100202", instanceNo, instance.getInstanceName())); } } public void stopNode(Long instanceNo) { PuppetInstance puppetInstance = puppetInstanceDao.read(instanceNo); if (puppetInstance == null) { // Puppet処理対象でない throw new AutoException("EPROCESS-000301", instanceNo); } Instance instance = instanceDao.read(instanceNo); if (log.isInfoEnabled()) { log.info(MessageUtils.getMessage("IPROCESS-100203", instanceNo, instance.getInstanceName())); } // イベントログ出力 processLogger.debug(null, instance, "InstanceBaseStop", null); // マニフェスト用情報モデルの作成 Map<String, Object> rootMap = createNodeMap(instanceNo, false); // マニフェストのリストア restoreManifest(instanceNo); try { // マニフェスト生成と実行 configureInstance(instanceNo, false, rootMap); } finally { // マニフェストファイルの削除 deleteManifest(instanceNo); } // PuppetMaster用のパスワード削除 deletePassword(instanceNo); // Puppetクライアントの認証情報を削除 clearCa(instanceNo); // イベントログ出力 processLogger.debug(null, instance, "InstanceBaseStopFinish", null); if (log.isInfoEnabled()) { log.info(MessageUtils.getMessage("IPROCESS-100204", instanceNo, instance.getInstanceName())); } } protected void createNodeManifest(Long instanceNo, boolean start, Map<String, Object> rootMap) { Instance instance = instanceDao.read(instanceNo); // ノード用マニフェストの生成 File manifestFile = new File(manifestDir, instance.getFqdn() + ".pp"); generateManifest("node.ftl", rootMap, manifestFile, "UTF-8"); } protected void configureInstance(Long instanceNo, boolean start, Map<String, Object> rootMap) { Instance instance = instanceDao.read(instanceNo); File manifestFile = new File(manifestDir, instance.getFqdn() + ".base.pp"); // 既にあるマニフェストのダイジェスト取得 String digest = getFileDigest(manifestFile, "UTF-8"); // 停止時でマニフェストが存在しない場合、スキップする if (digest == null && !start) { return; } // 基本マニフェストの生成 generateManifest("base.ftl", rootMap, manifestFile, "UTF-8"); // 生成したマニフェストのダイジェストの比較 if (digest != null) { String newDigest = getFileDigest(manifestFile, "UTF-8"); if (digest.equals(newDigest)) { // マニフェストファイルに変更がない場合 if (log.isDebugEnabled()) { log.debug(MessageUtils.format("Not changed manifest.(file={0})", manifestFile.getName())); } return; } } // マニフェストに変更があった場合、Puppetクライアントの設定更新処理を実行 try { runPuppet(instance); } catch (RuntimeException e) { if (!start) { // puppetrunに失敗した場合、警告ログを出力する log.warn(e.getMessage()); } else { throw 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 runPuppet(Instance instance) { // Puppetクライアントの設定更新処理を実行 try { processLogger.debug(null, instance, "PuppetManifestApply", new String[] { instance.getFqdn(), "base" }); puppetClient.runClient(instance.getFqdn()); } catch (RuntimeException e) { processLogger.debug(null, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), "base" }); // マニフェスト適用に失敗した場合、警告ログ出力した後にリトライする 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" }); try { puppetClient.runClient(instance.getFqdn()); } catch (RuntimeException e2) { processLogger.debug(null, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), "base" }); throw e2; } } else { throw e; } } processLogger.debug(null, instance, "PuppetManifestApplyFinish", new String[] { instance.getFqdn(), "base" }); } protected Map<String, Object> createNodeMap(Long instanceNo, boolean start) { Map<String, Object> map = new HashMap<String, Object>(); map.put("start", start); // Instance Instance instance = instanceDao.read(instanceNo); map.put("instance", instance); // Farm Farm farm = farmDao.read(instance.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); // Component List<Component> components = componentDao.readByFarmNo(instance.getFarmNo()); map.put("components", components); Map<Long, Component> componentMap = new HashMap<Long, Component>(); for (Component component: components) { componentMap.put(component.getComponentNo(), component); } // ComponentType List<ComponentType> componentTypes = componentTypeDao.readAll(); Map<Long, ComponentType> componentTypeMap = new HashMap<Long, ComponentType>(); for (ComponentType componentType: componentTypes) { componentTypeMap.put(componentType.getComponentTypeNo(), componentType); } // PuppetInstance PuppetInstance puppetInstance = puppetInstanceDao.read(instanceNo); map.put("puppetInstance", puppetInstance); // Platform Platform platform = platformDao.read(instance.getPlatformNo()); map.put("platform", platform); // TODO CLOUD BRANCHING if (PCCConstant.PLATFORM_TYPE_AWS.equals(platform.getPlatformType())) { // AwsVolume List<AwsVolume> awsVolumes = awsVolumeDao.readByInstanceNo(instanceNo); map.put("awsVolumes", awsVolumes); } else if (PCCConstant.PLATFORM_TYPE_CLOUDSTACK.equals(platform.getPlatformType())) { // CloudStackVolume List<CloudstackVolume> cloudstackVolumes = cloudstackVolumeDao.readByInstanceNo(instanceNo); map.put("cloudstackVolumes", cloudstackVolumes); } else if (PCCConstant.PLATFORM_TYPE_VMWARE.equals(platform.getPlatformType())) { // VmwareDisk List<VmwareDisk> vmwareDisks = vmwareDiskDao.readByInstanceNo(instanceNo); map.put("vmwareDisks", vmwareDisks); } else if (PCCConstant.PLATFORM_TYPE_VCLOUD.equals(platform.getPlatformType())) { // VcloudDisk List<VcloudDisk> vcloudDisks = vcloudDiskDao.readByInstanceNo(instanceNo); map.put("vcloudDisks", vcloudDisks); } else if (PCCConstant.PLATFORM_TYPE_AZURE.equals(platform.getPlatformType())) { // AzureDisk List<AzureDisk> azureDisks = azureDiskDao.readByInstanceNo(instanceNo); map.put("azureDisks", azureDisks); } else if (PCCConstant.PLATFORM_TYPE_OPENSTACK.equals(platform.getPlatformType())) { // OpenstackVolume List<OpenstackVolume> osVolumes = openstackVolumeDao.readByInstanceNo(instanceNo); map.put("osVolumes", osVolumes); } // その他 map.put("zabbixServer", Config.getProperty("zabbix.server")); map.put("rsyslogServer", Config.getProperty("rsyslog.server")); // Zabbix待ち受けIP String zabbixListenIp = instance.getPublicIp(); if (PCCConstant.PLATFORM_TYPE_AWS.equals(platform.getPlatformType())) { if (BooleanUtils.isTrue(platform.getInternal())) { // 内部のAWSプラットフォームの場合はprivateIpで待ち受ける // この分岐に来るパターンは以下の場合 // Eucalyptus // 内部AWS // 通常のVPC(VPC+VPNでは無い) zabbixListenIp = instance.getPrivateIp(); } } map.put("zabbixListenIp", zabbixListenIp); // Zabbix ホスト名 // Zabbixのホストの「名前」を prefix + - + fqdn で設定する // prefix値はconfig.propertiesから取得 String zabbixHostname = instance.getFqdn(); if (StringUtils.isNotEmpty(Config.getProperty("zabbix.prefix"))) { zabbixHostname = Config.getProperty("zabbix.prefix") + "-" + instance.getFqdn(); } log.debug("zabbixHostname =" + zabbixHostname); map.put("zabbixHostname", zabbixHostname); // 関連するコンポーネント List<Component> associatedComponents = new ArrayList<Component>(); List<ComponentType> associatedComponentTypes = new ArrayList<ComponentType>(); List<ComponentInstance> componentInstances = componentInstanceDao.readByInstanceNo(instanceNo); for (ComponentInstance componentInstance : componentInstances) { // 無効な関連は除外 if (BooleanUtils.isNotTrue(componentInstance.getEnabled()) || BooleanUtils.isNotTrue(componentInstance.getAssociate())) { continue; } for (Component component : components) { if (component.getComponentNo().equals(componentInstance.getComponentNo())) { associatedComponents.add(component); ComponentType componentType = componentTypeDao.read(component.getComponentTypeNo()); associatedComponentTypes.add(componentType); } } } map.put("associatedComponents", associatedComponents); map.put("associatedComponentTypes", associatedComponentTypes); //コンポーネントタイプ名マップ(コンポーネント番号,コンポーネントタイプ名) Map<String, String> componentTypeNameMap = new HashMap<String, String>(); for (ComponentInstance componentInstance : componentInstances) { if (!componentTypeNameMap.containsKey(componentInstance.getComponentNo())) { Component component = componentMap.get(componentInstance.getComponentNo()); ComponentType componentType = componentTypeMap.get(component.getComponentTypeNo()); componentTypeNameMap.put(componentInstance.getComponentNo().toString(), componentType.getComponentTypeName()); } } map.put("componentTypeNameMap", componentTypeNameMap); return map; } protected void restoreManifest(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); File manifestFile = new File(manifestDir, instance.getFqdn() + ".base.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.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); // マニフェストファイルの取得 final String fqdn = instance.getFqdn(); File[] manifestFiles = manifestDir.listFiles(new FilenameFilter() { @Override public boolean accept(File dir, String name) { return name.startsWith(fqdn) && name.endsWith(".pp"); } }); // マニフェストファイルの削除 for (File manifestFile : manifestFiles) { try { if (manifestFile.exists()) { FileUtils.forceDelete(manifestFile); } } catch (IOException e) { // マニフェストファイルの削除失敗時 log.warn(e.getMessage()); } } } protected void createPassword(Long instanceNo) { PuppetInstance puppetInstance = puppetInstanceDao.read(instanceNo); // パスワードが設定済みの場合はスキップする if (StringUtils.isNotEmpty(puppetInstance.getPassword())) { return; } // パスワードを発行 String password = passwordGenerator.generate(50); // データベース更新 puppetInstance.setPassword(password); puppetInstanceDao.update(puppetInstance); } protected void deletePassword(Long instanceNo) { PuppetInstance puppetInstance = puppetInstanceDao.read(instanceNo); // パスワードが空の場合はスキップする if (StringUtils.isEmpty(puppetInstance.getPassword())) { return; } puppetInstance.setPassword(null); puppetInstanceDao.update(puppetInstance); } protected void checkClient(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); // Puppetクライアントが起動していることを確認する int retry = 20; for (int count = 0; count <= retry; count++) { List<String> clients = puppetClient.listClients(); if (clients.contains(instance.getFqdn())) { break; } if (count == retry) { throw new AutoException("EPROCESS-000302", instance.getFqdn()); } try { Thread.sleep(1000 * 15); } catch (InterruptedException ignore) { } } } public void clearCa(Long instanceNo) { Instance instance = instanceDao.read(instanceNo); puppetClient.clearCa(instance.getFqdn()); } 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); } } /** * 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; } /** * processLoggerを設定します。 * * @param processLogger processLogger */ public void setProcessLogger(ProcessLogger processLogger) { this.processLogger = processLogger; } }