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