Browse Source

AMBARI-11697. Automatic RM URL Config (Erik Bergenholtz via rlevas)

Erik Bergenholtz 10 năm trước cách đây
mục cha
commit
7d793c2b33
33 tập tin đã thay đổi với 813 bổ sung85 xóa
  1. 7 1
      contrib/views/capacity-scheduler/pom.xml
  2. 2 17
      contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java
  3. 4 8
      contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/PropertyValidator.java
  4. 5 6
      contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js
  5. 4 0
      contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/data/all.json
  6. 6 1
      contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js
  7. 18 10
      contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js
  8. 12 0
      contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueBadge.js
  9. 3 1
      contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js
  10. 1 1
      contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js
  11. 38 7
      contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js
  12. 1 1
      contrib/views/capacity-scheduler/src/main/resources/ui/app/helpers/timeAgo.js
  13. 10 2
      contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js
  14. 9 7
      contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js
  15. 25 3
      contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js
  16. 1 2
      contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less
  17. 5 1
      contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs
  18. 4 0
      contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs
  19. 2 2
      contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/userGroupInput.hbs
  20. 63 0
      contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs
  21. 1 1
      contrib/views/capacity-scheduler/src/main/resources/ui/config.coffee
  22. 51 0
      contrib/views/capacity-scheduler/src/main/resources/ui/test/unit/controllers/queue_test.js
  23. 2 2
      contrib/views/capacity-scheduler/src/main/resources/view.xml
  24. 131 0
      contrib/views/capacity-scheduler/src/test/java/org/apache/ambari/view/capacityscheduler/PropertyValidatorTest.java
  25. 6 1
      contrib/views/utils/pom.xml
  26. 116 0
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ViewUserLocal.java
  27. 13 1
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java
  28. 131 0
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/Services.java
  29. 30 2
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/ConfigurationBuilder.java
  30. 10 5
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/HdfsApi.java
  31. 4 1
      contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/HdfsUtil.java
  32. 98 0
      contrib/views/utils/src/test/java/org/apache/ambari/view/utils/ViewUserLocalTest.java
  33. 0 2
      contrib/views/utils/src/test/java/org/apache/ambari/view/utils/hdfs/ConfigurationBuilderTest.java

+ 7 - 1
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.4.0-SNAPSHOT</version>
+    <version>1.0.0-SNAPSHOT</version>
     <name>Capacity Scheduler</name>
 
     <parent>
@@ -72,6 +72,12 @@
             <artifactId>ambari-views-utils</artifactId>
             <version>0.0.1-SNAPSHOT</version>
         </dependency>
+        <dependency>
+            <groupId>org.easymock</groupId>
+            <artifactId>easymock</artifactId>
+            <version>3.2</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 
     <properties>

+ 2 - 17
contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/ConfigurationService.java

@@ -98,9 +98,8 @@ public class ConfigurationService {
   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 static final String RM_GET_NODE_LABEL_URL = "http://%s:8088/ws/v1/cluster/get-node-labels";
+  private static final String RM_GET_NODE_LABEL_URL = "%s/ws/v1/cluster/get-node-labels";
 
   // ================================================================================
   // Privilege Reading
@@ -483,21 +482,7 @@ public class ConfigurationService {
   }
 
   private String getRMHost() {
-    String rmHost = null;
-    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");
-      if (roles.get("component_name").equals("RESOURCEMANAGER")) {
-        rmHost = (String) roles.get("host_name");
-        break;
-      }
-    }
-    if (rmHost == null)
-      throw new ServiceFormattedException("Can't find ResourceManager host");
-    return rmHost;
+    return ambariApi.getServices().getRMUrl();
   }
 
 } // end ConfigurationService

+ 4 - 8
contrib/views/capacity-scheduler/src/main/java/org/apache/ambari/view/capacityscheduler/PropertyValidator.java

@@ -43,14 +43,10 @@ public class PropertyValidator implements Validator {
 
     if (property.equals(AMBARI_SERVER_URL)) {
       String ambariServerUrl = viewInstanceDefinition.getPropertyMap().get(AMBARI_SERVER_URL);
-      URL url = null;
-      try {
-        url = new URL(ambariServerUrl);
-        url.toURI();
-      } catch (MalformedURLException e) {
-        return new InvalidPropertyValidationResult(false, "Must be valid URL");
-      } catch (URISyntaxException e) {
-        return new InvalidPropertyValidationResult(false, "Must be valid URL");
+
+      if (!ambariServerUrl.matches("^https?://[\\w\\d\\.]+:\\d+/api/v1/clusters/\\w+$")) {
+        return new InvalidPropertyValidationResult(false,
+            "URL should contain protocol, hostname, port, cluster name, e.g. http://ambari.server:8080/api/v1/clusters/MyCluster");
       }
     }
     return ValidationResult.SUCCESS;

+ 5 - 6
contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js

@@ -207,12 +207,8 @@ App.QueueAdapter = DS.Adapter.extend({
           return {name:label};
         }));
       }, function(jqXHR) {
-        if (jqXHR.status === 404) {
-          Ember.run(null, resolve, []);
-        } else {
-          jqXHR.then = null;
-          Ember.run(null, reject, jqXHR);
-        }
+        jqXHR.then = null;
+        Ember.run(null, reject, jqXHR);
       });
     }.bind(this),'App: QueueAdapter#getNodeLabels');
   },
@@ -306,6 +302,9 @@ App.TagAdapter = App.QueueAdapter.extend({
     var adapter = this;
     var uri = [_getCapacitySchedulerViewUri(this),'all'].join('/');
 
+    if (App.testMode)
+      uri = uri + ".json";
+
     return new Ember.RSVP.Promise(function(resolve, reject) {
       adapter.ajax(uri ,'GET').then(function(data) {
         Ember.run(null, resolve, data);

+ 4 - 0
contrib/views/capacity-scheduler/src/main/resources/ui/app/assets/data/all.json

@@ -0,0 +1,4 @@
+{
+  "items":[
+  ]
+}

+ 6 - 1
contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js

@@ -44,7 +44,12 @@ App.ExpandableInputComponent = Em.TextField.extend({
   },
   focusOut:function  (argument) {
     this.$().parent().removeClass('expanded').parent().removeClass('expanded-wrap');
-  }
+  },
+  checkBlank:function () {
+    if (Em.isBlank(this.get('value')) && !Em.isNone(this.get('value'))) {
+      this.set('value', null);
+    }
+  }.observes('value')
 });
 
 App.IntInputComponent = Ember.TextField.extend({

+ 18 - 10
contrib/views/capacity-scheduler/src/main/resources/ui/app/components/pathInput.js

@@ -24,15 +24,20 @@ App.PathInputComponent = Em.Component.extend({
     add:function () {
       var currentBasedir = this.get('currentBasedir'),
           path = this.get('path'),
-          basedir = path.substr(0,path.lastIndexOf('.')) || currentBasedir;
-      if (!path) {
-        return this.set('isError',true);
+          basedir = path.substr(0,path.lastIndexOf('.')) || currentBasedir,
+          queuePath = [basedir,path.substr(path.lastIndexOf('.')+1)].join('.'),
+          queueName = path.substr(path.lastIndexOf('.')+1),
+          alreadyExists = this.get('queues.firstObject.store').hasRecordForId('queue',queuePath.toLowerCase());
+
+      if (!path || !queueName) {
+        return this.setProperties({'isError':true,'errorMessage':'Enter queue name.'});
       }
-      if (this.get('pathMap').contains(path)) {
-        this.sendAction('action',path);
-        this.set('activeFlag',false);
-      } else if (this.get('pathMap').contains(basedir)) {
-        this.sendAction('action',basedir,path.substr(path.lastIndexOf('.')+1));
+      if (alreadyExists) {
+        return this.setProperties({'isError':true,'errorMessage':'Queue already exists.'});
+      }
+
+      if (this.get('pathMap').contains(basedir) && !alreadyExists) {
+        this.sendAction('action',basedir,queueName);
         this.set('activeFlag',false);
       }
     },
@@ -48,6 +53,7 @@ App.PathInputComponent = Em.Component.extend({
   }),
   path:'',
   isError:false,
+  errorMessage:'',
   didChangePath:function () {
     return this.set('isError',false);
   }.observes('path'),
@@ -69,11 +75,13 @@ App.PathInputComponent = Em.Component.extend({
     }.observes('pathSource').on('didInsertElement'),
     tooltipInit:function () {
       this.$().tooltip({
-        title:'Enter queue name.',
+        title:function() {
+          return this.get('parentView.errorMessage');
+        }.bind(this),
         placement:'bottom',
         trigger:'manual'
       });
-    }.on('didInsertElement'),
+    }.on('didInsertElement').observes('parentView.errorMessage'),
     tooltipToggle:function (e,o) {
       this.$().tooltip((e.get(o)?'show':'hide'));
     }.observes('parentView.isError'),

+ 12 - 0
contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueBadge.js

@@ -18,6 +18,18 @@
 
 var App = require('app');
 
+App.WarnBadgeComponent = Em.Component.extend({
+  layout:Em.Handlebars.compile('<i class="fa fa-exclamation"></i>'),
+  classNames:['label','label-warning'],
+  tagName:'span',
+  initTooltip: function(){
+    this.$().tooltip({
+      title:'No capacity',
+      placement:'bottom'
+    });
+  }.on('didInsertElement'),
+});
+
 App.QueueBadgeComponent = Em.Component.extend({
   layoutName:'components/queueBadge',
   tagName:'span',

+ 3 - 1
contrib/views/capacity-scheduler/src/main/resources/ui/app/components/totalCapacity.js

@@ -275,7 +275,9 @@ App.TotalCapacityComponent = Ember.Component.extend({
      * Returns true if total capacity of node labels in leaf are greater than 100.
      * @type {Boolean}
      */
-    warning:Em.computed.gt('capacityValue',100),
+    warning:Em.computed('capacityValue',function() {
+      return this.get('capacityValue') != 100;
+    }),
 
     // OBSERVABLES
 

+ 1 - 1
contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queue.js

@@ -142,7 +142,7 @@ App.QueueController = Ember.ObjectController.extend({
    * @return {App.Queue}
    */
   parentQueue: function () {
-    return this.store.getById('queue',this.get('content.parentPath'));
+    return this.store.getById('queue',this.get('content.parentPath').toLowerCase());
   }.property('content.parentPath'),
 
   /**

+ 38 - 7
contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queues.js

@@ -38,22 +38,38 @@ App.QueuesController = Ember.ArrayController.extend({
       }
       name = name || '';
       var newQueue,
-          existed = this.get('store.deletedQueues').findBy('path',parentPath+'.'+name);
+          store = this.get('store'),
+          existed = store.get('deletedQueues').findBy('path',[parentPath,name].join('.')),
+          leafQueueNames = store.getById('queue',parentPath.toLowerCase()).get('queuesArray'),
+          newInLeaf = Em.isEmpty(leafQueueNames),
+          totalLeafCapacity,
+          freeLeafCapacity;
 
       if (existed) {
-        newQueue = this.store.createFromDeleted(existed);
+        newQueue = store.createFromDeleted(existed);
       } else {
-        newQueue = this.store.createRecord('queue', {
+
+        if (!newInLeaf) {
+          totalLeafCapacity = leafQueueNames.reduce(function (capacity,queueName) {
+            return store.getById('queue', [parentPath,queueName].join('.').toLowerCase()).get('capacity') + capacity;
+          },0);
+
+          freeLeafCapacity = (totalLeafCapacity < 100) ? 100 - totalLeafCapacity : 0;
+        }
+
+        newQueue = store.createRecord('queue', {
           name:name,
           parentPath: parentPath,
           depth: parentPath.split('.').length,
-          isNewQueue:true
+          isNewQueue:true,
+          capacity: (newInLeaf) ? 100 : freeLeafCapacity,
+          maximum_capacity: (newInLeaf) ? 100: freeLeafCapacity
         });
         this.set('newQueue',newQueue);
       }
 
       if (name) {
-        this.get('store').saveAndUpdateQueue(newQueue,existed)
+        store.saveAndUpdateQueue(newQueue,existed)
           .then(Em.run.bind(this,'transitionToRoute','queue'))
           .then(Em.run.bind(this,'set','newQueue',null));
       } else {
@@ -68,6 +84,9 @@ App.QueuesController = Ember.ArrayController.extend({
       this.get('store').saveAndUpdateQueue(record, updates);
     },
     delQ:function (record) {
+      if (record.get('isNew')) {
+        this.set('newQueue',null);
+      }
       if (record.isCurrent) {
         this.transitionToRoute('queue',record.get('parentPath').toLowerCase())
           .then(Em.run.schedule('afterRender', function () {
@@ -99,6 +118,10 @@ App.QueuesController = Ember.ArrayController.extend({
     },
     clearAlert:function () {
       this.set('alertMessage',null);
+    },
+    toggleProperty:function (property,target) {
+      target = target || this;
+      target.toggleProperty(property);
     }
   },
 
@@ -216,7 +239,7 @@ App.QueuesController = Ember.ArrayController.extend({
           return +queue.get('capacity') + prev;
         },0);
 
-      leaf.setEach('overCapacity',total>100);
+      leaf.setEach('overCapacity',total != 100);
     }.bind(this));
   }.observes('content.length','content.@each.capacity'),
 
@@ -305,7 +328,7 @@ App.QueuesController = Ember.ArrayController.extend({
    * check if can save configs
    * @type {bool}
    */
-  canNotSave: cmp.any('hasOverCapacity', 'hasUncompetedAddings','hasNotValid'),
+  canNotSave: cmp.any('hasOverCapacity', 'hasUncompetedAddings','hasNotValid','hasNotValidLabels'),
 
   /**
    * List of not valid queues.
@@ -319,6 +342,14 @@ App.QueuesController = Ember.ArrayController.extend({
    */
   hasNotValid:cmp.notEmpty('notValid.[]'),
 
+  /**
+   * True if queues have not valid labels.
+   * @type {Boolean}
+   */
+  hasNotValidLabels:function(){
+    return this.get('content').anyBy('hasNotValidLabels',true);
+  }.property('content.@each.hasNotValidLabels'),
+
   /**
    * List of queues with excess of capacity
    * @type {Array}

+ 1 - 1
contrib/views/capacity-scheduler/src/main/resources/ui/app/helpers/timeAgo.js

@@ -31,7 +31,7 @@ Ember.Handlebars.registerHelper('timeAgo', function(property, options) {
       var value = Em._HandlebarsBoundView.prototype.normalizedValue.call(this);
 
       return function(value, options) {
-        return (value)?moment(value).fromNow(false):'';
+        return (moment.isMoment(value))?moment(value).fromNow(false):value;
       }.call(this, value, options);
     }
   });

+ 10 - 2
contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js

@@ -43,6 +43,8 @@ App.Scheduler = DS.Model.extend({
   maximum_applications: DS.attr('number', { defaultValue: 0 }),
   node_locality_delay: DS.attr('number', { defaultValue: 0 }),
   resource_calculator: DS.attr('string', { defaultValue: '' }),
+  queue_mappings: DS.attr('string'),
+  queue_mappings_override_enable: DS.attr('boolean'),
   isAnyDirty:Em.computed.alias('isDirty')
 });
 
@@ -57,7 +59,7 @@ App.Tag = DS.Model.extend({
     return this.get('tag') === this.get('store.current_tag');
   }.property('store.current_tag'),
   changed:function () {
-    return (this.get('tag').match(/version[1-9]+/))?moment(+this.get('tag').replace('version','')):'';
+    return (this.get('tag').match(/version[1-9][0-9][0-9]+/))?moment(+this.get('tag').replace('version','')):this.get('tag');
   }.property('tag')
 });
 
@@ -73,6 +75,10 @@ App.Queue = DS.Model.extend({
   sortBy:['name'],
   sortedLabels:Em.computed.sort('labels','sortBy'),
 
+  hasNotValidLabels: function(attribute){
+    return this.get('labels').anyBy('isValid',false);
+  }.property('labels.@each.isValid'),
+
   _accessAllLabels:DS.attr('boolean'),
   accessAllLabels:function (key,val) {
     var labels = this.get('store.nodeLabels').map(function(label) {
@@ -84,7 +90,7 @@ App.Queue = DS.Model.extend({
 
       if (this.get('_accessAllLabels')) {
           labels.forEach(function (lb) {
-            var containsByParent = (Em.isEmpty(this.get('parentPath')))?true:this.store.getById('queue',this.get('parentPath')).get('labels').findBy('name',lb.get('name'));
+            var containsByParent = (Em.isEmpty(this.get('parentPath')))?true:this.store.getById('queue',this.get('parentPath').toLowerCase()).get('labels').findBy('name',lb.get('name'));
             if (!this.get('labels').contains(lb) && !!containsByParent) {
               this.get('labels').pushObject(lb);
             }
@@ -157,6 +163,8 @@ App.Queue = DS.Model.extend({
     }));
   }.property('initialLabels', 'labels.[]', '_accessAllLabels'),
 
+  noCapacity:Em.computed.equal('capacity',0),
+
   name: DS.attr('string'),
   parentPath: DS.attr('string'),
   depth: DS.attr('number'),

+ 9 - 7
contrib/views/capacity-scheduler/src/main/resources/ui/app/serializers.js

@@ -106,7 +106,7 @@ App.SerializerMixin = Em.Mixin.create({
           ordering_policy:               props[base_path + ".ordering-policy"] || null,
           enable_size_based_weight:      props[base_path + ".ordering-policy.fair.enable-size-based-weight"] || null,
           default_node_label_expression: props[base_path + ".default-node-label-expression"] || null,
-          labelsEnabled: props.hasOwnProperty(labelsPath)
+          labelsEnabled:                 props.hasOwnProperty(labelsPath)
         };
 
     switch ((props.hasOwnProperty(labelsPath))?props[labelsPath]:'') {
@@ -141,10 +141,12 @@ App.SerializerMixin = Em.Mixin.create({
 
     var scheduler = [{
       id:                          'scheduler',
-      maximum_am_resource_percent: properties[this.PREFIX + ".maximum-am-resource-percent"]*100 || null, // convert to percent
-      maximum_applications:        properties[this.PREFIX + ".maximum-applications"] || null,
-      node_locality_delay:         properties[this.PREFIX + ".node-locality-delay"] || null,
-      resource_calculator:         properties[this.PREFIX + ".resource-calculator"] || null
+      maximum_am_resource_percent:    properties[this.PREFIX + ".maximum-am-resource-percent"]*100 || null, // convert to percent
+      maximum_applications:           properties[this.PREFIX + ".maximum-applications"] || null,
+      node_locality_delay:            properties[this.PREFIX + ".node-locality-delay"] || null,
+      resource_calculator:            properties[this.PREFIX + ".resource-calculator"] || null,
+      queue_mappings:                 properties[this.PREFIX + ".queue-mappings"] || null,
+      queue_mappings_override_enable: properties[this.PREFIX + ".queue-mappings-override.enable"] || null
     }];
     _recurseQueues(null, "root", 0, properties, queues, this.get('store'));
     this._setupLabels(properties,queues,labels,this.PREFIX);
@@ -185,6 +187,8 @@ App.SchedulerSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{
     json[this.PREFIX + ".maximum-applications"] = record.get('maximum_applications');
     json[this.PREFIX + ".node-locality-delay"] = record.get('node_locality_delay');
     json[this.PREFIX + ".resource-calculator"] = record.get('resource_calculator');
+    json[this.PREFIX + ".queue-mappings"] = record.get('queue_mappings') || null;
+    json[this.PREFIX + ".queue-mappings-override.enable"] = record.get('queue_mappings_override_enable');
 
     return json;
   }
@@ -267,10 +271,8 @@ App.QueueSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{
     }
 
     recordLabels.forEach(function (l) {
-      if (!record.get('store.nodeLabels').findBy('name',l.get('name')).notExist) {
         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');
-      }
     });
   }
 });

+ 25 - 3
contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js

@@ -89,7 +89,16 @@ function _fetchTagged(adapter, store, type, sinceToken) {
       store.didUpdateAll(type);
       return store.recordForId('scheduler','scheduler');
     }).then(function (scheduler) {
-      scheduler.setProperties(config.scheduler.objectAt(0));
+      var props = config.scheduler.objectAt(0);
+
+      scheduler.eachAttribute(function (attr,meta) {
+        if (meta.type === 'boolean') {
+          this.set(attr, (props[attr] === 'false' || !props[attr])?false:true);
+        } else {
+          this.set(attr, props[attr]);
+        }
+      },scheduler);
+
       scheduler.set('version',v);
     });
 
@@ -271,12 +280,25 @@ App.ApplicationStore = DS.Store.extend({
   },
 
   nodeLabels: function () {
-    var adapter = this.get('defaultAdapter');
+    var adapter = this.get('defaultAdapter'),
+        store = this,
+        promise = new Ember.RSVP.Promise(function(resolve, reject) {
+          adapter.getNodeLabels().then(function(data) {
+            store.set('isRmOffline',false);
+            resolve(data);
+          }, function() {
+            store.set('isRmOffline',true);
+            resolve([]);
+          });
+        });
+
     return Ember.ArrayProxy.extend(Ember.PromiseProxyMixin).create({
-      promise: adapter.getNodeLabels()
+      promise: promise
     });
   }.property(),
 
+  isRmOffline:false,
+
   isInitialized: Ember.computed.and('tag', 'clusterName'),
 
   markForRefresh:function () {

+ 1 - 2
contrib/views/capacity-scheduler/src/main/resources/ui/app/styles/application.less

@@ -574,7 +574,6 @@
             .labels-toggle {
               z-index: 1;
               margin-bottom: 5px;
-              display: inline-table;
 
               label {
                 span {
@@ -823,7 +822,7 @@
       top: 25px;
     }
     @media (min-width: @screen-lg-min) {
-      left: 40%;
+      left: 43.5%;
       top: -8px;
     }
     position: absolute;

+ 5 - 1
contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueContainer.hbs

@@ -65,7 +65,11 @@
           <div class="queue-capacity">
             {{#if label.isNotExist}}
               <span {{bind-attr class=":label label.overCapacity:label-danger:label-warning"}}>{{label.name}}</span>
-              <small>Label is not exist on cluster</small>
+              {{#if label.store.isRmOffline}}
+                <small>Unable to obtain information  about node label from the resource manager</small>
+              {{else}}
+                <small>Label is not exist on cluster</small>
+              {{/if}}
             {{else}}
               <span {{bind-attr class=":label label.overCapacity:label-danger:label-success"}}>{{label.name}}</span>
               <span {{bind-attr class=":label :label-default label.isDefault::hide"}}>default</span>

+ 4 - 0
contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueListItem.hbs

@@ -29,6 +29,10 @@
         <span class="label label-info">v{{queue.version}}</span>
     {{/if}}
 
+    {{#if queue.noCapacity}}
+      {{warn-badge}}
+    {{/if}}
+
     {{queue-badge q=queue class="badge pull-right"}}
 
     {{#if queue.isDeletedQueue}}

+ 2 - 2
contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/userGroupInput.hbs

@@ -28,6 +28,6 @@
   <div class="col-sm-8"> 
     {{input keyDown=noSpace disabledBinding="disabled" class="form-control input-sm" value=groups placeholder="Comma-separated list of groups"}} 
     <i class="fa fa-users form-control-feedback"></i>
-  </div> 
+    <div class="help-block pull-right"><small>leave blank to deny access for everyone</small></div>
+  </div>
 </div>
-

+ 63 - 0
contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/schedulerPanel.hbs

@@ -118,6 +118,69 @@
             </div>
           </div>
       {{/if}}
+      <div class="form-group">
+        {{tooltip-label
+          label='Queue Mappings'
+          class="col-xs-5 control-label"
+          message='This configuration specifies the mapping of user or group to aspecific queue. You can map a single user or a list of users to queues. Syntax: [u or g]:[name]:[queue_name][,next_mapping]*. Here, u or g indicates whether the mapping is for a user or group. The value is u for user and g for group. name indicates the user name or group name. To specify the user who has submitted the application, %user can be used. queue_name indicates the queue name for which the application has to be mapped. To specify queue name same as user name, %user can be used. To specify queue name same as the name of the primary group for which the user belongs to, %primary_group can be used.'
+        }}
+        {{#if isOperator}}
+          <div class="col-xs-7 control-value">
+            {{expandable-input value=scheduler.queue_mappings class="input-sm form-control input-expand"}}
+            {{#if schedulerDirtyFilelds.queue_mappings}}
+              <div class="btn-group btn-group-xs" >
+                  <a {{action 'rollbackProp' 'queue_mappings' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+              </div>
+            {{/if}}
+          </div>
+        {{else}}
+          <div class="col-xs-6">
+            {{#if scheduler.queue_mappings}}
+              <p class="form-control-static">{{scheduler.queue_mappings}}</p>
+            {{else}}
+              <p class="form-control-static">-</p>
+            {{/if}}
+          </div>
+        {{/if}}
+      </div>
+      <div class="form-group">
+        {{tooltip-label
+          class="col-xs-5 control-label"
+          label='Queue Mappings Override'
+          message='This function is used to specify whether the user specified queues can be overridden. This is a Boolean value and the default value is false.'
+        }}
+          {{#if isOperator}}
+          <div class="col-xs-7 control-value input-percent-wrap">
+            <div>
+              <div class="btn-group btn-group-sm pull-right">
+                <a href="#" {{action 'toggleProperty' 'queue_mappings_override_enable' scheduler}} class="btn btn-default">
+                  <i {{bind-attr class=":fa scheduler.queue_mappings_override_enable:fa-check-square-o:fa-square-o"}}></i>
+                  {{#if scheduler.queue_mappings_override_enable}}
+                    Enabled
+                  {{else}}
+                    Disabled
+                  {{/if}}
+                </a>
+              </div>
+            </div>
+            {{#if schedulerDirtyFilelds.queue_mappings_override_enable}}
+              <div class="btn-group btn-group-xs" >
+                <a {{action 'rollbackProp' 'queue_mappings_override_enable' scheduler}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a>
+              </div>
+            {{/if}}
+          </div>
+          {{else}}
+          <div class="col-xs-6">
+            <p class="form-control-static">
+            {{#if scheduler.queue_mappings_override_enable}}
+              Enabled
+            {{else}}
+              Disabled
+            {{/if}}
+            </p>
+          </div>
+          {{/if}}
+      </div>
     </form>
   </div>
 </div>

+ 1 - 1
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.4.0/CS_1/'
+        public: '/usr/lib/ambari-server/web/views-debug/CAPACITY-SCHEDULER/0.4.0/AUTO_CS_INSTANCE/'
 

+ 51 - 0
contrib/views/capacity-scheduler/src/main/resources/ui/test/unit/controllers/queue_test.js

@@ -0,0 +1,51 @@
+/**
+ * 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.
+ */
+
+moduleFor('controller:queue', 'App.QueueController',
+  {
+  needs:[ 'controller:queues' ],
+    teardown:function (container) {
+      App.reset();
+    }
+  },
+  function (container) {
+    container.register('model:queue', App.Queue);
+    container.register('model:label', App.Label);
+    container.register('store:main', App.ApplicationStore);
+    container.register('adapter:application', DS.FixtureAdapter);
+    container.register('serializer:application', DS.RESTSerializer);
+  }
+);
+
+test('can get parentQueue',function () {
+  var controller = this.subject(),
+      store = this.container.lookup('store:main'),
+      queues;
+
+
+  Em.run(function() {
+    queues = store.pushMany('queue',[
+      {id:'root.a',parentPath:'root',path:'root.A'},
+      {id:'root.a.b',parentPath:'root.a',path:'root.A.B'}
+    ]);
+    controller.set('store', store );
+    controller.set('model', queues.objectAt(1) );
+  });
+
+  deepEqual(controller.get('parentQueue'),queues.objectAt(0));
+});

+ 2 - 2
contrib/views/capacity-scheduler/src/main/resources/view.xml

@@ -17,7 +17,7 @@
 <view>
     <name>CAPACITY-SCHEDULER</name>
     <label>Capacity Scheduler</label>
-    <version>0.4.0</version>
+    <version>1.0.0</version>
 
     <min-ambari-version>2.1.*</min-ambari-version>
 
@@ -48,7 +48,7 @@
         <masked>true</masked>
     </parameter>
 
-    <resource>
+  <resource>
       <name>scheduler</name>
       <service-class>org.apache.ambari.view.capacityscheduler.CapacitySchedulerService</service-class>
     </resource>

+ 131 - 0
contrib/views/capacity-scheduler/src/test/java/org/apache/ambari/view/capacityscheduler/PropertyValidatorTest.java

@@ -0,0 +1,131 @@
+/**
+ * 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;
+
+import org.apache.ambari.view.ViewInstanceDefinition;
+import org.apache.ambari.view.validation.Validator;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.*;
+
+public class PropertyValidatorTest {
+  @Test
+  public void testValidatePropertyOk() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("http://hostname.com:8080/api/v1/clusters/Cluster");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertTrue(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyHttps() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("https://hostname.com:8080/api/v1/clusters/Cluster");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertTrue(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyNoPort() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("http://hostname.com/api/v1/clusters/Cluster");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyNoProtocol() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("hostname.com:8080/api/v1/clusters/Cluster");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyNoCluster() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("http://hostname.com:8080");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyNoClusterName() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("http://hostname.com:8080/api/v1/clusters/");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyMisspell() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("http://hostname.com:8080/api/v1/clAsters/MyCluster");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  @Test
+  public void testValidatePropertyOnlyHostname() throws Exception {
+    ViewInstanceDefinition instanceDefinition =
+        getViewInstanceDefinition("hostname.com");
+    PropertyValidator propertyValidator = new PropertyValidator();
+
+    assertFalse(propertyValidator.validateProperty(
+        PropertyValidator.AMBARI_SERVER_URL, instanceDefinition,
+        Validator.ValidationContext.PRE_CREATE).isValid());
+  }
+
+  private ViewInstanceDefinition getViewInstanceDefinition(String ambariServerUrl) {
+    ViewInstanceDefinition instanceDefinition = createNiceMock(ViewInstanceDefinition.class);
+    Map<String, String> map = new HashMap<String, String>();
+    expect(instanceDefinition.getPropertyMap()).andReturn(map).anyTimes();
+    replay(instanceDefinition);
+
+    map.put(PropertyValidator.AMBARI_SERVER_URL, ambariServerUrl);
+    return instanceDefinition;
+  }
+}

+ 6 - 1
contrib/views/utils/pom.xml

@@ -39,6 +39,11 @@
       <artifactId>hadoop-common</artifactId>
       <version>${hadoop-version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.apache.hadoop</groupId>
+      <artifactId>hadoop-yarn-common</artifactId>
+      <version>${hadoop-version}</version>
+    </dependency>
     <dependency>
       <groupId>junit</groupId>
       <artifactId>junit</artifactId>
@@ -115,7 +120,7 @@
 
   <properties>
     <ambari.dir>${project.parent.parent.parent.basedir}</ambari.dir>
-    <hadoop-version>2.2.0</hadoop-version>
+    <hadoop-version>2.6.0</hadoop-version>
   </properties>
   <build>
   </build>

+ 116 - 0
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ViewUserLocal.java

@@ -0,0 +1,116 @@
+/**
+ * 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.utils;
+
+import org.apache.ambari.view.ViewContext;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Manages end user specific objects.
+ * Ensures that the instance of specific class is exists only in one instance for
+ * specific user of specific instance.
+ * @param <T> user-local class
+ */
+public class ViewUserLocal<T> {
+  private static Map<Class, Map<String, Object>> viewSingletonObjects = new HashMap<Class, Map<String, Object>>();
+  private final Class<T> tClass;
+
+  public ViewUserLocal(Class<T> tClass) {
+    this.tClass = tClass;
+  }
+
+  /**
+   * Initial value of user-local class. Value can be set either by initialValue() on first get() call,
+   * or directly by calling set() method. Default initial value is null, it can be changed by overriding
+   * this method.
+   * @param context initial value usually based on user properties provided by View Context
+   * @return initial value of user-local variable
+   */
+  protected synchronized T initialValue(ViewContext context) {
+    return null;
+  }
+
+  /**
+   * Returns user-local instance.
+   * If instance of class is not present yet for user, calls initialValue to create it.
+   * @param context View context that provides instance and user names.
+   * @return instance
+   */
+  public T get(ViewContext context) {
+    if (!viewSingletonObjects.containsKey(tClass)) {
+      viewSingletonObjects.put(tClass, new HashMap<String, Object>());
+    }
+
+    Map<String, Object> instances = viewSingletonObjects.get(tClass);
+
+    if (!instances.containsKey(getTagName(context))) {
+      instances.put(getTagName(context), initialValue(context));
+    }
+    return (T) instances.get(getTagName(context));
+  }
+
+  /**
+   * Method for directly setting user-local singleton instances.
+   * @param obj new variable value for current user
+   * @param context ViewContext that provides username and instance name
+   */
+  public void set(T obj, ViewContext context) {
+    if (!viewSingletonObjects.containsKey(tClass)) {
+      viewSingletonObjects.put(tClass, new HashMap<String, Object>());
+    }
+
+    Map<String, Object> instances = viewSingletonObjects.get(tClass);
+    instances.put(getTagName(context), obj);
+  }
+
+  /**
+   * Returns unique key for Map to store a user-local variable.
+   * @param context ViewContext
+   * @return Unique identifier of pair instance-user.
+   */
+  private String getTagName(ViewContext context) {
+    if (context == null) {
+      return "<null>";
+    }
+    return String.format("%s:%s", context.getInstanceName(), context.getUsername());
+  }
+
+  /**
+   * Method for testing purposes, intended to clear the cached user-local instances.
+   * Method should not normally be called from production code.
+   * @param tClass classname instances of which should be dropped
+   */
+  public static void dropAllConnections(Class tClass) {
+    Map<String, Object> instances = viewSingletonObjects.get(tClass);
+    if (instances != null) {
+      viewSingletonObjects.get(tClass).clear();
+    }
+  }
+
+  /**
+   * Method for testing purposes, intended to clear the cached user-local instances.
+   * Drops all classes of user-local variables.
+   * Method should not normally be called from production code.
+   */
+  public static void dropAllConnections() {
+    viewSingletonObjects.clear();
+  }
+}

+ 13 - 1
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/AmbariApi.java

@@ -18,7 +18,6 @@
 
 package org.apache.ambari.view.utils.ambari;
 
-import org.apache.ambari.view.AmbariStreamProvider;
 import org.apache.ambari.view.URLStreamProvider;
 import org.apache.ambari.view.ViewContext;
 import org.apache.ambari.view.cluster.Cluster;
@@ -47,6 +46,8 @@ public class AmbariApi {
 
   private Cluster cluster;
   private ViewContext context;
+  private Services services;
+
   private String remoteUrlCluster;
   private String remoteUsername;
   private String remotePassword;
@@ -285,4 +286,15 @@ public class AmbariApi {
     urlStreamProviderBasicAuth.setRequestedBy(requestedBy);
     return urlStreamProviderBasicAuth;
   }
+
+  /**
+   * Provides access to service-specific utilities
+   * @return object with service-specific methods
+   */
+  public Services getServices() {
+    if (services == null) {
+      services = new Services(this, context);
+    }
+    return services;
+  }
 }

+ 131 - 0
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/ambari/Services.java

@@ -0,0 +1,131 @@
+/**
+ * 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.utils.ambari;
+
+import org.apache.ambari.view.ViewContext;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+/**
+ * Utilities for specific Hadoop services and util functions for them
+ */
+public class Services {
+  public static final String HTTPS_ONLY = "HTTPS_ONLY";
+  public static final String HTTP_ONLY = "HTTP_ONLY";
+  public static final String YARN_SITE = "yarn-site";
+  public static final String YARN_HTTP_POLICY = "yarn.http.policy";
+
+  private final AmbariApi ambariApi;
+  private ViewContext context;
+
+  protected final static Logger LOG = LoggerFactory.getLogger(Services.class);
+
+  public Services(AmbariApi ambariApi, ViewContext context) {
+    this.ambariApi = ambariApi;
+    this.context = context;
+  }
+
+  /**
+   * Returns URL to Resource Manager.
+   * If cluster associated, returns HTTP or HTTPS address based on "yarn.http.policy" property value.
+   * If not associated, retrieves RM URL from view instance properties by "yarn.resourcemanager.url" property.
+   * @return url of RM
+   */
+  public String getRMUrl() {
+    String url;
+
+    if (ambariApi.isClusterAssociated()) {
+      String protocol;
+
+      String httpPolicy = ambariApi.getCluster().getConfigurationValue(YARN_SITE, YARN_HTTP_POLICY);
+      if (httpPolicy.equals(HTTPS_ONLY)) {
+        protocol = "https";
+        url = ambariApi.getCluster().getConfigurationValue(YARN_SITE, "yarn.resourcemanager.webapp.https.address");
+
+      } else {
+        protocol = "http";
+        url = ambariApi.getCluster().getConfigurationValue(YARN_SITE, "yarn.resourcemanager.webapp.address");
+        if (!httpPolicy.equals(HTTP_ONLY))
+          LOG.error(String.format("RA030 Unknown value %s of yarn-site/yarn.http.policy. HTTP_ONLY assumed.", httpPolicy));
+      }
+
+      url = addProtocolIfMissing(url, protocol);
+    } else {
+      url = context.getProperties().get("yarn.resourcemanager.url");
+      if (!hasProtocol(url)) {
+        throw new AmbariApiException(
+            "RA070 View is not cluster associated. Resource Manager URL should contain protocol.");
+      }
+    }
+    return url;
+  }
+
+  /**
+   * Returns URL to WebHCat in format like http://<hostname>:<port>/templeton/v1
+   * @return url to WebHCat
+   */
+  public String getWebHCatURL() {
+    String host = null;
+
+    if (ambariApi.isClusterAssociated()) {
+      List<String> hiveServerHosts = ambariApi.getHostsWithComponent("WEBHCAT_SERVER");
+
+      if (!hiveServerHosts.isEmpty()) {
+        host = hiveServerHosts.get(0);
+        LOG.info("WEBHCAT_SERVER component was found on host " + host);
+      } else {
+        LOG.warn("No host was found with WEBHCAT_SERVER component. Using hive.host property to get hostname.");
+      }
+    }
+
+    if (host == null) {
+      host = context.getProperties().get("webhcat.hostname");
+      if (host == null || host.isEmpty()) {
+        throw new AmbariApiException(
+            "RA080 Can't determine WebHCat hostname neither by associated cluster nor by webhcat.hostname property.");
+      }
+    }
+
+    String port = context.getProperties().get("webhcat.port");
+    if (port == null || port.isEmpty()) {
+      throw new AmbariApiException(
+          "RA090 Can't determine WebHCat port neither by associated cluster nor by webhcat.port property.");
+    }
+
+    return String.format("http://%s:%s/templeton/v1", host, port);
+  }
+
+  public static String addProtocolIfMissing(String url, String protocol) throws AmbariApiException {
+    if (!hasProtocol(url)) {
+      url = protocol + "://" + url;
+    }
+    return url;
+  }
+
+  /**
+   * Checks if URL has the protocol
+   * @param url url
+   * @return true if protocol is present
+   */
+  public static boolean hasProtocol(String url) {
+    return url.matches("^[^:]+://.*$");
+  }
+}

+ 30 - 2
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/ConfigurationBuilder.java

@@ -30,6 +30,7 @@ import org.slf4j.LoggerFactory;
 
 import java.net.URI;
 import java.net.URISyntaxException;
+import java.util.Map;
 
 /**
  * Builds the Configuration of HDFS based on ViewContext.
@@ -63,6 +64,8 @@ public class ConfigurationBuilder {
   private Configuration conf = new Configuration();
   private ViewContext context;
   private AmbariApi ambariApi = null;
+  private AuthConfigurationBuilder authParamsBuilder;
+  private Map<String, String> authParams;
 
   /**
    * Constructor of ConfigurationBuilder based on ViewContext
@@ -70,7 +73,8 @@ public class ConfigurationBuilder {
    */
   public ConfigurationBuilder(ViewContext context) {
     this.context = context;
-    ambariApi = new AmbariApi(context);
+    this.ambariApi = new AmbariApi(context);
+    this.authParamsBuilder = new AuthConfigurationBuilder(context);
   }
 
   private void parseProperties() throws HdfsApiException {
@@ -180,13 +184,25 @@ public class ConfigurationBuilder {
     return defaultFs;
   }
 
+  /**
+   * Set properties relevant to authentication parameters to HDFS Configuration
+   * @param authParams list of auth params of View
+   */
+  public void setAuthParams(Map<String, String> authParams) {
+    String auth = authParams.get("auth");
+    if (auth != null) {
+      conf.set("hadoop.security.authentication", auth);
+    }
+  }
+
   /**
    * Build the HDFS configuration
    * @return configured HDFS Configuration object
    * @throws HdfsApiException if configuration parsing failed
    */
-  public Configuration build() throws HdfsApiException {
+  public Configuration buildConfig() throws HdfsApiException {
     parseProperties();
+    setAuthParams(buildAuthenticationConfig());
 
     conf.set("fs.hdfs.impl", DistributedFileSystem.class.getName());
     conf.set("fs.webhdfs.impl", WebHdfsFileSystem.class.getName());
@@ -194,4 +210,16 @@ public class ConfigurationBuilder {
 
     return conf;
   }
+
+  /**
+   * Builds the authentication configuration
+   * @return map of HDFS auth params for view
+   * @throws HdfsApiException
+   */
+  public Map<String, String> buildAuthenticationConfig() throws HdfsApiException {
+    if (authParams == null) {
+      authParams = authParamsBuilder.build();
+    }
+    return authParams;
+  }
 }

+ 10 - 5
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/HdfsApi.java

@@ -25,12 +25,19 @@ import org.apache.hadoop.fs.permission.FsPermission;
 
 import java.io.FileNotFoundException;
 import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
 import java.security.PrivilegedExceptionAction;
 import java.util.*;
 
+import org.apache.hadoop.hdfs.DistributedFileSystem;
+import org.apache.hadoop.hdfs.web.WebHdfsFileSystem;
+import org.apache.hadoop.security.Credentials;
 import org.apache.hadoop.security.UserGroupInformation;
 import org.json.simple.JSONArray;
 
+import javax.security.auth.Subject;
+
 /**
  * Hdfs Business Delegate
  */
@@ -44,15 +51,13 @@ private final Configuration conf;
   /**
    * Constructor
    * @param configurationBuilder hdfs configuration builder
-   * @param authParams map of parameters
    * @throws IOException
    * @throws InterruptedException
    */
-  public HdfsApi(ConfigurationBuilder configurationBuilder, String username, AuthConfigurationBuilder authParams) throws IOException,
+  public HdfsApi(ConfigurationBuilder configurationBuilder, String username) throws IOException,
       InterruptedException, HdfsApiException {
-    this.authParams = authParams.build();
-    conf = configurationBuilder.build();
-
+    this.authParams = configurationBuilder.buildAuthenticationConfig();
+    conf = configurationBuilder.buildConfig();
     ugi = UserGroupInformation.createProxyUser(username, getProxyUser());
 
     fs = ugi.doAs(new PrivilegedExceptionAction<FileSystem>() {

+ 4 - 1
contrib/views/utils/src/main/java/org/apache/ambari/view/utils/hdfs/HdfsUtil.java

@@ -119,8 +119,11 @@ public class HdfsUtil {
     ConfigurationBuilder configurationBuilder = new ConfigurationBuilder(context);
     AuthConfigurationBuilder authConfigurationBuilder = new AuthConfigurationBuilder(context);
 
+    Map<String, String> authParams = authConfigurationBuilder.build();
+    configurationBuilder.setAuthParams(authParams);
+
     try {
-      api = new HdfsApi(configurationBuilder, getHdfsUsername(context), authConfigurationBuilder);
+      api = new HdfsApi(configurationBuilder, getHdfsUsername(context));
       LOG.info("HdfsApi connected OK");
     } catch (IOException e) {
       String message = "HDFS040 Couldn't open connection to HDFS";

+ 98 - 0
contrib/views/utils/src/test/java/org/apache/ambari/view/utils/ViewUserLocalTest.java

@@ -0,0 +1,98 @@
+/**
+ * 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.utils;
+
+import org.apache.ambari.view.ViewContext;
+import org.junit.Test;
+
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.*;
+
+public class ViewUserLocalTest {
+  @Test
+  public void testDifferentUsers() throws Exception {
+    ViewContext viewContext = createNiceMock(ViewContext.class);
+    expect(viewContext.getInstanceName()).andReturn("INSTANCE1").anyTimes();
+    expect(viewContext.getUsername()).andReturn("luke").anyTimes();
+
+    ViewContext viewContext2 = createNiceMock(ViewContext.class);
+    expect(viewContext2.getInstanceName()).andReturn("INSTANCE1").anyTimes();
+    expect(viewContext2.getUsername()).andReturn("leia").anyTimes();
+    replay(viewContext, viewContext2);
+
+    ViewUserLocal<Object> test = new ViewUserLocal<Object>(Object.class) {
+      @Override
+      protected synchronized Object initialValue(ViewContext context) {
+        return new Object();
+      }
+    };
+
+    Object obj1 = test.get(viewContext);
+    Object obj2 = test.get(viewContext2);
+    assertNotSame(obj1, obj2);
+  }
+
+  @Test
+  public void testDifferentInstances() throws Exception {
+    ViewContext viewContext = createNiceMock(ViewContext.class);
+    expect(viewContext.getInstanceName()).andReturn("INSTANCE1").anyTimes();
+    expect(viewContext.getUsername()).andReturn("luke").anyTimes();
+
+    ViewContext viewContext2 = createNiceMock(ViewContext.class);
+    expect(viewContext2.getInstanceName()).andReturn("INSTANCE2").anyTimes();
+    expect(viewContext2.getUsername()).andReturn("luke").anyTimes();
+    replay(viewContext, viewContext2);
+
+    ViewUserLocal<Object> test = new ViewUserLocal<Object>(Object.class) {
+      @Override
+      protected synchronized Object initialValue(ViewContext context) {
+        return new Object();
+      }
+    };
+
+    Object obj1 = test.get(viewContext);
+    Object obj2 = test.get(viewContext2);
+    assertNotSame(obj1, obj2);
+  }
+
+  @Test
+  public void testSameUsers() throws Exception {
+    ViewContext viewContext = createNiceMock(ViewContext.class);
+    expect(viewContext.getInstanceName()).andReturn("INSTANCE1").anyTimes();
+    expect(viewContext.getUsername()).andReturn("luke").anyTimes();
+
+    ViewContext viewContext2 = createNiceMock(ViewContext.class);
+    expect(viewContext2.getInstanceName()).andReturn("INSTANCE1").anyTimes();
+    expect(viewContext2.getUsername()).andReturn("luke").anyTimes();
+    replay(viewContext, viewContext2);
+
+    ViewUserLocal<Object> test = new ViewUserLocal<Object>(Object.class) {
+      @Override
+      protected synchronized Object initialValue(ViewContext context) {
+        return new Object();
+      }
+    };
+
+    Object obj1 = test.get(viewContext);
+    Object obj2 = test.get(viewContext2);
+    assertSame(obj1, obj2);
+  }
+}

+ 0 - 2
contrib/views/utils/src/test/java/org/apache/ambari/view/utils/hdfs/ConfigurationBuilderTest.java

@@ -20,8 +20,6 @@ package org.apache.ambari.view.utils.hdfs;
 
 import org.junit.Test;
 
-import java.net.URI;
-
 import static org.junit.Assert.*;
 
 public class ConfigurationBuilderTest {