Repository: ambari Updated Branches: refs/heads/trunk 1f7dbd5df -> 40050513e
AMBARI-7348. Single Sign-On with Views API to impersonate account (alejandro) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/40050513 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/40050513 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/40050513 Branch: refs/heads/trunk Commit: 40050513e2cecaabf4139e40e09f8b198e6a7bd5 Parents: 1f7dbd5 Author: Alejandro Fernandez <afernan...@hortonworks.com> Authored: Wed Sep 17 19:52:41 2014 -0700 Committer: Alejandro Fernandez <afernan...@hortonworks.com> Committed: Fri Sep 19 11:47:20 2014 -0700 ---------------------------------------------------------------------- .../controller/internal/URLStreamProvider.java | 4 + .../ambari/server/proxy/ProxyService.java | 17 +- .../server/view/HttpImpersonatorImpl.java | 167 +++++++++++++++++++ .../server/view/ImpersonatorSettingImpl.java | 63 +++++++ .../ambari/server/view/ViewContextImpl.java | 18 +- .../ambari/server/proxy/ProxyServiceTest.java | 14 ++ .../server/view/HttpImpersonatorImplTest.java | 128 ++++++++++++++ .../apache/ambari/view/HttpImpersonator.java | 53 ++++++ .../apache/ambari/view/ImpersonatorSetting.java | 46 +++++ .../org/apache/ambari/view/ViewContext.java | 15 ++ ambari-web/app/utils/ajax/ajax.js | 22 --- contrib/views/jobs/pom.xml | 15 ++ .../apache/ambari/view/jobs/ProxyServlet.java | 68 ++++++++ .../jobs/src/main/resources/WEB-INF/web.xml | 37 ++++ .../app/scripts/controllers/jobs_controller.js | 10 +- .../resources/ui/app/scripts/helpers/ajax.js | 25 ++- .../resources/ui/app/scripts/helpers/jobs.js | 15 +- .../ui/app/scripts/routes/application_route.js | 8 + 18 files changed, 687 insertions(+), 38 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java index 2b32d11..1b57c84 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/URLStreamProvider.java @@ -218,4 +218,8 @@ public class URLStreamProvider implements StreamProvider { return connection; } + + public AppCookieManager getAppCookieManager() { + return appCookieManager; + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java b/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java index 30e998b..2cdffef 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/proxy/ProxyService.java @@ -20,6 +20,7 @@ package org.apache.ambari.server.proxy; import com.google.gson.Gson; import org.apache.ambari.server.controller.internal.URLStreamProvider; +import org.apache.ambari.server.view.ImpersonatorSettingImpl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,6 +37,7 @@ import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; import java.io.IOException; import java.io.InputStream; @@ -48,9 +50,9 @@ import java.util.HashMap; @Path("/") public class ProxyService { - private static final int URL_CONNECT_TIMEOUT = 20000; - private static final int URL_READ_TIMEOUT = 15000; - private static final int HTTP_ERROR_RANGE_START = Response.Status.BAD_REQUEST.getStatusCode(); + public static final int URL_CONNECT_TIMEOUT = 20000; + public static final int URL_READ_TIMEOUT = 15000; + public static final int HTTP_ERROR_RANGE_START = Response.Status.BAD_REQUEST.getStatusCode(); private static final String REQUEST_TYPE_GET = "GET"; private static final String REQUEST_TYPE_POST = "POST"; @@ -59,6 +61,7 @@ public class ProxyService { private static final String QUERY_PARAMETER_URL = "url="; private static final String AMBARI_PROXY_PREFIX = "AmbariProxy-"; private static final String ERROR_PROCESSING_URL = "Error occurred during processing URL "; + private static final String INVALID_PARAM_IN_URL = "Invalid query params found in URL "; private final static Logger LOG = LoggerFactory.getLogger(ProxyService.class); @@ -90,6 +93,14 @@ public class ProxyService { String query = ui.getRequestUri().getQuery(); if (query != null && query.indexOf(QUERY_PARAMETER_URL) != -1) { String url = query.replaceFirst(QUERY_PARAMETER_URL, ""); + + MultivaluedMap<String, String> m = ui.getQueryParameters(); + if (m.containsKey(ImpersonatorSettingImpl.DEFAULT_DO_AS_PARAM)) { // Case doesn't matter + LOG.error(INVALID_PARAM_IN_URL + url); + return Response.status(Response.Status.BAD_REQUEST.getStatusCode()).type(MediaType.TEXT_PLAIN). + entity(INVALID_PARAM_IN_URL).build(); + } + try { HttpURLConnection connection = urlStreamProvider.processURL(url, requestType, body, getHeaderParamsToForward(headers)); int responseCode = connection.getResponseCode(); http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java new file mode 100644 index 0000000..ed5ef3c --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/HttpImpersonatorImpl.java @@ -0,0 +1,167 @@ +/** + * 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.server.view; + + +import org.apache.ambari.server.controller.internal.URLStreamProvider; +import org.apache.ambari.server.proxy.ProxyService; +import org.apache.ambari.view.ImpersonatorSetting; +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.view.HttpImpersonator; +import org.apache.ambari.server.controller.internal.AppCookieManager; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.util.Map; +import java.util.HashMap; +import java.util.List; +import java.util.ArrayList; + + +/** + * Class for Ambari to impersonate users over HTTP request. + * This is handy for Views like Jobs that needs to query ATS via HTTP Get requests and + * impersonate the currently logged on user. + * Or a file browser view that needs to use WebHDFS with the credentials of the current user. + */ +public class HttpImpersonatorImpl implements HttpImpersonator { + private ViewContext context; + private AppCookieManager appCookieManager; + private FactoryHelper helper; + + /** + * Helper class that is mocked during unit testing. + */ + static class FactoryHelper{ + BufferedReader makeBR(InputStreamReader in){ + return new BufferedReader(in); + } + } + + public HttpImpersonatorImpl(ViewContext c, AppCookieManager appCookieManager) { + this.context = c; + this.appCookieManager = appCookieManager; + this.helper = new FactoryHelper(); + } + + public HttpImpersonatorImpl(ViewContext c, AppCookieManager appCookieManager, FactoryHelper h) { + this.context = c; + this.appCookieManager = appCookieManager; + this.helper = h; + } + + public ViewContext getContext() { + return this.context; + } + + public String getUsername() { + return getContext().getUsername(); + } + + /** + * @param conn HTTP connection that will be modified and returned + * @param type HTTP Request type: GET, PUT, POST, DELETE, etc. + * @return HTTP Connection object with the "doAs" query param set to the currently logged on user. + */ + @Override + public HttpURLConnection doAs(HttpURLConnection conn, String type) { + String username = getUsername(); + return doAs(conn, type, username, ImpersonatorSettingImpl.DEFAULT_DO_AS_PARAM); + } + + /** + * @param conn HTTP connection that will be modified and returned + * @param type HTTP Request type: GET, PUT, POST, DELETE, etc. + * @param username Username to impersonate + * @param doAsParamName Query param, typically "doAs" + * @return HTTP Connection object with the doAs query param set to the provider username. + */ + @Override + public HttpURLConnection doAs(HttpURLConnection conn, String type, String username, String doAsParamName) { + String url = conn.getURL().toString(); + if (url.toLowerCase().contains(doAsParamName.toLowerCase())) { + throw new IllegalArgumentException("URL cannot contain \"" + doAsParamName + "\" parameter"); + } + + try { + conn.setRequestMethod(type); + } catch (IOException e) { + return null; + } + + conn.setRequestProperty(doAsParamName, username); + return conn; + } + + /** + * Returns the result of the HTTP request by setting the "doAs" impersonation for the query param and username + * in @param impersonatorSetting. + * @param urlToRead URL to request + * @param requestType HTTP Request type: GET, PUT, POST, DELETE, etc. + * @param impersonatorSetting Setting class with default values for username and doAs param name. + * To use different values, call the setters of the object. + * @return Return a response as a String + */ + @Override + public String requestURL(String urlToRead, String requestType, final ImpersonatorSetting impersonatorSetting) { + String result = ""; + BufferedReader rd; + String line = null; + String url = urlToRead; + + if (url.toLowerCase().contains(impersonatorSetting.getDoAsParamName().toLowerCase())) { + throw new IllegalArgumentException("URL cannot contain \"" + impersonatorSetting.getDoAsParamName() + "\" parameter"); + } + + try { + URLStreamProvider urlStreamProvider = new URLStreamProvider(ProxyService.URL_CONNECT_TIMEOUT, ProxyService.URL_READ_TIMEOUT, null, null, null); + + Map<String, List<String>> headers = new HashMap<String, List<String>>(); + headers.put(impersonatorSetting.getDoAsParamName(), new ArrayList<String>() {{add(impersonatorSetting.getUsername()); }} ); + + HttpURLConnection connection = urlStreamProvider.processURL(url, requestType, null, headers); + + int responseCode = connection.getResponseCode(); + InputStream resultInputStream = null; + if (responseCode >= ProxyService.HTTP_ERROR_RANGE_START) { + resultInputStream = connection.getErrorStream(); + } else { + resultInputStream = connection.getInputStream(); + } + + rd = this.helper.makeBR(new InputStreamReader(resultInputStream)); + + if (rd != null) { + line = rd.readLine(); + while (line != null) { + result += line; + line = rd.readLine(); + } + rd.close(); + } + } catch (IOException e) { + e.printStackTrace(); + } catch (Exception e) { + e.printStackTrace(); + } + return result; + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java new file mode 100644 index 0000000..1f9c2b2 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ImpersonatorSettingImpl.java @@ -0,0 +1,63 @@ +/** + * 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.server.view; + +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.view.ImpersonatorSetting; + +/** + * Class that provides default values for impersonating, such as the username and doAs parameter name. + */ +public class ImpersonatorSettingImpl implements ImpersonatorSetting { + private String doAsParamName; + private String username; + + public static final String DEFAULT_DO_AS_PARAM = "doAs"; + + public ImpersonatorSettingImpl(ViewContext context) { + // Default values + this.doAsParamName = DEFAULT_DO_AS_PARAM; + this.username = context.getUsername(); + } + + /** + * @return The parameter name used for "doAs" impersonation. + */ + @Override + public String getDoAsParamName() { return this.doAsParamName; } + + /** + * @return The username value that will be used for "doAs" impersonation. + */ + @Override + public String getUsername() { return this.username; } + + /** + * Set the parameter name used for "doAs" impersonation. + * @param doAsParamName Query parameter name + */ + @Override + public void setDoAsParamName(String doAsParamName) { this.doAsParamName = doAsParamName; } + + /** + * Set the username value that will be used for "doAs" impersonation. + * @param username Username to impersonate as + */ + @Override + public void setUsername(String username) { this.username = username; } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java index 8915134..0254113 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/view/ViewContextImpl.java @@ -35,6 +35,8 @@ import org.apache.ambari.view.Masker; import org.apache.ambari.view.ResourceProvider; import org.apache.ambari.view.SecurityException; import org.apache.ambari.view.URLStreamProvider; +import org.apache.ambari.server.controller.internal.AppCookieManager; +import org.apache.ambari.view.ImpersonatorSetting; import org.apache.ambari.view.ViewContext; import org.apache.ambari.view.ViewController; import org.apache.ambari.view.ViewDefinition; @@ -88,7 +90,7 @@ public class ViewContextImpl implements ViewContext, ViewController { /** * The available stream provider. */ - private final URLStreamProvider streamProvider; + private final ViewURLStreamProvider streamProvider; /** * The data store. @@ -300,6 +302,15 @@ public class ViewContextImpl implements ViewContext, ViewController { return this; } + @Override + public HttpImpersonatorImpl getHttpImpersonator() { + return new HttpImpersonatorImpl(this, this.streamProvider.getAppCookieManager()); + } + + @Override + public ImpersonatorSetting getImpersonatorSetting() { + return new ImpersonatorSettingImpl(this); + } // ----- ViewController ---------------------------------------------------- @@ -409,7 +420,6 @@ public class ViewContextImpl implements ViewContext, ViewController { */ private final org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider; - // ----- Constructor ----------------------------------------------------- protected ViewURLStreamProvider(org.apache.ambari.server.controller.internal.URLStreamProvider streamProvider) { @@ -448,6 +458,10 @@ public class ViewContextImpl implements ViewContext, ViewController { configuration.getTruststoreType()); return new ViewURLStreamProvider(streamProvider); } + + protected AppCookieManager getAppCookieManager() { + return streamProvider.getAppCookieManager(); + } } // ----- Inner class : ParameterResolver ------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java b/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java index 2c1f1bc..8ad8889 100644 --- a/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java +++ b/ambari-server/src/test/java/org/apache/ambari/server/proxy/ProxyServiceTest.java @@ -65,6 +65,7 @@ class ProxyServiceTest extends BaseServiceTest { URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uriMock = PowerMock.createMock(URI.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class); @@ -79,6 +80,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock); expect(urlConnectionMock.getResponseCode()).andReturn(200); @@ -101,6 +103,7 @@ class ProxyServiceTest extends BaseServiceTest { URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uriMock = PowerMock.createMock(URI.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class); @@ -115,6 +118,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(getHttpHeaders().getMediaType()).andReturn(APPLICATION_FORM_URLENCODED_TYPE); expect(streamProviderMock.processURL("testurl", "POST", is, headerParamsToForward)).andReturn(urlConnectionMock); @@ -138,6 +142,7 @@ class ProxyServiceTest extends BaseServiceTest { URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uriMock = PowerMock.createMock(URI.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class); @@ -152,6 +157,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(getHttpHeaders().getMediaType()).andReturn(APPLICATION_FORM_URLENCODED_TYPE); expect(streamProviderMock.processURL("testurl", "PUT", is, headerParamsToForward)).andReturn(urlConnectionMock); @@ -175,6 +181,7 @@ class ProxyServiceTest extends BaseServiceTest { URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uriMock = PowerMock.createMock(URI.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class); @@ -189,6 +196,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(streamProviderMock.processURL("testurl", "DELETE", null, headerParamsToForward)).andReturn(urlConnectionMock); expect(urlConnectionMock.getResponseCode()).andReturn(200); @@ -214,6 +222,7 @@ class ProxyServiceTest extends BaseServiceTest { URI uriMock = PowerMock.createMock(URI.class); Response responseMock = createMock(ResponseImpl.class); InputStream es = new ByteArrayInputStream("error".getBytes()); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); headerParams.add("AmbariProxy-User-Remote","testuser"); @@ -225,6 +234,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock); expect(urlConnectionMock.getResponseCode()).andReturn(400).times(2); @@ -247,6 +257,7 @@ class ProxyServiceTest extends BaseServiceTest { URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uriMock = PowerMock.createMock(URI.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); Map<String, List<String>> headerParamsToForward = new HashMap<String, List<String>>(); Response.ResponseBuilder responseBuilderMock = PowerMock.createMock(ResponseBuilderImpl.class); @@ -261,6 +272,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uriMock); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(uriMock.getQuery()).andReturn("url=testurl"); expect(streamProviderMock.processURL("testurl", "GET", null, headerParamsToForward)).andReturn(urlConnectionMock); expect(urlConnectionMock.getResponseCode()).andReturn(200); @@ -281,6 +293,7 @@ class ProxyServiceTest extends BaseServiceTest { public void testEscapedURL() throws Exception { ProxyService ps = new ProxyService(); URLStreamProvider streamProviderMock = PowerMock.createNiceMock(URLStreamProvider.class); + MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl(); MultivaluedMap<String, String> headerParams = new MultivaluedMapImpl(); HttpURLConnection urlConnectionMock = createMock(HttpURLConnection.class); URI uri = UriBuilder.fromUri("http://dev01.hortonworks.com:8080/proxy?url=http%3a%2f%2fserver%3a8188%2fws%2fv1%2f" + @@ -295,6 +308,7 @@ class ProxyServiceTest extends BaseServiceTest { expect(getHttpHeaders().getRequestHeaders()).andReturn(headerParams); expect(getHttpHeaders().getRequestHeader("AmbariProxy-User-Remote")).andReturn(userRemoteParams); expect(getUriInfo().getRequestUri()).andReturn(uri); + expect(getUriInfo().getQueryParameters()).andReturn(queryParams); expect(urlConnectionMock.getResponseCode()).andReturn(200); expect(urlConnectionMock.getContentType()).andReturn("text/plain"); expect(urlConnectionMock.getInputStream()).andReturn(is); http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java b/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java new file mode 100644 index 0000000..5526592 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/view/HttpImpersonatorImplTest.java @@ -0,0 +1,128 @@ +/** + * 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.server.view; + +import junit.framework.TestCase; +import org.apache.ambari.view.ImpersonatorSetting; +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.server.controller.internal.AppCookieManager; +import org.junit.Assert; +import org.mockito.Mockito; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.net.URL; +import java.util.UUID; + +import static org.mockito.Matchers.*; +import static org.mockito.Mockito.when; + + +public class HttpImpersonatorImplTest extends TestCase { + + String cookie; + String username; + ViewContext viewContext; + HttpImpersonatorImpl impersonator; + String expectedResult; + + public void setUp() throws Exception { + String uuid = UUID.randomUUID().toString().replace("-", ""); + this.cookie = uuid; + this.username = "admin" + uuid; + + AppCookieManager mockAppCookieManager = Mockito.mock(AppCookieManager.class); + when(mockAppCookieManager.getAppCookie(anyString(), anyBoolean())).thenReturn(cookie); + + this.expectedResult = "Dummy text from HTTP response"; + BufferedReader mockBufferedReader = Mockito.mock(BufferedReader.class); + when(mockBufferedReader.readLine()).thenReturn(expectedResult).thenReturn(null); + + HttpImpersonatorImpl.FactoryHelper mockFactory = Mockito.mock(HttpImpersonatorImpl.FactoryHelper.class); + when(mockFactory.makeBR(any(InputStreamReader.class))).thenReturn(mockBufferedReader); + + this.viewContext = Mockito.mock(ViewContext.class); + when(this.viewContext.getUsername()).thenReturn(username); + + this.impersonator = new HttpImpersonatorImpl(this.viewContext, mockAppCookieManager, mockFactory); + when(this.viewContext.getHttpImpersonator()).thenReturn(this.impersonator); + } + + @org.junit.Test + public void testBasic() throws Exception { + String urlToRead = "http://foo.com"; + String requestMethod = "GET"; + URL url = new URL(urlToRead); + + // Test default params + HttpURLConnection conn1 = (HttpURLConnection) url.openConnection(); + + conn1 = this.viewContext.getHttpImpersonator().doAs(conn1, requestMethod); + Assert.assertEquals(requestMethod, conn1.getRequestMethod()); + Assert.assertEquals(username, conn1.getRequestProperty("doAs")); + + // Test specific params + HttpURLConnection conn2 = (HttpURLConnection) url.openConnection(); + conn2 = this.viewContext.getHttpImpersonator().doAs(conn1, requestMethod, "admin", "username"); + Assert.assertEquals(requestMethod, conn1.getRequestMethod()); + Assert.assertEquals("admin", conn1.getRequestProperty("username")); + } + + @org.junit.Test + public void testRequestURL() throws Exception { + String urlToRead = "http://foo.com"; + String requestMethod = "GET"; + + // Test default params + ImpersonatorSetting impersonatorSetting = new ImpersonatorSettingImpl(this.viewContext); + when(this.viewContext.getImpersonatorSetting()).thenReturn(impersonatorSetting); + String actualResult = this.viewContext.getHttpImpersonator().requestURL(urlToRead, requestMethod, impersonatorSetting); + Assert.assertEquals(this.expectedResult, actualResult); + } + + @org.junit.Test + public void testRequestURLWithCustom() throws Exception { + String urlToRead = "http://foo.com"; + String requestMethod = "GET"; + + // Test custom params + ImpersonatorSetting impersonatorSetting = new ImpersonatorSettingImpl(this.viewContext); + impersonatorSetting.setDoAsParamName("impersonate"); + impersonatorSetting.setUsername("hive"); + when(this.viewContext.getImpersonatorSetting()).thenReturn(impersonatorSetting); + String actualResult = this.viewContext.getHttpImpersonator().requestURL(urlToRead, requestMethod, impersonatorSetting); + Assert.assertEquals(this.expectedResult, actualResult); + } + + @org.junit.Test + public void testInvalidURL() throws Exception { + String urlToRead = "http://foo.com?" + "doAs" + "=hive"; + String requestMethod = "GET"; + URL url = new URL(urlToRead); + + HttpURLConnection conn = (HttpURLConnection) url.openConnection(); + try { + conn = this.viewContext.getHttpImpersonator().doAs(conn, requestMethod); + fail("Expected an exception to be thrown." ); + } catch(Exception e) { + ; + } + } +} http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java ---------------------------------------------------------------------- diff --git a/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java b/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java new file mode 100644 index 0000000..00d5d86 --- /dev/null +++ b/ambari-views/src/main/java/org/apache/ambari/view/HttpImpersonator.java @@ -0,0 +1,53 @@ +/** + * 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; + +import java.net.HttpURLConnection; + +/** + * Interface for views to impersonate users over HTTP request. + */ +public interface HttpImpersonator { + + /** + * @param conn HTTP connection that will be modified and returned + * @param type HTTP Request type: GET, PUT, POST, DELETE, etc. + * @return HTTP Connection object with the "doAs" query param set to the currently logged on user. + */ + public HttpURLConnection doAs(HttpURLConnection conn, String type); + + /** + * @param conn HTTP connection that will be modified and returned + * @param type HTTP Request type: GET, PUT, POST, DELETE, etc. + * @param username Username to impersonate + * @param doAsParamName Query param, typically "doAs" + * @return HTTP Connection object with the doAs query param set to the provider username. + */ + public HttpURLConnection doAs(HttpURLConnection conn, String type, String username, String doAsParamName); + + /** + * Returns the result of the HTTP request by setting the "doAs" impersonation for the query param and username + * in @param impersonatorSetting. + * @param urlToRead URL to request + * @param requestType HTTP Request type: GET, PUT, POST, DELETE, etc. + * @param impersonatorSetting Setting class with default values for username and doAs param name. + * To use different values, call the setters of the object. + * @return Return a response as a String + */ + public String requestURL(String urlToRead, String requestType, ImpersonatorSetting impersonatorSetting); +} http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java ---------------------------------------------------------------------- diff --git a/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java b/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java new file mode 100644 index 0000000..b52cc90 --- /dev/null +++ b/ambari-views/src/main/java/org/apache/ambari/view/ImpersonatorSetting.java @@ -0,0 +1,46 @@ +/** + * 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; + +/** + * Interface that provides default values for impersonating, such as the username and doAs parameter name. + */ +public interface ImpersonatorSetting { + + /** + * @return The parameter name used for "doAs" impersonation. + */ + public String getDoAsParamName(); + + /** + * @return The username value that will be used for "doAs" impersonation. + */ + public String getUsername(); + + /** + * Set the parameter name used for "doAs" impersonation. + * @param doAsParamName Query parameter name + */ + public void setDoAsParamName(String doAsParamName); + + /** + * Set the username value that will be used for "doAs" impersonation. + * @param username Username to impersonate as + */ + public void setUsername(String username); +} http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java ---------------------------------------------------------------------- diff --git a/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java b/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java index 37e91a2..97385d3 100644 --- a/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java +++ b/ambari-views/src/main/java/org/apache/ambari/view/ViewContext.java @@ -20,6 +20,7 @@ package org.apache.ambari.view; import java.util.Collection; import java.util.Map; +import org.apache.ambari.view.HttpImpersonator; /** * Context object available to the view components to provide access to @@ -174,4 +175,18 @@ public interface ViewContext { * @return the view controller */ public ViewController getController(); + + /** + * Get the HTTP Impersonator. + * + * @return the HTTP Impersonator, which internally uses the App Cookie Manager + */ + public HttpImpersonator getHttpImpersonator(); + + /** + * Get the default settings for the Impersonator. + * + * @return the Impersonator settings. + */ + public ImpersonatorSetting getImpersonatorSetting(); } http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/ambari-web/app/utils/ajax/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js index c2a971d..f4506ad 100644 --- a/ambari-web/app/utils/ajax/ajax.js +++ b/ambari-web/app/utils/ajax/ajax.js @@ -1698,28 +1698,6 @@ var urls = { } } }, - - 'jobs.lastID': { - 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true', - 'mock': 'data/jobs/hive-queries.json', - 'apiPrefix': '' - }, - - 'jobs.tezDag.NametoID': { - 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}', - 'mock': '/data/jobs/tezDag-name-to-id.json', - 'apiPrefix': '' - }, - 'jobs.tezDag.tezDagId': { - 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo', - 'mock': '/data/jobs/tezDag.json', - 'apiPrefix': '' - }, - 'jobs.tezDag.tezDagVertexId': { - 'real': '/proxy?url=http://{historyServerHostName}:{ahsWebPort}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo', - 'mock': '/data/jobs/tezDagVertex.json', - 'apiPrefix': '' - }, 'views.info': { 'real': '/views', 'mock': '/data/views/views.json' http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/pom.xml ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/pom.xml b/contrib/views/jobs/pom.xml index c4b646e..f2173e3 100644 --- a/contrib/views/jobs/pom.xml +++ b/contrib/views/jobs/pom.xml @@ -158,6 +158,7 @@ <directory>src/main/resources</directory> <filtering>false</filtering> <includes> + <include>WEB-INF/web.xml</include> <include>META-INF/**/*</include> <include>view.xml</include> </includes> @@ -168,4 +169,18 @@ </resource> </resources> </build> +<dependencies> + <dependency> + <groupId>org.apache.ambari</groupId> + <artifactId>ambari-views</artifactId> + <version>${ambari.version}</version> + </dependency> + + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + <version>2.5</version> + <scope>provided</scope> + </dependency> + </dependencies> </project> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java b/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java new file mode 100644 index 0000000..f02844e --- /dev/null +++ b/contrib/views/jobs/src/main/java/org/apache/ambari/view/jobs/ProxyServlet.java @@ -0,0 +1,68 @@ +/** + * 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.jobs; + +import org.apache.ambari.view.ViewContext; +import org.apache.ambari.view.HttpImpersonator; +import org.apache.ambari.view.ImpersonatorSetting; + +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; + + +/** + * Simple servlet for proxying requests with doAs impersonation. + */ +public class ProxyServlet extends HttpServlet { + + private ViewContext viewContext; + private HttpImpersonator impersonator; + private ImpersonatorSetting impersonatorSetting; + + @Override + public void init(ServletConfig config) throws ServletException { + super.init(config); + + ServletContext context = config.getServletContext(); + viewContext = (ViewContext) context.getAttribute(ViewContext.CONTEXT_ATTRIBUTE); + + this.impersonator = viewContext.getHttpImpersonator(); + this.impersonatorSetting = viewContext.getImpersonatorSetting(); + } + + @Override + protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { + String urlToRead = request.getParameter("url"); + response.setContentType("text/html"); + response.setStatus(HttpServletResponse.SC_OK); + + // Getting the result is super simply by using the impersonator and the default values in the factory. + String result = this.impersonator.requestURL(urlToRead, "GET", this.impersonatorSetting); + + PrintWriter writer = response.getWriter(); + writer.print(result); + } +} + http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/resources/WEB-INF/web.xml b/contrib/views/jobs/src/main/resources/WEB-INF/web.xml new file mode 100644 index 0000000..aa3da09 --- /dev/null +++ b/contrib/views/jobs/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.jobs.ProxyServlet</servlet-class> + </servlet> + <servlet-mapping> + <servlet-name>ProxyServlet</servlet-name> + <url-pattern>/proxy</url-pattern> + </servlet-mapping> +</web-app> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js index 16acb56..6a04c98 100644 --- a/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js +++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/controllers/jobs_controller.js @@ -591,7 +591,10 @@ App.JobsController = Ember.ArrayController.extend(App.RunPeriodically, { name: 'jobs_lastID', sender: this, data: { - atsURL: atsURL + atsURL: atsURL, + view: App.get("view"), + version: App.get("version"), + instanceName: App.get("instanceName") }, success: 'lastIDSuccessCallback', error : 'lastIDErrorCallback' @@ -601,7 +604,10 @@ App.JobsController = Ember.ArrayController.extend(App.RunPeriodically, { sender: this, data: { atsURL: atsURL, - filtersLink: this.get('filterObject').createJobsFiltersLink() + filtersLink: this.get('filterObject').createJobsFiltersLink(), + view: App.get("view"), + version: App.get("version"), + instanceName: App.get("instanceName") }, success: 'loadJobsSuccessCallback', error: 'loadJobsErrorCallback' http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js index e648e61..728b26a 100644 --- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js +++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/ajax.js @@ -27,41 +27,54 @@ * testInProduction - can this request be executed on production tests (used only in tests) * * @type {Object} + * + * Any property inside {braces} is substituted dynamically by the formatUrl function provided that the property is passed into the "data" dictionary + * by the ajax call. + * E.g., + App.ajax.send({ + name: 'key_foo', + data: { + property1: value1, + property2: value2 + } + }); + + Where the "urls" dictionary contains 'key_foo': {real: 'some_value_with_{property1}_and_{property2}' } */ var urls = { 'load_jobs': { - real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID{filtersLink}', + real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID{filtersLink}', mock: '/scripts/assets/hive-queries.json', apiPrefix: '' }, 'jobs_lastID': { - real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true', + real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID?limit=1&secondaryFilter=tez:true', mock: '/scripts/assets/hive-queries.json', apiPrefix: '' }, 'job_details': { - real: '/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID/{job_id}?fields=events,otherinfo', + real: '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/HIVE_QUERY_ID/{job_id}?fields=events,otherinfo', mock: '/scripts/assets/hive-query-2.json', apiPrefix: '' }, 'jobs.tezDag.NametoID': { - 'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}', + 'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID?primaryFilter=dagName:{tezDagName}', 'mock': '/scripts/assets/tezDag-name-to-id.json', 'apiPrefix': '' }, 'jobs.tezDag.tezDagId': { - 'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo', + 'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_DAG_ID/{tezDagId}?fields=relatedentities,otherinfo', 'mock': '/scripts/assets/tezDag.json', 'apiPrefix': '' }, 'jobs.tezDag.tezDagVertexId': { - 'real': '/proxy?url={atsURL}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo', + 'real': '/views/{view}/{version}/{instanceName}/proxy?url={atsURL}/ws/v1/timeline/TEZ_VERTEX_ID/{tezDagVertexId}?fields=otherinfo', 'mock': '/scripts/assets/tezDagVertex.json', 'apiPrefix': '' }, http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js index f0759da..75511b4 100644 --- a/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js +++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/helpers/jobs.js @@ -88,7 +88,10 @@ App.Helpers.jobs = { sender: sender, data: { atsURL: params.atsURL, - tezDagName: tezDagName + tezDagName: tezDagName, + view: App.get("view"), + version: App.get("version"), + instanceName: App.get("instanceName") }, success: 'dagNameToIdSuccess', error: 'dagNameToIdError' @@ -146,7 +149,10 @@ App.Helpers.jobs = { sender: sender, data: { tezDagId: tezDagInstanceId, - atsURL: atsURL + atsURL: atsURL, + view: App.get("view"), + version: App.get("version"), + instanceName: App.get("instanceName") }, success: 'loadTezDagSuccess', error: 'loadTezDagError' @@ -241,7 +247,10 @@ App.Helpers.jobs = { sender: sender, data: { atsURL: atsURL, - tezDagVertexId: tezVertexInstanceId + tezDagVertexId: tezVertexInstanceId, + view: App.get("view"), + version: App.get("version"), + instanceName: App.get("instanceName") }, success: 'loadTezDagVertexSuccess', error: 'loadTezDagVertexError' http://git-wip-us.apache.org/repos/asf/ambari/blob/40050513/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js ---------------------------------------------------------------------- diff --git a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js index ff13b88..b2aeea6 100644 --- a/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js +++ b/contrib/views/jobs/src/main/resources/ui/app/scripts/routes/application_route.js @@ -36,6 +36,14 @@ App.JobsRoute = Ember.Route.extend({ setupController: function(controller, model) { this._super(controller, model); + var hashArray = location.pathname.split('/'); + var view = hashArray[2]; + var version = hashArray[3]; + var instanceName = hashArray[4]; + App.set('view', view); + App.set('version', version); + App.set('instanceName', instanceName); + controller.set('interval', 6000); controller.loop('loadJobs', true); // This observer should be set with addObserver