/***************************************************************************
* Copyright (c) 2014 VMware, Inc. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
***************************************************************************/
package com.vmware.bdd.manager;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.MalformedURLException;
import java.net.Socket;
import java.net.URL;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.codehaus.plexus.util.Base64;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.vmware.aurora.global.Configuration;
import com.vmware.bdd.apitypes.AppManagerAdd;
import com.vmware.bdd.apitypes.AppManagerRead;
import com.vmware.bdd.entity.AppManagerEntity;
import com.vmware.bdd.entity.ClusterEntity;
import com.vmware.bdd.exception.BddException;
import com.vmware.bdd.exception.SoftwareManagerCollectorException;
import com.vmware.bdd.manager.i18n.Messages;
import com.vmware.bdd.manager.intf.IClusterEntityManager;
import com.vmware.bdd.plugin.ironfan.impl.DefaultSoftwareManagerImpl;
import com.vmware.bdd.service.resmgmt.IAppManagerService;
import com.vmware.bdd.software.mgmt.plugin.exception.SoftwareManagementPluginException;
import com.vmware.bdd.software.mgmt.plugin.intf.SoftwareManager;
import com.vmware.bdd.software.mgmt.plugin.intf.SoftwareManagerFactory;
import com.vmware.bdd.software.mgmt.plugin.utils.ReflectionUtils;
import com.vmware.bdd.utils.CommonUtil;
import com.vmware.bdd.utils.Constants;
@Service
public class SoftwareManagerCollector implements InitializingBean {
private static final Logger logger = Logger
.getLogger(SoftwareManagerCollector.class);
@Autowired
private IAppManagerService appManagerService;
@Autowired
private IClusterEntityManager clusterEntityManager;
private ConcurrentHashMap<String, SoftwareManager> cache =
new ConcurrentHashMap<String, SoftwareManager>();
private String privateKey = null;
protected static String configurationPrefix = "appmanager.factoryclass.";
private static String appmanagerTypesKey = "appmanager.types";
private static String appmgrConnTimeOutKey = "appmanager.connect.timeout.seconds";
// the value of wait time to connect to application manager, with default 30 seconds
private static int waitTimeForAppMgrConn = Configuration.getInt(appmgrConnTimeOutKey, Constants.APPMGR_CONNECT_TIMEOUT_SECONDS);
public void setAppManagerService(IAppManagerService appManagerService) {
this.appManagerService = appManagerService;
}
protected int getCacheSize() {
return cache.size();
}
public void setClusterEntityManager(IClusterEntityManager clusterEntityManager) {
this.clusterEntityManager = clusterEntityManager;
}
/**
* Software manager name will be unique inside of BDE. Otherwise, creation
* will fail. The appmanager information should be persisted in meta-db
*
* @param appManagerAdd
*/
public void createSoftwareManager(AppManagerAdd appManagerAdd) {
logger.info("First we need check if the appmgr is valid for use.");
//validate url in appManagerAdd
List<String> errorMsgs = new ArrayList<String>();
if (!CommonUtil.validateUrl(appManagerAdd.getUrl(), errorMsgs)) {
throw SoftwareManagerCollectorException.INVALID_URL(errorMsgs);
}
logger.info("Check AppManager already exist: " + appManagerAdd.getName());
if (appManagerService.findAppManagerByName(appManagerAdd.getName()) != null) {
logger.error("Name " + appManagerAdd.getName() + " already exists.");
throw SoftwareManagerCollectorException.DUPLICATE_NAME(appManagerAdd.getName());
}
String sslCertificate = appManagerAdd.getSslCertificate();
if (!CommonUtil.isBlank(sslCertificate)) {
saveAppMgrCertificate(sslCertificate);
}
loadSoftwareManager(appManagerAdd);
// add to meta-db through AppManagerService
logger.info("Add app manager to meta-db.");
try {
appManagerService.addAppManager(appManagerAdd);
} catch (SoftwareManagerCollectorException ex) {
cache.remove(appManagerAdd.getName());
throw ex;
}
catch (Exception ex) {
cache.remove(appManagerAdd.getName());
throw BddException.wrapIfNeeded(ex, Messages.getString("SW_MGR_COLLECTOR.FAILED_WRITE_META_DB"));
}
}
private String getPrivateKey() {
if (privateKey == null) {
privateKey = loadPrivateKey(Constants.SERENGETI_PRIVATE_KEY_FILE);
}
return privateKey;
}
protected static String loadPrivateKey(String path) {
try {
return CommonUtil.dataFromFile(path);
} catch (IOException e) {
String errMsg = String.format(
Messages.getString("SW_MGR_COLLECTOR.FAIL_READ_PRI_KEY"), path);
logger.error(errMsg, e);
//should be an internal exception instead of an external one. lixl
throw new SWMgrCollectorInternalException(e,errMsg);
// throw SoftwareManagerCollectorException.PRIVATE_KEY_READ_ERROR(e,Constants.SERENGETI_PRIVATE_KEY_FILE);
}
}
protected void setPrivateKey(String value) {
privateKey = value;
}
/**
*
* @param appManagerEntity
* @return
*/
private SoftwareManager loadSoftwareManager(AppManagerEntity appManagerEntity) {
if (appManagerEntity.getName().equals(Constants.IRONFAN)) {
SoftwareManager ironfanSoftwareManager = new DefaultSoftwareManagerImpl();
cache.put(Constants.IRONFAN, ironfanSoftwareManager);
return ironfanSoftwareManager;
} else {
// Do not block initialization in case of Exception
return loadSoftwareManager(toAppManagerAdd(appManagerEntity));
}
}
/**
* wrap cache hit, instantiate, connection check and cache add together to simplify currency issue
* @param appManagerAdd
* @return
*/
protected synchronized SoftwareManager loadSoftwareManager(AppManagerAdd appManagerAdd) {
// we need to check the server connection each time we get a application manager, otherwise
// it will cause a lot of trouble later when accessing it.
String appMgrType = appManagerAdd.getType();
String name = appManagerAdd.getName();
if ( !appMgrType.equals(Constants.IRONFAN) ) {
// check the server connection before do the real connection to the application manager.
// this is to avoid long time waiting of socket connect when the server is shutdown or
// even does not exist at all.
checkServerConnection( name, appManagerAdd.getUrl() );
}
if (cache.containsKey(appManagerAdd.getName())) {
return cache.get(appManagerAdd.getName());
}
String factoryClassName = Configuration.getString(configurationPrefix + appManagerAdd.getType());
if (CommonUtil.isBlank(factoryClassName)) {
String errMsg = String.format(Messages.getString("SW_MGR_COLLECTOR.APP_MGR_FACTORY_UNDEFINED"), appManagerAdd.getType());
logger.error(errMsg);
throw new SWMgrCollectorInternalException(null, errMsg);
//should be internal exception, lixl
// throw SoftwareManagerCollectorException.CLASS_NOT_DEFINED(appManagerAdd.getType());
}
logger.info("Factory class name is " + factoryClassName);
SoftwareManagerFactory softwareManagerFactory = null;
try {
Class<? extends SoftwareManagerFactory> clazz = ReflectionUtils.getClass(factoryClassName, SoftwareManagerFactory.class);
logger.info("Factory class loaded.");
softwareManagerFactory = ReflectionUtils.newInstance(clazz);
} catch (Exception e) {
String errMsg = String.format(Messages.getString("SW_MGR_COLLECTOR.CANNT_INSTANTIATE_APP_MGR_FACTORY"), factoryClassName);
logger.error(errMsg, e);
throw new SWMgrCollectorInternalException(e, errMsg);
// throw SoftwareManagerCollectorException.CAN_NOT_INSTANTIATE(e, factoryClassName);
}
logger.info("Start to invoke application manager factory to create application manager.");
SoftwareManager softwareManager = null;
try {
softwareManager =
softwareManagerFactory.getSoftwareManager(appManagerAdd.getUrl(), appManagerAdd
.getUsername(), appManagerAdd.getPassword().toCharArray(),
getPrivateKey());
} catch (Exception ex) {
//TODO the handling is not clear: was it a connection failure (external), or some instantiating error (internal). lixl
logger.error("Create application manager failed: " + ex.getMessage(), ex);
throw SoftwareManagerCollectorException.CONNECT_FAILURE(
appManagerAdd.getName(), ExceptionUtils.getRootCauseMessage(ex));
}
validateSoftwareManager(appManagerAdd.getName(), softwareManager);
logger.info("The appmgr " + appManagerAdd.getName() + " can be reached and will be created.");
cache.put(appManagerAdd.getName(), softwareManager);
return softwareManager;
}
/**
*
* @param name
* @param softwareManager
*/
private void validateSoftwareManager(String name, final SoftwareManager softwareManager) {
logger.info("Check echo() of application manager.");
// validate instance is reachable
try {
if ( !softwareManager.echo() ) {
logger.error("Application manager "
+ name
+ " status is unhealthy. Please check application manager console for more details.");
throw SoftwareManagerCollectorException.ECHO_FAILURE(name);
}
} catch (SoftwareManagementPluginException e) {
//TODO we won't catch anything here! consider to remove it, lixl
logger.error("Cannot connect to application manager "
+ name + ", check the connection information.", e);
throw SoftwareManagerCollectorException.CONNECT_FAILURE(name,
e.getMessage());
}
validateSoftwareManagerVersion(softwareManager);
}
private void validateSoftwareManagerVersion(SoftwareManager softwareManager) throws SoftwareManagerCollectorException {
softwareManager.validateServerVersion();
}
/**
* Get software manager instance
*
* @param name
* @return null if the name does not exist
*/
public SoftwareManager getSoftwareManager(String name) {
if (CommonUtil.isBlank(name) || Constants.IRONFAN.equals(name)) {
return cache.get(Constants.IRONFAN);
}
AppManagerEntity appManagerEntity = appManagerService.findAppManagerByName(name);
if (appManagerEntity == null) {
logger.error("Cannot find app manager " + name);
throw SoftwareManagerCollectorException.APPMANAGER_NOT_FOUND(name);
} else {
if (cache.containsKey(name)) {
String appMgrType = appManagerEntity.getType();
if ( !appMgrType.equals(Constants.IRONFAN) ) {
// check the server connection before do the real connection to the application manager.
// this is to avoid long time waiting of socket connect when the server is shutdown or
// even does not exist at all.
checkServerConnection( name, appManagerEntity.getUrl() );
}
return cache.get(name);
}
return loadSoftwareManager(appManagerEntity);
}
}
/**
* Get software manager instance by cluster name (do not call this api before
* cluster information is written to meta-db
*
* @param name
* @return null if cluster name does not have a corresponding software
* manager instance
*/
public SoftwareManager getSoftwareManagerByClusterName(String name) {
ClusterEntity clusterEntity = clusterEntityManager.findByName(name);
if (clusterEntity == null) {
logger.warn("Can't find cluster with name: " + name);
return null;
}
return getSoftwareManager(clusterEntity.getAppManager());
}
public synchronized void loadSoftwareManagers() {
boolean defaultMgrExists = false;
List<AppManagerEntity> appManagers = appManagerService.findAll();
for (AppManagerEntity appManager : appManagers) {
if(!defaultMgrExists) {
defaultMgrExists = Constants.IRONFAN.equals(appManager.getName());
}
// if any appmgr cannot be connected, we should not block the initialization, or
// the serengeti-ws server will fail startup
try {
loadSoftwareManager(appManager);
} catch (Exception e) {
logger.error("One of the appliation manager cannot be loaded: " + appManager.getName(), e);
}
}
if(!defaultMgrExists) {
AppManagerAdd appManagerAdd = new AppManagerAdd();
appManagerAdd.setName(Constants.IRONFAN);
appManagerAdd.setDescription(Constants.IRONFAN_DESCRIPTION);
appManagerAdd.setType(Constants.IRONFAN);
appManagerAdd.setUrl("");
appManagerAdd.setUsername("");
appManagerAdd.setPassword("");
appManagerAdd.setSslCertificate("");
appManagerService.addAppManager(appManagerAdd);
try {
loadSoftwareManager(new AppManagerEntity(appManagerAdd));
} catch (Exception e) {
logger.error("One of the appliation manager cannot be loaded: " + appManagerAdd.getName(), e);
}
}
}
/**
* @param appManager
* @return
*/
public static AppManagerAdd toAppManagerAdd(AppManagerEntity appManager) {
AppManagerAdd appManagerAdd = new AppManagerAdd();
appManagerAdd.setName(appManager.getName());
appManagerAdd.setDescription(appManager.getDescription());
appManagerAdd.setType(appManager.getType());
appManagerAdd.setUrl(appManager.getUrl());
appManagerAdd.setUsername(appManager.getUsername());
appManagerAdd.setPassword(appManager.getPassword());
appManagerAdd.setSslCertificate(appManager.getSslCertificate());
return appManagerAdd;
}
public List<AppManagerRead> getAllAppManagerReads() {
logger.debug("get all app managers");
List<AppManagerRead> appManagerReads =
appManagerService.getAllAppManagerReads();
for (AppManagerRead appManagerRead: appManagerReads) {
updateManagedClusters(appManagerRead);
}
logger.debug("got all app managers");
return appManagerReads;
}
public AppManagerRead getAppManagerRead(String appManagerName) {
AppManagerRead appManagerRead =
appManagerService.getAppManagerRead(appManagerName);
if (appManagerRead == null) {
logger.error("Cannot find app manager " + appManagerName);
throw SoftwareManagerCollectorException.APPMANAGER_NOT_FOUND(appManagerName);
} else {
setAppManagerReadDynamicProperties(appManagerRead);
return appManagerRead;
}
}
private void updateManagedClusters(AppManagerRead appManagerRead) {
appManagerRead.setManagedClusters(clusterEntityManager
.findByAppManager(appManagerRead.getName()));
}
private void updateVersion(AppManagerRead appManagerRead) {
String softMgrVersion = "UNKNOWN";
final SoftwareManager softwareManager = this.getSoftwareManager(appManagerRead.getName());
// fork a child thread to do the actual connecting action
// this is to avoid the time out issue for the socket connection when the target host is shutdown
ExecutorService exec = Executors.newFixedThreadPool(1);
Future<String> futureResult = exec.submit(new Callable<String>(){
@Override
public String call() throws Exception {
// TODO Auto-generated method stub
return softwareManager.getVersion();
}
});
String result = (String)CommonUtil.waitForThreadResult(futureResult, waitTimeForAppMgrConn);
if (null != result) {
softMgrVersion = result;
}
exec.shutdown();
appManagerRead.setVersion(softMgrVersion);
}
/**
* @param appManagerRead
*/
private void setAppManagerReadDynamicProperties(AppManagerRead appManagerRead) {
updateManagedClusters(appManagerRead);
updateVersion(appManagerRead);
}
public List<String> getAllAppManagerTypes() {
String[] types = null;
String appmanagerTypes =
Configuration.getStrings(appmanagerTypesKey, "");
if (appmanagerTypes != null) {
types = appmanagerTypes.split(",");
} else {
types = new String[0];
}
return Arrays.asList(types);
}
/* (non-Javadoc)
* @see org.springframework.beans.factory.InitializingBean#afterPropertiesSet()
*/
@Override
public void afterPropertiesSet() throws Exception {
this.loadSoftwareManagers();
}
public synchronized void deleteSoftwareManager(String appManagerName) {
logger.debug("delete app manager " + appManagerName);
if (Constants.IRONFAN.equals(appManagerName)) {
logger.error("Cannot delete default application manager.");
throw SoftwareManagerCollectorException.CAN_NOT_DELETE_DEFAULT();
}
appManagerService.deleteAppManager(appManagerName);
logger.debug("successfully deleted app manager " + appManagerName);
cache.remove(appManagerName);
logger.debug("app manager " + appManagerName + " removed from cache");
}
public synchronized void modifySoftwareManager(AppManagerAdd appManagerAdd) {
logger.debug("modify app manager " + appManagerAdd);
String name = appManagerAdd.getName();
if (Constants.IRONFAN.equals(name)) {
logger.error("Cannot modify default application manager.");
throw SoftwareManagerCollectorException.CAN_NOT_MODIFY_DEFAULT();
}
AppManagerEntity appManager = appManagerService.findAppManagerByName(name);
if (null == appManager) {
logger.error("Cannot find app manager " + name);
throw SoftwareManagerCollectorException.APPMANAGER_NOT_FOUND(name);
}
//validate url in appManagerAdd
List<String> errorMsgs = new ArrayList<String>();
if (!CommonUtil.validateUrl(appManagerAdd.getUrl(), errorMsgs)) {
throw SoftwareManagerCollectorException.INVALID_URL(errorMsgs);
}
String sslCertificate = appManagerAdd.getSslCertificate();
if (!CommonUtil.isBlank(sslCertificate)) {
saveAppMgrCertificate(sslCertificate);
}
logger.info("Load software manager using new properties " + appManagerAdd);
cache.remove(name);
loadSoftwareManager(appManagerAdd);
logger.info("Modify meta db");
appManagerService.modifyAppManager(appManagerAdd);
logger.debug("successfully modified app manager " + appManagerAdd);
}
private void saveAppMgrCertificate(String certificate) {
saveSslCertificate(certificate, Constants.APPMANAGER_KEYSTORE_PATH);
}
/**
* TODO this method has to be reverted:
* because if the target path is not accessible, it will load cert from the default keystore in java home,
* but still try to write it to the non accessible path.
* @param certificate
* @param keyStorePath
*/
protected static void saveSslCertificate(String certificate, String keyStorePath) {
Certificate[] certs;
//parse certificates
try {
if (CommonUtil.isBlank(certificate)) {
throw SoftwareManagerCollectorException.BAD_CERT(null);
}
byte[] certBytes = Base64
.decodeBase64(certificate
.replaceAll("-----BEGIN CERTIFICATE-----", "")
.replaceAll("-----END CERTIFICATE-----", "")
.getBytes());
CertificateFactory cf = CertificateFactory.getInstance("X.509");
Collection c = cf.generateCertificates(new ByteArrayInputStream(certBytes));
certs = new Certificate[c.toArray().length];
if (c.size() == 0) {
throw SoftwareManagerCollectorException.BAD_CERT(null);
} else if (c.size() == 1) {
certs[0] = cf.generateCertificate(new ByteArrayInputStream(certBytes));
} else {
certs = (Certificate[]) c.toArray(certs);
}
} catch (CertificateException e){
throw SoftwareManagerCollectorException.BAD_CERT(e);
}
//load & save keystore
OutputStream out = null;
try {
KeyStore keyStore = CommonUtil.loadAppMgrKeyStore(keyStorePath);
if (keyStore == null) {
logger.error(Messages.getString("SW_MGR_COLLECTOR.CANNT_READ_KEYSTORE"));
throw new SWMgrCollectorInternalException(Messages.getString("SW_MGR_COLLECTOR.CANNT_READ_KEYSTORE"));
}
MessageDigest md5 = MessageDigest.getInstance("MD5");
String md5Fingerprint = "";
for (Certificate cert : certs) {
md5.update(cert.getEncoded());
md5Fingerprint = CommonUtil.toHexString(md5.digest());
logger.debug("md5 finger print: " + md5Fingerprint);
logger.debug("added cert: " + cert);
keyStore.setCertificateEntry(md5Fingerprint, cert);
}
out = new FileOutputStream(keyStorePath + Constants.APPMANAGER_KEYSTORE_FILE);
keyStore.store(new BufferedOutputStream(out), Constants.APPMANAGER_KEYSTORE_PASSWORD);
}catch (CertificateException | NoSuchAlgorithmException | IOException | KeyStoreException e) {
logger.error(Messages.getString("SW_MGR_COLLECTOR.FAIL_SAVE_CERT"), e);
throw new SWMgrCollectorInternalException(e, Messages.getString("SW_MGR_COLLECTOR.FAIL_SAVE_CERT"));
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
logger.warn("Output stream of appmanagers.jks close failed.");
}
}
}
}
/**
*
* @param appMgrName
* @param urlStr
*/
private void checkServerConnection(String appMgrName, String urlStr) {
URL url = null;
try {
url = new URL(urlStr);
} catch (MalformedURLException e) {
logger.error("Url parse error: " + e.getMessage());
throw SoftwareManagerCollectorException.CONNECT_FAILURE(appMgrName,
e.getMessage());
}
final String host = url.getHost();
final int port = url.getPort();
logger.debug("Check the connection to the application manager.");
boolean connectOK = CommonUtil.checkServerConnection(host, port, waitTimeForAppMgrConn);
if ( !connectOK ) {
logger.error("Cannot connect to application manager "
+ appMgrName + ", check the connection information.");
throw SoftwareManagerCollectorException.CONNECT_FAILURE(appMgrName,
"Failed to connect to the server.");
}
}
}