This project has retired. For details please refer to its Attic page.
Source code
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}