ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rle...@apache.org
Subject ambari git commit: AMBARI-11436. CapScheduler Auto-Create and Cluster association (Erik Bergenholtz via rlevas)
Date Thu, 28 May 2015 16:11:57 GMT
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 <ebergenholtz@hortonworks.com>
Authored: Thu May 28 12:11:44 2015 -0400
Committer: Robert Levas <rlevas@hortonworks.com>
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);
   }
 }


Mime
View raw message