OOZIE-3179 Adding a configurable config-default.xml location to a workflow (jphelps via asalamon74)
diff --git a/client/src/main/java/org/apache/oozie/client/OozieClient.java b/client/src/main/java/org/apache/oozie/client/OozieClient.java
index a0f586d..1c6966f 100644
--- a/client/src/main/java/org/apache/oozie/client/OozieClient.java
+++ b/client/src/main/java/org/apache/oozie/client/OozieClient.java
@@ -174,6 +174,8 @@
public static final String LIBPATH = "oozie.libpath";
+ public static final String CONFIG_PATH = "oozie.default.configuration.path";
+
public static final String USE_SYSTEM_LIBPATH = "oozie.use.system.libpath";
public static final String OOZIE_SUSPEND_ON_NODES = "oozie.suspend.on.nodes";
diff --git a/core/src/main/java/org/apache/oozie/command/wf/SubmitXCommand.java b/core/src/main/java/org/apache/oozie/command/wf/SubmitXCommand.java
index 1352db9..c63a24e 100644
--- a/core/src/main/java/org/apache/oozie/command/wf/SubmitXCommand.java
+++ b/core/src/main/java/org/apache/oozie/command/wf/SubmitXCommand.java
@@ -135,30 +135,54 @@
Configuration fsConf = has.createConfiguration(uri.getAuthority());
FileSystem fs = has.createFileSystem(user, uri, fsConf);
- Path configDefault = null;
- Configuration defaultConf = null;
+
+
+ // Generate a list of all default configuration files to be processed
+ // A default-conf.xml in the workflow directory should have higher precedence than service defaults
+ // Adding oozie.default.configuration.path files first will allow default-conf.xml to override duplicates
+ ArrayList<Path> configDefault = new ArrayList<>();
+ Configuration defaultConf = new XConfiguration();
+
+ String[] defaultConfFiles = conf.getStrings(OozieClient.CONFIG_PATH);
+ if (defaultConfFiles != null && defaultConfFiles.length > 0) {
+ for ( String defaultConfFile : defaultConfFiles ) {
+ if (defaultConfFile.trim().length() > 0) {
+ configDefault.add(new Path(defaultConfFile.trim()));
+ }
+
+ }
+ }
+
// app path could be a directory
Path path = new Path(uri.getPath());
if (!fs.isFile(path)) {
- configDefault = new Path(path, CONFIG_DEFAULT);
+ configDefault.add(new Path(path, CONFIG_DEFAULT));
} else {
- configDefault = new Path(path.getParent(), CONFIG_DEFAULT);
+ configDefault.add(new Path(path.getParent(), CONFIG_DEFAULT));
}
- if (fs.exists(configDefault)) {
- try {
- defaultConf = new XConfiguration(fs.open(configDefault));
- PropertiesUtils.checkDisallowedProperties(defaultConf, DISALLOWED_USER_PROPERTIES);
- PropertiesUtils.checkDefaultDisallowedProperties(defaultConf);
- XConfiguration.injectDefaults(defaultConf, conf);
+ //
+ for (Path configDefaultFile: configDefault) {
+
+ LOG.debug("Loading Configuration file {0}", configDefaultFile.getName());
+ Configuration defaultConfigs = null;
+ if (fs.exists(configDefaultFile)) {
+ try {
+ defaultConfigs = new XConfiguration(fs.open(configDefaultFile));
+ PropertiesUtils.checkDisallowedProperties(defaultConfigs, DISALLOWED_USER_PROPERTIES);
+ PropertiesUtils.checkDefaultDisallowedProperties(defaultConfigs);
+ XConfiguration.injectDefaults(defaultConfigs, conf);
+
+ }
+ catch (IOException ex) {
+ throw new IOException("Failed Loading default configuration file: " +
+ configDefaultFile.getName() + ex.getMessage(), ex);
+ }
}
- catch (IOException ex) {
- throw new IOException("default configuration file, " + ex.getMessage(), ex);
+ if (defaultConfigs != null) {
+ XConfiguration.copy(resolveDefaultConfVariables(defaultConfigs),defaultConf);
}
}
- if (defaultConf != null) {
- defaultConf = resolveDefaultConfVariables(defaultConf);
- }
WorkflowApp app = wps.parseDef(conf, defaultConf);
XConfiguration protoActionConf = wps.createProtoActionConf(conf, true);
diff --git a/core/src/test/java/org/apache/oozie/command/wf/TestSubmitXCommand.java b/core/src/test/java/org/apache/oozie/command/wf/TestSubmitXCommand.java
index 2cd263f..c27ce23 100644
--- a/core/src/test/java/org/apache/oozie/command/wf/TestSubmitXCommand.java
+++ b/core/src/test/java/org/apache/oozie/command/wf/TestSubmitXCommand.java
@@ -421,6 +421,113 @@
assertEquals(getNameNodeUri()+"/default-output-dir", actionConf.get("mixed"));
}
+ public void testWFConfigPathVarResolve() throws Exception {
+ final OozieClient wfClient = LocalOozie.getClient();
+
+ OutputStream os1 = new FileOutputStream(getTestCaseDir() + "/config-default.xml");
+ XConfiguration defaultConf = new XConfiguration();
+ defaultConf.set("outputDir", "default-output-dir");
+ defaultConf.set("should_resolve", "${should.resolve}");
+ defaultConf.set("foo.bar", "default-foo-bar");
+ defaultConf.set("foobarRef", "${foo.bar}");
+ defaultConf.writeXml(os1);
+ os1.close();
+
+ OutputStream os2 = new FileOutputStream(getTestCaseDir() + "/extra-config-file1.xml");
+ XConfiguration confFile1 = new XConfiguration();
+ confFile1.set("outputDir", "default-output-dir_unused1");
+ confFile1.set("should_resolve_file1", "${should.resolve.file1}");
+ confFile1.set("key", "default_value_unused");
+ confFile1.set("foobarRef1", "${foo.bar}");
+ confFile1.writeXml(os2);
+ os2.close();
+
+
+ OutputStream os3 = new FileOutputStream(getTestCaseDir() + "/extra-config-file2.xml");
+ XConfiguration confFile2 = new XConfiguration();
+ confFile2.set("outputDir", "default-output-dir_unused2");
+ confFile2.set("should_resolve_file2", "${should.resolve.file2}");
+ confFile2.set("key", "default_value");
+ confFile2.set("foobarRef2", "${foobarRef1}");
+ confFile2.writeXml(os3);
+ os3.close();
+
+
+ String workflowUri = getTestCaseFileUri("workflow.xml");
+ String actionXml = "<map-reduce>"
+ + "<job-tracker>${jobTracker}</job-tracker>"
+ + "<name-node>${nameNode}</name-node>"
+ + " <prepare>"
+ + " <delete path=\"${nameNode}/user/${wf:user()}/mr/${outputDir}\"/>"
+ + " </prepare>"
+ + " <configuration>"
+ + " <property><name>bb</name><value>BB</value></property>"
+ + " <property><name>cc</name><value>from_action</value></property>"
+ + " </configuration>"
+ + " </map-reduce>";
+ String wfXml = "<workflow-app xmlns=\"uri:oozie:workflow:0.5\" name=\"map-reduce-wf\">"
+ + " <start to=\"mr-node\"/>"
+ + " <action name=\"mr-node\">"
+ + actionXml
+ + " <ok to=\"end\"/>"
+ + " <error to=\"fail\"/>"
+ + "</action>"
+ + "<kill name=\"fail\">"
+ + " <message>Map/Reduce failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>"
+ + "</kill>"
+ + "<end name=\"end\"/>"
+ + "</workflow-app>";
+
+ writeToFile(wfXml, workflowUri);
+ Configuration conf = new XConfiguration();
+ conf.set("nameNode", getNameNodeUri());
+ conf.set("jobTracker", getJobTrackerUri());
+ conf.set("foobarRef", "foobarRef");
+ conf.set(OozieClient.APP_PATH, workflowUri);
+ conf.set(OozieClient.USER_NAME, getTestUser());
+ conf.set("should.resolve", "resolved");
+ conf.set("should.resolve.file1", "resolved");
+ conf.set("should.resolve.file2", "resolved");
+ conf.set("oozie.default.configuration.path", getTestCaseDir() + "/extra-config-file1.xml,"
+ + getTestCaseDir() + "/extra-config-file2.xml");
+ SubmitXCommand sc = new SubmitXCommand(conf);
+ final String jobId = sc.call();
+ new StartXCommand(jobId).call();
+ waitFor(15 * 1000, new Predicate() {
+ public boolean evaluate() throws Exception {
+ return wfClient.getJobInfo(jobId).getStatus() == WorkflowJob.Status.KILLED;
+ }
+ });
+ String actionId = jobId + "@mr-node";
+ WorkflowActionBean action = WorkflowActionQueryExecutor.getInstance().get(WorkflowActionQueryExecutor.WorkflowActionQuery
+ .GET_ACTION, actionId);
+ Element eAction = XmlUtils.parseXml(action.getConf());
+ Element eConf = eAction.getChild("configuration", eAction.getNamespace());
+ Configuration actionConf = new XConfiguration(new StringReader(XmlUtils.prettyPrint(eConf).toString()));
+ assertEquals("Variable should_resolve!='resolved' from config-default.xml",
+ "resolved", actionConf.get("should_resolve"));
+ assertEquals("Variable 'should_resolve_file1' from extra-config-file1 did not resolve correctly",
+ "resolved", actionConf.get("should_resolve_file1"));
+ assertEquals("Variable 'should_resolve_file2' from extra-config-file2 did not resolve correctly",
+ "resolved", actionConf.get("should_resolve_file2"));
+ assertEquals("Variable 'outputDir' from config-default did not resolve correctly",
+ "default-output-dir", actionConf.get("outputDir"));
+ assertEquals("Variable 'bb' from workflow did not resolve correctly",
+ "BB", actionConf.get("bb"));
+ assertEquals("Variable 'cc' from workflow did not resolve correctly",
+ "from_action", actionConf.get("cc"));
+ assertEquals("Variable 'foo.bar' from config-default did not resolve correctly",
+ "default-foo-bar", actionConf.get("foo.bar"));
+ assertEquals("Variable 'foobarRef' from config-default did not resolve correctly",
+ "default-foo-bar", actionConf.get("foobarRef"));
+ assertEquals("Variable 'foobarRef1' from extra-config-file1 did not resolve correctly",
+ "default-foo-bar", actionConf.get("foobarRef1"));
+ assertEquals("Variable 'foobarRef2' from extra-config-file2 did not resolve correctly",
+ "default-foo-bar", actionConf.get("foobarRef2"));
+ assertEquals("Variable 'key' from extra-config-file2 did not resolve correctly",
+ "default_value", actionConf.get("key"));
+ }
+
private void writeToFile(String appXml, String appPath) throws IOException {
File wf = new File(URI.create(appPath));
diff --git a/release-log.txt b/release-log.txt
index c5cbc5f..d777a38 100644
--- a/release-log.txt
+++ b/release-log.txt
@@ -1,5 +1,6 @@
-- Oozie 5.2.0 release (trunk - unreleased)
+OOZIE-3179 Adding a configurable config-default.xml location to a workflow (jphelps via asalamon74)
OOZIE-3405 SSH action shows empty error Message and Error code (matijhs via asalamon74)
OOZIE-3542 Handle better old Hdfs implementations in ECPolicyDisabler (zsombor dionusos via kmarton)
OOZIE-3540 Use StringBuilder instead of StringBuffer if concurrent access is not required (zsombor via asalamon74)