001/** 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.oozie.service; 019 020import org.apache.hadoop.conf.Configuration; 021import org.apache.oozie.util.Instrumentable; 022import org.apache.oozie.util.Instrumentation; 023import org.apache.oozie.util.XLog; 024import org.apache.oozie.util.XConfiguration; 025import org.apache.oozie.ErrorCode; 026 027import java.io.File; 028import java.io.FileInputStream; 029import java.io.IOException; 030import java.io.InputStream; 031import java.io.StringWriter; 032import java.util.HashSet; 033import java.util.Map; 034import java.util.Set; 035import java.util.Arrays; 036import org.apache.oozie.util.ZKUtils; 037 038/** 039 * Built in service that initializes the services configuration. 040 * <p/> 041 * The configuration loading sequence is identical to Hadoop configuration loading sequence. 042 * <p/> 043 * Default values are loaded from the 'oozie-default.xml' file from the classpath, then site configured values 044 * are loaded from a site configuration file from the Oozie configuration directory. 045 * <p/> 046 * The Oozie configuration directory is resolved using the <code>OOZIE_HOME<code> environment variable as 047 * <code>${OOZIE_HOME}/conf</code>. If the <code>OOZIE_HOME<code> environment variable is not defined the 048 * initialization of the <code>ConfigurationService</code> fails. 049 * <p/> 050 * The site configuration is loaded from the <code>oozie-site.xml</code> file in the configuration directory. 051 * <p/> 052 * The site configuration file name to use can be changed by setting the <code>OOZIE_CONFIG_FILE</code> environment 053 * variable to an alternate file name. The alternate file must ber in the Oozie configuration directory. 054 * <p/> 055 * Configuration properties, prefixed with 'oozie.', passed as system properties overrides default and site values. 056 * <p/> 057 * The configuration service logs details on how the configuration was loaded as well as what properties were overrode 058 * via system properties settings. 059 */ 060public class ConfigurationService implements Service, Instrumentable { 061 private static final String INSTRUMENTATION_GROUP = "configuration"; 062 063 public static final String CONF_PREFIX = Service.CONF_PREFIX + "ConfigurationService."; 064 065 public static final String CONF_IGNORE_SYS_PROPS = CONF_PREFIX + "ignore.system.properties"; 066 067 /** 068 * System property that indicates the configuration directory. 069 */ 070 public static final String OOZIE_CONFIG_DIR = "oozie.config.dir"; 071 072 073 /** 074 * System property that indicates the data directory. 075 */ 076 public static final String OOZIE_DATA_DIR = "oozie.data.dir"; 077 078 /** 079 * System property that indicates the name of the site configuration file to load. 080 */ 081 public static final String OOZIE_CONFIG_FILE = "oozie.config.file"; 082 083 private static final Set<String> IGNORE_SYS_PROPS = new HashSet<String>(); 084 private static final Set<String> CONF_SYS_PROPS = new HashSet<String>(); 085 086 private static final String IGNORE_TEST_SYS_PROPS = "oozie.test."; 087 private static final Set<String> MASK_PROPS = new HashSet<String>(); 088 089 static { 090 091 //all this properties are seeded as system properties, no need to log changes 092 IGNORE_SYS_PROPS.add(CONF_IGNORE_SYS_PROPS); 093 IGNORE_SYS_PROPS.add(Services.OOZIE_HOME_DIR); 094 IGNORE_SYS_PROPS.add(OOZIE_CONFIG_DIR); 095 IGNORE_SYS_PROPS.add(OOZIE_CONFIG_FILE); 096 IGNORE_SYS_PROPS.add(OOZIE_DATA_DIR); 097 IGNORE_SYS_PROPS.add(XLogService.OOZIE_LOG_DIR); 098 IGNORE_SYS_PROPS.add(XLogService.LOG4J_FILE); 099 IGNORE_SYS_PROPS.add(XLogService.LOG4J_RELOAD); 100 101 CONF_SYS_PROPS.add("oozie.http.hostname"); 102 CONF_SYS_PROPS.add("oozie.http.port"); 103 CONF_SYS_PROPS.add(ZKUtils.OOZIE_INSTANCE_ID); 104 105 // These properties should be masked when displayed because they contain sensitive info (e.g. password) 106 MASK_PROPS.add(JPAService.CONF_PASSWORD); 107 MASK_PROPS.add("oozie.authentication.signature.secret"); 108 } 109 110 public static final String DEFAULT_CONFIG_FILE = "oozie-default.xml"; 111 public static final String SITE_CONFIG_FILE = "oozie-site.xml"; 112 113 private static XLog log = XLog.getLog(ConfigurationService.class); 114 115 private String configDir; 116 private String configFile; 117 118 private LogChangesConfiguration configuration; 119 120 public ConfigurationService() { 121 log = XLog.getLog(ConfigurationService.class); 122 } 123 124 /** 125 * Initialize the log service. 126 * 127 * @param services services instance. 128 * @throws ServiceException thrown if the log service could not be initialized. 129 */ 130 public void init(Services services) throws ServiceException { 131 configDir = getConfigurationDirectory(); 132 configFile = System.getProperty(OOZIE_CONFIG_FILE, SITE_CONFIG_FILE); 133 if (configFile.contains("/")) { 134 throw new ServiceException(ErrorCode.E0022, configFile); 135 } 136 log.info("Oozie home dir [{0}]", Services.getOozieHome()); 137 log.info("Oozie conf dir [{0}]", configDir); 138 log.info("Oozie conf file [{0}]", configFile); 139 configFile = new File(configDir, configFile).toString(); 140 configuration = loadConf(); 141 } 142 143 public static String getConfigurationDirectory() throws ServiceException { 144 String oozieHome = Services.getOozieHome(); 145 String configDir = System.getProperty(OOZIE_CONFIG_DIR, null); 146 File file = configDir == null 147 ? new File(oozieHome, "conf") 148 : new File(configDir); 149 if (!file.exists()) { 150 throw new ServiceException(ErrorCode.E0024, configDir); 151 } 152 return file.getPath(); 153 } 154 155 /** 156 * Destroy the configuration service. 157 */ 158 public void destroy() { 159 configuration = null; 160 } 161 162 /** 163 * Return the public interface for configuration service. 164 * 165 * @return {@link ConfigurationService}. 166 */ 167 public Class<? extends Service> getInterface() { 168 return ConfigurationService.class; 169 } 170 171 /** 172 * Return the services configuration. 173 * 174 * @return the services configuration. 175 */ 176 public Configuration getConf() { 177 if (configuration == null) { 178 throw new IllegalStateException("Not initialized"); 179 } 180 return configuration; 181 } 182 183 /** 184 * Return Oozie configuration directory. 185 * 186 * @return Oozie configuration directory. 187 */ 188 public String getConfigDir() { 189 return configDir; 190 } 191 192 private InputStream getDefaultConfiguration() throws ServiceException, IOException { 193 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 194 InputStream inputStream = classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE); 195 if (inputStream == null) { 196 throw new ServiceException(ErrorCode.E0023, DEFAULT_CONFIG_FILE); 197 } 198 return inputStream; 199 } 200 201 private LogChangesConfiguration loadConf() throws ServiceException { 202 XConfiguration configuration; 203 try { 204 InputStream inputStream = getDefaultConfiguration(); 205 configuration = new XConfiguration(inputStream); 206 File file = new File(configFile); 207 if (!file.exists()) { 208 log.info("Missing site configuration file [{0}]", configFile); 209 } 210 else { 211 inputStream = new FileInputStream(configFile); 212 XConfiguration siteConfiguration = new XConfiguration(inputStream); 213 XConfiguration.injectDefaults(configuration, siteConfiguration); 214 configuration = siteConfiguration; 215 } 216 } 217 catch (IOException ex) { 218 throw new ServiceException(ErrorCode.E0024, configFile, ex.getMessage(), ex); 219 } 220 221 if (log.isTraceEnabled()) { 222 try { 223 StringWriter writer = new StringWriter(); 224 for (Map.Entry<String, String> entry : configuration) { 225 String value = getValue(configuration, entry.getKey()); 226 writer.write(" " + entry.getKey() + " = " + value + "\n"); 227 } 228 writer.close(); 229 log.trace("Configuration:\n{0}---", writer.toString()); 230 } 231 catch (IOException ex) { 232 throw new ServiceException(ErrorCode.E0025, ex.getMessage(), ex); 233 } 234 } 235 236 String[] ignoreSysProps = configuration.getStrings(CONF_IGNORE_SYS_PROPS); 237 if (ignoreSysProps != null) { 238 IGNORE_SYS_PROPS.addAll(Arrays.asList(ignoreSysProps)); 239 } 240 241 for (Map.Entry<String, String> entry : configuration) { 242 String sysValue = System.getProperty(entry.getKey()); 243 if (sysValue != null && !IGNORE_SYS_PROPS.contains(entry.getKey())) { 244 log.info("Configuration change via System Property, [{0}]=[{1}]", entry.getKey(), sysValue); 245 configuration.set(entry.getKey(), sysValue); 246 } 247 } 248 for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { 249 String name = (String) entry.getKey(); 250 if (!IGNORE_SYS_PROPS.contains(name)) { 251 if (name.startsWith("oozie.") && !name.startsWith(IGNORE_TEST_SYS_PROPS)) { 252 if (configuration.get(name) == null) { 253 log.warn("System property [{0}] no defined in Oozie configuration, ignored", name); 254 } 255 } 256 } 257 } 258 259 //Backward compatible, we should still support -Dparam. 260 for (String key : CONF_SYS_PROPS) { 261 String sysValue = System.getProperty(key); 262 if (sysValue != null && !IGNORE_SYS_PROPS.contains(key)) { 263 log.info("Overriding configuration with system property. Key [{0}], Value [{1}] ", key, sysValue); 264 configuration.set(key, sysValue); 265 } 266 } 267 268 return new LogChangesConfiguration(configuration); 269 } 270 271 private class LogChangesConfiguration extends XConfiguration { 272 273 public LogChangesConfiguration(Configuration conf) { 274 for (Map.Entry<String, String> entry : conf) { 275 if (get(entry.getKey()) == null) { 276 setValue(entry.getKey(), entry.getValue()); 277 } 278 } 279 } 280 281 public String[] getStrings(String name) { 282 String s = get(name); 283 return (s != null && s.trim().length() > 0) ? super.getStrings(name) : new String[0]; 284 } 285 286 public String get(String name, String defaultValue) { 287 String value = get(name); 288 if (value == null) { 289 boolean maskValue = MASK_PROPS.contains(name); 290 value = defaultValue; 291 String logValue = (maskValue) ? "**MASKED**" : defaultValue; 292 log.warn(XLog.OPS, "Configuration property [{0}] not found, using default [{1}]", name, logValue); 293 } 294 return value; 295 } 296 297 public void set(String name, String value) { 298 setValue(name, value); 299 boolean maskValue = MASK_PROPS.contains(name); 300 value = (maskValue) ? "**MASKED**" : value; 301 log.info(XLog.OPS, "Programmatic configuration change, property[{0}]=[{1}]", name, value); 302 } 303 304 private void setValue(String name, String value) { 305 super.set(name, value); 306 } 307 308 } 309 310 /** 311 * Instruments the configuration service. <p/> It sets instrumentation variables indicating the config dir and 312 * config file used. 313 * 314 * @param instr instrumentation to use. 315 */ 316 public void instrument(Instrumentation instr) { 317 instr.addVariable(INSTRUMENTATION_GROUP, "config.dir", new Instrumentation.Variable<String>() { 318 public String getValue() { 319 return configDir; 320 } 321 }); 322 instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() { 323 public String getValue() { 324 return configFile; 325 } 326 }); 327 } 328 329 /** 330 * Return a configuration with all sensitive values masked. 331 * 332 * @return masked configuration. 333 */ 334 public Configuration getMaskedConfiguration() { 335 XConfiguration maskedConf = new XConfiguration(); 336 Configuration conf = getConf(); 337 for (Map.Entry<String, String> entry : conf) { 338 String name = entry.getKey(); 339 String value = getValue(conf, name); 340 maskedConf.set(name, value); 341 } 342 return maskedConf; 343 } 344 345 private String getValue(Configuration config, String key) { 346 String value; 347 if (MASK_PROPS.contains(key)) { 348 value = "**MASKED**"; 349 } 350 else { 351 value = config.get(key); 352 } 353 return value; 354 } 355}