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 */ 018 019package org.apache.oozie.service; 020 021import org.apache.hadoop.conf.Configuration; 022import org.apache.oozie.util.ConfigUtils; 023import org.apache.oozie.util.Instrumentable; 024import org.apache.oozie.util.Instrumentation; 025import org.apache.oozie.util.XLog; 026import org.apache.oozie.util.XConfiguration; 027import org.apache.oozie.ErrorCode; 028 029import java.io.File; 030import java.io.FileInputStream; 031import java.io.IOException; 032import java.io.InputStream; 033import java.io.StringWriter; 034import java.util.HashMap; 035import java.util.HashSet; 036import java.util.Map; 037import java.util.Set; 038import java.util.Arrays; 039 040import org.apache.oozie.util.ZKUtils; 041 042import com.google.common.annotations.VisibleForTesting; 043 044/** 045 * Built in service that initializes the services configuration. 046 * <p/> 047 * The configuration loading sequence is identical to Hadoop configuration loading sequence. 048 * <p/> 049 * Default values are loaded from the 'oozie-default.xml' file from the classpath, then site configured values 050 * are loaded from a site configuration file from the Oozie configuration directory. 051 * <p/> 052 * The Oozie configuration directory is resolved using the <code>OOZIE_HOME<code> environment variable as 053 * <code>${OOZIE_HOME}/conf</code>. If the <code>OOZIE_HOME<code> environment variable is not defined the 054 * initialization of the <code>ConfigurationService</code> fails. 055 * <p/> 056 * The site configuration is loaded from the <code>oozie-site.xml</code> file in the configuration directory. 057 * <p/> 058 * The site configuration file name to use can be changed by setting the <code>OOZIE_CONFIG_FILE</code> environment 059 * variable to an alternate file name. The alternate file must ber in the Oozie configuration directory. 060 * <p/> 061 * Configuration properties, prefixed with 'oozie.', passed as system properties overrides default and site values. 062 * <p/> 063 * The configuration service logs details on how the configuration was loaded as well as what properties were overrode 064 * via system properties settings. 065 */ 066public class ConfigurationService implements Service, Instrumentable { 067 private static final String INSTRUMENTATION_GROUP = "configuration"; 068 069 public static final String CONF_PREFIX = Service.CONF_PREFIX + "ConfigurationService."; 070 071 public static final String CONF_IGNORE_SYS_PROPS = CONF_PREFIX + "ignore.system.properties"; 072 073 public static final String CONF_VERIFY_AVAILABLE_PROPS = CONF_PREFIX + "verify.available.properties"; 074 075 /** 076 * System property that indicates the configuration directory. 077 */ 078 public static final String OOZIE_CONFIG_DIR = "oozie.config.dir"; 079 080 081 /** 082 * System property that indicates the data directory. 083 */ 084 public static final String OOZIE_DATA_DIR = "oozie.data.dir"; 085 086 /** 087 * System property that indicates the name of the site configuration file to load. 088 */ 089 public static final String OOZIE_CONFIG_FILE = "oozie.config.file"; 090 091 private static final Set<String> IGNORE_SYS_PROPS = new HashSet<String>(); 092 private static final Set<String> CONF_SYS_PROPS = new HashSet<String>(); 093 094 private static final String IGNORE_TEST_SYS_PROPS = "oozie.test."; 095 private static final Set<String> MASK_PROPS = new HashSet<String>(); 096 private static Map<String,String> defaultConfigs = new HashMap<String,String>(); 097 098 static { 099 100 //all this properties are seeded as system properties, no need to log changes 101 IGNORE_SYS_PROPS.add(CONF_IGNORE_SYS_PROPS); 102 IGNORE_SYS_PROPS.add(Services.OOZIE_HOME_DIR); 103 IGNORE_SYS_PROPS.add(OOZIE_CONFIG_DIR); 104 IGNORE_SYS_PROPS.add(OOZIE_CONFIG_FILE); 105 IGNORE_SYS_PROPS.add(OOZIE_DATA_DIR); 106 IGNORE_SYS_PROPS.add(XLogService.OOZIE_LOG_DIR); 107 IGNORE_SYS_PROPS.add(XLogService.LOG4J_FILE); 108 IGNORE_SYS_PROPS.add(XLogService.LOG4J_RELOAD); 109 110 CONF_SYS_PROPS.add("oozie.http.hostname"); 111 CONF_SYS_PROPS.add("oozie.http.port"); 112 CONF_SYS_PROPS.add(ZKUtils.OOZIE_INSTANCE_ID); 113 114 // These properties should be masked when displayed because they contain sensitive info (e.g. password) 115 MASK_PROPS.add(JPAService.CONF_PASSWORD); 116 MASK_PROPS.add("oozie.authentication.signature.secret"); 117 } 118 119 public static final String DEFAULT_CONFIG_FILE = "oozie-default.xml"; 120 public static final String SITE_CONFIG_FILE = "oozie-site.xml"; 121 122 private static XLog log = XLog.getLog(ConfigurationService.class); 123 124 private String configDir; 125 private String configFile; 126 127 private LogChangesConfiguration configuration; 128 129 public ConfigurationService() { 130 log = XLog.getLog(ConfigurationService.class); 131 } 132 133 /** 134 * Initialize the log service. 135 * 136 * @param services services instance. 137 * @throws ServiceException thrown if the log service could not be initialized. 138 */ 139 public void init(Services services) throws ServiceException { 140 configDir = getConfigurationDirectory(); 141 configFile = System.getProperty(OOZIE_CONFIG_FILE, SITE_CONFIG_FILE); 142 if (configFile.contains("/")) { 143 throw new ServiceException(ErrorCode.E0022, configFile); 144 } 145 log.info("Oozie home dir [{0}]", Services.getOozieHome()); 146 log.info("Oozie conf dir [{0}]", configDir); 147 log.info("Oozie conf file [{0}]", configFile); 148 configFile = new File(configDir, configFile).toString(); 149 configuration = loadConf(); 150 if (configuration.getBoolean(CONF_VERIFY_AVAILABLE_PROPS, false)) { 151 verifyConfigurationName(); 152 } 153 } 154 155 public static String getConfigurationDirectory() throws ServiceException { 156 String oozieHome = Services.getOozieHome(); 157 String configDir = System.getProperty(OOZIE_CONFIG_DIR, null); 158 File file = configDir == null 159 ? new File(oozieHome, "conf") 160 : new File(configDir); 161 if (!file.exists()) { 162 throw new ServiceException(ErrorCode.E0024, configDir); 163 } 164 return file.getPath(); 165 } 166 167 /** 168 * Destroy the configuration service. 169 */ 170 public void destroy() { 171 configuration = null; 172 } 173 174 /** 175 * Return the public interface for configuration service. 176 * 177 * @return {@link ConfigurationService}. 178 */ 179 public Class<? extends Service> getInterface() { 180 return ConfigurationService.class; 181 } 182 183 /** 184 * Return the services configuration. 185 * 186 * @return the services configuration. 187 */ 188 public Configuration getConf() { 189 if (configuration == null) { 190 throw new IllegalStateException("Not initialized"); 191 } 192 return configuration; 193 } 194 195 /** 196 * Return Oozie configuration directory. 197 * 198 * @return Oozie configuration directory. 199 */ 200 public String getConfigDir() { 201 return configDir; 202 } 203 204 private InputStream getDefaultConfiguration() throws ServiceException, IOException { 205 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 206 InputStream inputStream = classLoader.getResourceAsStream(DEFAULT_CONFIG_FILE); 207 if (inputStream == null) { 208 throw new ServiceException(ErrorCode.E0023, DEFAULT_CONFIG_FILE); 209 } 210 return inputStream; 211 } 212 213 private LogChangesConfiguration loadConf() throws ServiceException { 214 XConfiguration configuration; 215 try { 216 InputStream inputStream = getDefaultConfiguration(); 217 configuration = loadConfig(inputStream, true); 218 File file = new File(configFile); 219 if (!file.exists()) { 220 log.info("Missing site configuration file [{0}]", configFile); 221 } 222 else { 223 inputStream = new FileInputStream(configFile); 224 XConfiguration siteConfiguration = loadConfig(inputStream, false); 225 XConfiguration.injectDefaults(configuration, siteConfiguration); 226 configuration = siteConfiguration; 227 } 228 } 229 catch (IOException ex) { 230 throw new ServiceException(ErrorCode.E0024, configFile, ex.getMessage(), ex); 231 } 232 233 if (log.isTraceEnabled()) { 234 try { 235 StringWriter writer = new StringWriter(); 236 for (Map.Entry<String, String> entry : configuration) { 237 String value = getValue(configuration, entry.getKey()); 238 writer.write(" " + entry.getKey() + " = " + value + "\n"); 239 } 240 writer.close(); 241 log.trace("Configuration:\n{0}---", writer.toString()); 242 } 243 catch (IOException ex) { 244 throw new ServiceException(ErrorCode.E0025, ex.getMessage(), ex); 245 } 246 } 247 248 String[] ignoreSysProps = configuration.getStrings(CONF_IGNORE_SYS_PROPS); 249 if (ignoreSysProps != null) { 250 IGNORE_SYS_PROPS.addAll(Arrays.asList(ignoreSysProps)); 251 } 252 253 for (Map.Entry<String, String> entry : configuration) { 254 String sysValue = System.getProperty(entry.getKey()); 255 if (sysValue != null && !IGNORE_SYS_PROPS.contains(entry.getKey())) { 256 log.info("Configuration change via System Property, [{0}]=[{1}]", entry.getKey(), sysValue); 257 configuration.set(entry.getKey(), sysValue); 258 } 259 } 260 for (Map.Entry<Object, Object> entry : System.getProperties().entrySet()) { 261 String name = (String) entry.getKey(); 262 if (!IGNORE_SYS_PROPS.contains(name)) { 263 if (name.startsWith("oozie.") && !name.startsWith(IGNORE_TEST_SYS_PROPS)) { 264 if (configuration.get(name) == null) { 265 log.warn("System property [{0}] no defined in Oozie configuration, ignored", name); 266 } 267 } 268 } 269 } 270 271 //Backward compatible, we should still support -Dparam. 272 for (String key : CONF_SYS_PROPS) { 273 String sysValue = System.getProperty(key); 274 if (sysValue != null && !IGNORE_SYS_PROPS.contains(key)) { 275 log.info("Overriding configuration with system property. Key [{0}], Value [{1}] ", key, sysValue); 276 configuration.set(key, sysValue); 277 } 278 } 279 280 return new LogChangesConfiguration(configuration); 281 } 282 283 private XConfiguration loadConfig(InputStream inputStream, boolean defaultConfig) throws IOException, ServiceException { 284 XConfiguration configuration; 285 configuration = new XConfiguration(inputStream); 286 for(Map.Entry<String,String> entry: configuration) { 287 if (defaultConfig) { 288 defaultConfigs.put(entry.getKey(), entry.getValue()); 289 } 290 else { 291 log.debug("Overriding configuration with oozie-site, [{0}]", entry.getKey()); 292 } 293 } 294 return configuration; 295 } 296 297 private class LogChangesConfiguration extends XConfiguration { 298 299 public LogChangesConfiguration(Configuration conf) { 300 for (Map.Entry<String, String> entry : conf) { 301 if (get(entry.getKey()) == null) { 302 setValue(entry.getKey(), entry.getValue()); 303 } 304 } 305 } 306 307 public String[] getStrings(String name) { 308 String s = get(name); 309 return (s != null && s.trim().length() > 0) ? super.getStrings(name) : new String[0]; 310 } 311 312 public String[] getStrings(String name, String[] defaultValue) { 313 String s = get(name); 314 if (s == null) { 315 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, 316 Arrays.asList(defaultValue).toString()); 317 } 318 return (s != null && s.trim().length() > 0) ? super.getStrings(name) : defaultValue; 319 } 320 321 public String get(String name, String defaultValue) { 322 String value = get(name); 323 if (value == null) { 324 boolean maskValue = MASK_PROPS.contains(name); 325 value = defaultValue; 326 String logValue = (maskValue) ? "**MASKED**" : defaultValue; 327 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, logValue); 328 } 329 return value; 330 } 331 332 public void set(String name, String value) { 333 setValue(name, value); 334 boolean maskValue = MASK_PROPS.contains(name); 335 value = (maskValue) ? "**MASKED**" : value; 336 log.info(XLog.OPS, "Programmatic configuration change, property[{0}]=[{1}]", name, value); 337 } 338 339 public boolean getBoolean(String name, boolean defaultValue) { 340 String value = get(name); 341 if (value == null) { 342 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 343 } 344 return super.getBoolean(name, defaultValue); 345 } 346 347 public int getInt(String name, int defaultValue) { 348 String value = get(name); 349 if (value == null) { 350 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 351 } 352 return super.getInt(name, defaultValue); 353 } 354 355 public long getLong(String name, long defaultValue) { 356 String value = get(name); 357 if (value == null) { 358 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 359 } 360 return super.getLong(name, defaultValue); 361 } 362 363 public float getFloat(String name, float defaultValue) { 364 String value = get(name); 365 if (value == null) { 366 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 367 } 368 return super.getFloat(name, defaultValue); 369 } 370 371 public Class<?>[] getClasses(String name, Class<?> ... defaultValue) { 372 String value = get(name); 373 if (value == null) { 374 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 375 } 376 return super.getClasses(name, defaultValue); 377 } 378 379 public Class<?> getClass(String name, Class<?> defaultValue) { 380 String value = get(name); 381 if (value == null) { 382 log.debug(XLog.OPS, "Configuration property [{0}] not found, use given value [{1}]", name, defaultValue); 383 return defaultValue; 384 } 385 try { 386 return getClassByName(value); 387 } catch (ClassNotFoundException e) { 388 throw new RuntimeException(e); 389 } 390 } 391 392 private void setValue(String name, String value) { 393 super.set(name, value); 394 } 395 396 } 397 398 /** 399 * Instruments the configuration service. <p/> It sets instrumentation variables indicating the config dir and 400 * config file used. 401 * 402 * @param instr instrumentation to use. 403 */ 404 public void instrument(Instrumentation instr) { 405 instr.addVariable(INSTRUMENTATION_GROUP, "config.dir", new Instrumentation.Variable<String>() { 406 public String getValue() { 407 return configDir; 408 } 409 }); 410 instr.addVariable(INSTRUMENTATION_GROUP, "config.file", new Instrumentation.Variable<String>() { 411 public String getValue() { 412 return configFile; 413 } 414 }); 415 } 416 417 /** 418 * Return a configuration with all sensitive values masked. 419 * 420 * @return masked configuration. 421 */ 422 public Configuration getMaskedConfiguration() { 423 XConfiguration maskedConf = new XConfiguration(); 424 Configuration conf = getConf(); 425 for (Map.Entry<String, String> entry : conf) { 426 String name = entry.getKey(); 427 String value = getValue(conf, name); 428 maskedConf.set(name, value); 429 } 430 return maskedConf; 431 } 432 433 private String getValue(Configuration config, String key) { 434 String value; 435 if (MASK_PROPS.contains(key)) { 436 value = "**MASKED**"; 437 } 438 else { 439 value = config.get(key); 440 } 441 return value; 442 } 443 444 445 /** 446 * Gets the oozie configuration value in oozie-default. 447 * @param name 448 * @return the configuration value of the <code>name</code> otherwise null 449 */ 450 private String getDefaultOozieConfig(String name) { 451 return defaultConfigs.get(name); 452 } 453 454 /** 455 * Verify the configuration is in oozie-default 456 */ 457 public void verifyConfigurationName() { 458 for (Map.Entry<String, String> entry: configuration) { 459 if (getDefaultOozieConfig(entry.getKey()) == null) { 460 log.warn("Invalid configuration defined, [{0}] ", entry.getKey()); 461 } 462 } 463 } 464 465 @VisibleForTesting 466 public static void set(String name, String value) { 467 Configuration conf = Services.get().getConf(); 468 conf.set(name, value); 469 } 470 471 @VisibleForTesting 472 public static void setBoolean(String name, boolean value) { 473 Configuration conf = Services.get().getConf(); 474 conf.setBoolean(name, value); 475 } 476 477 public static String get(String name) { 478 Configuration conf = Services.get().getConf(); 479 return get(conf, name); 480 } 481 482 public static String get(Configuration conf, String name) { 483 return conf.get(name, ConfigUtils.STRING_DEFAULT); 484 } 485 486 public static String[] getStrings(String name) { 487 Configuration conf = Services.get().getConf(); 488 return getStrings(conf, name); 489 } 490 491 public static String[] getStrings(Configuration conf, String name) { 492 return conf.getStrings(name, new String[0]); 493 } 494 495 public static boolean getBoolean(String name) { 496 Configuration conf = Services.get().getConf(); 497 return getBoolean(conf, name); 498 } 499 500 public static boolean getBoolean(Configuration conf, String name) { 501 return conf.getBoolean(name, ConfigUtils.BOOLEAN_DEFAULT); 502 } 503 504 public static int getInt(String name) { 505 Configuration conf = Services.get().getConf(); 506 return getInt(conf, name); 507 } 508 509 public static int getInt(Configuration conf, String name) { 510 return conf.getInt(name, ConfigUtils.INT_DEFAULT); 511 } 512 513 public static float getFloat(String name) { 514 Configuration conf = Services.get().getConf(); 515 return conf.getFloat(name, ConfigUtils.FLOAT_DEFAULT); 516 } 517 518 public static long getLong(String name) { 519 Configuration conf = Services.get().getConf(); 520 return getLong(conf, name); 521 } 522 523 public static long getLong(Configuration conf, String name) { 524 return conf.getLong(name, ConfigUtils.LONG_DEFAULT); 525 } 526 527 public static Class<?>[] getClasses(String name) { 528 Configuration conf = Services.get().getConf(); 529 return getClasses(conf, name); 530 } 531 532 public static Class<?>[] getClasses(Configuration conf, String name) { 533 return conf.getClasses(name); 534 } 535 536 public static Class<?> getClass(Configuration conf, String name) { 537 return conf.getClass(name, Object.class); 538 } 539 540}