AMBARI-17817. Storm Ambari View should use proxy for secure clusters (sriharsha)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/a30d1573 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/a30d1573 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/a30d1573 Branch: refs/heads/branch-embedded-views Commit: a30d1573850dcbc9abc2aef49c481b20f447fc07 Parents: 1e05dc2 Author: Sriharsha Chintalapani <[email protected]> Authored: Thu Jul 21 13:49:40 2016 -0700 Committer: Sriharsha Chintalapani <[email protected]> Committed: Thu Jul 21 13:49:40 2016 -0700 ---------------------------------------------------------------------- .../org/apache/ambari/storm/ProxyServlet.java | 100 +++++++++++++++++++ .../storm/src/main/resources/WEB-INF/web.xml | 37 +++++++ .../resources/scripts/components/SearchLogs.jsx | 2 +- .../scripts/containers/ClusterSummary.jsx | 4 +- .../scripts/containers/NimbusConfigSummary.jsx | 4 +- .../scripts/containers/NimbusSummary.jsx | 4 +- .../scripts/containers/SupervisorSummary.jsx | 4 +- .../scripts/containers/TopologyListing.jsx | 4 +- .../main/resources/scripts/utils/Overrides.js | 10 +- .../src/main/resources/scripts/utils/Utils.js | 4 +- .../scripts/views/ComponentDetailView.jsx | 12 +-- .../src/main/resources/scripts/views/Footer.jsx | 4 +- .../scripts/views/TopologyDetailView.jsx | 52 +++++----- 13 files changed, 193 insertions(+), 48 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/java/org/apache/ambari/storm/ProxyServlet.java ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/java/org/apache/ambari/storm/ProxyServlet.java b/contrib/views/storm/src/main/java/org/apache/ambari/storm/ProxyServlet.java new file mode 100644 index 0000000..6e6bea2 --- /dev/null +++ b/contrib/views/storm/src/main/java/org/apache/ambari/storm/ProxyServlet.java @@ -0,0 +1,100 @@ +/** + * 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.ambari.view.storm; + +import org.apache.ambari.view.ViewContext; + +import java.io.IOException; +import java.io.PrintWriter; +import javax.servlet.ServletConfig; +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.net.URLDecoder; +import java.util.*; +import java.io.*; + +/** + * Simple servlet for proxying requests with doAs impersonation. + */ +public class ProxyServlet extends HttpServlet { + + private ViewContext viewContext; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + + ServletContext context = config.getServletContext(); + viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + InputStream body = null; + String urlToRead = URLDecoder.decode(request.getParameter("url")); + HashMap<String,String> headersMap = this.getHeaders(request); + InputStream resultStream = viewContext.getURLStreamProvider().readAsCurrent(urlToRead, "GET", body, headersMap); + this.setResponse(request, response, resultStream); + } + + @Override + protected void doPost(HttpServletRequest request, HttpServletResponse response) throws IOException { + InputStream stream = request.getInputStream(); + String urlToRead = URLDecoder.decode(request.getParameter("url")); + HashMap<String,String> headersMap = this.getHeaders(request); + InputStream resultStream = viewContext.getURLStreamProvider().readAsCurrent(urlToRead, "POST", stream, headersMap); + this.setResponse(request, response, resultStream); + } + + /** + * Get headers from request + * @param request HTTPServletRequest + * @return HashMap map containing headers + */ + public HashMap<String,String> getHeaders(HttpServletRequest request){ + Enumeration headerNames = request.getHeaderNames(); + HashMap<String,String> map = new HashMap<String, String>(); + while (headerNames.hasMoreElements()) { + String key = (String) headerNames.nextElement(); + String value = request.getHeader(key); + map.put(key, value); + } + map.put("Content-Type",request.getContentType()); + return map; + } + + /** + * Set response to the get/post request + * @param request HttpServletRequest + * @param response HttpServletResponse + * @param resultStream InputStream + */ + public void setResponse(HttpServletRequest request, HttpServletResponse response, InputStream resultStream) throws IOException{ + Scanner scanner = new Scanner(resultStream).useDelimiter("\\A"); + String result = scanner.hasNext() ? scanner.next() : ""; + Boolean notFound = result == "" || result.indexOf("\"exception\":\"NotFoundException\"") != -1; + response.setContentType(request.getContentType()); + response.setStatus(notFound ? HttpServletResponse.SC_NOT_FOUND : HttpServletResponse.SC_OK); + PrintWriter writer = response.getWriter(); + writer.print(result); + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/WEB-INF/web.xml b/contrib/views/storm/src/main/resources/WEB-INF/web.xml new file mode 100644 index 0000000..e406de1 --- /dev/null +++ b/contrib/views/storm/src/main/resources/WEB-INF/web.xml @@ -0,0 +1,37 @@ +<?xml version="1.0" encoding="ISO-8859-1" ?> + +<!-- +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. Kerberos, LDAP, Custom. Binary/Htt +--> + +<web-app xmlns="http://java.sun.com/xml/ns/j2ee" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd" + version="2.4"> + + <display-name>Proxy Application</display-name> + <description> + This is the proxy application. + </description> + <servlet> + <servlet-name>ProxyServlet</servlet-name> + <servlet-class>org.apache.ambari.view.storm.ProxyServlet</servlet-class> + </servlet> + <servlet-mapping> + <servlet-name>ProxyServlet</servlet-name> + <url-pattern>/proxy</url-pattern> + </servlet-mapping> +</web-app> http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx b/contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx index 854d732..b37170c 100644 --- a/contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx +++ b/contrib/views/storm/src/main/resources/scripts/components/SearchLogs.jsx @@ -61,7 +61,7 @@ define(['react', var searchArchivedLogsEl = document.getElementById('searchArchivedLogs'); var deepSearchEl = document.getElementById('deepSearch'); - var url = App.baseURL+'/'; + var url = App.baseURL.split('?url=')[1]+'/'; if(deepSearchEl.checked == true){ url += "deep_search_result.html"; }else{ http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/containers/ClusterSummary.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/containers/ClusterSummary.jsx b/contrib/views/storm/src/main/resources/scripts/containers/ClusterSummary.jsx index b022987..ad0d9d7 100644 --- a/contrib/views/storm/src/main/resources/scripts/containers/ClusterSummary.jsx +++ b/contrib/views/storm/src/main/resources/scripts/containers/ClusterSummary.jsx @@ -41,8 +41,8 @@ define(['react', this.model = new VCluster(); this.model.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.setState(model.attributes); } http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/containers/NimbusConfigSummary.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/containers/NimbusConfigSummary.jsx b/contrib/views/storm/src/main/resources/scripts/containers/NimbusConfigSummary.jsx index f0457bd..19846c8 100644 --- a/contrib/views/storm/src/main/resources/scripts/containers/NimbusConfigSummary.jsx +++ b/contrib/views/storm/src/main/resources/scripts/containers/NimbusConfigSummary.jsx @@ -38,8 +38,8 @@ define(['react', this.collection.comparator = "key"; this.collection.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { var result = []; var keys = _.keys(response); http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/containers/NimbusSummary.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/containers/NimbusSummary.jsx b/contrib/views/storm/src/main/resources/scripts/containers/NimbusSummary.jsx index a0ec366..be2f18d 100644 --- a/contrib/views/storm/src/main/resources/scripts/containers/NimbusSummary.jsx +++ b/contrib/views/storm/src/main/resources/scripts/containers/NimbusSummary.jsx @@ -39,8 +39,8 @@ define(['react', this.collection = new VNimbusList(); this.collection.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { var result = []; if(!_.isArray(response.nimbuses)){ http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/containers/SupervisorSummary.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/containers/SupervisorSummary.jsx b/contrib/views/storm/src/main/resources/scripts/containers/SupervisorSummary.jsx index d2727f5..15fe53a 100644 --- a/contrib/views/storm/src/main/resources/scripts/containers/SupervisorSummary.jsx +++ b/contrib/views/storm/src/main/resources/scripts/containers/SupervisorSummary.jsx @@ -40,8 +40,8 @@ define(['react', this.collection = new VSupervisorList(); this.collection.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { var result = []; if(!_.isArray(response.supervisors)){ http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/containers/TopologyListing.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/containers/TopologyListing.jsx b/contrib/views/storm/src/main/resources/scripts/containers/TopologyListing.jsx index d0649b9..4c624cb 100644 --- a/contrib/views/storm/src/main/resources/scripts/containers/TopologyListing.jsx +++ b/contrib/views/storm/src/main/resources/scripts/containers/TopologyListing.jsx @@ -41,8 +41,8 @@ define(['react', this.collection = new VTopologyList(); this.collection.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { var result = []; if(!_.isArray(response.topologies)){ http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/utils/Overrides.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/utils/Overrides.js b/contrib/views/storm/src/main/resources/scripts/utils/Overrides.js index 499ae07..1170e04 100644 --- a/contrib/views/storm/src/main/resources/scripts/utils/Overrides.js +++ b/contrib/views/storm/src/main/resources/scripts/utils/Overrides.js @@ -18,5 +18,13 @@ define(['backbone'], function(Backbone){ 'use strict'; - + Backbone.ajax = function() { + if(!arguments[0].data){ + var urlPart = arguments[0].url.split('url=')[0]; + var stormUrlPart = arguments[0].url.split('url=')[1]; + urlPart += 'url=' + encodeURIComponent(stormUrlPart); + arguments[0].url = urlPart; + } + return Backbone.$.ajax.apply(Backbone.$, arguments); + }; }); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/utils/Utils.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/utils/Utils.js b/contrib/views/storm/src/main/resources/scripts/utils/Utils.js index 6653a75..8b329c5 100644 --- a/contrib/views/storm/src/main/resources/scripts/utils/Utils.js +++ b/contrib/views/storm/src/main/resources/scripts/utils/Utils.js @@ -26,7 +26,7 @@ define(['require', var Utils = {}; Utils.getStormHostDetails = function() { - var url; + var url = location.pathname+'proxy?url='; var urlParts = location.pathname.split('/'); var apiUrl = '/api/v1/'+urlParts[1]+'/'+urlParts[2]+'/versions/'+urlParts[3]+'/instances/'+urlParts[4]; $.ajax({ @@ -38,7 +38,7 @@ define(['require', success: function(response){ var props = response.ViewInstanceInfo.properties; if(props['storm.host'] && props['storm.port']){ - url = "http://"+props['storm.host']+":"+props['storm.port']; + url += "http://"+props['storm.host']+":"+props['storm.port']; } else { Utils.notifyError("Failed to get storm hostname and port."); } http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx b/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx index 2777b5c..5847ef9 100644 --- a/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx +++ b/contrib/views/storm/src/main/resources/scripts/views/ComponentDetailView.jsx @@ -116,8 +116,8 @@ define([ window: this.windowSize, sys: this.systemFlag, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.setState({"componentObj": model}); } @@ -489,8 +489,8 @@ define([ debugType: 'enable', percent: result, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Debugging enabled successfully."); @@ -512,8 +512,8 @@ define([ debugType: 'disable', percent: '0', success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Debugging disabled successfully."); http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx b/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx index a6d2ed6..98e63e9 100644 --- a/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx +++ b/contrib/views/storm/src/main/resources/scripts/views/Footer.jsx @@ -30,8 +30,8 @@ define(['react', 'react-dom', 'models/VCluster', 'utils/Utils'], function(React, this.model = new VCluster(); this.model.fetch({ success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.setState({version: model.get('stormVersion')}); } http://git-wip-us.apache.org/repos/asf/ambari/blob/a30d1573/contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx b/contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx index c22f3da..24d3271 100644 --- a/contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx +++ b/contrib/views/storm/src/main/resources/scripts/views/TopologyDetailView.jsx @@ -130,8 +130,8 @@ define([ window: this.windowSize, sys: this.systemFlag, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.model.set(model); this.setState({"model": this.model}); @@ -151,8 +151,8 @@ define([ id: this.model.get('id'), window: this.windowSize, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { if(_.isString(model)){ model = JSON.parse(model); @@ -171,8 +171,8 @@ define([ this.model.getLogConfig({ id: this.model.get('id'), success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.resetLogCollection(model); } @@ -187,8 +187,8 @@ define([ this.model.getTopologyLag({ id: this.model.get('id'), success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { if(model && model.length){ var result = JSON.parse(model[0].spoutLagResult); @@ -208,8 +208,8 @@ define([ this.model.getWorkerHost({ id: this.model.get('id'), success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { var workerHostPortArr = model.hostPortList; var result = ''; @@ -451,8 +451,8 @@ define([ data: JSON.stringify(dataObj), contentType: "application/json", success: function(model, response, options){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.resetLogCollection(model); Utils.notifySuccess("Log configuration added successfully."); @@ -487,8 +487,8 @@ define([ data: JSON.stringify(dataObj), contentType: "application/json", success: function(model, response, options){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.resetLogCollection(model); Utils.notifySuccess("Log configuration applied successfully."); @@ -520,8 +520,8 @@ define([ data: JSON.stringify(dataObj), contentType: "application/json", success: function(model, response, options){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.resetLogCollection(model); Utils.notifySuccess("Log configuration cleared successfully."); @@ -870,8 +870,8 @@ define([ this.model.activateTopology({ id: this.model.get('id'), success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Topology activated successfully.") @@ -892,8 +892,8 @@ define([ this.model.deactivateTopology({ id: this.model.get('id'), success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Topology deactivated successfully.") @@ -933,8 +933,8 @@ define([ id: this.model.get('id'), waitTime: result, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Topology killed successfully.") @@ -976,8 +976,8 @@ define([ debugType: 'enable', percent: result, success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Debugging enabled successfully.") @@ -998,8 +998,8 @@ define([ debugType: 'disable', percent: '0', success: function(model, response){ - if(response.error){ - Utils.notifyError(response.error); + if(response.error || model.error){ + Utils.notifyError(response.error || model.error+'('+model.errorMessage.split('(')[0]+')'); } else { this.initializeData(); Utils.notifySuccess("Debugging disabled successfully.")
