And the error message from the Oozie server looked like below:Action failed, error message[${wf:errorMessage(wf:lastErrorNode())}] [email protected] ls / >> /tmp/test.log
2018-01-03 06:12:45,347 ERROR org.apache.oozie.command.wf.ActionStartXCommand: SERVER[{oozie-server-url}] USER[admin] GROUP[-] TOKEN[] APP[SSH Action Test] JOB[0000000-180103010440574-ooz ie-oozi-W] ACTION[[email protected]] Exception, java.lang.IllegalArgumentException: externalId cannot be empty at org.apache.oozie.util.ParamChecker.notEmpty(ParamChecker.java:90) at org.apache.oozie.util.ParamChecker.notEmpty(ParamChecker.java:74) at org.apache.oozie.WorkflowActionBean.setStartData(WorkflowActionBean.java:503) at org.apache.oozie.command.wf.ActionXCommand$ActionExecutorContext.setStartData(ActionXCommand.java:387) at org.apache.oozie.action.ssh.SshActionExecutor.start(SshActionExecutor.java:269) at org.apache.oozie.command.wf.ActionStartXCommand.execute(ActionStartXCommand.java:232) at org.apache.oozie.command.wf.ActionStartXCommand.execute(ActionStartXCommand.java:63) at org.apache.oozie.command.XCommand.call(XCommand.java:286) at org.apache.oozie.service.CallableQueueService$CompositeCallable.call(CallableQueueService.java:332) at org.apache.oozie.service.CallableQueueService$CompositeCallable.call(CallableQueueService.java:261) at java.util.concurrent.FutureTask.run(FutureTask.java:262) at org.apache.oozie.service.CallableQueueService$CallableWrapper.run(CallableQueueService.java:179) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:745)We confirmed that the passwordless connection from the Ooize server to the remote server worked correctly without issues. After digging through the Oozie source code, I found out that it was due to the fact that Oozie uses Java’s Runtime.exec library to execute the commands remotely. And Runtime.exec does not work in the same way as shell, especially when re-directing output to a file, which Runtime.exec does not support at all. What happened under the hood was that Oozie will split the full command “ls / >> /tmp/test.log” into tokens “ls”, “/”, “>>”, “/tmp/test.log”, and pass all of them into Runtime.exec. And when Runtime.exec executed the command, it treated all tokens, apart from “ls” as the parameters to “ls” command. As you would expect, “>>” is not a file, and “ls” command will fail complain that file does not exist, hence will return exit status of 1, rather than 0. Oozie tried to capture the PID of the remote process, but failed, and hence returned “externalId cannot be empty” error. The workaround is simple, just store the full command you want to run into a new script file and ask Oozie to execute that script instead: 1. Create a file “ssh-action.sh” on the target host, for example, under /home/{user}/scripts/ssh-action.sh 2. Add command “ls / >> /tmp/ssh.log” to the file 3. Make the file executable by running:
chmod 744 /home/{user}/scripts/ssh-action.sh4. Update Oozie workflow to run the new shell script instead:
And then the SSH action should work perfectly.[email protected] /home/{user}/scripts/ssh-action.sh
Thanks a lot for your post, it helped me a lot when solving my SSH action in Oozie.