Repository: oozie Updated Branches: refs/heads/master 168de6607 -> cc94ad8e5
OOZIE-2159 'oozie validate' command should be moved server-side (seoeun25 via rkanter) Project: http://git-wip-us.apache.org/repos/asf/oozie/repo Commit: http://git-wip-us.apache.org/repos/asf/oozie/commit/cc94ad8e Tree: http://git-wip-us.apache.org/repos/asf/oozie/tree/cc94ad8e Diff: http://git-wip-us.apache.org/repos/asf/oozie/diff/cc94ad8e Branch: refs/heads/master Commit: cc94ad8e56c47a5b647f5da3a3dd9e8c5200fb30 Parents: 168de66 Author: Robert Kanter <[email protected]> Authored: Tue Jun 23 16:52:09 2015 -0700 Committer: Robert Kanter <[email protected]> Committed: Tue Jun 23 16:52:09 2015 -0700 ---------------------------------------------------------------------- .../java/org/apache/oozie/cli/OozieCLI.java | 42 +- .../org/apache/oozie/client/OozieClient.java | 64 ++ .../org/apache/oozie/client/rest/JsonTags.java | 3 + .../apache/oozie/client/rest/RestConstants.java | 7 + .../org/apache/oozie/cli/TestValidation.java | 34 +- .../apache/oozie/servlet/V2ValidateServlet.java | 150 ++++ .../org/apache/oozie/client/TestOozieCLI.java | 25 +- .../oozie/servlet/TestV2ValidateServlet.java | 676 +++++++++++++++++++ distro/src/main/tomcat/ssl-web.xml | 12 + docs/src/site/twiki/DG_CommandLineTool.twiki | 23 +- docs/src/site/twiki/WebServicesAPI.twiki | 69 ++ release-log.txt | 1 + webapp/src/main/webapp/WEB-INF/web.xml | 12 + 13 files changed, 1102 insertions(+), 16 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/client/src/main/java/org/apache/oozie/cli/OozieCLI.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/cli/OozieCLI.java b/client/src/main/java/org/apache/oozie/cli/OozieCLI.java index c381432..48bac7d 100644 --- a/client/src/main/java/org/apache/oozie/cli/OozieCLI.java +++ b/client/src/main/java/org/apache/oozie/cli/OozieCLI.java @@ -514,6 +514,19 @@ public class OozieCLI { } /** + * Create option for command line option 'validate' + * + * @return validate options + */ + protected Options createValidateOptions() { + Option oozie = new Option(OOZIE_OPTION, true, "Oozie URL"); + Options validateOption = new Options(); + validateOption.addOption(oozie); + addAuthOptions(validateOption); + return validateOption; + } + + /** * Create option for command line option 'pig' or 'hive' * @return pig or hive options */ @@ -652,7 +665,7 @@ public class OozieCLI { parser.addCommand(JOB_CMD, "", "job operations", createJobOptions(), false); parser.addCommand(JOBS_CMD, "", "jobs status", createJobsOptions(), false); parser.addCommand(ADMIN_CMD, "", "admin operations", createAdminOptions(), false); - parser.addCommand(VALIDATE_CMD, "", "validate a workflow XML file", new Options(), true); + parser.addCommand(VALIDATE_CMD, "", "validate a workflow, coordinator, bundle XML file", createValidateOptions(), true); parser.addCommand(SLA_CMD, "", "sla operations (Deprecated with Oozie 4.0)", createSlaOptions(), false); parser.addCommand(PIG_CMD, "-X ", "submit a pig job, everything after '-X' are pass-through parameters to pig, any '-D' " + "arguments after '-X' are put in <configuration>", createScriptLanguageOptions(PIG_CMD), true); @@ -1985,6 +1998,33 @@ public class OozieCLI { if (args.length != 1) { throw new OozieCLIException("One file must be specified"); } + try { + XOozieClient wc = createXOozieClient(commandLine); + String result = wc.validateXML(args[0].toString()); + if (result == null) { + // TODO This is only for backward compatibility. Need to remove after 4.2.0 higher version. + System.out.println("Using client-side validation. Check out Oozie server version."); + validateCommandV41(commandLine); + return; + } + System.out.println(result); + } catch (OozieClientException e) { + throw new OozieCLIException(e.getMessage(), e); + } + } + + /** + * Validate on client-side. This is only for backward compatibility. Need to removed after <tt>4.2.0</tt> higher version. + * @param commandLine + * @throws OozieCLIException + */ + @Deprecated + @VisibleForTesting + void validateCommandV41(CommandLine commandLine) throws OozieCLIException { + String[] args = commandLine.getArgs(); + if (args.length != 1) { + throw new OozieCLIException("One file must be specified"); + } File file = new File(args[0]); if (file.exists()) { try { http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/client/src/main/java/org/apache/oozie/client/OozieClient.java ---------------------------------------------------------------------- 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 6b7e750..67a62c6 100644 --- a/client/src/main/java/org/apache/oozie/client/OozieClient.java +++ b/client/src/main/java/org/apache/oozie/client/OozieClient.java @@ -35,6 +35,8 @@ import javax.xml.transform.TransformerFactory; import javax.xml.transform.dom.DOMSource; import javax.xml.transform.stream.StreamResult; import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -2023,6 +2025,42 @@ public class OozieClient { } } + private class ValidateXML extends ClientCallable<String> { + + String file = null; + + ValidateXML(String file, String user) { + super("POST", RestConstants.VALIDATE, "", + prepareParams(RestConstants.FILE_PARAM, file, RestConstants.USER_PARAM, user)); + this.file = file; + } + + @Override + protected String call(HttpURLConnection conn) throws IOException, OozieClientException { + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + if (file.startsWith("/")) { + FileInputStream fi = new FileInputStream(new File(file)); + byte[] buffer = new byte[1024]; + int n = 0; + while (-1 != (n = fi.read(buffer))) { + conn.getOutputStream().write(buffer, 0, n); + } + } + if ((conn.getResponseCode() == HttpURLConnection.HTTP_OK)) { + Reader reader = new InputStreamReader(conn.getInputStream()); + JSONObject json = (JSONObject) JSONValue.parse(reader); + return (String) json.get(JsonTags.VALIDATE); + } + else if ((conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND)) { + return null; + } + else { + handleError(conn); + } + return null; + } + } + private class UpdateSharelib extends ClientCallable<String> { @@ -2123,6 +2161,32 @@ public class OozieClient { } /** + * Return the workflow application is valid. + * + * @param file local file or hdfs file. + * @return the workflow application is valid. + * @throws OozieClientException throw if it the workflow application's validation could not be retrieved. + */ + public String validateXML(String file) throws OozieClientException { + String fileName = file; + if (file.startsWith("file://")) { + fileName = file.substring(7, file.length()); + } + if (!fileName.contains("://")) { + File f = new File(fileName); + if (!f.isFile()) { + throw new OozieClientException("File error", "File does not exist : " + f.getAbsolutePath()); + } + fileName = f.getAbsolutePath(); + } + String user = USER_NAME_TL.get(); + if (user == null) { + user = System.getProperty("user.name"); + } + return new ValidateXML(fileName, user).call(); + } + + /** * Return the info of the coordinator jobs that match the filter. * * @param filter job filter. Refer to the {@link OozieClient} for the filter syntax. http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java b/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java index ab6699f..b3b148a 100644 --- a/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java +++ b/client/src/main/java/org/apache/oozie/client/rest/JsonTags.java @@ -244,4 +244,7 @@ public interface JsonTags { public static final String COORD_UPDATE_DIFF = "diff"; public static final String STATUS = "status"; + + public static final String VALIDATE = "validate"; + } http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java ---------------------------------------------------------------------- diff --git a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java index 2434b19..3c69319 100644 --- a/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java +++ b/client/src/main/java/org/apache/oozie/client/rest/RestConstants.java @@ -208,4 +208,11 @@ public interface RestConstants { public static final String SLA_MAX_DURATION = "sla-max-duration"; public static final String JOB_COORD_SCOPE_ACTION_LIST = "action-list"; + + public static final String VALIDATE = "validate"; + + public static final String FILE_PARAM = "file"; + + public static final String USER_PARAM = "user"; + } http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/client/src/test/java/org/apache/oozie/cli/TestValidation.java ---------------------------------------------------------------------- diff --git a/client/src/test/java/org/apache/oozie/cli/TestValidation.java b/client/src/test/java/org/apache/oozie/cli/TestValidation.java index 76c1445..82d4430 100644 --- a/client/src/test/java/org/apache/oozie/cli/TestValidation.java +++ b/client/src/test/java/org/apache/oozie/cli/TestValidation.java @@ -19,7 +19,11 @@ package org.apache.oozie.cli; import junit.framework.TestCase; +import org.apache.commons.cli.CommandLine; +import org.apache.commons.cli.ParseException; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; import java.net.URL; import java.net.URI; import java.io.File; @@ -35,11 +39,37 @@ public class TestValidation extends TestCase { public void testValid() throws Exception { String[] args = new String[]{"validate", getPath("valid.xml")}; - assertEquals(0, new OozieCLI().run(args)); + assertTrue(captureOutput(args).contains("Valid workflow-app")); } public void testInvalid() throws Exception { String[] args = new String[]{"validate", getPath("invalid.xml")}; - assertEquals(-1, new OozieCLI().run(args)); + assertTrue(captureOutput(args).contains("Invalid app definition")); + } + + private String captureOutput(String[] args) throws ParseException { + OozieCLI cli = new OozieCLI(); + CLIParser parser = cli.getCLIParser(); + CLIParser.Command command = parser.parse(args); + PrintStream original = System.out; + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + PrintStream ps = new PrintStream(baos); + String outStr = null; + System.out.flush(); + try { + System.setOut(ps); + cli.validateCommandV41(command.getCommandLine()); + System.out.flush(); + outStr = baos.toString(); + } catch (OozieCLIException e) { + outStr = e.getMessage(); + } finally { + System.setOut(original); + if (outStr != null) { + System.out.print(outStr); + } + System.out.flush(); + } + return outStr; } } http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/core/src/main/java/org/apache/oozie/servlet/V2ValidateServlet.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/oozie/servlet/V2ValidateServlet.java b/core/src/main/java/org/apache/oozie/servlet/V2ValidateServlet.java new file mode 100644 index 0000000..dbb3d49 --- /dev/null +++ b/core/src/main/java/org/apache/oozie/servlet/V2ValidateServlet.java @@ -0,0 +1,150 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.oozie.servlet; + +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.fs.FileSystem; +import org.apache.hadoop.fs.Path; +import org.apache.oozie.ErrorCode; +import org.apache.oozie.cli.OozieCLIException; +import org.apache.oozie.client.rest.JsonTags; +import org.apache.oozie.client.rest.RestConstants; +import org.apache.oozie.service.HadoopAccessorService; +import org.apache.oozie.service.SchemaService; +import org.apache.oozie.service.Services; +import org.apache.oozie.util.IOUtils; +import org.json.simple.JSONObject; +import org.xml.sax.SAXException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.xml.transform.stream.StreamSource; +import javax.xml.validation.Schema; +import javax.xml.validation.Validator; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.io.StringReader; +import java.io.StringWriter; +import java.net.URI; +import java.util.Arrays; + +public class V2ValidateServlet extends JsonRestServlet { + private static final String INSTRUMENTATION_NAME = "v2validate"; + + private static final ResourceInfo RESOURCE_INFO = + new ResourceInfo("", Arrays.asList("POST"), Arrays.asList( + new ParameterInfo(RestConstants.FILE_PARAM, String.class, true, Arrays.asList("POST")), + new ParameterInfo(RestConstants.USER_PARAM, String.class, true, Arrays.asList("POST")))); + + + public V2ValidateServlet() { + super(INSTRUMENTATION_NAME, RESOURCE_INFO); + } + + /** + * Validate workflow definition. + */ + @Override + @SuppressWarnings("unchecked") + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { + validateContentType(request, RestConstants.XML_CONTENT_TYPE); + + String file = request.getParameter(RestConstants.FILE_PARAM); + String user = request.getParameter(RestConstants.USER_PARAM); + + stopCron(); + + StringWriter stringWriter = new StringWriter(); + if (file.startsWith("hdfs://")) { + try { + URI uri = new URI(file); + HadoopAccessorService has = Services.get().get(HadoopAccessorService.class); + Configuration fsConf = has.createJobConf(uri.getAuthority()); + FileSystem fs = has.createFileSystem(user, uri, fsConf); + + Path path = new Path(uri.getPath()); + IOUtils.copyCharStream(new InputStreamReader(fs.open(path)), stringWriter); + + } catch (Exception e) { + throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0505, + "File does not exist, "+ file); + } + } + else { + IOUtils.copyCharStream(new InputStreamReader(request.getInputStream()), stringWriter); + } + try { + validate(stringWriter.toString()); + } catch (Exception e) { + throw new XServletException(HttpServletResponse.SC_BAD_REQUEST, ErrorCode.E0701, + file + ", " + e.toString()); + } + + JSONObject json = createJSON("Valid workflow-app"); + startCron(); + sendJsonResponse(response, HttpServletResponse.SC_OK, json); + } + + private void validate(String xml) throws Exception{ + SchemaService schemaService = Services.get().get(SchemaService.class); + Schema[] schemas = {schemaService.getSchema(SchemaService.SchemaName.WORKFLOW), + schemaService.getSchema(SchemaService.SchemaName.COORDINATOR), + schemaService.getSchema(SchemaService.SchemaName.BUNDLE), + schemaService.getSchema(SchemaService.SchemaName.SLA_ORIGINAL)}; + + Exception exception = null; + for (int i = 0; i < schemas.length; i++) { + try{ + validateSchema(schemas[i], new StringReader(xml)); + exception = null; + break; + } catch (SAXException e) { + if (i == 0) { + exception = e; + } + // Check the root element declaration(workflow-app, coordinator-app, bundle-app). + // If invalid, move to next schema validation. + if (!e.getMessage().contains("cvc-elt.1.a")) { + exception = e; + break; + } + } catch (Exception e) { + exception = e; + break; + } + } + if (exception != null) { + throw exception; + } + } + + private void validateSchema(Schema schema, Reader src) throws SAXException, IOException, OozieCLIException{ + Validator validator = schema.newValidator(); + validator.validate(new StreamSource(src)); + } + + private JSONObject createJSON(String content) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(JsonTags.VALIDATE, content); + return jsonObject; + } + +} http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java b/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java index b654e48..57e3433 100644 --- a/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java +++ b/core/src/test/java/org/apache/oozie/client/TestOozieCLI.java @@ -31,6 +31,7 @@ import java.util.concurrent.Callable; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; +import org.apache.oozie.BaseEngine; import org.apache.oozie.BuildInfo; import org.apache.oozie.cli.CLIParser; import org.apache.oozie.cli.OozieCLI; @@ -47,6 +48,7 @@ import org.apache.oozie.servlet.V1JobServlet; import org.apache.oozie.servlet.V1JobsServlet; import org.apache.oozie.servlet.V2AdminServlet; import org.apache.oozie.servlet.V2JobServlet; +import org.apache.oozie.servlet.V2ValidateServlet; import org.apache.oozie.util.IOUtils; import org.apache.oozie.util.XConfiguration; @@ -60,13 +62,15 @@ public class TestOozieCLI extends DagServletTestCase { new V1AdminServlet(); new V2AdminServlet(); new V2JobServlet(); + new V2ValidateServlet(); } static final boolean IS_SECURITY_ENABLED = false; static final String VERSION = "/v" + OozieClient.WS_PROTOCOL_VERSION; - static final String[] END_POINTS = {"/versions", VERSION + "/jobs", VERSION + "/job/*", VERSION + "/admin/*"}; + static final String[] END_POINTS = {"/versions", VERSION + "/jobs", VERSION + "/job/*", VERSION + "/admin/*", + VERSION + "/validate/*"}; static final Class[] SERVLET_CLASSES = { HeaderTestingVersionServlet.class, V1JobsServlet.class, - V2JobServlet.class, V2AdminServlet.class, V2JobServlet.class, V2AdminServlet.class }; + V2JobServlet.class, V2AdminServlet.class, V2ValidateServlet.class, V2JobServlet.class, V2AdminServlet.class}; @Override protected void setUp() throws Exception { @@ -1155,8 +1159,8 @@ public class TestOozieCLI extends DagServletTestCase { runTest(END_POINTS, SERVLET_CLASSES, IS_SECURITY_ENABLED, new Callable<Void>() { @Override public Void call() throws Exception { - String validFileName = "./test-workflow-app.xml"; - String invalidFileName = "./test-invalid-workflow-app.xml"; + String validFileName = "test-workflow-app.xml"; + String invalidFileName = "test-invalid-workflow-app.xml"; String validContent = "<workflow-app xmlns=\"uri:oozie:workflow:0.2\" name=\"no-op-wf\"> "+ " <start to=\"end\"/> <end name=\"end\"/> </workflow-app>"; @@ -1167,20 +1171,21 @@ public class TestOozieCLI extends DagServletTestCase { validfile.delete(); invalidfile.delete(); + String oozieUrl = getContextURL(); - IOUtils.copyCharStream(new StringReader(validContent), new FileWriter(validfile)); - String [] args = new String[] { "validate", validFileName }; + IOUtils.copyCharStream(new StringReader(validContent), new FileWriter(validfile)); + String [] args = new String[] { "validate", "-oozie", oozieUrl, validfile.getAbsolutePath() }; String out = runOozieCLIAndGetStdout(args); assertTrue(out.contains("Valid")); IOUtils.copyCharStream(new StringReader(invalidContent), new FileWriter(invalidfile)); - args = new String[] { "validate", invalidFileName }; + args = new String[] { "validate", "-oozie", oozieUrl, invalidfile.getAbsolutePath() }; out = runOozieCLIAndGetStderr(args); - assertTrue(out.contains("Invalid")); + assertTrue(out.contains("XML schema error")); return null; - } - }); + } + }); } /** http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/core/src/test/java/org/apache/oozie/servlet/TestV2ValidateServlet.java ---------------------------------------------------------------------- diff --git a/core/src/test/java/org/apache/oozie/servlet/TestV2ValidateServlet.java b/core/src/test/java/org/apache/oozie/servlet/TestV2ValidateServlet.java new file mode 100644 index 0000000..2eb6c01 --- /dev/null +++ b/core/src/test/java/org/apache/oozie/servlet/TestV2ValidateServlet.java @@ -0,0 +1,676 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.oozie.servlet; + +import org.apache.hadoop.fs.Path; +import org.apache.oozie.client.rest.JsonTags; +import org.apache.oozie.client.rest.RestConstants; +import org.json.simple.JSONObject; +import org.json.simple.JSONValue; + +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.Callable; + +public class TestV2ValidateServlet extends DagServletTestCase { + + static { + new V2ValidateServlet(); + } + + private static final boolean IS_SECURITY_ENABLED = false; + + @Override + protected void setUp() throws Exception { + super.setUp(); + } + + public void testValidateWF() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + + "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <job-tracker>${jobTracker}</job-tracker>\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode()); + JSONObject obj = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream())); + assertEquals("Valid workflow-app", obj.get(JsonTags.VALIDATE)); + + return null; + } + }); + } + + public void testValidateWFonHDFS() throws Exception { + String xml = "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n" + + "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <job-tracker>${jobTracker}</job-tracker>\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + final Path path = new Path(getFsTestCaseDir(), "workflow.xml"); + OutputStreamWriter writer = new OutputStreamWriter(getFileSystem().create(path)); + writer.write(xml.toCharArray()); + writer.flush(); + writer.close(); + + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", path.toString()); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + + assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode()); + JSONObject obj = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream())); + assertEquals("Valid workflow-app", obj.get(JsonTags.VALIDATE)); + + return null; + } + }); + } + + public void testValidateWFNegative() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <name-node2>${nameNode}</name-node2>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Invalid content was found starting with element 'name-node2'")); + return null; + } + }); + } + + public void testValidateWFNegative2() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill-invalid name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill-invalid>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Invalid content was found starting with element 'kill-invalid'")); + return null; + } + }); + } + + public void testValidateWFNegative3() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app-invalid xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app-invalid>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Cannot find the declaration of element 'workflow-app-invalid")); + return null; + } + }); + } + + public void testValidateWFNegative4() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("cvc-complex-type.2.4.a: " + + "Invalid content was found starting with element 'start'")); + return null; + } + }); + } + + public void testValidateWFonHDFSNegative() throws Exception { + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.3\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <name-node2>${nameNode}</name-node2>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + final Path path = new Path(getFsTestCaseDir(), "workflow.xml"); + OutputStreamWriter writer = new OutputStreamWriter(getFileSystem().create(path)); + writer.write(xml.toCharArray()); + writer.flush(); + writer.close(); + + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", path.toString()); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Invalid content was found starting with element 'name-node2'")); + return null; + } + }); + } + + public void testValidateCoordinator() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "coordinator.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<coordinator-app name=\"coord-simple\" frequency=\"${coord:minutes(1)}\"\n" + + " start=\"${startTime}\" end=\"${endTime}\"\n" + + " timezone=\"Asia/Seoul\"\n" + + " xmlns=\"uri:oozie:coordinator:0.1\">\n" + + " <action>\n" + + " <workflow>\n" + + " <app-path>${nameNode}/user/seoeun/workflow-ndap/apps/v40/shell-outstream</app-path>\n" + + " </workflow>\n" + + " </action>\n" + + "</coordinator-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode()); + JSONObject obj = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream())); + assertEquals("Valid workflow-app", obj.get(JsonTags.VALIDATE)); + + return null; + + } + }); + } + + public void testValidateCoordinatorNegative1() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "coordinator.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<coordinator-app name=\"coord-simple\" frequency=\"${coord:minutes(1)}\"\n" + + " start=\"${startTime}\" end=\"${endTime}\"\n" + + " timezone=\"Asia/Seoul\"\n" + + " xmlns=\"uri:oozie:coordinator:0.1\">\n" + + " <action>\n" + + " <workflow>\n" + + " <app-path>${nameNode}/user/seoeun/workflow-ndap/apps/v40/shell-outstream</app-path>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <job-tracker>${jobTracker}</job-tracker>\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " </action>\n" + + " </workflow>\n" + + " </action>\n" + + "</coordinator-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Invalid content was found starting with element 'action'")); + return null; + + } + }); + } + + public void testValidateCoordinatorNegative2() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "coordinator.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<coordinator-app-invalid name=\"coord-simple\" frequency=\"${coord:minutes(1)}\"\n" + + " start=\"${startTime}\" end=\"${endTime}\"\n" + + " timezone=\"Asia/Seoul\"\n" + + " xmlns=\"uri:oozie:coordinator:0.1\">\n" + + " <action>\n" + + " <workflow>\n" + + " <app-path>${nameNode}/user/seoeun/workflow-ndap/apps/v40/shell-outstream</app-path>\n" + + " </workflow>\n" + + " </action>\n" + + "</coordinator-app-invalid>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Cannot find the declaration of element 'coordinator-app-invalid'")); + return null; + + } + }); + } + + public void testValidateBundle() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "bundle.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<bundle-app name='test_bundle' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + + "xmlns='uri:oozie:bundle:0.1'> " + + "<controls> <kick-off-time>2009-02-02T00:00Z</kick-off-time> </controls> " + + "<coordinator name='c12'> " + + "<app-path>#app_path1</app-path>" + + "<configuration> " + + "<property> <name>START_TIME</name> <value>2009-02-01T00:00Z</value> </property> </configuration> " + + "</coordinator></bundle-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode()); + JSONObject obj = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream())); + assertEquals("Valid workflow-app", obj.get(JsonTags.VALIDATE)); + + return null; + + } + }); + } + + public void testValidateBundleNegative1() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "bundle.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<bundle-app name='test_bundle' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + + "xmlns='uri:oozie:bundle:0.1'> " + + "<controls> <kick-off-time>2009-02-02T00:00Z</kick-off-time> </controls> " + + "</bundle-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("cvc-complex-type.2.4.b: The content of element 'bundle-app' is not " + + "complete. One of '{\"uri:oozie:bundle:0.1\":coordinator}' is expected")); + return null; + + } + }); + } + + public void testValidateBundleNegative2() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "bundle.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<bundle-app-invalid name='test_bundle' xmlns:xsi='http://www.w3.org/2001/XMLSchema-instance' " + + "xmlns='uri:oozie:bundle:0.1'> " + + "<controls> <kick-off-time>2009-02-02T00:00Z</kick-off-time> </controls> " + + "<coordinator name='c12'> " + + "<app-path>#app_path1</app-path>" + + "<configuration> " + + "<property> <name>START_TIME</name> <value>2009-02-01T00:00Z</value> </property> </configuration> " + + "</coordinator></bundle-app-invalid>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("cvc-elt.1.a: Cannot find the declaration of element 'bundle-app-invalid")); + return null; + + } + }); + } + + public void testValidateSla() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.5\" xmlns:sla=\"uri:oozie:sla:0.2\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <job-tracker>${jobTracker}</job-tracker>\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " <sla:info>\n" + + " <sla:nominal-time>${nominal_time}</sla:nominal-time>\n" + + " <sla:should-start>${10 * MINUTES}</sla:should-start>\n" + + " <sla:should-end>${30 * MINUTES}</sla:should-end>\n" + + " <sla:max-duration>${30 * MINUTES}</sla:max-duration>\n" + + " <sla:alert-events>start_miss,end_met,end_miss</sla:alert-events>\n" + + " <sla:alert-contact>[email protected]</sla:alert-contact>\n" + + " </sla:info>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_OK, conn.getResponseCode()); + JSONObject obj = (JSONObject) JSONValue.parse(new InputStreamReader(conn.getInputStream())); + assertEquals("Valid workflow-app", obj.get(JsonTags.VALIDATE)); + + return null; + + } + }); + } + + public void testValidateSlaNegative() throws Exception { + runTest("/v2/validate", V2ValidateServlet.class, IS_SECURITY_ENABLED, new Callable<Void>() { + public Void call() throws Exception { + + Map<String, String> params = new HashMap<String, String>(); + params.put("file", "workflow.xml"); + params.put("user", getTestUser()); + URL url = createURL("", params); + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + conn.setRequestMethod("POST"); + conn.setRequestProperty("content-type", RestConstants.XML_CONTENT_TYPE); + conn.setDoOutput(true); + String xml = "<workflow-app xmlns=\"uri:oozie:workflow:0.5\" xmlns:sla=\"uri:oozie:sla:0.2\" name=\"test\">\n" + + " <start to=\"shell-1\"/>\n" + + " <action name=\"shell-1\">\n" + + " <shell xmlns=\"uri:oozie:shell-action:0.3\">\n" + + " <job-tracker>${jobTracker}</job-tracker>\n" + + " <name-node>${nameNode}</name-node>\n" + + " <exec>script-outstream.sh</exec>\n" + + " <argument></argument>\n" + + " <file>script-outstream.sh</file>\n" + + " <capture-output/>\n" + + " </shell>\n" + + " <ok to=\"end\"/>\n" + + " <error to=\"fail\"/>\n" + + " <sla:info>\n" + + " <sla:app-name>${nominal_time}</sla:app-name>\n" + + " <sla:nominal-time>${nominal_time}</sla:nominal-time>\n" + + " <sla:should-start>${10 * MINUTES}</sla:should-start>\n" + + " <sla:should-end>${30 * MINUTES}</sla:should-end>\n" + + " <sla:max-duration>${30 * MINUTES}</sla:max-duration>\n" + + " <sla:alert-events>start_miss,end_met,end_miss</sla:alert-events>\n" + + " <sla:alert-contact>[email protected]</sla:alert-contact>\n" + + " </sla:info>\n" + + " </action>\n" + + " <kill name=\"fail\">\n" + + " <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message>\n" + + " </kill>\n" + + " <end name=\"end\"/>\n" + + "</workflow-app>"; + writeXML(conn.getOutputStream(), xml); + + assertEquals(HttpServletResponse.SC_BAD_REQUEST, conn.getResponseCode()); + String error = conn.getHeaderField(RestConstants.OOZIE_ERROR_CODE); + String message = conn.getHeaderField(RestConstants.OOZIE_ERROR_MESSAGE); + assertEquals("E0701", error); + assertEquals(true, message.contains("Invalid content was found starting with element 'sla:app-name'")); + return null; + + } + }); + } + + + private void writeXML(OutputStream outputStream, String xml) throws IOException { + outputStream.write(xml.getBytes()); + } + +} http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/distro/src/main/tomcat/ssl-web.xml ---------------------------------------------------------------------- diff --git a/distro/src/main/tomcat/ssl-web.xml b/distro/src/main/tomcat/ssl-web.xml index 103e12d..cf0d621 100644 --- a/distro/src/main/tomcat/ssl-web.xml +++ b/distro/src/main/tomcat/ssl-web.xml @@ -122,6 +122,13 @@ <load-on-startup>1</load-on-startup> </servlet> + <servlet> + <servlet-name>validate</servlet-name> + <display-name>WS API for Workflow Applications</display-name> + <servlet-class>org.apache.oozie.servlet.V2ValidateServlet</servlet-class> + <load-on-startup>1</load-on-startup> + </servlet> + <!-- servlet-mapping --> <servlet-mapping> <servlet-name>versions</servlet-name> @@ -188,6 +195,11 @@ <url-pattern>/v2/sla/*</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>validate</servlet-name> + <url-pattern>/v2/validate</url-pattern> + </servlet-mapping> + <!-- welcome-file --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/docs/src/site/twiki/DG_CommandLineTool.twiki ---------------------------------------------------------------------- diff --git a/docs/src/site/twiki/DG_CommandLineTool.twiki b/docs/src/site/twiki/DG_CommandLineTool.twiki index 9494b22..1823247 100644 --- a/docs/src/site/twiki/DG_CommandLineTool.twiki +++ b/docs/src/site/twiki/DG_CommandLineTool.twiki @@ -136,7 +136,9 @@ usage: system mode [NORMAL|NOWEBSERVICE|SAFEMODE] -version show Oozie server build version . - oozie validate <ARGS> : validate a workflow XML file + oozie validate <OPTIONS> <ARGS> : validate a workflow, coordinator, bundle XML file + -auth <arg> select authentication type [SIMPLE|KERBEROS] + -oozie <arg> Oozie URL . oozie sla <OPTIONS> : sla operations (Deprecated as of Oozie 4.0) -auth <arg> select authentication type [SIMPLE|KERBEROS] @@ -1430,13 +1432,28 @@ Example: <verbatim> $ oozie validate myApp/workflow.xml . -Error: Invalid workflow-app, org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: +Error: E0701: XML schema error, workflow.xml, org.xml.sax.SAXParseException: cvc-complex-type.2.4.a: Invalid content was found starting with element 'xend'. One of '{"uri:oozie:workflow:0.1":decision, "uri:oozie:workflow:0.1":fork, "uri:oozie:workflow:0.1":join, "uri:oozie:workflow:0.1":kill, "uri:oozie:workflow:0.1":action, "uri:oozie:workflow:0.1":end}' is expected. </verbatim> -It performs an XML Schema validation on the specified workflow XML file. +<verbatim> +$ oozie validate /home/test/myApp/coordinator.xml +. +Error: E0701: XML schema error, coordinator.xml, org.xml.sax.SAXParseException; lineNumber: 4; columnNumber: 52; cvc-elt.1.a: + Cannot find the declaration of element 'coordinator-app-invalid'. +</verbatim> + +<verbatim> +$ oozie validate hdfs://localhost:8020/user/test/myApp/bundle.xml +. +Error: E0701: XML schema error, bundle.xml, org.xml.sax.SAXParseException: cvc-complex-type.2.4.b: + The content of element 'bundle-app' is not complete. One of '{"uri:oozie:bundle:0.1":coordinator}' is expected. +</verbatim> + +It performs an XML Schema validation on the specified workflow, coordinator, bundle XML file. +The XML file can be a local file or in HDFS. ---+++ Getting list of available sharelib This command is used to get list of available sharelib. http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/docs/src/site/twiki/WebServicesAPI.twiki ---------------------------------------------------------------------- diff --git a/docs/src/site/twiki/WebServicesAPI.twiki b/docs/src/site/twiki/WebServicesAPI.twiki index 3d97447..a34f2d3 100644 --- a/docs/src/site/twiki/WebServicesAPI.twiki +++ b/docs/src/site/twiki/WebServicesAPI.twiki @@ -2025,6 +2025,75 @@ Ignore coordinator actions <verbatim> PUT /oozie/v2/job/job-3?action=ignore&type=action&scope=3-4 </verbatim> + +---+++ Validate End-Point + +This endpoint is to validate a workflow, coordinator, bundle XML file. + +---++++ Validate a local file + +*Request:* + +<verbatim> +POST /oozie/v2/validate?file=/home/test/myApp/workflow.xml +Content-Type: application/xml;charset=UTF-8 +. +<?xml version="1.0" encoding="UTF-8" standalone="yes"?> +<workflow-app xmlns="uri:oozie:workflow:0.3" name="test"> + <start to="shell"/> + <action name="shell"> + <shell xmlns="uri:oozie:shell-action:0.3"> + <job-tracker>${jobTracker}</job-tracker> + <name-node>${nameNode}</name-node> + <exec>script.sh</exec> + <argument></argument> + <file>script.sh</file> + <capture-output/> + </shell> + <ok to="end"/> + <error to="fail"/> + </action> + <kill name="fail"> + <message>failed, error message[${wf:errorMessage(wf:lastErrorNode())}]</message> + </kill> + <end name="end"/> +</workflow-app> +</verbatim> + +*Response:* + +<verbatim> +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +. +{ + validate: "Valid workflow-app" +} +</verbatim> + +---++++ Validate a file in HDFS + +You can validate a workflow, coordinator, bundle XML file in HDFS. The XML file must already exist in HDFS. + +*Request:* + +<verbatim> +POST /oozie/v2/validate?file=hdfs://localhost:8020/user/test/myApp/workflow.xml +Content-Type: application/xml;charset=UTF-8 +. +</verbatim> + +*Response:* + +<verbatim> +HTTP/1.1 200 OK +Content-Type: application/json;charset=UTF-8 +. +{ + validate: "Valid workflow-app" +} +</verbatim> + </noautolink> http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/release-log.txt ---------------------------------------------------------------------- diff --git a/release-log.txt b/release-log.txt index 8df39ce..fe8d1fc 100644 --- a/release-log.txt +++ b/release-log.txt @@ -1,5 +1,6 @@ -- Oozie 4.3.0 release (trunk - unreleased) +OOZIE-2159 'oozie validate' command should be moved server-side (seoeun25 via rkanter) OOZIE-2271 Upgrade Tomcat to 6.0.44 (rkanter) OOZIE-2266 Fix 'total' actions returned in coordinator job info (sai-krish via rkanter) OOZIE-2264 Fix coord:offset(n,"DAY") to resolve correct data set(kailongs via puru) http://git-wip-us.apache.org/repos/asf/oozie/blob/cc94ad8e/webapp/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/webapp/src/main/webapp/WEB-INF/web.xml b/webapp/src/main/webapp/WEB-INF/web.xml index d865d23..89f15f3 100644 --- a/webapp/src/main/webapp/WEB-INF/web.xml +++ b/webapp/src/main/webapp/WEB-INF/web.xml @@ -122,6 +122,13 @@ <load-on-startup>1</load-on-startup> </servlet> + <servlet> + <servlet-name>validate</servlet-name> + <display-name>WS API for Workflow Applications</display-name> + <servlet-class>org.apache.oozie.servlet.V2ValidateServlet</servlet-class> + <load-on-startup>1</load-on-startup> + </servlet> + <!-- servlet-mapping --> <servlet-mapping> <servlet-name>versions</servlet-name> @@ -188,6 +195,11 @@ <url-pattern>/v2/sla/*</url-pattern> </servlet-mapping> + <servlet-mapping> + <servlet-name>validate</servlet-name> + <url-pattern>/v2/validate</url-pattern> + </servlet-mapping> + <!-- welcome-file --> <welcome-file-list> <welcome-file>index.jsp</welcome-file>
