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