/*************************************************************************** * 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."); } } }