Repository: ambari Updated Branches: refs/heads/trunk adbfa0933 -> e2b8a0e92
AMBARI-11436. CapScheduler Auto-Create and Cluster association (Erik Bergenholtz via rlevas) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e2b8a0e9 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e2b8a0e9 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e2b8a0e9 Branch: refs/heads/trunk Commit: e2b8a0e92c8e73fd52050bb986238996b7b6cd94 Parents: adbfa09 Author: Erik Bergenholtz <[email protected]> Authored: Thu May 28 12:11:44 2015 -0400 Committer: Robert Levas <[email protected]> Committed: Thu May 28 12:11:44 2015 -0400 ---------------------------------------------------------------------- contrib/views/capacity-scheduler/pom.xml | 15 +- contrib/views/capacity-scheduler/readme.md | 10 +- .../capacityscheduler/ConfigurationService.java | 183 +++++++++++-------- .../view/capacityscheduler/proxy/Proxy.java | 120 ------------ .../capacityscheduler/proxy/RequestBuilder.java | 160 ---------------- .../proxy/ResponseTranslator.java | 100 ---------- .../ui/app/components/dropdownButtons.js | 2 +- .../main/resources/ui/app/controllers/queue.js | 2 +- .../main/resources/ui/app/controllers/queues.js | 6 +- .../src/main/resources/ui/app/serializers.js | 29 ++- .../resources/ui/app/styles/application.less | 22 ++- .../app/templates/components/queueContainer.hbs | 2 +- .../main/resources/ui/app/templates/queues.hbs | 4 +- .../main/resources/ui/app/templates/refuse.hbs | 10 +- .../src/main/resources/ui/config.coffee | 2 +- .../ui/test/integration/serializers_test.js | 124 +++++++++++++ .../src/main/resources/view.xml | 20 +- contrib/views/pom.xml | 1 + .../ambari/view/utils/ambari/AmbariApi.java | 132 ++++++++++--- .../ambari/URLStreamProviderBasicAuth.java | 61 +++++-- 20 files changed, 481 insertions(+), 524 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/pom.xml ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/pom.xml b/contrib/views/capacity-scheduler/pom.xml index 2712953..23e5aee 100644 --- a/contrib/views/capacity-scheduler/pom.xml +++ b/contrib/views/capacity-scheduler/pom.xml @@ -19,7 +19,7 @@ <modelVersion>4.0.0</modelVersion> <groupId>org.apache.ambari.contrib.views</groupId> <artifactId>capacity-scheduler</artifactId> - <version>0.3.0-SNAPSHOT</version> + <version>0.4.0-SNAPSHOT</version> <name>Capacity Scheduler</name> <parent> @@ -37,12 +37,11 @@ <dependency> <groupId>org.glassfish.jersey.containers</groupId> <artifactId>jersey-container-servlet</artifactId> - <version>2.6</version> <!-- version --> </dependency> <dependency> - <groupId>org.slf4j</groupId> - <artifactId>slf4j-api</artifactId> - <version>1.7.5</version> + <groupId>org.apache.hadoop</groupId> + <artifactId>hadoop-common</artifactId> + <version>${hadoop-version}</version> </dependency> <dependency> <groupId>commons-io</groupId> @@ -68,9 +67,15 @@ <artifactId>ambari-views</artifactId> <scope>provided</scope> </dependency> + <dependency> + <groupId>org.apache.ambari.contrib.views</groupId> + <artifactId>ambari-views-utils</artifactId> + <version>0.0.1-SNAPSHOT</version> + </dependency> </dependencies> <properties> + <hadoop-version>2.6.0</hadoop-version> <ambari.dir>${project.parent.parent.parent.basedir}</ambari.dir> <ui.directory>${basedir}/src/main/resources/ui</ui.directory> </properties> http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/readme.md ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/readme.md b/contrib/views/capacity-scheduler/readme.md index e772508..4e5db9d 100644 --- a/contrib/views/capacity-scheduler/readme.md +++ b/contrib/views/capacity-scheduler/readme.md @@ -23,7 +23,7 @@ This View provides a UI to manage queues for the YARN Capacity Scheduler. Requirements ----- -- Ambari 1.7.0 or later +- Ambari 2.1.0 or later - YARN Build @@ -35,11 +35,11 @@ The view can be built as a maven project. The build will produce the view archive. - target/capacity-scheduler-0.3.0-SNAPSHOT.jar + target/capacity-scheduler-???-SNAPSHOT.jar Place the view archive on the Ambari Server and restart to deploy. - cp capacity-scheduler-0.3.0-SNAPSHOT /var/lib/ambari-server/resources/views/ + cp capacity-scheduler-???-SNAPSHOT /var/lib/ambari-server/resources/views/ ambari-server restart Deploying the View @@ -49,7 +49,7 @@ Use the [Ambari Vagrant](https://cwiki.apache.org/confluence/display/AMBARI/Quic Deploy the Capacity Scheduler view into Ambari. - cp capacity-scheduler-0.3.0-SNAPSHOT /var/lib/ambari-server/resources/views/ + cp capacity-scheduler-???-SNAPSHOT /var/lib/ambari-server/resources/views/ ambari-server restart From the Ambari Administration interface, create a view instance. @@ -65,7 +65,7 @@ From the Ambari Administration interface, create a view instance. Login to Ambari and browse to the view instance. - http://c6401.ambari.apache.org:8080/#/main/views/CAPACITY-SCHEDULER/0.3.0/CS_1 + http://c6401.ambari.apache.org:8080/#/main/views/CAPACITY-SCHEDULER/???/CS_1 Local Development ----- http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java index 8e1b6a6..b192543 100644 --- a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java +++ b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java @@ -20,8 +20,9 @@ package org.apache.ambari.view.capacityscheduler; import org.apache.ambari.view.ViewContext; import org.apache.ambari.view.capacityscheduler.utils.MisconfigurationFormattedException; -import org.apache.ambari.view.capacityscheduler.proxy.Proxy; import org.apache.ambari.view.capacityscheduler.utils.ServiceFormattedException; +import org.apache.ambari.view.utils.ambari.AmbariApi; +import org.apache.commons.io.IOUtils; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.JSONValue; @@ -31,22 +32,22 @@ import org.slf4j.LoggerFactory; import javax.ws.rs.*; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import java.io.InputStream; +import java.net.ConnectException; import java.util.HashMap; -import java.net.URL; -import java.net.MalformedURLException; +import java.util.Map; /** - * Help service + * Configuration service for accessing Ambari REST API for capacity scheduler config. + * */ public class ConfigurationService { private static final Logger LOG = LoggerFactory.getLogger(ConfigurationService.class); - private final Proxy proxy; - private final String baseUrl; - private final String serverUrl; + private final AmbariApi ambariApi; private ViewContext context; - private final String refreshRMRequestData = + private static final String REFRESH_RM_REQUEST_DATA = "{\n" + " \"RequestInfo\" : {\n" + " \"command\" : \"REFRESHQUEUES\",\n" + @@ -59,12 +60,12 @@ public class ConfigurationService { " \"hosts\" : \"%s\"\n" + " }]\n" + "}"; - private final String restartRMRequestData = "{\"RequestInfo\": {\n" + + private static final String RESTART_RM_REQUEST_DATA = "{\"RequestInfo\": {\n" + " \"command\":\"RESTART\",\n" + " \"context\":\"Restart ResourceManager\",\n" + " \"operation_level\": {\n" + " \"level\":\"HOST_COMPONENT\",\n" + - " \"cluster_name\":\"MyCluster\",\n" + + " \"cluster_name\":\"%s\",\n" + " \"host_name\":\"%s\",\n" + " \"service_name\":\"YARN\",\n" + " \"hostcomponent_name\":\"RESOURCEMANAGER\"\n" + @@ -86,46 +87,27 @@ public class ConfigurationService { */ public ConfigurationService(ViewContext context) { this.context = context; - - proxy = new Proxy(context.getURLStreamProvider()); - proxy.setUseAuthorization(true); - proxy.setUsername(context.getProperties().get("ambari.server.username")); - proxy.setPassword(context.getProperties().get("ambari.server.password")); - - HashMap<String, String> customHeaders = new HashMap<String, String>(); - customHeaders.put("X-Requested-By", "view-capacity-scheduler"); - proxy.setCustomHeaders(customHeaders); - - baseUrl = context.getProperties().get("ambari.server.url"); - - URL url = null; - try { - url = new URL(baseUrl); - } catch (MalformedURLException ex) { - throw new ServiceFormattedException("Error in ambari.server.url property: " + ex.getMessage(), ex); - } - - String clusterName = url.getFile(); // TODO: make it more robust - serverUrl = baseUrl.substring(0, baseUrl.length() - clusterName.length()); + this.ambariApi = new AmbariApi(context); + this.ambariApi.setRequestedBy("view-capacity-scheduler"); } // ================================================================================ // Configuration Reading // ================================================================================ - private final String versionTagUrl = "%s?fields=Clusters/desired_configs/capacity-scheduler"; - private final String configurationUrl = "%s/configurations?type=capacity-scheduler"; - private final String configurationUrlByTag = "%%s/configurations?type=capacity-scheduler&tag=%s"; - private final String rmHostUrl = "%s/services/YARN/components/RESOURCEMANAGER?fields=host_components/host_name"; + private static final String VERSION_TAG_URL = "?fields=Clusters/desired_configs/capacity-scheduler"; + private static final String CONFIGURATION_URL = "configurations?type=capacity-scheduler"; + private static final String CONFIGURATION_URL_BY_TAG = "configurations?type=capacity-scheduler&tag=%s"; + private static final String RM_HOST_URL = "services/YARN/components/RESOURCEMANAGER?fields=host_components/host_name"; - private final String rmGetNodeLabelUrl = "http://%s:8088/ws/v1/cluster/get-node-labels"; + private static final String RM_GET_NODE_LABEL_URL = "http://%s:8088/ws/v1/cluster/get-node-labels"; // ================================================================================ // Privilege Reading // ================================================================================ - private final String clusterOperatorPrivilegeUrl = "%s?privileges/PrivilegeInfo/permission_name=CLUSTER.OPERATE&privileges/PrivilegeInfo/principal_name=%s"; - private final String ambariAdminPrivilegeUrl = "%s/api/v1/users/%s?Users/admin=true"; + private static final String CLUSTER_OPERATOR_PRIVILEGE_URL = "?privileges/PrivilegeInfo/permission_name=CLUSTER.OPERATE&privileges/PrivilegeInfo/principal_name=%s"; + private static final String AMBARI_ADMIN_PRIVILEGE_URL = "/api/v1/users/%s?Users/admin=true"; /** * Gets capacity scheduler configuration. @@ -164,7 +146,7 @@ public class ConfigurationService { try { validateViewConfiguration(); - JSONObject configurations = proxy.request(baseUrl).get().asJSON(); + JSONObject configurations = readFromCluster(""); response = Response.ok(configurations).build(); } catch (WebApplicationException ex) { throw ex; @@ -188,9 +170,8 @@ public class ConfigurationService { try { validateViewConfiguration(); - String url = String.format(configurationUrl, baseUrl); - JSONObject configurations = proxy.request(url).get().asJSON(); - response = Response.ok(configurations).build(); + JSONObject responseJSON = readFromCluster(CONFIGURATION_URL); + response = Response.ok( responseJSON ).build(); } catch (WebApplicationException ex) { throw ex; } catch (Exception ex) { @@ -234,10 +215,10 @@ public class ConfigurationService { @Path("/privilege") public Response getPrivilege() { Response response = null; - + try { boolean operator = isOperator(); - + response = Response.ok(operator).build(); } catch (WebApplicationException ex) { throw ex; @@ -260,9 +241,15 @@ public class ConfigurationService { Response response; try { - String nodeLabels = proxy.request(String.format(rmGetNodeLabelUrl, getRMHost())).get().asString(); + String url = String.format(RM_GET_NODE_LABEL_URL, getRMHost()); + + InputStream rmResponse = context.getURLStreamProvider().readFrom( + url, "GET", (String) null, new HashMap<String, String>()); + String nodeLabels = IOUtils.toString(rmResponse); response = Response.ok(nodeLabels).build(); + } catch (ConnectException ex) { + throw new ServiceFormattedException("Connection to Resource Manager refused", ex); } catch (WebApplicationException ex) { throw ex; } catch (Exception ex) { @@ -282,43 +269,85 @@ public class ConfigurationService { validateViewConfiguration(); // first check if the user is an CLUSTER.OPERATOR - String url = String.format(clusterOperatorPrivilegeUrl, baseUrl, context.getUsername()); - JSONObject json = proxy.request(url).get().asJSON(); + String url = String.format(CLUSTER_OPERATOR_PRIVILEGE_URL, context.getUsername()); + JSONObject json = readFromCluster(url); if (json == null || json.size() <= 0) { // user is not a CLUSTER.OPERATOR but might be an AMBARI.ADMIN - url = String.format(ambariAdminPrivilegeUrl, serverUrl, context.getUsername()); - json = proxy.request(url).get().asJSON(); - if (json == null || json.size() <= 0) - return false; + url = String.format(AMBARI_ADMIN_PRIVILEGE_URL, context.getUsername()); + String response = ambariApi.readFromAmbari(url, "GET", null, null); + if (response == null || response.isEmpty()) { + return false; + } + json = getJsonObject(response); + if (json == null || json.size() <= 0) { + return false; + } } return true; } - + + private JSONObject readFromCluster(String url) { + String response = ambariApi.requestClusterAPI(url); + if (response == null || response.isEmpty()) { + return null; + } + + return getJsonObject(response); + } + + private JSONObject getJsonObject(String response) { + if (response == null || response.isEmpty()) { + return null; + } + JSONObject jsonObject = (JSONObject) JSONValue.parse(response); + + if (jsonObject.get("status") != null && (Long)jsonObject.get("status") >= 400L) { + // Throw exception if HTTP status is not OK + String message; + if (jsonObject.containsKey("message")) { + message = (String) jsonObject.get("message"); + } else { + message = "without message"; + } + throw new ServiceFormattedException("Proxy: Server returned error " + jsonObject.get("status") + " " + + message + ". Check Capacity-Scheduler instance properties."); + } + return jsonObject; + } + /** * Validates the view configuration properties. * * @throws MisconfigurationFormattedException if one of the required view configuration properties are not set */ private void validateViewConfiguration() { + // check if we are cluster config'd, if so, just go + if (ambariApi.isLocalCluster()) { + return; + } + String hostname = context.getProperties().get("ambari.server.url"); - if (hostname == null) + if (hostname == null) { throw new MisconfigurationFormattedException("ambari.server.url"); + } String username = context.getProperties().get("ambari.server.username"); - if (username == null) + if (username == null) { throw new MisconfigurationFormattedException("ambari.server.username"); + } String password = context.getProperties().get("ambari.server.password"); - if (password == null) + if (password == null) { throw new MisconfigurationFormattedException("ambari.server.password"); + } } private JSONObject getConfigurationFromAmbari(String versionTag) { - String urlTemplate = String.format(configurationUrlByTag, versionTag); - String url = String.format(urlTemplate, baseUrl); - return proxy.request(url).get().asJSON(); + String url = String.format(CONFIGURATION_URL_BY_TAG, versionTag); + JSONObject responseJSON = readFromCluster(url); + return responseJSON; } /** @@ -351,8 +380,8 @@ public class ConfigurationService { * @return the desired config JSON object */ private JSONObject getDesiredConfigs() { - String url = String.format(versionTagUrl, baseUrl); - return proxy.request(url).get().asJSON(); + JSONObject response = readFromCluster(VERSION_TAG_URL); + return response; } // ================================================================================ @@ -376,9 +405,10 @@ public class ConfigurationService { return Response.status(401).build(); } - response = proxy.request(baseUrl). - setData(request). - put().asJSON(); + Map<String, String> headers = new HashMap<String, String>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + String responseString = ambariApi.requestClusterAPI("", "PUT", request.toJSONString(), headers); + response = getJsonObject(responseString); } catch (WebApplicationException ex) { throw ex; @@ -406,10 +436,11 @@ public class ConfigurationService { } String rmHost = getRMHost(); - JSONObject data = (JSONObject) JSONValue.parse(String.format(refreshRMRequestData, rmHost)); - proxy.request(baseUrl + "/requests/"). - setData(data). - post(); + JSONObject data = getJsonObject(String.format(REFRESH_RM_REQUEST_DATA, rmHost)); + + Map<String, String> headers = new HashMap<String, String>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + ambariApi.requestClusterAPI("requests/", "POST", data.toJSONString(), headers); } catch (WebApplicationException ex) { throw ex; @@ -436,10 +467,12 @@ public class ConfigurationService { } String rmHost = getRMHost(); - JSONObject data = (JSONObject) JSONValue.parse(String.format(restartRMRequestData, rmHost, rmHost)); - proxy.request(baseUrl + "/requests/"). - setData(data). - post(); + JSONObject data = getJsonObject(String.format(RESTART_RM_REQUEST_DATA, + ambariApi.getCluster().getName(), rmHost, rmHost)); + + Map<String, String> headers = new HashMap<String, String>(); + headers.put("Content-Type", "application/x-www-form-urlencoded"); + ambariApi.requestClusterAPI("requests/", "POST", data.toJSONString(), headers); } catch (WebApplicationException ex) { throw ex; @@ -451,7 +484,9 @@ public class ConfigurationService { private String getRMHost() { String rmHost = null; - JSONObject rmData = proxy.request(String.format(rmHostUrl, baseUrl)).get().asJSON(); + JSONObject rmData = readFromCluster(RM_HOST_URL); + if (rmData == null) + throw new ServiceFormattedException("Cannot retrieve ResourceManager host"); JSONArray components = (JSONArray) rmData.get("host_components"); for(Object component : components) { JSONObject roles = (JSONObject) ((JSONObject) component).get("HostRoles"); @@ -461,8 +496,8 @@ public class ConfigurationService { } } if (rmHost == null) - throw new ServiceFormattedException("Can't retrieve Resource Manager Host"); + throw new ServiceFormattedException("Can't find ResourceManager host"); return rmHost; } -} +} // end ConfigurationService http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/Proxy.java ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/Proxy.java b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/Proxy.java deleted file mode 100644 index c026b41..0000000 --- a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/Proxy.java +++ /dev/null @@ -1,120 +0,0 @@ -/** - * 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.capacityscheduler.proxy; - -import org.apache.ambari.view.URLStreamProvider; -import org.apache.ambari.view.capacityscheduler.utils.ServiceFormattedException; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.IOUtils; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -/** - * Proxy with ability to make authorized request - * with simple authorization headers - */ -public class Proxy { - private static final Logger LOG = LoggerFactory.getLogger(Proxy.class); - - private URLStreamProvider urlStreamProvider; - private boolean useAuthorization = false; - private String username; - private String password; - private Map<String, String> customHeaders; - - /** - * Constructor - * @param urlStreamProvider url stream provider - */ - public Proxy(URLStreamProvider urlStreamProvider) { - this.urlStreamProvider = urlStreamProvider; - } - - /** - * Create RequestBuilder object with - * initialized proxy options - * @param url url - * @return RequestBuilder instance - */ - public RequestBuilder request(String url) { - return new RequestBuilder(urlStreamProvider, url). - setHeaders(makeHeaders()); - } - - private HashMap<String, String> makeHeaders() { - HashMap<String, String> headers = new HashMap<String, String>(); - headers.putAll(customHeaders); - - if (isUseAuthorization()) { - String authString = username + ":" + password; - byte[] authEncBytes = Base64.encodeBase64(authString.getBytes()); - String authStringEnc = new String(authEncBytes); - - headers.put("Authorization", "Basic " + authStringEnc); - } - return headers; - } - - public boolean isUseAuthorization() { - return useAuthorization; - } - - public void setUseAuthorization(boolean useAuthorization) { - this.useAuthorization = useAuthorization; - } - - public String getUsername() { - return username; - } - - public void setUsername(String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(String password) { - this.password = password; - } - - public URLStreamProvider getUrlStreamProvider() { - return urlStreamProvider; - } - - public void setUrlStreamProvider(URLStreamProvider urlStreamProvider) { - this.urlStreamProvider = urlStreamProvider; - } - - public Map<String, String> getCustomHeaders() { - return customHeaders; - } - - public void setCustomHeaders(Map<String, String> customHeaders) { - this.customHeaders = customHeaders; - } -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/RequestBuilder.java ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/RequestBuilder.java b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/RequestBuilder.java deleted file mode 100644 index 88e19bb..0000000 --- a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/RequestBuilder.java +++ /dev/null @@ -1,160 +0,0 @@ -/** - * 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.capacityscheduler.proxy; - -import jersey.repackaged.com.google.common.collect.ImmutableMap; -import org.apache.ambari.view.URLStreamProvider; -import org.apache.ambari.view.capacityscheduler.utils.ServiceFormattedException; -import org.json.simple.JSONObject; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.net.UnknownHostException; -import java.util.HashMap; - -/** - * Request builder with fluent interface - */ -public class RequestBuilder { - private static final Logger LOG = LoggerFactory.getLogger(RequestBuilder.class); - - //request options - private String url = null; - private String method = null; - private String data = null; - - private HashMap<String, String> headers = null; - - private URLStreamProvider urlStreamProvider; - - /** - * Constructor for RequestBuilder - * @param urlStreamProvider url stream provider - * @param url url - */ - public RequestBuilder(URLStreamProvider urlStreamProvider, String url) { - this.urlStreamProvider = urlStreamProvider; - this.url = url; - } - - /** - * Shortcut for making GET request - * @return ResponseTranslator object that encapsulates InputStream - */ - public ResponseTranslator get() { - setMethod("GET"); - return doRequest(); - } - - /** - * Shortcut for making PUT request - * @return ResponseTranslator object that encapsulates InputStream - */ - public ResponseTranslator put() { - setMethod("PUT"); - return doRequest(); - } - - /** - * Shortcut for making POST request - * @return ResponseTranslator object that encapsulates InputStream - */ - public ResponseTranslator post() { - setMethod("POST"); - return doRequest(); - } - - /** - * Shortcut for making DELETE request - * @return ResponseTranslator object that encapsulates InputStream - */ - public ResponseTranslator delete() { - setMethod("DELETE"); - return doRequest(); - } - - /** - * Make request - * @return ResponseTranslator object that encapsulates InputStream - */ - public ResponseTranslator doRequest() { - LOG.debug(String.format("%s Request to %s", method, url)); - InputStream inputStream = null; - try { - inputStream = urlStreamProvider.readFrom(url, method, data, headers); - } catch (UnknownHostException e) { - throw new ServiceFormattedException(e.getMessage() + " is unknown host. Check Capacity-Scheduler instance properties.", e); - } catch (IOException e) { - throw new ServiceFormattedException(e.toString(), e); - } - return new ResponseTranslator(inputStream); - } - - public String getUrl() { - return url; - } - - public RequestBuilder setUrl(String url) { - this.url = url; - return this; - } - - public String getMethod() { - return method; - } - - public RequestBuilder setMethod(String method) { - this.method = method; - return this; - } - - public String getData() { - return data; - } - - public RequestBuilder setData(String data) { - this.data = data; - return this; - } - - public RequestBuilder setData(JSONObject data) { - this.data = data.toString(); - return this; - } - - public HashMap<String, String> getHeaders() { - return headers; - } - - public RequestBuilder setHeaders(HashMap<String, String> headers) { - this.headers = headers; - return this; - } - - public URLStreamProvider getUrlStreamProvider() { - return urlStreamProvider; - } - - public RequestBuilder setUrlStreamProvider(URLStreamProvider urlStreamProvider) { - this.urlStreamProvider = urlStreamProvider; - return this; - } -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/ResponseTranslator.java ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/ResponseTranslator.java b/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/ResponseTranslator.java deleted file mode 100644 index 8e3b4a6..0000000 --- a/contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/proxy/ResponseTranslator.java +++ /dev/null @@ -1,100 +0,0 @@ -/** - * 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.capacityscheduler.proxy; - -import org.apache.ambari.view.capacityscheduler.utils.ServiceFormattedException; -import org.apache.commons.io.IOUtils; -import org.json.simple.JSONObject; -import org.json.simple.JSONValue; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; - -/** - * Response translator encapsulates InputStream - * and able to convert it to different data types - */ -public class ResponseTranslator { - private static final Logger LOG = LoggerFactory.getLogger(RequestBuilder.class); - - private InputStream inputStream; - - /** - * Constructor for ResponseTranslator - * @param inputStream InputStream instance - */ - public ResponseTranslator(InputStream inputStream) { - this.inputStream = inputStream; - } - - /** - * Get InputStream of response from server for manual processing - * @return input stream of response - */ - public InputStream asInputStream() { - return inputStream; - } - - /** - * Retrieve response as String - * @return response as String - */ - public String asString() { - String response; - try { - response = IOUtils.toString(inputStream); - } catch (IOException e) { - throw new ServiceFormattedException("Can't read from target host", e); - } - LOG.debug(String.format("Response: %s", response)); - - return response; - } - - /** - * Retrieve response as JSON - * @return response as JSON - */ - public JSONObject asJSON() { - String jsonString = asString(); - JSONObject jsonObject = (JSONObject) JSONValue.parse(jsonString); - if (jsonObject.get("status") != null && (Long)jsonObject.get("status") >= 400L) { - // Throw exception if HTTP status is not OK - String message; - if (jsonObject.containsKey("message")) { - message = (String) jsonObject.get("message"); - } else { - message = "without message"; - } - throw new ServiceFormattedException("Proxy: Server returned error " + jsonObject.get("status") + " " + - message + ". Check Capacity-Scheduler instance properties."); - } - return jsonObject; - } - - /** - * InputStream setter - * @param inputStream InputStream instance - */ - public void setInputStream(InputStream inputStream) { - this.inputStream = inputStream; - } -} http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownButtons.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownButtons.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownButtons.js index cc15563..9b0950a 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownButtons.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/dropdownButtons.js @@ -51,4 +51,4 @@ App.DropdownButtonsComponent = Em.Component.extend(App.ClickElsewhereMixin,{ this.sendAction('action',arg); } } -}); +}) http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js index 9828399..019df5f 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js @@ -209,7 +209,7 @@ App.QueueController = Ember.ObjectController.extend({ if (arguments.length > 1) { if (!this.get('isFairOP')) { this.send('rollbackProp','enable_size_based_weight',this.get('content')); - } + }; this.set('content.ordering_policy',val || null); } return this.get('content.ordering_policy'); http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js index b26697f..7b69c92 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js @@ -228,7 +228,7 @@ App.QueuesController = Ember.ArrayController.extend({ * check if RM needs restart * @type {bool} */ - needRestart: Em.computed.alias('hasDeletedQueues'), + needRestart: Em.computed.and('hasDeletedQueues','isOperator'), /** * True if some queue of desired configs was removed. @@ -244,7 +244,7 @@ App.QueuesController = Ember.ArrayController.extend({ * check if RM needs refresh * @type {bool} */ - needRefresh: cmp.and('needRefreshProps','noNeedRestart'), + needRefresh: cmp.and('needRefreshProps','noNeedRestart','isOperator'), /** * Inverted needRestart value. @@ -305,7 +305,7 @@ App.QueuesController = Ember.ArrayController.extend({ * check if can save configs * @type {bool} */ - canNotSave: cmp.any('hasOverCapacity', 'hasUncompetedAddings','hasNotValid','isNotOperator'), + canNotSave: cmp.any('hasOverCapacity', 'hasUncompetedAddings','hasNotValid'), /** * List of not valid queues. http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js index 4dfb5e5..4b722f9 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js @@ -248,15 +248,30 @@ App.QueueSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{ return json; }, serializeHasMany:function (record, json, relationship) { - var key = relationship.key; - json[[this.PREFIX, record.get('path'), 'accessible-node-labels'].join('.')] = (record.get('labelsEnabled'))?'':null; - record.get(key).map(function (l,idx,labels) { - json[[this.PREFIX, record.get('path'), 'accessible-node-labels'].join('.')] = (record.get('accessAllLabels'))?'*':labels.mapBy('name').join(','); + var key = relationship.key, + recordLabels = record.get(key), + accessible_node_labels_key = [this.PREFIX, record.get('path'), 'accessible-node-labels'].join('.'); + + switch (true) { + case (record.get('accessAllLabels')): + json[accessible_node_labels_key] = '*'; + break; + case (!Em.isEmpty(recordLabels)): + json[accessible_node_labels_key] = recordLabels.mapBy('name').join(','); + break; + case (record.get('labelsEnabled')): + json[accessible_node_labels_key] = ''; + break; + default: + json[accessible_node_labels_key] = null; + } + + recordLabels.forEach(function (l) { if (!record.get('store.nodeLabels').findBy('name',l.get('name')).notExist) { - json[[this.PREFIX, record.get('path'), 'accessible-node-labels', l.get('name'), 'capacity'].join('.')] = l.get('capacity'); - json[[this.PREFIX, record.get('path'), 'accessible-node-labels', l.get('name'), 'maximum-capacity'].join('.')] = l.get('maximum_capacity'); + json[[accessible_node_labels_key, l.get('name'), 'capacity'].join('.')] = l.get('capacity'); + json[[accessible_node_labels_key, l.get('name'), 'maximum-capacity'].join('.')] = l.get('maximum_capacity'); } - },this); + }); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less b/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less index 82fbc07..23ce2ef 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less @@ -234,7 +234,7 @@ margin-bottom: 20px; min-height: 20px; padding: 15px; - padding-top: 0; + padding-top: 0px; // ----- QUEUE -> HEADING ROW ----- @@ -736,6 +736,24 @@ } } +// ----- REFUSE PAGE ----- + +.refuse-page { + .page-body a { + &.collapsed i { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -webkit-transform: rotate(180deg); + -moz-transform: rotate(180deg); + -ms-transform: rotate(180deg); + -o-transform: rotate(180deg); + transform: rotate(180deg); + } + text-decoration: none; + font-size: .8em; + text-transform: uppercase; + } +} + // ----- COMPONENTS ----- @@ -814,7 +832,7 @@ padding: 8px; border-radius: 4px; background-color: #fff; - box-shadow: 0 0 5px; + box-shadow: 0px 0 5px; } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs index cdc9044..19c6cfe 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs @@ -81,4 +81,4 @@ {{/each}} </div> {{/if}} -</div> +</div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs index b412d7b..f785a62 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/queues.hbs @@ -45,10 +45,10 @@ isNotOperator=isNotOperator }} <li > - <a href="#" {{action 'saveModal' 'refresh' target="view"}} {{bind-attr class=":btn isNotOperator:disabled needRefresh::disabled"}}><i class="fa fa-fw fa-refresh"></i> Save and Refresh Queues</a> + <a href="#" {{action 'saveModal' 'refresh' target="view"}} {{bind-attr class=":btn needRefresh::disabled"}}><i class="fa fa-fw fa-refresh"></i> Save and Refresh Queues</a> </li> <li> - <a href="#" {{action 'saveModal' target="view"}} {{bind-attr class=":btn isNotOperator:disabled needSave::disabled"}}><i class="fa fa-fw fa-save"></i> Save Only</a> + <a href="#" {{action 'saveModal' target="view"}} {{bind-attr class=":btn needSave::disabled"}}><i class="fa fa-fw fa-save"></i> Save Only</a> </li> {{dropdown-buttons action='downloadConfig' layoutName='components/dropdownDownload'}} </ul> http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/refuse.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/refuse.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/refuse.hbs index 8671d7d..ec83d11 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/refuse.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/refuse.hbs @@ -16,10 +16,16 @@ * limitations under the License. }} -<div class="col-md-12"> +<div class="col-md-12 refuse-page"> <div class="page-header"> <div class="pull-right"> {{#link-to 'queues'}} <i class="fa fa-refresh" ></i> Retry connection {{/link-to}} </div> <h3>Couldn't connect to the cluster</h3> </div> - <div>{{content.message}}</div> + <div class="page-body"> + <p>{{content.message}}</p> + <a data-toggle="collapse" href="#trace" aria-expanded="false" {{bind-attr class=":collapsed content.trace::hide"}} > + trace <i class="fa fa-angle-up"></i> + </a> + <div class="collapse" id="trace"> <pre> {{content.trace}} </pre> </div> + </div> </div> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee b/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee index 0c86396..5f9bf8b 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee @@ -67,5 +67,5 @@ exports.config = overrides: development: paths: - public: '/usr/lib/ambari-server/web/views-debug/CAPACITY-SCHEDULER/0.3.0/CS_1/' + public: '/usr/lib/ambari-server/web/views-debug/CAPACITY-SCHEDULER/0.4.0/CS_1/' http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/ui/test/integration/serializers_test.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/test/integration/serializers_test.js b/contrib/views/capacity-scheduler/src/main/resources/ui/test/integration/serializers_test.js new file mode 100644 index 0000000..9de1ea1 --- /dev/null +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/test/integration/serializers_test.js @@ -0,0 +1,124 @@ +/** + * 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. + */ + +var run = Ember.run; + +var setupStore = function(options) { + var env = {}; + options = options || {}; + + var container = env.container = new Ember.Container(); + + var adapter = env.adapter = (options.adapter || DS.Adapter); + delete options.adapter; + + for (var prop in options) { + container.register('model:' + prop, options[prop]); + } + + container.register('store:main', App.ApplicationStore.extend({ + adapter: adapter + })); + + container.register('serializer:queue', App.QueueSerializer); + container.register('serializer:scheduler', App.SchedulerSerializer); + container.register('serializer:label', App.SchedulerSerializer); + container.register('serializer:tag', App.TagSerializer); + + container.injection('serializer', 'store', 'store:main'); + + env.queueSerializer = container.lookup('serializer:queue'); + env.schedulerSerializer = container.lookup('serializer:scheduler'); + env.labelSerializer = container.lookup('serializer:label'); + env.tagSerializer = container.lookup('serializer:tag'); + + env.store = container.lookup('store:main'); + env.adapter = env.store.get('defaultAdapter'); + + return env; +}; + +var createStore = function(options) { + return setupStore(options).store; +}; + +QUnit.module("integration/serializers", { + setup: function (options) { + store = createStore({queue: App.Queue,label:App.Label,adapter:App.QueueAdapter}); + }, + teardown: function() { + store = null; + } +}); + +test('serialize accessible-node-labels property', function () { + + expect(4); + + var queueSerializer = store.serializerFor('queue'), + rootQueue, + storeLabels, + label1, + label2, + json; + + run(function () { + store.set('nodeLabels',[{name:'label1'}, {name:'label2'}]); + }); + + run(function () { + label1 = store.push('label', {'id':'root.label1','name':'label1'}); + label2 = store.push('label', {'id':'root.label2','name':'label2'}); + rootQueue = store.push('queue', { + 'id':'root', + 'path':'root', + 'labelsEnabled':true, + '_accessAllLabels':true, + 'labels':['root.label1', 'root.label2'] + }); + }); + + json = queueSerializer.serialize(rootQueue); + + equal(json['yarn.scheduler.capacity.root.accessible-node-labels'],'*'); + + run(function () { + rootQueue.set('_accessAllLabels',false); + }); + + json = queueSerializer.serialize(rootQueue); + + equal(json['yarn.scheduler.capacity.root.accessible-node-labels'],'label1,label2'); + + run(function () { + rootQueue.get('labels').clear(); + }); + + json = queueSerializer.serialize(rootQueue); + + equal(json['yarn.scheduler.capacity.root.accessible-node-labels'],''); + + run(function () { + rootQueue.set('labelsEnabled',false); + }); + + json = queueSerializer.serialize(rootQueue); + + equal(json['yarn.scheduler.capacity.root.accessible-node-labels'],undefined); + +}); \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/capacity-scheduler/src/main/resources/view.xml ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/view.xml b/contrib/views/capacity-scheduler/src/main/resources/view.xml index 2974f86..564f353 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/view.xml +++ b/contrib/views/capacity-scheduler/src/main/resources/view.xml @@ -17,16 +17,18 @@ <view> <name>CAPACITY-SCHEDULER</name> <label>Capacity Scheduler</label> - <version>0.3.0</version> - <min-ambari-version>2.0.*</min-ambari-version> + <version>0.4.0</version> + + <min-ambari-version>2.1.*</min-ambari-version> <validator-class>org.apache.ambari.view.capacityscheduler.PropertyValidator</validator-class> <parameter> <name>ambari.server.url</name> - <description>Enter the Ambari REST API cluster resource.</description> + <description>Enter the Ambari Server REST API cluster resource.</description> <label>Ambari Cluster URL</label> <placeholder>http://ambari.server:8080/api/v1/clusters/MyCluster</placeholder> + <cluster-config>fake</cluster-config> <required>true</required> </parameter> <parameter> @@ -34,12 +36,14 @@ <description>Enter the Cluster Operator username (for example: admin).</description> <label>Operator Username</label> <placeholder>admin</placeholder> + <cluster-config>fake</cluster-config> <required>true</required> </parameter> <parameter> <name>ambari.server.password</name> <description>Enter the Cluster Operator password (for example: password).</description> <label>Operator Password</label> + <cluster-config>fake</cluster-config> <required>true</required> <masked>true</masked> </parameter> @@ -49,4 +53,14 @@ <service-class>org.apache.ambari.view.capacityscheduler.CapacitySchedulerService</service-class> </resource> + <auto-instance> + <name>AUTO_CS_INSTANCE</name> + <label>YARN Queue Manager</label> + <description>Manage YARN Capacity Scheduler Queues</description> + <stack-id>HDP-2.*</stack-id> + <services> + <service>YARN</service> + </services> + </auto-instance> + </view> http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/pom.xml ---------------------------------------------------------------------- diff --git a/contrib/views/pom.xml b/contrib/views/pom.xml index 9fa698b..dc74568 100644 --- a/contrib/views/pom.xml +++ b/contrib/views/pom.xml @@ -91,6 +91,7 @@ <exclude>**/assets/stylesheets/**</exclude> <exclude>**/assets/static/javascripts/**</exclude> <exclude>**/assets/static/stylesheets/**</exclude> + <exclude>storm/src/main/resources/**</exclude> </excludes> </configuration> </plugin> http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java ---------------------------------------------------------------------- diff --git a/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java b/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java index 88e5f48..e49eea5 100644 --- a/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java +++ b/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java @@ -29,8 +29,11 @@ import org.json.simple.JSONValue; import java.io.IOException; import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * Provides API to Ambari. Supports both Local and Remote cluster association. @@ -44,9 +47,10 @@ public class AmbariApi { private Cluster cluster; private ViewContext context; - private String remoteUrl; + private String remoteUrlCluster; private String remoteUsername; private String remotePassword; + private String requestedBy = "views"; /** * Constructor for Ambari API based on ViewContext @@ -55,12 +59,35 @@ public class AmbariApi { public AmbariApi(ViewContext context) { this.context = context; - remoteUrl = context.getProperties().get(AMBARI_SERVER_URL_INSTANCE_PROPERTY); + remoteUrlCluster = context.getProperties().get(AMBARI_SERVER_URL_INSTANCE_PROPERTY); remoteUsername = context.getProperties().get(AMBARI_SERVER_USERNAME_INSTANCE_PROPERTY); remotePassword = context.getProperties().get(AMBARI_SERVER_PASSWORD_INSTANCE_PROPERTY); } /** + * X-Requested-By header value + * @param requestedBy value of X-Requested-By header + */ + public void setRequestedBy(String requestedBy) { + this.requestedBy = requestedBy; + } + + private String parseAmbariHostname(String remoteUrlCluster) { + String hostname; + try { + URI uri = new URI(remoteUrlCluster); + hostname = String.format("%s://%s", uri.getScheme(), uri.getHost()); + if (uri.getPort() != -1) { + hostname += ":" + uri.getPort(); + } + } catch (URISyntaxException e) { + throw new AmbariApiException("RA060 Malformed URI of remote Ambari"); + } + + return hostname; + } + + /** * Provides ability to get cluster topology * @param requestComponent name of component * @return list of hostnames with component @@ -68,7 +95,7 @@ public class AmbariApi { */ public List<String> getHostsWithComponent(String requestComponent) throws AmbariApiException { String method = "hosts?fields=Hosts/public_host_name,host_components/HostRoles/component_name"; - String response = readFromAmbari(method); + String response = requestClusterAPI(method); List<String> foundHosts = new ArrayList<String>(); @@ -90,32 +117,44 @@ public class AmbariApi { } /** - * Request to Ambari REST API. Supports both local and remote cluster - * @param method REST API path, e.g. /api/v1/clusters/mycluster?... + * Shortcut for GET method + * @param path REST API path + * @return response + * @throws AmbariApiException + */ + public String requestClusterAPI(String path) throws AmbariApiException { + return requestClusterAPI(path, "GET", null, null); + } + + /** + * Request to Ambari REST API for current cluster. Supports both local and remote cluster + * @param path REST API path after cluster name e.g. /api/v1/clusters/mycluster/[method] + * @param method HTTP method + * @param data HTTP data + * @param headers HTTP headers * @return response * @throws AmbariApiException IO error or not associated with cluster */ - public String readFromAmbari(String method) throws AmbariApiException { + public String requestClusterAPI(String path, String method, String data, Map<String, String> headers) throws AmbariApiException { String response; + URLStreamProviderBasicAuth urlStreamProvider = getUrlStreamProviderBasicAuth(); try { - InputStream inputStream; + String url; if (isLocalCluster()) { - AmbariStreamProvider ambariStreamProvider = context.getAmbariStreamProvider(); - String url = String.format("/api/v1/clusters/%s/%s", getCluster().getName(), method); - inputStream = ambariStreamProvider.readFrom(url, "GET", (String) null, null, true); + url = String.format("/api/v1/clusters/%s/%s", getCluster().getName(), path); } else if (isRemoteCluster()) { - URLStreamProvider urlStreamProvider = getUrlStreamProviderBasicAuth(); - String url = String.format("%s/%s", remoteUrl, method); - inputStream = urlStreamProvider.readFrom(url, "GET", (String) null, null); + + url = String.format("%s/%s", remoteUrlCluster, path); } else { throw new NoClusterAssociatedException( "RA030 View is not associated with any cluster. No way to request Ambari."); } + InputStream inputStream = urlStreamProvider.readFrom(url, method, data, headers); response = IOUtils.toString(inputStream); } catch (IOException e) { throw new AmbariApiException("RA040 I/O error while requesting Ambari", e); @@ -124,6 +163,43 @@ public class AmbariApi { } /** + * Request to Ambari REST API. Supports both local and remote cluster + * @param path REST API path, e.g. /api/v1/clusters/mycluster/ + * @param method HTTP method + * @param data HTTP data + * @param headers HTTP headers + * @return response + * @throws AmbariApiException IO error or not associated with cluster + */ + public String readFromAmbari(String path, String method, String data, Map<String, String> headers) throws AmbariApiException { + String response; + URLStreamProviderBasicAuth urlStreamProvider = getUrlStreamProviderBasicAuth(); + + try { + String url; + + if (isLocalCluster()) { + url = path; + + } else if (isRemoteCluster()) { + String ambariUrl = parseAmbariHostname(remoteUrlCluster); + url = ambariUrl + path; + + } else { + throw new NoClusterAssociatedException( + "RA060 View is not associated with any cluster. No way to request Ambari."); + } + + InputStream inputStream = urlStreamProvider.readFrom(url, method, data, headers); + response = IOUtils.toString(inputStream); + } catch (IOException e) { + throw new AmbariApiException("RA050 I/O error while requesting Ambari", e); + } + return response; + } + + + /** * Check if associated with local or remote cluster * @return true if associated */ @@ -170,7 +246,7 @@ public class AmbariApi { * @return true if associated */ public boolean isRemoteCluster() { - return remoteUrl != null && !remoteUrl.isEmpty(); + return remoteUrlCluster != null && !remoteUrlCluster.isEmpty(); } /** @@ -182,21 +258,31 @@ public class AmbariApi { return null; URLStreamProvider urlStreamProviderBasicAuth = getUrlStreamProviderBasicAuth(); - return new RemoteCluster(remoteUrl, urlStreamProviderBasicAuth); + return new RemoteCluster(remoteUrlCluster, urlStreamProviderBasicAuth); } /** * Build URLStreamProvider with Basic Authentication for Remote Cluster * @return URLStreamProvider */ - public URLStreamProvider getUrlStreamProviderBasicAuth() { - if (remoteUsername == null || remoteUsername.isEmpty() || - remotePassword == null || remotePassword.isEmpty()) { - throw new AmbariApiException("RA020 Remote Ambari username and password are not filled"); - } - - URLStreamProvider urlStreamProvider = context.getURLStreamProvider(); + public URLStreamProviderBasicAuth getUrlStreamProviderBasicAuth() { + URLStreamProviderBasicAuth urlStreamProviderBasicAuth; + if (isRemoteCluster()) { + if (remoteUsername == null || remoteUsername.isEmpty() || + remotePassword == null || remotePassword.isEmpty()) { + throw new AmbariApiException("RA020 Remote Ambari username and password are not filled"); + } - return new URLStreamProviderBasicAuth(urlStreamProvider, remoteUsername, remotePassword); + URLStreamProvider urlStreamProvider = context.getURLStreamProvider(); + urlStreamProviderBasicAuth = + new URLStreamProviderBasicAuth(urlStreamProvider, remoteUsername, remotePassword); + } else if (isLocalCluster()) { + urlStreamProviderBasicAuth = new URLStreamProviderBasicAuth(context.getAmbariStreamProvider()); + } else { + throw new NoClusterAssociatedException( + "RA070 Not associated with any cluster. URLStreamProvider is not available"); + } + urlStreamProviderBasicAuth.setRequestedBy(requestedBy); + return urlStreamProviderBasicAuth; } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e2b8a0e9/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/URLStreamProviderBasicAuth.java ---------------------------------------------------------------------- diff --git a/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/URLStreamProviderBasicAuth.java b/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/URLStreamProviderBasicAuth.java index 87a4acb..c9f735a 100644 --- a/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/URLStreamProviderBasicAuth.java +++ b/contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/URLStreamProviderBasicAuth.java @@ -18,24 +18,26 @@ package org.apache.ambari.view.utils.ambari; +import org.apache.ambari.view.AmbariStreamProvider; import org.apache.ambari.view.URLStreamProvider; import org.apache.commons.codec.binary.Base64; import java.io.IOException; import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; import java.util.HashMap; -import java.util.List; import java.util.Map; /** - * Wrapper for URLStreamProvider that adds authentication header + * Wrapper for URLStreamProvider that adds authentication header. + * Also supports AmbariStreamProvider. readAs or readAsCurrent should not be used + * with AmbariStreamProvider. */ public class URLStreamProviderBasicAuth implements URLStreamProvider { private URLStreamProvider urlStreamProvider; + private AmbariStreamProvider ambariStreamProvider; private String username; private String password; + private String requestedBy = "views"; public URLStreamProviderBasicAuth(URLStreamProvider urlStreamProvider, String username, String password) { this.urlStreamProvider = urlStreamProvider; @@ -43,47 +45,78 @@ public class URLStreamProviderBasicAuth implements URLStreamProvider { this.password = password; } + public URLStreamProviderBasicAuth(AmbariStreamProvider urlStreamProvider) { + this.ambariStreamProvider = urlStreamProvider; + } + + /** + * X-Requested-By header value + * @param requestedBy value of X-Requested-By header + */ + public void setRequestedBy(String requestedBy) { + this.requestedBy = requestedBy; + } + @Override public InputStream readFrom(String url, String method, String data, Map<String, String> headers) throws IOException { - return urlStreamProvider.readFrom(url, method, data, addAuthHeaders(headers)); + if (urlStreamProvider != null) { + return urlStreamProvider.readFrom(url, method, data, addHeaders(headers)); + } else { + return ambariStreamProvider.readFrom(url, method, data, addHeaders(headers), true); + } } @Override public InputStream readFrom(String url, String method, InputStream data, Map<String, String> headers) throws IOException { - return urlStreamProvider.readFrom(url, method, data, addAuthHeaders(headers)); + if (urlStreamProvider != null) { + return urlStreamProvider.readFrom(url, method, data, addHeaders(headers)); + } else { + return ambariStreamProvider.readFrom(url, method, data, addHeaders(headers), true); + } } @Override public InputStream readAs(String url, String method, String data, Map<String, String> headers, String doAs) throws IOException { - return urlStreamProvider.readAs(url, method, data, addAuthHeaders(headers), doAs); + return urlStreamProvider.readAs(url, method, data, addHeaders(headers), doAs); } @Override public InputStream readAs(String url, String method, InputStream data, Map<String, String> headers, String doAs) throws IOException { - return urlStreamProvider.readAs(url, method, data, addAuthHeaders(headers), doAs); + return urlStreamProvider.readAs(url, method, data, addHeaders(headers), doAs); } @Override public InputStream readAsCurrent(String url, String method, String data, Map<String, String> headers) throws IOException { - return urlStreamProvider.readAsCurrent(url, method, data, addAuthHeaders(headers)); + return urlStreamProvider.readAsCurrent(url, method, data, addHeaders(headers)); } @Override public InputStream readAsCurrent(String url, String method, InputStream data, Map<String, String> headers) throws IOException { - return urlStreamProvider.readAsCurrent(url, method, data, addAuthHeaders(headers)); + return urlStreamProvider.readAsCurrent(url, method, data, addHeaders(headers)); } - private HashMap<String, String> addAuthHeaders(Map<String, String> customHeaders) { + private HashMap<String, String> addHeaders(Map<String, String> customHeaders) { HashMap<String, String> newHeaders = new HashMap<String, String>(); if (customHeaders != null) newHeaders.putAll(customHeaders); + if (urlStreamProvider != null) { + // basic auth is not needed for AmbariStreamProvider + addBasicAuthHeaders(newHeaders); + } + addRequestedByHeaders(newHeaders); + return newHeaders; + } + + private void addRequestedByHeaders(HashMap<String, String> newHeaders) { + newHeaders.put("X-Requested-By", requestedBy); + } + + private void addBasicAuthHeaders(HashMap<String, String> headers) { String authString = username + ":" + password; byte[] authEncBytes = Base64.encodeBase64(authString.getBytes()); String authStringEnc = new String(authEncBytes); - newHeaders.put("Authorization", "Basic " + authStringEnc); - newHeaders.put("X-Requested-By", "views"); - return newHeaders; + headers.put("Authorization", "Basic " + authStringEnc); } }
