/*
* 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.lb;
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 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.InstanceStatus;
import jp.primecloud.auto.entity.crud.Component;
import jp.primecloud.auto.entity.crud.ComponentConfig;
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.LoadBalancer;
import jp.primecloud.auto.entity.crud.LoadBalancerHealthCheck;
import jp.primecloud.auto.entity.crud.LoadBalancerInstance;
import jp.primecloud.auto.entity.crud.LoadBalancerListener;
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.User;
import jp.primecloud.auto.exception.AutoException;
import jp.primecloud.auto.exception.MultiCauseException;
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;
/**
* <p>
* TODO: クラスコメントを記述
* </p>
*
*/
public class PuppetLoadBalancerProcess extends ServiceSupport {
protected File manifestDir;
protected FreeMarkerGenerator freeMarkerGenerator;
protected PuppetClient puppetClient;
protected ExecutorService executorService;
protected ProcessLogger processLogger;
public void configure(final Long loadBalancerNo, final Long componentNo, List<Long> instanceNos) {
final Map<String, Object> rootMap = createRootMap(loadBalancerNo, componentNo, instanceNos);
// インスタンスが1つの場合は並列実行しない
if (instanceNos.size() == 1) {
configureInstance(loadBalancerNo, componentNo, instanceNos.get(0), rootMap);
return;
}
// 並列実行
List<Callable<Void>> callables = new ArrayList<Callable<Void>>();
final Map<String, Object> loggingContext = LoggingUtils.getContext();
for (final Long instanceNo : instanceNos) {
Callable<Void> callable = new Callable<Void>() {
@Override
public Void call() throws Exception {
LoggingUtils.setContext(loggingContext);
try {
Map<String, Object> map = new HashMap<String, Object>(rootMap);
configureInstance(loadBalancerNo, componentNo, instanceNo, map);
} catch (Exception e) {
log.error(e.getMessage(), e);
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 void configureInstance(Long loadBalancerNo, Long componentNo, Long instanceNo, Map<String, Object> rootMap) {
LoadBalancer loadBalancer = loadBalancerDao.read(loadBalancerNo);
Component component = componentDao.read(componentNo);
Instance instance = instanceDao.read(instanceNo);
// インスタンス情報
Map<String, Object> instanceMap = createInstanceMap(instanceNo);
rootMap.putAll(instanceMap);
// マニフェストファイル
ComponentType componentType = componentTypeDao.read(component.getComponentTypeNo());
File manifestFile = new File(manifestDir, instance.getFqdn() + "." + component.getComponentName() + ".pp");
// マニフェストファイルのリストア
restoreManifest(manifestFile);
// 既にあるマニフェストファイルのダイジェストを求める
String digest = getFileDigest(manifestFile, "UTF-8");
// ロードバランサ停止時でマニフェストが存在しない場合、スキップする
if (digest == null && BooleanUtils.isNotTrue(loadBalancer.getEnabled())) {
return;
}
// マニフェストの生成
String templateName = "loadBalancer_" + componentType.getComponentTypeName() + ".ftl";
generateManifest(templateName, 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, component);
} finally {
if (BooleanUtils.isTrue(loadBalancer.getEnabled())) {
// マニフェストファイルのバックアップ
backupManifest(manifestFile);
} else {
// マニフェストファイルの削除
deleteManifest(manifestFile);
}
}
}
protected void runPuppet(Instance instance, Component component) {
ComponentType componentType = componentTypeDao.read(component.getComponentTypeNo());
String type = "loadBalancer_" + componentType.getComponentTypeName();
// Puppetクライアントの設定更新処理を実行
try {
processLogger.debug(component, instance, "PuppetManifestApply", new String[] { instance.getFqdn(), type });
puppetClient.runClient(instance.getFqdn());
} catch (RuntimeException e) {
processLogger.debug(component, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), type });
// マニフェスト適用に失敗した場合、警告ログ出力した後にリトライする
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(component, instance, "PuppetManifestApply", new String[] { instance.getFqdn(), type });
try {
puppetClient.runClient(instance.getFqdn());
} catch (RuntimeException e2) {
processLogger.debug(component, instance, "PuppetManifestApplyFail", new String[] { instance.getFqdn(), type });
throw e2;
}
} else {
throw e;
}
}
processLogger.debug(component, instance, "PuppetManifestApplyFinish", new String[] { instance.getFqdn(), type });
}
protected Map<String, Object> createRootMap(Long loadBalancerNo, Long componentNo, List<Long> instanceNos) {
Map<String, Object> map = new HashMap<String, Object>();
// Component
Component component = componentDao.read(componentNo);
map.put("component", component);
// Instance
List<Instance> instances = instanceDao.readInInstanceNos(instanceNos);
map.put("instances", instances);
// Farm
Farm farm = farmDao.read(component.getFarmNo());
map.put("farm", farm);
// User
User user = userDao.read(farm.getUserNo());
PccSystemInfo pccSystemInfo = pccSystemInfoDao.read();
PasswordEncryptor encryptor = new PasswordEncryptor();
String decryptPass = encryptor.decrypt(user.getPassword(), pccSystemInfo.getSecretKey());
user.setPassword(decryptPass);
map.put("user", user);
// ComponentConfig
List<ComponentConfig> componentConfigs = componentConfigDao.readByComponentNo(componentNo);
Map<String, Object> configs = new HashMap<String, Object>();
for (ComponentConfig componentConfig : componentConfigs) {
configs.put(componentConfig.getConfigName(), componentConfig.getConfigValue());
}
map.put("configs", configs);
// ロードバランサ情報
Map<String, Object> loadBalancerMap = createLoadBalancerMap(loadBalancerNo);
map.putAll(loadBalancerMap);
return map;
}
protected Map<String, Object> createLoadBalancerMap(Long loadBalancerNo) {
Map<String, Object> map = new HashMap<String, Object>();
// LoadBalancer
LoadBalancer loadBalancer = loadBalancerDao.read(loadBalancerNo);
map.put("loadBalancer", loadBalancer);
// LoadBalancerListener
List<LoadBalancerListener> allListeners = loadBalancerListenerDao.readByLoadBalancerNo(loadBalancerNo);
List<LoadBalancerListener> listeners = new ArrayList<LoadBalancerListener>();
for (LoadBalancerListener listener : allListeners) {
if (BooleanUtils.isTrue(listener.getEnabled())) {
listeners.add(listener);
}
}
map.put("listeners", listeners);
// LoadBalancerHealthCheck
LoadBalancerHealthCheck healthCheck = loadBalancerHealthCheckDao.read(loadBalancerNo);
map.put("healthCheck", healthCheck);
// LoadBalancerinstance
List<LoadBalancerInstance> lbInstances = loadBalancerInstanceDao.readByLoadBalancerNo(loadBalancerNo);
List<Long> targetInstanceNos = new ArrayList<Long>();
for (LoadBalancerInstance lbInstance : lbInstances) {
if (BooleanUtils.isTrue(lbInstance.getEnabled())) {
targetInstanceNos.add(lbInstance.getInstanceNo());
}
}
List<Instance> tmpInstances = instanceDao.readInInstanceNos(targetInstanceNos);
List<Instance> targetInstances = new ArrayList<Instance>();
for (Instance tmpInstance : tmpInstances) {
if (BooleanUtils.isTrue(tmpInstance.getEnabled())) {
InstanceStatus status = InstanceStatus.fromStatus(tmpInstance.getStatus());
if (status == InstanceStatus.RUNNING) {
targetInstances.add(tmpInstance);
}
}
}
map.put("targetInstances", targetInstances);
return map;
}
protected Map<String, Object> createInstanceMap(Long instanceNo) {
Map<String, Object> map = new HashMap<String, Object>();
// Instance
Instance instance = instanceDao.read(instanceNo);
map.put("instance", instance);
// アクセスIP
List<Instance> allInstances = instanceDao.readByFarmNo(instance.getFarmNo());
Platform platform = platformDao.read(instance.getPlatformNo());
Map<String, String> accessIps = new HashMap<String, String>();
for (Instance instance2 : allInstances) {
// 起動していないインスタンスは対象外
InstanceStatus status = InstanceStatus.fromStatus(instance2.getStatus());
if (status != InstanceStatus.RUNNING) {
continue;
}
// 基本はpublicIpでアクセスする
String accessIp = instance2.getPublicIp();
if (instance.getPlatformNo().equals(instance2.getPlatformNo())) {
// 同一のプラットフォームの場合
// TODO CLOUD BRANCHING
if (PCCConstant.PLATFORM_TYPE_AWS.equals(platform.getPlatformType())) {
PlatformAws platformAws = platformAwsDao.read(instance2.getPlatformNo());
if (BooleanUtils.isFalse(platformAws.getVpc())) {
// VPCを使用しない場合はprivateIpでアクセスする
accessIp = instance2.getPrivateIp();
}
} else if (PCCConstant.PLATFORM_TYPE_CLOUDSTACK.equals(platform.getPlatformType())) {
// CloudStackプラットフォームの場合はgetPublicIpでアクセスする
accessIp = instance2.getPublicIp();
} else if (PCCConstant.PLATFORM_TYPE_VMWARE.equals(platform.getPlatformType())) {
// VMwareプラットフォームの場合はprivateIpでアクセスする
accessIp = instance2.getPrivateIp();
//nifty
} else if (PCCConstant.PLATFORM_TYPE_NIFTY.equals(platform.getPlatformType())) {
// ニフティクラウドプラットフォームの場合はprivateIpでアクセスする
accessIp = instance2.getPrivateIp();
} else if (PCCConstant.PLATFORM_TYPE_VCLOUD.equals(platform.getPlatformType())) {
// VCloudプラットフォームの場合はprivateIpでアクセスする
accessIp = instance2.getPrivateIp();
} else if (PCCConstant.PLATFORM_TYPE_AZURE.equals(platform.getPlatformType())) {
// Azureプラットフォームの場合はgetPublicIpでアクセスする
accessIp = instance2.getPublicIp();
} else if (PCCConstant.PLATFORM_TYPE_OPENSTACK.equals(platform.getPlatformType())) {
// Openstackプラットフォームの場合はgetPublicIpでアクセスする
accessIp = instance2.getPublicIp();
}
}
accessIps.put(instance2.getInstanceNo().toString(), accessIp);
}
map.put("accessIps", accessIps);
// 待ち受けIP
List<String> listenIps = new ArrayList<String>();
// TODO CLOUD BRANCHING
if (PCCConstant.PLATFORM_TYPE_AWS.equals(platform.getPlatformType())) {
// AWSの場合
listenIps.add(instance.getPrivateIp());
PlatformAws platformAws = platformAwsDao.read(instance.getPlatformNo());
if (BooleanUtils.isFalse(platform.getInternal()) && BooleanUtils.isFalse(platformAws.getVpc())) {
// 外部のAWSプラットフォームでVPNを利用する場合(VPC+VPNでは無い、通常のVPNの場合)、PublicIpでも待ち受ける
listenIps.add(instance.getPublicIp());
}
} else if (PCCConstant.PLATFORM_TYPE_CLOUDSTACK.equals(platform.getPlatformType())) {
// CloudStackの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
} else if (PCCConstant.PLATFORM_TYPE_VMWARE.equals(platform.getPlatformType())) {
// VMwareの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
} else if (PCCConstant.PLATFORM_TYPE_NIFTY.equals(platform.getPlatformType())) {
// Niftyの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
} else if (PCCConstant.PLATFORM_TYPE_VCLOUD.equals(platform.getPlatformType())) {
// VCloudの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
} else if (PCCConstant.PLATFORM_TYPE_AZURE.equals(platform.getPlatformType())) {
// Azureの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
} else if (PCCConstant.PLATFORM_TYPE_OPENSTACK.equals(platform.getPlatformType())) {
// Openstackの場合
listenIps.add(instance.getPublicIp());
listenIps.add(instance.getPrivateIp());
}
map.put("listenIps", listenIps);
return map;
}
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 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 restoreManifest(File manifestFile) {
// マニフェストファイルのリストア
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(File manifestFile) {
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(File manifestFile) {
// マニフェストファイルを削除する
if (!manifestFile.exists()) {
return;
}
try {
FileUtils.forceDelete(manifestFile);
} 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;
}
/**
* processLoggerを設定します。
*
* @param processLogger processLogger
*/
public void setProcessLogger(ProcessLogger processLogger) {
this.processLogger = processLogger;
}
}