AMBARI-18691. Improve and Update Workflow designer to support coordinators and bundles. (Belliraj HB via dipayanb)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/d1b0bb9e Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/d1b0bb9e Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/d1b0bb9e Branch: refs/heads/branch-feature-AMBARI-18634 Commit: d1b0bb9ebf97313195d7e95ebb475bdc51534b8c Parents: a936542 Author: Dipayan Bhowmick <[email protected]> Authored: Fri Oct 28 00:33:36 2016 +0530 Committer: Dipayan Bhowmick <[email protected]> Committed: Fri Oct 28 00:34:29 2016 +0530 ---------------------------------------------------------------------- .../apache/oozie/ambari/view/HDFSFileUtils.java | 87 + .../org/apache/oozie/ambari/view/JobType.java | 22 + .../ambari/view/OozieProxyImpersonator.java | 1042 ++++++------ .../apache/oozie/ambari/view/OozieUtils.java | 71 + .../org/apache/oozie/ambari/view/Utils.java | 154 ++ .../wfmanager/src/main/resources/ui/.jshintrc | 38 + .../main/resources/ui/app/components/.gitkeep | 0 .../ui/app/components/archive-config.js | 3 +- .../ui/app/components/bundle-config.js | 262 +++ .../ui/app/components/bundle-coord-config.js | 108 ++ .../ui/app/components/conditional-data-input.js | 78 + .../ui/app/components/confirmation-dialog.js | 25 + .../resources/ui/app/components/coord-config.js | 521 ++++++ .../ui/app/components/credentials-config.js | 175 +- .../app/components/data-input-output-config.js | 97 ++ .../resources/ui/app/components/data-input.js | 41 + .../ui/app/components/dataset-config.js | 103 ++ .../ui/app/components/date-with-expr.js | 78 + .../ui/app/components/decision-add-branch.js | 78 +- .../ui/app/components/decision-config.js | 43 +- .../ui/app/components/designer-workspace.js | 158 ++ .../ui/app/components/distcp-action-info.js | 26 + .../ui/app/components/distcp-action.js | 6 +- .../ui/app/components/email-action-info.js | 26 + .../resources/ui/app/components/email-action.js | 33 +- .../resources/ui/app/components/file-config.js | 3 +- .../ui/app/components/flow-designer.js | 728 +++++---- .../ui/app/components/fs-action-info.js | 26 + .../resources/ui/app/components/fs-action.js | 64 +- .../ui/app/components/fsaction-info.js | 21 + .../resources/ui/app/components/hdfs-browser.js | 5 +- .../ui/app/components/hive-action-info.js | 26 + .../resources/ui/app/components/hive-action.js | 38 +- .../ui/app/components/hive2-action-info.js | 25 + .../resources/ui/app/components/hive2-action.js | 44 +- .../resources/ui/app/components/info-header.js | 26 + .../ui/app/components/instance-list-config.js | 54 + .../ui/app/components/java-action-info.js | 25 + .../resources/ui/app/components/java-action.js | 25 +- .../resources/ui/app/components/job-config.js | 303 ++++ .../resources/ui/app/components/job-details.js | 391 ++++- .../ui/app/components/killnode-config.js | 29 + .../ui/app/components/killnode-manager.js | 62 + .../ui/app/components/map-red-action.js | 6 +- .../ui/app/components/map-reduce-action-info.js | 25 + .../ui/app/components/name-value-config.js | 7 +- .../ui/app/components/name-value-info.js | 21 + .../ui/app/components/named-properties.js | 25 +- .../ui/app/components/pig-action-info.js | 25 + .../resources/ui/app/components/pig-action.js | 16 +- .../ui/app/components/prepare-config-fs.js | 7 +- .../ui/app/components/prepare-config-info.js | 21 + .../ui/app/components/prepare-config.js | 3 +- .../ui/app/components/preview-dialog.js | 20 + .../ui/app/components/property-value-config.js | 21 + .../main/resources/ui/app/components/save-wf.js | 170 ++ .../ui/app/components/shell-action-info.js | 25 + .../resources/ui/app/components/shell-action.js | 20 +- .../resources/ui/app/components/sla-info.js | 148 +- .../ui/app/components/spark-action-info.js | 25 + .../resources/ui/app/components/spark-action.js | 59 +- .../ui/app/components/sqoop-action-info.js | 25 + .../resources/ui/app/components/sqoop-action.js | 5 +- .../ui/app/components/ssh-action-info.js | 25 + .../resources/ui/app/components/ssh-action.js | 24 +- .../app/components/sub-workflow-action-info.js | 25 + .../resources/ui/app/components/sub-workflow.js | 17 +- .../ui/app/components/transition-config.js | 44 +- .../ui/app/components/workflow-action-editor.js | 58 +- .../ui/app/components/workflow-actions.js | 7 + .../ui/app/components/workflow-credentials.js | 65 +- .../app/components/workflow-job-action-info.js | 22 + .../ui/app/components/workflow-node.js | 9 + .../ui/app/components/workflow-parameters.js | 64 +- .../resources/ui/app/components/workflow-sla.js | 15 +- .../main/resources/ui/app/controllers/.gitkeep | 0 .../main/resources/ui/app/controllers/design.js | 5 - .../ui/app/domain/action-type-resolver.js | 62 + .../ui/app/domain/actionjob_hanlder.js | 1 + .../app/domain/bundle/bundle-xml-generator.js | 55 + .../ui/app/domain/bundle/bundle-xml-importer.js | 87 + .../resources/ui/app/domain/bundle/bundle.js | 22 + .../coordinator/coordinator-xml-generator.js | 204 +++ .../coordinator/coordinator-xml-importer.js | 272 ++++ .../ui/app/domain/coordinator/coordinator.js | 22 + .../ui/app/domain/cytoscape-flow-renderer.js | 348 ++++ .../resources/ui/app/domain/cytoscape-style.js | 123 ++ .../ui/app/domain/default-layout-manager.js | 10 +- .../resources/ui/app/domain/findnode-mixin.js | 113 +- .../src/main/resources/ui/app/domain/id-gen.js | 4 + .../ui/app/domain/jsplumb-flow-renderer.js | 194 +++ .../resources/ui/app/domain/mapping-utils.js | 31 +- .../resources/ui/app/domain/node-factory.js | 10 +- .../resources/ui/app/domain/node-handler.js | 49 +- .../src/main/resources/ui/app/domain/node.js | 3 +- .../main/resources/ui/app/domain/sla-info.js | 38 +- .../main/resources/ui/app/domain/transition.js | 5 +- .../ui/app/domain/workflow-importer.js | 8 +- .../ui/app/domain/workflow-json-importer.js | 92 ++ .../ui/app/domain/workflow-path-util.js | 73 + .../ui/app/domain/workflow-xml-generator.js | 1 - .../main/resources/ui/app/domain/workflow.js | 114 +- .../ui/app/domain/workflow_xml_mapper.js | 5 +- .../src/main/resources/ui/app/helpers/.gitkeep | 0 .../src/main/resources/ui/app/index.html | 9 + .../src/main/resources/ui/app/routes/.gitkeep | 0 .../main/resources/ui/app/routes/dashboard.js | 10 +- .../src/main/resources/ui/app/routes/design.js | 38 +- .../ui/app/services/workflow-clipboard.js | 34 + .../ui/app/services/workspace-manager.js | 62 + .../src/main/resources/ui/app/styles/app.less | 1497 ++++++++++++++++++ .../ui/app/templates/components/.gitkeep | 0 .../app/templates/components/bundle-config.hbs | 129 ++ .../components/bundle-coord-config.hbs | 58 + .../templates/components/bundle-job-details.hbs | 17 +- .../components/conditional-data-input.hbs | 64 + .../components/confirmation-dialog.hbs | 34 + .../app/templates/components/coord-config.hbs | 352 ++++ .../templates/components/coord-job-details.hbs | 17 +- .../templates/components/credentials-config.hbs | 33 +- .../components/data-input-output-config.hbs | 68 + .../ui/app/templates/components/data-input.hbs | 40 + .../app/templates/components/dataset-config.hbs | 70 + .../app/templates/components/date-with-expr.hbs | 41 + .../components/decision-add-branch.hbs | 4 +- .../templates/components/decision-config.hbs | 2 +- .../templates/components/designer-workspace.hbs | 106 ++ .../templates/components/distcp-action-info.hbs | 35 + .../templates/components/email-action-info.hbs | 28 + .../app/templates/components/email-action.hbs | 10 +- .../ui/app/templates/components/field-error.hbs | 3 + .../app/templates/components/flow-designer.hbs | 300 ++-- .../app/templates/components/fs-action-info.hbs | 33 + .../ui/app/templates/components/fs-action.hbs | 3 +- .../app/templates/components/fsaction-info.hbs | 39 + .../app/templates/components/hdfs-browser.hbs | 2 +- .../templates/components/hive-action-info.hbs | 47 + .../ui/app/templates/components/hive-action.hbs | 6 +- .../templates/components/hive2-action-info.hbs | 49 + .../app/templates/components/hive2-action.hbs | 6 +- .../ui/app/templates/components/info-header.hbs | 18 + .../components/instance-list-config.hbs | 35 + .../templates/components/java-action-info.hbs | 52 + .../ui/app/templates/components/java-action.hbs | 4 +- .../ui/app/templates/components/job-config.hbs | 126 ++ .../ui/app/templates/components/job-details.hbs | 2 +- .../templates/components/killnode-config.hbs | 67 + .../templates/components/killnode-manager.hbs | 69 + .../components/map-reduce-action-info.hbs | 41 + .../templates/components/name-value-info.hbs | 22 + .../templates/components/named-properties.hbs | 2 +- .../templates/components/pig-action-info.hbs | 47 + .../ui/app/templates/components/pig-action.hbs | 2 +- .../templates/components/prepare-config-fs.hbs | 46 +- .../components/prepare-config-info.hbs | 22 + .../app/templates/components/preview-dialog.hbs | 33 + .../components/property-value-config.hbs | 22 + .../ui/app/templates/components/save-wf.hbs | 79 + .../templates/components/shell-action-info.hbs | 48 + .../app/templates/components/shell-action.hbs | 4 +- .../ui/app/templates/components/sla-info.hbs | 17 +- .../templates/components/spark-action-info.hbs | 46 + .../app/templates/components/spark-action.hbs | 6 +- .../templates/components/sqoop-action-info.hbs | 41 + .../templates/components/ssh-action-info.hbs | 32 + .../ui/app/templates/components/ssh-action.hbs | 4 +- .../components/sub-workflow-action-info.hbs | 29 + .../app/templates/components/sub-workflow.hbs | 2 +- .../templates/components/transition-config.hbs | 23 +- .../templates/components/version-settings.hbs | 2 +- .../templates/components/workflow-actions.hbs | 5 + .../templates/components/workflow-config.hbs | 2 +- .../components/workflow-credentials.hbs | 34 +- .../components/workflow-job-action-info.hbs | 80 + .../components/workflow-job-details.hbs | 158 +- .../app/templates/components/workflow-node.hbs | 3 +- .../components/workflow-parameters.hbs | 23 +- .../resources/ui/app/templates/dashboard.hbs | 2 +- .../main/resources/ui/app/templates/design.hbs | 2 +- .../main/resources/ui/app/utils/constants.js | 43 +- .../app/validators/decission-node-validator.js | 58 + .../app/validators/duplicate-data-node-name.js | 60 + .../validators/duplicate-flattened-node-name.js | 66 + .../app/validators/duplicate-kill-node-name.js | 58 + .../ui/app/validators/fs-action-validator.js | 76 + .../ui/app/validators/job-params-validator.js | 54 + .../ui/app/validators/operand-length.js | 46 + .../resources/ui/app/validators/unique-name.js | 59 + .../wfmanager/src/main/resources/ui/bower.json | 5 +- .../src/main/resources/ui/ember-cli-build.js | 10 + .../hdfs-directory-viewer/addon/.gitkeep | 0 .../hdfs-directory-viewer/app/.gitkeep | 0 .../tests/dummy/app/components/.gitkeep | 0 .../tests/dummy/app/controllers/.gitkeep | 0 .../tests/dummy/app/helpers/.gitkeep | 0 .../tests/dummy/app/models/.gitkeep | 0 .../tests/dummy/app/routes/.gitkeep | 0 .../dummy/app/templates/components/.gitkeep | 0 .../tests/integration/.gitkeep | 0 .../hdfs-directory-viewer/tests/unit/.gitkeep | 0 .../hdfs-directory-viewer/vendor/.gitkeep | 0 .../resources/ui/mock-service/mock-server.js | 52 + .../main/resources/ui/mock-service/mockData.js | 316 ++++ .../src/main/resources/ui/package.json | 18 +- .../main/resources/ui/public/assets/favicon.ico | Bin 0 -> 1150 bytes .../main/resources/ui/public/assets/join.png | Bin 0 -> 331 bytes .../main/resources/ui/public/assets/logo.png | Bin 0 -> 4568 bytes .../main/resources/ui/public/assets/play.png | Bin 0 -> 1164 bytes .../main/resources/ui/public/assets/sitemap.png | Bin 0 -> 317 bytes .../main/resources/ui/public/assets/stop.png | Bin 0 -> 1164 bytes .../src/main/resources/ui/public/loader.gif | Bin 0 -> 42435 bytes .../resources/ui/public/sampledata/bundle.xml | 32 + .../ui/public/sampledata/coordinator.xml | 113 ++ .../resources/ui/tests/integration/.gitkeep | 0 .../components/bundle-config-test.js | 40 + .../components/bundle-coord-config-test.js | 40 + .../components/conditional-data-input-test.js | 40 + .../components/confirmation-dialog-test.js | 40 + .../integration/components/coord-config-test.js | 40 + .../components/data-input-output-config-test.js | 40 + .../integration/components/data-input-test.js | 40 + .../components/dataset-config-test.js | 40 + .../components/date-with-expr-test.js | 40 + .../components/designer-workspace-test.js | 40 + .../components/distcp-action-info-test.js | 41 + .../components/email-action-info-test.js | 41 + .../components/fs-action-info-test.js | 41 + .../components/fsaction-info-test.js | 41 + .../components/hive-action-info-test.js | 41 + .../components/hive2-action-info-test.js | 41 + .../integration/components/info-header-test.js | 41 + .../components/instance-list-config-test.js | 41 + .../components/java-action-info-test.js | 41 + .../integration/components/job-config-test.js | 41 + .../components/killnode-config-test.js | 41 + .../components/killnode-manager-test.js | 41 + .../components/map-reduce-action-info-test.js | 41 + .../components/name-value-info-test.js | 41 + .../components/pig-action-info-test.js | 41 + .../components/prepare-config-info-test.js | 41 + .../components/preview-dialog-test.js | 40 + .../components/property-value-config-test.js | 41 + .../integration/components/save-wf-test.js | 40 + .../components/shell-action-info-test.js | 41 + .../components/spark-action-info-test.js | 41 + .../components/sqoop-action-info-test.js | 41 + .../components/ssh-action-info-test.js | 41 + .../components/sub-workflow-action-info-test.js | 41 + .../src/main/resources/ui/tests/unit/.gitkeep | 0 .../unit/services/workflow-clipboard-test.js | 29 + .../unit/services/workspace-manager-test.js | 29 + .../validators/decission-node-validator-test.js | 26 + .../validators/duplicate-data-node-name-test.js | 27 + .../duplicate-flattened-node-name-test.js | 27 + .../validators/duplicate-kill-node-name-test.js | 27 + .../unit/validators/fs-action-validator-test.js | 26 + .../validators/job-params-validator-test.js | 26 + .../unit/validators/operand-length-test.js | 27 + .../tests/unit/validators/unique-name-test.js | 27 + 259 files changed, 13407 insertions(+), 1980 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/HDFSFileUtils.java ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/HDFSFileUtils.java b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/HDFSFileUtils.java new file mode 100644 index 0000000..58c3980 --- /dev/null +++ b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/HDFSFileUtils.java @@ -0,0 +1,87 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.ambari.view; + +import java.io.IOException; + +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.view.utils.hdfs.HdfsApi; +import org.apache.ambari.view.utils.hdfs.HdfsUtil; +import org.apache.hadoop.fs.FSDataInputStream; +import org.apache.hadoop.fs.FSDataOutputStream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class HDFSFileUtils { + private final static Logger LOGGER = LoggerFactory + .getLogger(HDFSFileUtils.class); + private ViewContext viewContext; + + public HDFSFileUtils(ViewContext viewContext) { + super(); + this.viewContext = viewContext; + } + public boolean fileExists(String path) { + boolean fileExists; + try { + fileExists = getHdfsgetApi().exists(path); + } catch (IOException e) { + LOGGER.error(e.getMessage(), e); + throw new RuntimeException(e); + } catch (InterruptedException e) { + LOGGER.error(e.getMessage(), e); + throw new RuntimeException(e); + } + LOGGER.info("FILE exists for [" + path + "] returned [" + fileExists + + "]"); + return fileExists; + } + public FSDataInputStream read(String filePath)throws IOException{ + FSDataInputStream is; + try { + is = getHdfsgetApi().open(filePath); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + return is; + } + public String createWorkflowFile( String workflowFile,String postBody, + boolean overwrite) throws IOException { + FSDataOutputStream fsOut; + try { + fsOut = getHdfsgetApi().create(workflowFile, + overwrite); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + fsOut.write(postBody.getBytes()); + fsOut.close(); + return workflowFile; + } + private HdfsApi getHdfsgetApi() { + try { + return HdfsUtil.connectToHDFSApi(viewContext); + } catch (Exception ex) { + LOGGER.error("Error in getting HDFS Api", ex); + throw new RuntimeException( + "HdfsApi connection failed. Check \"webhdfs.url\" property", + ex); + } + } + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/JobType.java ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/JobType.java b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/JobType.java new file mode 100644 index 0000000..5a47b68 --- /dev/null +++ b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/JobType.java @@ -0,0 +1,22 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.ambari.view; + +public enum JobType { + WORKFLOW, COORDINATOR, BUNDLE +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java index 3ed6352..0533f04 100644 --- a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java +++ b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieProxyImpersonator.java @@ -20,14 +20,10 @@ package org.apache.oozie.ambari.view; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; -import java.io.StringReader; -import java.io.StringWriter; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; import javax.inject.Inject; import javax.ws.rs.Consumes; @@ -48,522 +44,550 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; import javax.ws.rs.core.StreamingOutput; import javax.ws.rs.core.UriInfo; -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.OutputKeys; -import javax.xml.transform.Transformer; -import javax.xml.transform.TransformerException; -import javax.xml.transform.TransformerFactory; -import javax.xml.transform.TransformerFactoryConfigurationError; -import javax.xml.transform.dom.DOMSource; -import javax.xml.transform.stream.StreamResult; import org.apache.ambari.view.URLStreamProvider; import org.apache.ambari.view.ViewContext; -import org.apache.ambari.view.utils.ambari.AmbariApi; -import org.apache.ambari.view.utils.hdfs.HdfsApi; -import org.apache.ambari.view.utils.hdfs.HdfsUtil; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.apache.commons.lang.exception.ExceptionUtils; import org.apache.hadoop.fs.FSDataInputStream; -import org.apache.hadoop.fs.FSDataOutputStream; +import org.apache.hadoop.security.AccessControlException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.w3c.dom.Document; -import org.w3c.dom.Element; -import org.xml.sax.InputSource; -import org.xml.sax.SAXException; + +import com.google.inject.Singleton; /** * This is a class used to bridge the communication between the and the Oozie * API executing inside ambari. */ +@Singleton public class OozieProxyImpersonator { - - private static final String OOZIE_WF_APPLICATION_PATH_CONF_KEY = "oozie.wf.application.path"; - private static final String OOZIE_WF_RERUN_FAILNODES_CONF_KEY = "oozie.wf.rerun.failnodes"; - private static final String OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY = "oozie.use.system.libpath"; - private static final String XML_INDENT_SPACES = "4"; - private static final String XML_INDENT_AMT_PROP_NAME = "{http://xml.apache.org/xslt}indent-amount"; - private ViewContext viewContext; - private AmbariApi ambariApi; - private HdfsApi _hdfsApi = null; - - private static final String USER_NAME_HEADER = "user.name"; - private static final String USER_OOZIE_SUPER = "oozie"; - private static final String DO_AS_HEADER = "doAs"; - - private static final String SERVICE_URI_PROP = "oozie.service.uri"; - private static final String DEFAULT_SERVICE_URI = "http://sandbox.hortonworks.com:11000/oozie"; - - private final static Logger LOGGER = LoggerFactory - .getLogger(OozieProxyImpersonator.class); - - @Inject - public OozieProxyImpersonator(ViewContext viewContext) { - this.viewContext = viewContext; - this.ambariApi = new AmbariApi(viewContext); - LOGGER.info(String.format( - "OozieProxyImpersonator initialized for instance: %s", - viewContext.getInstanceName())); - } - - @Path("/fileServices") - public FileServices fileServices() { - return new FileServices(viewContext); - } - - @POST - @Path("/submitWorkflow") - @Consumes({MediaType.TEXT_PLAIN + "," + MediaType.TEXT_XML}) - public Response submitWorkflow(String postBody, @Context HttpHeaders headers, - @Context UriInfo ui, @QueryParam("app.path") String appPath, - @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite) { - LOGGER.info("submit workflow job called"); - try { - if (StringUtils.isEmpty(appPath)) { - throw new RuntimeException("app path can't be empty."); - } - appPath = appPath.trim(); - if (!overwrite) { - boolean fileExists = getHdfsgetApi().exists(appPath); - LOGGER.info("FILE exists for [" + appPath + "] returned [" + fileExists - + "]"); - if (fileExists) { - HashMap<String, String> resp = new HashMap<String, String>(); - resp.put("status", "workflow.folder.exists"); - resp.put("message", "Workflow Folder exists"); - return Response.status(Response.Status.BAD_REQUEST).entity(resp) - .build(); - } - } - String workflowFile = null; - if (appPath.endsWith(".xml")) { - workflowFile = appPath; - } else { - workflowFile = appPath + (appPath.endsWith("/") ? "" : "/") - + "workflow.xml"; - } - postBody = formatXml(postBody); - try { - String filePath = createWorkflowFile(postBody, workflowFile, overwrite); - LOGGER.info(String.format("submit workflow job done. filePath=[%s]", - filePath)); - } catch (org.apache.hadoop.security.AccessControlException ace) { - HashMap<String, String> resp = new HashMap<String, String>(); - resp.put("status", "workflow.oozie.error"); - resp.put("message", "You dont seem to have access to folder path."); - return Response.status(Response.Status.BAD_REQUEST).entity(resp) - .build(); - } - - String response = submitWorkflowJobToOozie(headers, appPath, - ui.getQueryParameters()); - if (response != null && response.trim().startsWith("{")) { - // dealing with oozie giving error but with 200 response. - return Response.status(Response.Status.OK).entity(response).build(); - } else { - HashMap<String, String> resp = new HashMap<String, String>(); - resp.put("status", "workflow.oozie.error"); - resp.put("message", response); - return Response.status(Response.Status.BAD_REQUEST).entity(resp) - .build(); - } - } catch (InterruptedException e) { - throw new RuntimeException(e); - } catch (Exception e) { - LOGGER.error("Error in submit workflow", e); - throw new RuntimeException(e); - } - } - - @GET - @Path("/readWorkflowXml") - public Response readWorkflowXxml( - @QueryParam("workflowXmlPath") String workflowPath) { - if (StringUtils.isEmpty(workflowPath)) { - throw new RuntimeException("workflowXmlPath can't be empty."); - } - try { - final FSDataInputStream is = getHdfsgetApi().open(workflowPath); - StreamingOutput streamer = new StreamingOutput() { - - @Override - public void write(OutputStream os) throws IOException, - WebApplicationException { - IOUtils.copy(is, os); - is.close(); - os.close(); - } - }; - return Response.ok(streamer).status(200).build(); - } catch (org.apache.hadoop.security.AccessControlException ace) { - HashMap<String, String> resp = new HashMap<String, String>(); - resp.put("status", "workflow.oozie.error"); - resp.put("message", "Access denied to file path"); - return Response.status(Response.Status.FORBIDDEN).entity(resp).build(); - } catch (IOException e) { - LOGGER.error("Error in read worfklow file", e); - throw new RuntimeException(e); - } catch (InterruptedException e) { - LOGGER.error("Error in read worfklow file", e); - throw new RuntimeException(e); - } - } - - @GET - @Path("/getDag") - @Produces("image/png") - public Response submitWorkflow(@Context HttpHeaders headers, - @Context UriInfo ui, @QueryParam("jobid") String jobid) { - String imgUrl = getServiceUri() + "/v2/job/" + jobid + "?show=graph"; - Map<String, String> newHeaders = getHeaders(headers); - final InputStream is = readFromOozie(headers, imgUrl, HttpMethod.GET, null, - newHeaders); - StreamingOutput streamer = new StreamingOutput() { - - @Override - public void write(OutputStream os) throws IOException, - WebApplicationException { - IOUtils.copy(is, os); - is.close(); - os.close(); - } - - }; - return Response.ok(streamer).status(200).build(); - } - - @GET - @Path("/{path: .*}") - public Response handleGet(@Context HttpHeaders headers, @Context UriInfo ui) { - try { - String serviceURI = buildURI(ui); - return consumeService(headers, serviceURI, HttpMethod.GET, null); - } catch (Exception ex) { - LOGGER.error("Error in GET proxy", ex); - return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString()) - .build(); - } - } - - @POST - @Path("/{path: .*}") - public Response handlePost(String xml, @Context HttpHeaders headers, @Context UriInfo ui) { - try { - String serviceURI = buildURI(ui); - return consumeService(headers, serviceURI, HttpMethod.POST, xml); - } catch (Exception ex) { - LOGGER.error("Error in POST proxy", ex); - return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString()) - .build(); - } - } - - @DELETE - @Path("/{path: .*}") - public Response handleDelete(@Context HttpHeaders headers, @Context UriInfo ui) { - try { - String serviceURI = buildURI(ui); - return consumeService(headers, serviceURI, HttpMethod.POST, null); - } catch (Exception ex) { - LOGGER.error("Error in DELETE proxy", ex); - return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString()) - .build(); - } - } - - @PUT - @Path("/{path: .*}") - public Response handlePut(String body, @Context HttpHeaders headers, @Context UriInfo ui) { - - try { - String serviceURI = buildURI(ui); - return consumeService(headers, serviceURI, HttpMethod.PUT, body); - } catch (Exception ex) { - LOGGER.error("Error in PUT proxy", ex); - return Response.status(Response.Status.BAD_REQUEST).entity(ex.toString()) - .build(); - } - } - - private String submitWorkflowJobToOozie(HttpHeaders headers, String filePath, - MultivaluedMap<String, String> queryParams) { - String nameNode = "hdfs://" + viewContext.getCluster().getConfigurationValue("hdfs-site", "dfs.namenode.rpc-address"); - - if (!queryParams.containsKey("config.nameNode")) { - ArrayList<String> nameNodes = new ArrayList<String>(); - LOGGER.info("Namenode===" + nameNode); - nameNodes.add(nameNode); - queryParams.put("config.nameNode", nameNodes); - } - - HashMap<String, String> workflowConigs = new HashMap<String, String>(); - if (queryParams.containsKey("resourceManager") - && "useDefault".equals(queryParams.getFirst("resourceManager"))) { - String jobTrackerNode = viewContext.getCluster().getConfigurationValue( - "yarn-site", "yarn.resourcemanager.address"); - LOGGER.info("jobTrackerNode===" + jobTrackerNode); - workflowConigs.put("resourceManager", jobTrackerNode); - workflowConigs.put("jobTracker", jobTrackerNode); - } - if (queryParams != null) { - for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { - if (entry.getKey().startsWith("config.")) { - if (entry.getValue() != null && entry.getValue().size() > 0) { - workflowConigs.put(entry.getKey().substring(7), entry.getValue() - .get(0)); - } - } - } - } - - if (queryParams.containsKey("oozieconfig.useSystemLibPath")) { - String useSystemLibPath = queryParams - .getFirst("oozieconfig.useSystemLibPath"); - workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, useSystemLibPath); - } else { - workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, "true"); - } - if (queryParams.containsKey("oozieconfig.rerunOnFailure")) { - String rerunFailnodes = queryParams - .getFirst("oozieconfig.rerunOnFailure"); - workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, rerunFailnodes); - } else { - workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, "true"); - } - - workflowConigs.put("user.name", viewContext.getUsername()); - workflowConigs.put(OOZIE_WF_APPLICATION_PATH_CONF_KEY, nameNode + filePath); - String configXMl = generateConigXml(workflowConigs); - LOGGER.info("Config xml==" + configXMl); - HashMap<String, String> customHeaders = new HashMap<String, String>(); - customHeaders.put("Content-Type", "application/xml;charset=UTF-8"); - Response serviceResponse = consumeService(headers, getServiceUri() - + "/v2/jobs", HttpMethod.POST, configXMl, customHeaders); - - LOGGER - .info("REsp from oozie status entity==" + serviceResponse.getEntity()); - if (serviceResponse.getEntity() instanceof String) { - return (String) serviceResponse.getEntity(); - } else { - return "success"; - } - - } - - private String createWorkflowFile(String postBody, String workflowFile, boolean overwrite) throws IOException, InterruptedException { - FSDataOutputStream fsOut = getHdfsgetApi().create(workflowFile, overwrite); - fsOut.write(postBody.getBytes()); - fsOut.close(); - return workflowFile; - } - - private String buildURI(UriInfo ui) { - String uiURI = ui.getAbsolutePath().getPath(); - int index = uiURI.indexOf("proxy/") + 5; - uiURI = uiURI.substring(index); - String serviceURI = getServiceUri(); - serviceURI += uiURI; - - MultivaluedMap<String, String> parameters = ui.getQueryParameters(); - StringBuilder urlBuilder = new StringBuilder(serviceURI); - boolean firstEntry = true; - for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { - if ("user.name".equals(entry.getKey())) { - ArrayList<String> vals = new ArrayList<String>(); - vals.add(viewContext.getUsername()); - entry.setValue(vals); - } - if (firstEntry) { - urlBuilder.append("?"); - } else { - urlBuilder.append("&"); - } - boolean firstVal = true; - for (String val : entry.getValue()) { - urlBuilder.append(firstVal ? "" : "&").append(entry.getKey()) - .append("=").append(val); - firstVal = false; - } - firstEntry = false; - } - return urlBuilder.toString(); - } - - private String getServiceUri() { - String serviceURI = viewContext.getProperties().get(SERVICE_URI_PROP) != null ? viewContext - .getProperties().get(SERVICE_URI_PROP) : DEFAULT_SERVICE_URI; - return serviceURI; - } - - public Response consumeService(HttpHeaders headers, String urlToRead, - String method, String body, Map<String, String> customHeaders) { - Response response = null; - InputStream stream = readFromOozie(headers, urlToRead, method, body, - customHeaders); - String stringResponse = null; - try { - stringResponse = IOUtils.toString(stream); - } catch (IOException e) { - LOGGER.error("Error while converting stream to string", e); - throw new RuntimeException(e); - } - if (stringResponse.contains(Response.Status.BAD_REQUEST.name())) { - response = Response.status(Response.Status.BAD_REQUEST) - .entity(stringResponse).type(MediaType.TEXT_PLAIN).build(); - } else { - response = Response.status(Response.Status.OK).entity(stringResponse) - .type(deduceType(stringResponse)).build(); - } - return response; - } - - private InputStream readFromOozie(HttpHeaders headers, String urlToRead, - String method, String body, Map<String, String> customHeaders) { - URLStreamProvider streamProvider = viewContext.getURLStreamProvider(); - Map<String, String> newHeaders = getHeaders(headers); - newHeaders.put(USER_NAME_HEADER, USER_OOZIE_SUPER); - - newHeaders.put(DO_AS_HEADER, viewContext.getUsername()); - newHeaders.put("Accept", MediaType.APPLICATION_JSON); - if (customHeaders != null) { - newHeaders.putAll(customHeaders); - } - LOGGER.info(String.format("Proxy request for url: [%s] %s", method, - urlToRead)); - boolean securityEnabled = isSecurityEnabled(); - LOGGER.debug(String.format("IS security enabled:[%b]", securityEnabled)); - InputStream stream = null; - try { - if (securityEnabled) { - stream = streamProvider.readAsCurrent(urlToRead, method, body, newHeaders); - - } else { - stream = streamProvider.readFrom(urlToRead, method, body, newHeaders); - } - } catch (IOException e) { - LOGGER.error("error talking to oozie", e); - throw new RuntimeException(e); - } - return stream; - } - - public Response consumeService(HttpHeaders headers, String urlToRead, - String method, String body) throws Exception { - return consumeService(headers, urlToRead, method, body, null); - } - - public Map<String, String> getHeaders(HttpHeaders headers) { - MultivaluedMap<String, String> requestHeaders = headers.getRequestHeaders(); - Set<Entry<String, List<String>>> headerEntrySet = requestHeaders.entrySet(); - HashMap<String, String> headersMap = new HashMap<String, String>(); - for (Entry<String, List<String>> headerEntry : headerEntrySet) { - String key = headerEntry.getKey(); - List<String> values = headerEntry.getValue(); - headersMap.put(key, strJoin(values, ",")); - } - return headersMap; - } - - public String strJoin(List<String> strings, String separator) { - StringBuilder stringBuilder = new StringBuilder(); - for (int i = 0, il = strings.size(); i < il; i++) { - if (i > 0) { - stringBuilder.append(separator); - } - stringBuilder.append(strings.get(i)); - } - return stringBuilder.toString(); - } - - private MediaType deduceType(String stringResponse) { - if (stringResponse.startsWith("{")) { - return MediaType.APPLICATION_JSON_TYPE; - } else if (stringResponse.startsWith("<")) { - return MediaType.TEXT_XML_TYPE; - } else { - return MediaType.APPLICATION_JSON_TYPE; - } - } - - private HdfsApi getHdfsgetApi() { - if (_hdfsApi == null) { - try { - _hdfsApi = HdfsUtil.connectToHDFSApi(viewContext); - } catch (Exception ex) { - LOGGER.error("Error in getting HDFS Api", ex); - throw new RuntimeException("HdfsApi connection failed. Check \"webhdfs.url\" property", ex); - } - } - return _hdfsApi; - } - - private String generateConigXml(Map<String, String> map) { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db; - try { - db = dbf.newDocumentBuilder(); - Document doc = db.newDocument(); - Element configElement = doc.createElement("configuration"); - doc.appendChild(configElement); - for (Map.Entry<String, String> entry : map.entrySet()) { - Element propElement = doc.createElement("property"); - configElement.appendChild(propElement); - Element nameElem = doc.createElement("name"); - nameElem.setTextContent(entry.getKey()); - Element valueElem = doc.createElement("value"); - valueElem.setTextContent(entry.getValue()); - propElement.appendChild(nameElem); - propElement.appendChild(valueElem); - } - DOMSource domSource = new DOMSource(doc); - StringWriter writer = new StringWriter(); - StreamResult result = new StreamResult(writer); - TransformerFactory tf = TransformerFactory.newInstance(); - Transformer transformer = tf.newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer - .setOutputProperty(XML_INDENT_AMT_PROP_NAME, XML_INDENT_SPACES); - transformer.transform(domSource, result); - return writer.toString(); - } catch (ParserConfigurationException | TransformerException e) { - LOGGER.error("error in generating config xml", e); - throw new RuntimeException(e); - } - - } - - private String formatXml(String xml) { - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - try { - DocumentBuilder db = dbf.newDocumentBuilder(); - StreamResult result = new StreamResult(new StringWriter()); - Document document = db.parse(new InputSource(new StringReader(xml))); - Transformer transformer = TransformerFactory.newInstance() - .newTransformer(); - transformer.setOutputProperty(OutputKeys.INDENT, "yes"); - transformer - .setOutputProperty(XML_INDENT_AMT_PROP_NAME, XML_INDENT_SPACES); - DOMSource source = new DOMSource(document); - transformer.transform(source, result); - return result.getWriter().toString(); - } catch (ParserConfigurationException | SAXException | IOException - | TransformerFactoryConfigurationError | TransformerException e) { - LOGGER.error("Error in formatting xml", e); - throw new RuntimeException(e); - } - } - - private boolean isSecurityEnabled() { - boolean securityEnabled = Boolean.valueOf(getHadoopConfigs().get( - "security_enabled")); - return securityEnabled; - } - - private Map<String, String> getHadoopConfigs() { - return viewContext.getInstanceData(); - } - -} + private final static Logger LOGGER = LoggerFactory + .getLogger(OozieProxyImpersonator.class); + + private static final String OOZIEPARAM_PREFIX = "oozieparam."; + private static final int OOZIEPARAM_PREFIX_LENGTH = OOZIEPARAM_PREFIX + .length(); + private static final String EQUAL_SYMBOL = "="; + private static final String OOZIE_WF_RERUN_FAILNODES_CONF_KEY = "oozie.wf.rerun.failnodes"; + private static final String OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY = "oozie.use.system.libpath"; + + private ViewContext viewContext; + + private static final String USER_NAME_HEADER = "user.name"; + private static final String USER_OOZIE_SUPER = "oozie"; + private static final String DO_AS_HEADER = "doAs"; + + private static final String SERVICE_URI_PROP = "oozie.service.uri"; + private static final String DEFAULT_SERVICE_URI = "http://sandbox.hortonworks.com:11000/oozie"; + private Utils utils=new Utils(); + private OozieUtils oozieUtils=new OozieUtils(); + private HDFSFileUtils hdfsFileUtils; + private static enum ErrorCodes { + OOZIE_SUBMIT_ERROR("error.oozie.submit", "Oozie Submit error"), OOZIE_IO_ERROR( + "error.oozie.io", "Oozie I/O error"), FILE_ACCESS_ACL_ERROR( + "error.file.access.control", + "Access Error to file due to access control"), FILE_ACCESS_UNKNOWN_ERROR( + "error.file.access", "Error accessing file"), WORKFLOW_PATH_EXISTS( + "error.workflow.path.exists", "Worfklow path exists"); + private String errorCode; + private String description; + + ErrorCodes(String errorCode, String description) { + this.errorCode = errorCode; + this.description = description; + } + + public String getErrorCode() { + return errorCode; + } + + public String getDescription() { + return description; + } + } + + @Inject + public OozieProxyImpersonator(ViewContext viewContext) { + this.viewContext = viewContext; + hdfsFileUtils=new HDFSFileUtils(viewContext); + LOGGER.info(String.format( + "OozieProxyImpersonator initialized for instance: %s", + viewContext.getInstanceName())); + } + + @Path("/fileServices") + public FileServices fileServices() { + return new FileServices(viewContext); + } + + @GET + @Path("/getCurrentUserName") + public Response getCurrentUserName() { + return Response.ok(viewContext.getUsername()).build(); + } + + @POST + @Path("/submitJob") + @Consumes({ MediaType.TEXT_PLAIN + "," + MediaType.TEXT_XML }) + public Response submitJob(String postBody, @Context HttpHeaders headers, + @Context UriInfo ui, @QueryParam("app.path") String appPath, + @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite, + @QueryParam("jobType") String jobType) { + LOGGER.info("submit workflow job called"); + return submitJobInternal(postBody, headers, ui, appPath, overwrite, + JobType.valueOf(jobType)); + } + + @POST + @Path("/submitWorkflow") + @Consumes({ MediaType.TEXT_PLAIN + "," + MediaType.TEXT_XML }) + public Response submitWorkflow(String postBody, + @Context HttpHeaders headers, @Context UriInfo ui, + @QueryParam("app.path") String appPath, + @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite) { + LOGGER.info("submit workflow job called"); + return submitJobInternal(postBody, headers, ui, appPath, overwrite, + JobType.WORKFLOW); + } + + @POST + @Path("/saveWorkflow") + @Consumes({ MediaType.TEXT_PLAIN + "," + MediaType.TEXT_XML }) + public Response saveWorkflow(String postBody, @Context HttpHeaders headers, + @Context UriInfo ui, @QueryParam("app.path") String appPath, + @DefaultValue("false") @QueryParam("overwrite") Boolean overwrite) { + LOGGER.info("save workflow called"); + if (StringUtils.isEmpty(appPath)) { + throw new RuntimeException("app path can't be empty."); + } + appPath = appPath.trim(); + if (!overwrite) { + boolean fileExists = hdfsFileUtils.fileExists(appPath); + if (fileExists) { + return getFileExistsResponse(); + } + } + postBody = utils.formatXml(postBody); + try { + String filePath = hdfsFileUtils.createWorkflowFile(getWorkflowFileName(appPath),postBody, overwrite); + LOGGER.info(String.format( + "submit workflow job done. filePath=[%s]", filePath)); + return Response.ok().build(); + } catch (Exception ex) { + LOGGER.error(ex.getMessage(), ex); + return getRespCodeForException(ex); + + } + } + + private Response submitJobInternal(String postBody, HttpHeaders headers, + UriInfo ui, String appPath, Boolean overwrite, JobType jobType) { + if (StringUtils.isEmpty(appPath)) { + throw new RuntimeException("app path can't be empty."); + } + appPath = appPath.trim(); + if (!overwrite) { + boolean fileExists = hdfsFileUtils.fileExists(appPath); + if (fileExists) { + return getFileExistsResponse(); + } + } + postBody = utils.formatXml(postBody); + try { + String filePath = hdfsFileUtils.createWorkflowFile(getWorkflowFileName(appPath),postBody,overwrite); + LOGGER.info(String.format( + "submit workflow job done. filePath=[%s]", filePath)); + } catch (Exception ex) { + LOGGER.error(ex.getMessage(), ex); + return getRespCodeForException(ex); + + } + String response = submitWorkflowJobToOozie(headers, appPath, + ui.getQueryParameters(), jobType); + if (response != null && response.trim().startsWith("{")) { + // dealing with oozie giving error but with 200 response. + return Response.status(Response.Status.OK).entity(response).build(); + } else { + HashMap<String, String> resp = new HashMap<String, String>(); + resp.put("status", ErrorCodes.OOZIE_SUBMIT_ERROR.getErrorCode()); + resp.put("message", response); + return Response.status(Response.Status.BAD_REQUEST).entity(resp) + .build(); + } + + } + + private Response getRespCodeForException(Exception ex) { + if (ex instanceof AccessControlException) { + HashMap<String, String> errorDetails = getErrorDetails( + ErrorCodes.FILE_ACCESS_ACL_ERROR.getErrorCode(), + ErrorCodes.FILE_ACCESS_ACL_ERROR.getDescription(), ex); + return Response.status(Response.Status.BAD_REQUEST) + .entity(errorDetails).build(); + }else if (ex instanceof IOException){ + HashMap<String, String> errorDetails = getErrorDetails( + ErrorCodes.FILE_ACCESS_UNKNOWN_ERROR.getErrorCode(), + ErrorCodes.FILE_ACCESS_UNKNOWN_ERROR.getDescription(), ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(errorDetails).build(); + }else { + HashMap<String, String> errorDetails = getErrorDetails( + ErrorCodes.FILE_ACCESS_UNKNOWN_ERROR.getErrorCode(), + ErrorCodes.FILE_ACCESS_UNKNOWN_ERROR.getDescription(), ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(errorDetails).build(); + } + + + } + + private Response getFileExistsResponse() { + HashMap<String, String> resp = new HashMap<String, String>(); + resp.put("status", ErrorCodes.WORKFLOW_PATH_EXISTS.getErrorCode()); + resp.put("message", ErrorCodes.WORKFLOW_PATH_EXISTS.getDescription()); + return Response.status(Response.Status.BAD_REQUEST).entity(resp) + .build(); + } + + private String getWorkflowFileName(String appPath) { + String workflowFile = null; + if (appPath.endsWith(".xml")) { + workflowFile = appPath; + } else { + workflowFile = appPath + (appPath.endsWith("/") ? "" : "/") + + "workflow.xml"; + } + return workflowFile; + } + + @GET + @Path("/readWorkflowXml") + public Response readWorkflowXxml( + @QueryParam("workflowXmlPath") String workflowPath) { + if (StringUtils.isEmpty(workflowPath)) { + throw new RuntimeException("workflowXmlPath can't be empty."); + } + try { + final FSDataInputStream is = hdfsFileUtils.read(workflowPath); + StreamingOutput streamer = new StreamingOutput() { + @Override + public void write(OutputStream os) throws IOException, + WebApplicationException { + IOUtils.copy(is, os); + is.close(); + os.close(); + } + }; + return Response.ok(streamer).status(200).build(); + } catch(IOException e){ + return getRespCodeForException(e); + } + } + + private HashMap<String, String> getErrorDetails(String status, + String message, Exception ex) { + HashMap<String, String> resp = new HashMap<String, String>(); + resp.put("status", status); + if (message != null) { + resp.put("message", message); + } + if (ex != null) { + resp.put("stackTrace", ExceptionUtils.getFullStackTrace(ex)); + } + return resp; + } + + @GET + @Path("/getDag") + @Produces("image/png") + public Response submitWorkflow(@Context HttpHeaders headers, + @Context UriInfo ui, @QueryParam("jobid") String jobid) { + String imgUrl = getServiceUri() + "/v2/job/" + jobid + "?show=graph"; + Map<String, String> newHeaders = utils.getHeaders(headers); + final InputStream is = readFromOozie(headers, imgUrl, HttpMethod.GET, + null, newHeaders); + StreamingOutput streamer = new StreamingOutput() { + + @Override + public void write(OutputStream os) throws IOException, + WebApplicationException { + IOUtils.copy(is, os); + is.close(); + os.close(); + } + + }; + return Response.ok(streamer).status(200).build(); + } + + @GET + @Path("/{path: .*}") + public Response handleGet(@Context HttpHeaders headers, @Context UriInfo ui) { + try { + String serviceURI = buildURI(ui); + return consumeService(headers, serviceURI, HttpMethod.GET, null); + } catch (Exception ex) { + LOGGER.error("Error in GET proxy", ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(getErrorDetailsForException("Oozie", ex)).build(); + } + } + + @POST + @Path("/{path: .*}") + public Response handlePost(String xml, @Context HttpHeaders headers, + @Context UriInfo ui) { + try { + String serviceURI = buildURI(ui); + return consumeService(headers, serviceURI, HttpMethod.POST, xml); + } catch (Exception ex) { + LOGGER.error("Error in POST proxy", ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(getErrorDetailsForException("Oozie", ex)).build(); + } + } + + @DELETE + @Path("/{path: .*}") + public Response handleDelete(@Context HttpHeaders headers, + @Context UriInfo ui) { + try { + String serviceURI = buildURI(ui); + return consumeService(headers, serviceURI, HttpMethod.POST, null); + } catch (Exception ex) { + LOGGER.error("Error in DELETE proxy", ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(getErrorDetailsForException("Oozie", ex)).build(); + } + } + + @PUT + @Path("/{path: .*}") + public Response handlePut(String body, @Context HttpHeaders headers, + @Context UriInfo ui) { + try { + String serviceURI = buildURI(ui); + return consumeService(headers, serviceURI, HttpMethod.PUT, body); + } catch (Exception ex) { + LOGGER.error("Error in PUT proxy", ex); + return Response.status(Response.Status.INTERNAL_SERVER_ERROR) + .entity(getErrorDetailsForException("Oozie", ex)).build(); + } + } + + private Map<String, String> getErrorDetailsForException(String component, + Exception ex) { + String errorCode = component + "exception"; + String errorMessage = component + " Exception"; + if (ex instanceof RuntimeException) { + Throwable cause = ex.getCause(); + if (cause instanceof IOException) { + errorCode = component + "io.exception"; + errorMessage = component + "IO Exception"; + } + } + return getErrorDetails(errorCode, errorMessage, ex); + } + + private String submitWorkflowJobToOozie(HttpHeaders headers, + String filePath, MultivaluedMap<String, String> queryParams, + JobType jobType) { + String nameNode = "hdfs://" + + viewContext.getCluster().getConfigurationValue("hdfs-site", + "dfs.namenode.rpc-address"); + + if (!queryParams.containsKey("config.nameNode")) { + ArrayList<String> nameNodes = new ArrayList<String>(); + LOGGER.info("Namenode===" + nameNode); + nameNodes.add(nameNode); + queryParams.put("config.nameNode", nameNodes); + } + + HashMap<String, String> workflowConigs = getWorkflowConfigs(filePath, + queryParams, jobType, nameNode); + String configXMl = oozieUtils.generateConfigXml(workflowConigs); + LOGGER.info("Config xml==" + configXMl); + HashMap<String, String> customHeaders = new HashMap<String, String>(); + customHeaders.put("Content-Type", "application/xml;charset=UTF-8"); + Response serviceResponse = consumeService(headers, getServiceUri() + + "/v2/jobs?" + getJobSumbitOozieParams(queryParams), + HttpMethod.POST, configXMl, customHeaders); + + LOGGER.info("REsp from oozie status entity==" + + serviceResponse.getEntity()); + if (serviceResponse.getEntity() instanceof String) { + return (String) serviceResponse.getEntity(); + } else { + return "success"; + } + + } + + private HashMap<String, String> getWorkflowConfigs(String filePath, + MultivaluedMap<String, String> queryParams, JobType jobType, + String nameNode) { + HashMap<String, String> workflowConigs = new HashMap<String, String>(); + if (queryParams.containsKey("resourceManager") + && "useDefault".equals(queryParams.getFirst("resourceManager"))) { + String jobTrackerNode = viewContext.getCluster() + .getConfigurationValue("yarn-site", + "yarn.resourcemanager.address"); + LOGGER.info("jobTrackerNode===" + jobTrackerNode); + workflowConigs.put("resourceManager", jobTrackerNode); + workflowConigs.put("jobTracker", jobTrackerNode); + } + if (queryParams != null) { + for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { + if (entry.getKey().startsWith("config.")) { + if (entry.getValue() != null && entry.getValue().size() > 0) { + workflowConigs.put(entry.getKey().substring(7), entry + .getValue().get(0)); + } + } + } + } + + if (queryParams.containsKey("oozieconfig.useSystemLibPath")) { + String useSystemLibPath = queryParams + .getFirst("oozieconfig.useSystemLibPath"); + workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, + useSystemLibPath); + } else { + workflowConigs.put(OOZIE_USE_SYSTEM_LIBPATH_CONF_KEY, "true"); + } + if (queryParams.containsKey("oozieconfig.rerunOnFailure")) { + String rerunFailnodes = queryParams + .getFirst("oozieconfig.rerunOnFailure"); + workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, + rerunFailnodes); + } else { + workflowConigs.put(OOZIE_WF_RERUN_FAILNODES_CONF_KEY, "true"); + } + workflowConigs.put("user.name", viewContext.getUsername()); + workflowConigs.put(oozieUtils.getJobPathPropertyKey(jobType), nameNode + filePath); + return workflowConigs; + } + + private String getJobSumbitOozieParams( + MultivaluedMap<String, String> queryParams) { + StringBuilder query = new StringBuilder(); + if (queryParams != null) { + for (Map.Entry<String, List<String>> entry : queryParams.entrySet()) { + if (entry.getKey().startsWith(OOZIEPARAM_PREFIX)) { + if (entry.getValue() != null && entry.getValue().size() > 0) { + for (String val : entry.getValue()) { + query.append( + entry.getKey().substring( + OOZIEPARAM_PREFIX_LENGTH)) + .append(EQUAL_SYMBOL).append(val) + .append("&"); + } + } + } + } + } + return query.toString(); + } + + private String buildURI(UriInfo ui) { + String uiURI = ui.getAbsolutePath().getPath(); + int index = uiURI.indexOf("proxy/") + 5; + uiURI = uiURI.substring(index); + String serviceURI = getServiceUri(); + serviceURI += uiURI; + MultivaluedMap<String, String> params = addOrReplaceUserName(ui.getQueryParameters()); + return serviceURI+utils.convertParamsToUrl(params); + } + private MultivaluedMap<String, String> addOrReplaceUserName(MultivaluedMap<String, String> parameters){ + for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { + if ("user.name".equals(entry.getKey())) { + ArrayList<String> vals = new ArrayList<String>(1); + vals.add(viewContext.getUsername()); + entry.setValue(vals); + } + } + return parameters; + } + + private String getServiceUri() { + String serviceURI = viewContext.getProperties().get(SERVICE_URI_PROP) != null ? viewContext + .getProperties().get(SERVICE_URI_PROP) : DEFAULT_SERVICE_URI; + return serviceURI; + } + + private Response consumeService(HttpHeaders headers, String urlToRead, + String method, String body) throws Exception { + return consumeService(headers, urlToRead, method, body, null); + } + + private Response consumeService(HttpHeaders headers, String urlToRead, + String method, String body, Map<String, String> customHeaders) { + Response response = null; + InputStream stream = readFromOozie(headers, urlToRead, method, body, + customHeaders); + String stringResponse = null; + try { + stringResponse = IOUtils.toString(stream); + } catch (IOException e) { + LOGGER.error("Error while converting stream to string", e); + throw new RuntimeException(e); + } + if (stringResponse.contains(Response.Status.BAD_REQUEST.name())) { + response = Response.status(Response.Status.BAD_REQUEST) + .entity(stringResponse).type(MediaType.TEXT_PLAIN).build(); + } else { + response = Response.status(Response.Status.OK) + .entity(stringResponse).type(utils.deduceType(stringResponse)) + .build(); + } + return response; + } + + private InputStream readFromOozie(HttpHeaders headers, String urlToRead, + String method, String body, Map<String, String> customHeaders) { + + Map<String, String> newHeaders = utils.getHeaders(headers); + newHeaders.put(USER_NAME_HEADER, USER_OOZIE_SUPER); + + newHeaders.put(DO_AS_HEADER, viewContext.getUsername()); + newHeaders.put("Accept", MediaType.APPLICATION_JSON); + if (customHeaders != null) { + newHeaders.putAll(customHeaders); + } + LOGGER.info(String.format("Proxy request for url: [%s] %s", method, + urlToRead)); + + return readFromUrl(urlToRead, method, body, newHeaders); + } + + private InputStream readFromUrl(String urlToRead, String method, String body, + Map<String, String> newHeaders) { + URLStreamProvider streamProvider = viewContext.getURLStreamProvider(); + InputStream stream = null; + try { + if (isSecurityEnabled()) { + stream = streamProvider.readAsCurrent(urlToRead, method, body, + newHeaders); + + } else { + stream = streamProvider.readFrom(urlToRead, method, body, + newHeaders); + } + } catch (IOException e) { + LOGGER.error("error talking to oozie", e); + throw new RuntimeException(e); + } + return stream; + } + + + private boolean isSecurityEnabled() { + String authType = viewContext.getCluster().getConfigurationValue( + "core-site", "hadoop.security.authentication"); + LOGGER.info("Auth Type=" + authType); + return !"simple".equalsIgnoreCase(authType); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieUtils.java ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieUtils.java b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieUtils.java new file mode 100644 index 0000000..170132f --- /dev/null +++ b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/OozieUtils.java @@ -0,0 +1,71 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.ambari.view; + +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.w3c.dom.Element; + +public class OozieUtils { + private final static Logger LOGGER = LoggerFactory + .getLogger(OozieUtils.class); + private Utils utils = new Utils(); + + public String generateConfigXml(Map<String, String> map) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + DocumentBuilder db; + try { + db = dbf.newDocumentBuilder(); + Document doc = db.newDocument(); + Element configElement = doc.createElement("configuration"); + doc.appendChild(configElement); + for (Map.Entry<String, String> entry : map.entrySet()) { + Element propElement = doc.createElement("property"); + configElement.appendChild(propElement); + Element nameElem = doc.createElement("name"); + nameElem.setTextContent(entry.getKey()); + Element valueElem = doc.createElement("value"); + valueElem.setTextContent(entry.getValue()); + propElement.appendChild(nameElem); + propElement.appendChild(valueElem); + } + return utils.generateXml(doc); + } catch (ParserConfigurationException e) { + LOGGER.error("error in generating config xml", e); + throw new RuntimeException(e); + } + } + public String getJobPathPropertyKey(JobType jobType) { + switch (jobType) { + case WORKFLOW: + return "oozie.wf.application.path"; + case COORDINATOR: + return "oozie.coord.application.path"; + case BUNDLE: + return "oozie.bundle.application.path"; + } + throw new RuntimeException("Unknown Job Type"); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/Utils.java ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/Utils.java b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/Utils.java new file mode 100644 index 0000000..61d878e --- /dev/null +++ b/contrib/views/wfmanager/src/main/java/org/apache/oozie/ambari/view/Utils.java @@ -0,0 +1,154 @@ +/** + * 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 + * <p> + * http://www.apache.org/licenses/LICENSE-2.0 + * <p> + * 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.ambari.view; + +import java.io.IOException; +import java.io.StringReader; +import java.io.StringWriter; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Map.Entry; + +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; +import javax.xml.transform.OutputKeys; +import javax.xml.transform.Transformer; +import javax.xml.transform.TransformerConfigurationException; +import javax.xml.transform.TransformerException; +import javax.xml.transform.TransformerFactory; +import javax.xml.transform.TransformerFactoryConfigurationError; +import javax.xml.transform.dom.DOMSource; +import javax.xml.transform.stream.StreamResult; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.w3c.dom.Document; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; + +public class Utils { + private static final String XML_INDENT_SPACES = "4"; + private static final String XML_INDENT_AMT_PROP_NAME = "{http://xml.apache.org/xslt}indent-amount"; + private final static Logger LOGGER = LoggerFactory + .getLogger(Utils.class); + public String formatXml(String xml) { + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + try { + DocumentBuilder db = dbf.newDocumentBuilder(); + StreamResult result = new StreamResult(new StringWriter()); + Document document = db + .parse(new InputSource(new StringReader(xml))); + Transformer transformer = TransformerFactory.newInstance() + .newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(XML_INDENT_AMT_PROP_NAME, + XML_INDENT_SPACES); + DOMSource source = new DOMSource(document); + transformer.transform(source, result); + return result.getWriter().toString(); + } catch (ParserConfigurationException | SAXException | IOException + | TransformerFactoryConfigurationError | TransformerException e) { + LOGGER.error("Error in formatting xml", e); + throw new RuntimeException(e); + } + } + public String generateXml(Document doc){ + DOMSource domSource = new DOMSource(doc); + StringWriter writer = new StringWriter(); + StreamResult result = new StreamResult(writer); + TransformerFactory tf = TransformerFactory.newInstance(); + Transformer transformer; + try { + transformer = tf.newTransformer(); + transformer.setOutputProperty(OutputKeys.INDENT, "yes"); + transformer.setOutputProperty(XML_INDENT_AMT_PROP_NAME, + XML_INDENT_SPACES); + try { + transformer.transform(domSource, result); + } catch (TransformerException e) { + throw new RuntimeException(e); + } + return writer.toString(); + } catch (TransformerConfigurationException tce) { + throw new RuntimeException(tce); + } + + } + + public Map<String, String> getHeaders(HttpHeaders headers) { + MultivaluedMap<String, String> requestHeaders = headers + .getRequestHeaders(); + Set<Entry<String, List<String>>> headerEntrySet = requestHeaders + .entrySet(); + HashMap<String, String> headersMap = new HashMap<String, String>(); + for (Entry<String, List<String>> headerEntry : headerEntrySet) { + String key = headerEntry.getKey(); + List<String> values = headerEntry.getValue(); + headersMap.put(key, strJoin(values, ",")); + } + return headersMap; + } + + public String strJoin(List<String> strings, String separator) { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0, il = strings.size(); i < il; i++) { + if (i > 0) { + stringBuilder.append(separator); + } + stringBuilder.append(strings.get(i)); + } + return stringBuilder.toString(); + } + public MediaType deduceType(String stringResponse) { + if (stringResponse.startsWith("{")) { + return MediaType.APPLICATION_JSON_TYPE; + } else if (stringResponse.startsWith("<")) { + return MediaType.TEXT_XML_TYPE; + } else { + return MediaType.APPLICATION_JSON_TYPE; + } + } + + public String convertParamsToUrl(MultivaluedMap<String, String> parameters) { + StringBuilder urlBuilder = new StringBuilder(); + boolean firstEntry = true; + for (Map.Entry<String, List<String>> entry : parameters.entrySet()) { + if (firstEntry) { + urlBuilder.append("?"); + } else { + urlBuilder.append("&"); + } + boolean firstVal = true; + for (String val : entry.getValue()) { + urlBuilder.append(firstVal ? "" : "&").append(entry.getKey()) + .append("=").append(val); + firstVal = false; + } + firstEntry = false; + } + return urlBuilder.toString(); + } + + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/.jshintrc ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/.jshintrc b/contrib/views/wfmanager/src/main/resources/ui/.jshintrc new file mode 100644 index 0000000..ed9a144 --- /dev/null +++ b/contrib/views/wfmanager/src/main/resources/ui/.jshintrc @@ -0,0 +1,38 @@ +{ + "predef": [ + "document", + "window", + "-Promise", + "vkbeautify", + "moment", + "X2JS", + "jsPlumb", + "cytoscape", + "dagre" + ], + "browser": true, + "boss": true, + "curly": true, + "debug": false, + "devel": true, + "eqeqeq": true, + "evil": true, + "forin": false, + "immed": false, + "laxbreak": false, + "newcap": true, + "noarg": true, + "noempty": false, + "nonew": false, + "nomen": false, + "onevar": false, + "plusplus": false, + "regexp": false, + "undef": true, + "sub": true, + "strict": false, + "white": false, + "eqnull": true, + "esnext": true, + "unused": true +} http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/.gitkeep ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/.gitkeep b/contrib/views/wfmanager/src/main/resources/ui/app/components/.gitkeep new file mode 100644 index 0000000..e69de29 http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js index d53b459..7ea46c4 100644 --- a/contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/archive-config.js @@ -16,9 +16,8 @@ */ import Ember from 'ember'; -import EmberValidations from 'ember-validations'; -export default Ember.Component.extend(EmberValidations,{ +export default Ember.Component.extend({ fileBrowser : Ember.inject.service('file-browser'), initialize : function(){ this.on('fileSelected',function(fileName){ http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-config.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-config.js new file mode 100644 index 0000000..2799db5 --- /dev/null +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-config.js @@ -0,0 +1,262 @@ +/* +* 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. +*/ +import Ember from 'ember'; +import {Bundle} from '../domain/bundle/bundle'; +import {BundleGenerator} from '../domain/bundle/bundle-xml-generator'; +import {BundleXmlImporter} from '../domain/bundle/bundle-xml-importer'; +import { validator, buildValidations } from 'ember-cp-validations'; +import Constants from '../utils/constants'; + +const Validations = buildValidations({ + 'bundle.name': validator('presence', { + presence : true + }), + 'bundle.coordinators': { + validators: [ + validator('operand-length', { + min : 1, + dependentKeys: ['bundle','bundle.coordinators.[]'], + message : 'Alteast one coordinator is required', + disabled(model, attribute) { + return !model.get('bundle'); + } + }) + ] + } +}); + +export default Ember.Component.extend(Ember.Evented, Validations, { + bundle : null, + propertyExtractor : Ember.inject.service('property-extractor'), + fileBrowser : Ember.inject.service('file-browser'), + workspaceManager : Ember.inject.service('workspace-manager'), + initialize : function(){ + var draftBundle = this.get('workspaceManager').restoreWorkInProgress(this.get('tabInfo.id')); + if(draftBundle){ + this.set('bundle', JSON.parse(draftBundle)); + }else{ + this.set('bundle', this.createBundle()); + } + this.get('fileBrowser').on('fileBrowserOpened',function(context){ + this.get('fileBrowser').setContext(context); + }.bind(this)); + this.on('fileSelected',function(fileName){ + this.set(this.get('filePathModel'), fileName); + }.bind(this)); + if(Ember.isBlank(this.get('bundle.name'))){ + this.set('bundle.name', Ember.copy(this.get('tabInfo.name'))); + } + this.set('showErrorMessage', false); + this.schedulePersistWorkInProgress(); + }.on('init'), + onDestroy : function(){ + Ember.run.cancel(this.schedulePersistWorkInProgress); + this.persistWorkInProgress(); + }.on('willDestroyElement'), + observeFilePath : Ember.observer('bundleFilePath', function(){ + if(!this.get('bundleFilePath') || null === this.get('bundleFilePath')){ + return; + }else{ + this.sendAction('changeFilePath', this.get('tabInfo'), this.get('bundleFilePath')); + } + }), + nameObserver : Ember.observer('bundle.name', function(){ + if(!this.get('bundle')){ + return; + }else if(this.get('bundle') && Ember.isBlank(this.get('bundle.name'))){ + if(!this.get('clonedTabInfo')){ + this.set('clonedTabInfo', Ember.copy(this.get('tabInfo'))); + } + this.sendAction('changeTabName', this.get('tabInfo'), this.get('clonedTabInfo.name')); + }else{ + this.sendAction('changeTabName', this.get('tabInfo'), this.get('bundle.name')); + } + }), + schedulePersistWorkInProgress (){ + Ember.run.later(function(){ + this.persistWorkInProgress(); + this.schedulePersistWorkInProgress(); + }.bind(this), Constants.persistWorkInProgressInterval); + }, + persistWorkInProgress (){ + if(!this.get('bundle')){ + return; + } + var json = JSON.stringify(this.get("bundle")); + this.get('workspaceManager').saveWorkInProgress(this.get('tabInfo.id'), json); + }, + createBundle (){ + return Bundle.create({ + name : '', + kickOffTime : { + value : '', + displayValue : '', + type : 'date' + }, + coordinators : null + }); + }, + importSampleBundle (){ + var deferred = Ember.RSVP.defer(); + Ember.$.ajax({ + url: "/sampledata/bundle.xml", + dataType: "text", + cache:false, + success: function(data) { + deferred.resolve(data); + }.bind(this), + failure : function(data){ + deferred.reject(data); + } + }); + return deferred; + }, + importBundle (filePath){ + this.set("bundleFilePath", filePath); + this.set("isImporting", false); + var deferred = this.getBundleFromHdfs(filePath); + deferred.promise.then(function(data){ + this.getBundleFromXml(data); + this.set("isImporting", false); + }.bind(this)).catch(function(){ + this.set("isImporting", false); + this.set("isImportingSuccess", false); + }.bind(this)); + }, + getBundleFromHdfs(filePath){ + var url = Ember.ENV.API_URL + "/readWorkflowXml?workflowXmlPath="+filePath; + var deferred = Ember.RSVP.defer(); + Ember.$.ajax({ + url: url, + method: 'GET', + dataType: "text", + beforeSend: function (xhr) { + xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000)); + xhr.setRequestHeader("X-Requested-By", "Ambari"); + } + }).done(function(data){ + deferred.resolve(data); + }).fail(function(){ + deferred.reject(); + }); + return deferred; + }, + getBundleFromXml(bundleXml){ + var bundleXmlImporter = BundleXmlImporter.create({}); + var bundle = bundleXmlImporter.importBundle(bundleXml); + this.set("bundle", bundle); + }, + actions : { + closeFileBrowser(){ + this.set("showingFileBrowser", false); + this.get('fileBrowser').getContext().trigger('fileSelected', this.get('filePath')); + if(this.get('bundleFilePath')){ + this.importBundle(Ember.copy(this.get('bundleFilePath'))); + this.set('bundleFilePath', null); + } + }, + openFileBrowser(model, context){ + if(!context){ + context = this; + } + this.get('fileBrowser').trigger('fileBrowserOpened',context); + this.set('filePathModel', model); + this.set('showingFileBrowser', true); + }, + createCoordinator(){ + this.set('coordinatorEditMode', false); + this.set('coordinatorCreateMode', true); + this.set('currentCoordinator',{ + name : undefined, + appPath : undefined, + configuration : { + property : Ember.A([]) + } + }); + }, + editCoordinator(index){ + this.set('coordinatorEditMode', true); + this.set('coordinatorCreateMode', false); + this.set('currentCoordinatorIndex', index); + this.set('currentCoordinator', Ember.copy(this.get('bundle.coordinators').objectAt(index))); + }, + addCoordinator(){ + if(!this.get('bundle.coordinators')){ + this.set('bundle.coordinators', Ember.A([])); + } + this.get('bundle.coordinators').pushObject(Ember.copy(this.get('currentCoordinator'))); + this.set('coordinatorCreateMode', false); + }, + updateCoordinator(){ + this.get('bundle.coordinators').replace(this.get('currentCoordinatorIndex'), 1, Ember.copy(this.get('currentCoordinator'))); + this.set('coordinatorEditMode', false); + }, + deleteCoordinator(index){ + this.get('bundle.coordinators').removeAt(index); + if(index === this.get('currentCoordinatorIndex')){ + this.set('coordinatorEditMode', false); + } + }, + cancelCoordinatorOperation(){ + this.set('coordinatorCreateMode', false); + this.set('coordinatorEditMode', false); + }, + confirmReset(){ + this.set('showingResetConfirmation', true); + }, + resetBundle(){ + this.set('bundle', this.createBundle()); + }, + closeBundleSubmitConfig(){ + this.set("showingJobConfig", false); + }, + submitBundle(){ + if(this.get('validations.isInvalid')) { + this.set('showErrorMessage', true); + return; + } + var bundleGenerator = BundleGenerator.create({bundle:this.get("bundle")}); + var bundleXml = bundleGenerator.process(); + var dynamicProperties = this.get('propertyExtractor').getDynamicProperties(bundleXml); + var configForSubmit = {props : dynamicProperties, xml : bundleXml, params : this.get('bundle.parameters')}; + this.set("bundleConfigs", configForSubmit); + this.set("showingJobConfig", true); + }, + preview(){ + if(this.get('validations.isInvalid')) { + this.set('showErrorMessage', true); + return; + } + this.set("showingPreview", false); + var bundleGenerator = BundleGenerator.create({bundle:this.get("bundle")}); + var bundleXml = bundleGenerator.process(); + this.set("previewXml", vkbeautify.xml(bundleXml)); + this.set("showingPreview", true); + }, + importBundleTest(){ + var deferred = this.importSampleBundle(); + deferred.promise.then(function(data){ + this.getBundleFromXml(data); + }.bind(this)).catch(function(e){ + throw new Error(e); + }); + }, + openTab(type, path){ + this.sendAction('openTab', type, path); + } + } +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/d1b0bb9e/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-coord-config.js ---------------------------------------------------------------------- diff --git a/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-coord-config.js b/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-coord-config.js new file mode 100644 index 0000000..229b2cc --- /dev/null +++ b/contrib/views/wfmanager/src/main/resources/ui/app/components/bundle-coord-config.js @@ -0,0 +1,108 @@ +/* +* 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. +*/ +import Ember from 'ember'; +import { validator, buildValidations } from 'ember-cp-validations'; + +const Validations = buildValidations({ + 'coordinator.name': validator('presence', { + presence : true + }), + 'coordinator.appPath': validator('presence', { + presence : true + }) +}); +export default Ember.Component.extend(Validations, { + initialize : function(){ + this.on('fileSelected',function(fileName){ + this.set(this.get('filePathModel'), fileName); + }.bind(this)); + this.set('showErrorMessage', false); + }.on('init'), + isValid(){ + if(this.get('validations.isInvalid')) { + this.set('showErrorMessage', true); + return false; + } + return true; + }, + readFromHdfs(filePath){ + var url = Ember.ENV.API_URL + "/readWorkflowXml?workflowXmlPath="+filePath; + var deferred = Ember.RSVP.defer(); + Ember.$.ajax({ + url: url, + method: 'GET', + dataType: "text", + beforeSend: function (xhr) { + xhr.setRequestHeader("X-XSRF-HEADER", Math.round(Math.random()*100000)); + xhr.setRequestHeader("X-Requested-By", "Ambari"); + } + }).done(function(data){ + deferred.resolve(data); + }).fail(function(){ + deferred.reject(); + }); + return deferred; + }, + importSampleCoordinator (){ + var deferred = Ember.RSVP.defer(); + Ember.$.ajax({ + url: "/sampledata/coordinator.xml", + dataType: "text", + cache:false, + success: function(data) { + deferred.resolve(data); + }.bind(this), + failure : function(data){ + deferred.reject(data); + } + }); + return deferred; + }, + actions : { + openFileBrowser(model){ + this.set('filePathModel', model); + this.sendAction("openFileBrowser", model, this); + }, + addCoordinator(){ + if(this.isValid()){ + this.sendAction('add'); + } + }, + updateCoordinator(){ + if(this.isValid()){ + this.sendAction('update'); + } + }, + cancelCoordinatorOperation(){ + this.sendAction('cancel'); + }, + openTab(type, path){ + this.sendAction('openTab', type, path); + }, + showCoordinatorName(){ + this.set('coordinatorName', null); + var deferred = this.readFromHdfs(this.get('coordinator.appPath')); + deferred.promise.then(function(data){ + var x2js = new X2JS(); + var coordJson = x2js.xml_str2json(data); + this.set('coordinatorName', coordJson["coordinator-app"]._name); + }.bind(this)).catch(function(){ + this.set('coordinatorName', null); + }.bind(this)); + } + } +});
