/*************************************************************************** * Copyright (c) 2012-2015 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.aurora.global; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.URL; import java.util.Enumeration; import java.util.Iterator; import org.apache.commons.configuration.ConfigurationException; import org.apache.commons.configuration.ConfigurationUtils; import org.apache.commons.configuration.ConversionException; import org.apache.commons.configuration.PropertiesConfiguration; import org.apache.commons.lang.ArrayUtils; import org.apache.log4j.Logger; import com.vmware.aurora.exception.AuroraException; import com.vmware.aurora.exception.CommonException; import com.vmware.aurora.util.AuAssert; import com.vmware.aurora.util.StringUtil; /** * <code>Configuration</code> is a util class for accessing configurations of * CMS. <br> * * @since 0.4.2 * @version 0.4.2 * @author Gene Zhang */ public class Configuration { private static Logger logger = Logger.getLogger(Configuration.class); private static String configFileName; private static PropertiesConfiguration config = init(); private static PropertiesConfiguration serengetiCfg; private static PropertiesConfiguration vcCfg; private static String storedCmsInstanceId; private static final String NGC_PROP_FILE = "ngc_registrar.properties"; private static PropertiesConfiguration ngcCfg; /** * * @return a memory view of all properties inside serengeti.properties and vc.properties */ private static PropertiesConfiguration init() { PropertiesConfiguration config = null; String homeDir = System.getProperties().getProperty("serengeti.home.dir"); String ngcConfigFile = NGC_PROP_FILE; if (homeDir != null && homeDir.length() > 0) { StringBuilder builder = new StringBuilder(); builder.append(homeDir).append(File.separator).append("conf") .append(File.separator); configFileName = builder.toString() + "serengeti.properties"; ngcConfigFile = builder.toString() + NGC_PROP_FILE; } else { configFileName = "serengeti.properties"; } try { URL url = ConfigurationUtils.locate(null, configFileName); logger.info("Reading properties file serengeti.properties from " + url.getPath()); serengetiCfg = new PropertiesConfiguration(); serengetiCfg.setEncoding("UTF-8"); serengetiCfg.setFileName(configFileName); serengetiCfg.load(); config = (PropertiesConfiguration) serengetiCfg.clone(); } catch (ConfigurationException ex) { String message = "Failed to load serengeti.properties file."; logger.fatal(message, ex); throw AuroraException.APP_INIT_ERROR(ex, message); } String propertyFilePrefix = System.getProperty("PROPERTY_FILE_PREFIX", "vc"); String propertyFileName = propertyFilePrefix + ".properties"; try { logger.info("Reading properties file " + propertyFileName); vcCfg = new PropertiesConfiguration(propertyFileName); Iterator<?> keys = vcCfg.getKeys(); while (keys.hasNext()) { String key = (String) keys.next(); config.setProperty(key, vcCfg.getProperty(key)); } } catch (ConfigurationException ex) { // error out if the configuration file is not there String message = "Failed to load vc.properties file."; logger.fatal(message, ex); throw AuroraException.APP_INIT_ERROR(ex, message); } // load ngc_registrar.properties try { logger.info("Reading properties file " + ngcConfigFile); ngcCfg = new PropertiesConfiguration(ngcConfigFile); ngcCfg.setEncoding("UTF-8"); Iterator<?> keys = ngcCfg.getKeys(); while (keys.hasNext()) { String key = (String) keys.next(); config.setProperty(key, ngcCfg.getProperty(key)); } } catch (ConfigurationException ex) { // error out if the configuration file is not there String message = "Failed to load file " + NGC_PROP_FILE; logger.fatal(message, ex); throw AuroraException.APP_INIT_ERROR(ex, message); } logConfig(config); return config; } private static void logConfig( org.apache.commons.configuration.Configuration config) { Iterator<?> keys = config.getKeys(); while (keys.hasNext()) { String key = (String) keys.next(); logger.debug("Config '" + key + "=" + config.getProperty(key) + "'"); } } /** * * Gets a property of type int. <br> * * @param key * The key of property. * @return The property value. */ public static int getInt(String key) { return config.getInt(key); } /** * * Gets a property of type int. <br> * * @param defaultValue * The default value. * @param key * The key of property. * @return The property value. */ public static int getInt(String key, int defaultValue) { try { return config.getInt(key, defaultValue); } catch (ConversionException ce) { logger.error(key + " in serengeti.properties is not an integer!"); } return defaultValue; } /** * * Gets a property of type bool. <br> * * @param key * The key of property. * @return The property value. */ public static Boolean getBoolean(String key) { return config.getBoolean(key); } /** * * Gets a property of type bool. <br> * * @param defaultValue * The default value. * @param key * The key of property. * @return The property value. */ public static Boolean getBoolean(String key, Boolean defaultValue) { try { return config.getBoolean(key, defaultValue); } catch (ConversionException ce) { logger.error(key + " in serengeti.properties is not a boolean!"); } return defaultValue; } /** * * Gets a property of type string. <br> * * @param key * The key of property. * @return The property value. */ public static String getString(String key) { return config.getString(key); } /** * * Gets a property of type string. <br> * * @param defaultValue * The default value. * @param key * The key of property. * @return The property value. */ public static String getString(String key, String defaultValue) { return config.getString(key, defaultValue); } /** * Gets a whitespace trimmed non-empty string property. * * @param key * The key of property. * @return The property value or null. */ public static String getNonEmptyString(String key) { String s = getString(key, null); return StringUtil.trimNonEmpty(s); } /** * * Gets a property of type double. <br> * * @param key * The key of property. * @return The property value. */ public static Double getDouble(String key) { return config.getDouble(key); } /** * * Gets a property of type double. <br> * * @param defaultValue * The default value. * @param key * The key of property. * @return The property value. */ public static Double getDouble(String key, Double defaultValue) { try { return config.getDouble(key, defaultValue); } catch (ConversionException ce) { logger.error(key + " in serengeti.properties is not a double!"); } return defaultValue; } /** * * Gets a property of type Long. <br> * * @param key * The key of property. * @return The property value. */ public static long getLong(String key) { return config.getLong(key); } /** * * Gets a property of type Long. <br> * * @param key * The key of property. * @param defaultValue * The default value. * @return The property value. */ public static long getLong(String key, long defaultValue) { try { return config.getLong(key, defaultValue); } catch (ConversionException ce) { logger.error(key + " in serengeti.properties is not a long!"); } return defaultValue; } /** * Test if a key exists * * @param key * . * @return true if key exists. */ public static boolean containsKey(String key) { return config.containsKey(key); } /** * Return the CMS instance identifier, global to this instance of the CMS. * * @return CMS instance identifier */ public static String getCmsInstanceId() { /* * We have two sources for the instance ID: the database ("stored instance * id") and properties file ("bootstrap instance id"). We prefer the * stored instance id, but use the bootstrap one if it's not available * (mostly for bootstrapping, also for unit tests that don't have a * database). * * In normal use, during the setup process we read the bootstrap instance * id and store it in the database, after which we always use the stored * one. (They would be the same unless some disaster happens where the * VM gets reprovisioned from backup and the bootstrap one in the properties * file changes, which is why we prefer using the one we stored in the db.) * * As a further complication, for dependency reasons we want to expose the * instance ID from this layer, but also for dependency reasons we're unable * to read the DB here. So we can't read the stored instance ID ourselves; * we require some other layer (CmsConfig) to read it and inject it here. * VirtualCenterResourceManagement will copy the bootstrap instance ID to * the DB during setup, and also call us, and thereafter Organization will * call us during system initialization. */ if (bootstrapMode == Configuration.BootstrapUsage.ALLOWED) { // In the window after we notice setup hasn't run yet and before we run // setup, we initialize the VC context and it's allowed to use the // bootstrap instance ID. AuAssert.check(storedCmsInstanceId == null); return getBootstrapInstanceId(); } // Otherwise, we require that we're using the stored instance ID. AuAssert.check(storedCmsInstanceId != null); return storedCmsInstanceId; } public enum BootstrapUsage { DISALLOWED, ALLOWED, FINALIZED; } private static BootstrapUsage bootstrapMode = BootstrapUsage.DISALLOWED; /** * Set the CMS instance identifier which will be returned by * getCmsInstanceId(). Can be called only once per process during * initialization. This variant is used only during system setup * (VirtualCenterResourceManagement.initializeSystem); otherwise * setCmsInstanceId is preferred. */ public static String approveBootstrapInstanceId(BootstrapUsage usage) { switch (usage) { case ALLOWED: // transition from DISALLOWED to ALLOWED AuAssert.check(bootstrapMode == BootstrapUsage.DISALLOWED); AuAssert.check(storedCmsInstanceId == null); bootstrapMode = usage; return null; case FINALIZED: // transition from ALLOWED to FINALIZED AuAssert.check(bootstrapMode == BootstrapUsage.ALLOWED); AuAssert.check(storedCmsInstanceId == null); setCmsInstanceId(getBootstrapInstanceId()); bootstrapMode = usage; return getCmsInstanceId(); default: // no other transition is legal AuAssert.check(false); return null; } } /** * Set the CMS instance identifier which will be returned by * getCmsInstanceId(). Can be called only once per process during * initialization. * * @param id * Instance ID retrieved from database. */ public static void setCmsInstanceId(String id) { /* * This can be called only once per process, so the value can't change after * it's set. The current caller is CmsConfig.init(). * * We require this be injected from an external source because we can't * calculate it here due to dependencies; however (also due to dependencies) * we want to provide access to this value from here. So we're happy to * store-and-forward here. */ AuAssert.check(storedCmsInstanceId == null); storedCmsInstanceId = id; // sanity check String bootstrapId = getBootstrapInstanceId(); if (!id.equals(bootstrapId)) { logger.warn("Instance ID has changed from " + bootstrapId + " to " + id); } } /** * Generate the instance ID from the stored moref to the CMS VM. * * @return Bootstrap instance ID. */ private static String getBootstrapInstanceId() { String cmsVmIdText = Configuration.getString("vim.cms_moref"); int pos = cmsVmIdText.lastIndexOf('-'); if (pos == -1) { logger.error("Unrecognized CMS VM moref Id: " + cmsVmIdText); throw CommonException.INTERNAL(); } Long cmsVmId = Long.parseLong(cmsVmIdText.substring(pos + 1)); return Integer.toHexString(cmsVmId.hashCode()); // enforce maximum of 8 digits } /** * Gets the fqdn of cms server. If it's not presented, the method tries to * retrieve the ip address of first network interface and reports it as the * fqdn. * * @return The FQDN of CMS. */ public static String getCmsFQDN() { String fqdn = Configuration.getString("cms.mgmtnet.fqdn"); if (fqdn == null || fqdn.isEmpty()) { try { // try to retrieve the ip addr of eth0 NetworkInterface net = NetworkInterface.getByName("eth0"); Enumeration<InetAddress> addresses = net.getInetAddresses(); while (addresses.hasMoreElements()) { InetAddress addr = addresses.nextElement(); if (addr instanceof Inet4Address) { String ip = addr.toString(); fqdn = ip.substring(ip.lastIndexOf("/") + 1); break; } } } catch (Exception e) { logger.info("Error in retrieving ip of eth0", e); throw CommonException.INTERNAL(e); } } return fqdn; } /** * Get all of values in a property as a String[] type. * * @param key * The key of property. * @return The property value. */ public static String[] getStringArray(String key){ return config.getStringArray(key); } /** * Get all of values in a property as a String[] type. * * @param key * The key of property. * @param defautValue * The default value. * @return The property value. */ public static String[] getStringArray(String key, String[] defautValue) { String[] values = getStringArray(key); if(!ArrayUtils.isEmpty(values)) return values; return defautValue; } /** * Get all of values in a property as a String[] type * and judge the legality of ThreadPoolConfig. * four default parameters here * * @param key * The key of property. * @param defautValue * The default value. * @return The property value. */ public static String[] getThreadConfig(String key, String[] defautValue){ String[] values = getStringArray(key, defautValue); if(values.length == 3) return values; return defautValue; } /** * Get all of values in a property as a string type. * * @param key * The key of property. * @param defautValue * The default value. * @return The property value. */ public static String getStrings(String key, String defautValue) { String[] values = config.getStringArray(key); if (values != null && values.length > 0) { StringBuffer buffer = new StringBuffer(); for (String value : values) { buffer.append(value.trim()).append(","); } buffer.delete(buffer.length() - 1, buffer.length()); return buffer.toString(); } else { return defautValue; } } /** * Set the boolean value of a given key * * @param key * @param value */ public static void setBoolean(String key, Boolean value) { config.setProperty(key, value); if (vcCfg.containsKey(key)) { vcCfg.setProperty(key, value); } else { serengetiCfg.setProperty(key, value); } } public static void setString(String key, String value) { config.setProperty(key, value); if (vcCfg.containsKey(key)) { vcCfg.setProperty(key, value); } else { serengetiCfg.setProperty(key, value); } } public static void save() { OutputStream out = null; try { // we only have reqs to save serengeti.properties currently out = new FileOutputStream(new File(serengetiCfg.getPath())); serengetiCfg.save(out); } catch (Exception ex) { // error out if the configuration file is not there String message = "Failed to save serengeti configuration file: " + configFileName; logger.fatal(message, ex); } finally { try { if (out != null) { out.close(); } } catch (IOException e) { logger.error("Failed to close file: " + configFileName + ", after save configuration."); } } } }