Repository: ambari Updated Branches: refs/heads/branch-2.5 4334b3e73 -> 6a89de639
AMBARI-17602. Capacity Scheduler View - Fetching current RM configuration of queues and preemption implementation (Akhil PB via pallavkul) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/6a89de63 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/6a89de63 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/6a89de63 Branch: refs/heads/branch-2.5 Commit: 6a89de639bb071b987997ebc315bf2aedd703558 Parents: 4334b3e Author: Pallav Kulshreshtha <[email protected]> Authored: Thu Aug 25 14:55:31 2016 +0530 Committer: Pallav Kulshreshtha <[email protected]> Committed: Thu Sep 8 22:51:13 2016 +0530 ---------------------------------------------------------------------- .../capacityscheduler/ConfigurationService.java | 25 +++ .../src/main/resources/ui/app/adapters.js | 6 + .../ui/app/components/capacityInput.js | 75 +++++++ .../ui/app/components/displayNodeLabels.js | 8 +- .../ui/app/components/editLabelCapacity.js | 3 +- .../resources/ui/app/components/queueSummary.js | 27 ++- .../resources/ui/app/controllers/capsched.js | 11 +- .../resources/ui/app/controllers/editqueue.js | 225 ++++++++++++++++--- .../resources/ui/app/controllers/queuesconf.js | 101 +++++++-- .../src/main/resources/ui/app/models/queue.js | 25 ++- .../src/main/resources/ui/app/router.js | 21 +- .../src/main/resources/ui/app/serializers.js | 9 +- .../src/main/resources/ui/app/store.js | 76 +++++++ .../resources/ui/app/styles/application.less | 69 ++++-- .../src/main/resources/ui/app/templates.js | 1 + .../resources/ui/app/templates/capsched.hbs | 13 +- .../capsched/partials/labelCapacity.hbs | 46 +++- .../templates/capsched/partials/preemption.hbs | 75 +++++++ .../capsched/partials/queueCapacity.hbs | 18 +- .../capsched/partials/queueResources.hbs | 48 ++-- .../ui/app/templates/capsched/queuesconf.hbs | 13 +- .../templates/capsched/queuesconf/editqueue.hbs | 4 + .../ui/app/templates/capsched/trace.hbs | 21 ++ .../templates/components/editLabelCapacity.hbs | 30 +-- .../templates/components/editQueueCapacity.hbs | 4 +- .../templates/components/labelCapacityBar.hbs | 4 +- .../app/templates/components/queueMapping.hbs | 29 ++- .../app/templates/components/queueSummary.hbs | 22 +- .../main/resources/ui/app/views/editqueue.js | 12 +- 29 files changed, 877 insertions(+), 144 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/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 e5f5cb5..a20893a 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 @@ -107,6 +107,7 @@ public class ConfigurationService { private static final String CONFIGURATION_URL_BY_TAG = "configurations?type=capacity-scheduler&tag=%s"; private static final String RM_GET_NODE_LABEL_URL = "%s/ws/v1/cluster/get-node-labels"; + private static final String RM_GET_SCHEDULER_CONFIG = "%s/ws/v1/cluster/scheduler"; // ================================================================================ // Privilege Reading @@ -283,6 +284,30 @@ public class ConfigurationService { return response; } + /** + * Gets scheduler info from RM + * + * @return scheduler info + */ + @GET + @Produces(MediaType.APPLICATION_JSON) + @Path("/rmCurrentConfig") + public Response getRmSchedulerConfig() { + try { + String url = String.format(RM_GET_SCHEDULER_CONFIG, getRMUrl()); + + InputStream rmResponse = context.getURLStreamProvider().readFrom( + url, "GET", (String) null, new HashMap<String, String>()); + String result = IOUtils.toString(rmResponse); + return Response.ok(result).build(); + } catch (ConnectException ex) { + throw new ServiceFormattedException("Connection to Resource Manager refused", ex); + } catch (WebApplicationException ex) { + throw ex; + } catch (Exception ex) { + throw new ServiceFormattedException(ex.getMessage(), ex); + } + } /** * Checks if the user is an operator. http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js index 770db51..f300b5b 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/adapters.js @@ -255,6 +255,12 @@ App.QueueAdapter = DS.Adapter.extend({ _ajax(uri,'GET').then(function(data) { var parsedData = JSON.parse(data), labels; + if (parsedData !== null) { + store.set('isNodeLabelsEnabledByRM', true); + } else { + store.set('isNodeLabelsEnabledByRM', false); + } + if (stackVersion >= 2.5) { if (parsedData && Em.isArray(parsedData.nodeLabelInfo)) { labels = parsedData.nodeLabelInfo; http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js index bf74af6..b86719f 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/capacityInput.js @@ -116,3 +116,78 @@ App.MaxCapacityInputComponent = App.CapacityInputComponent.extend({ }.observes('queue.maximum_capacity','queue.capacity') }); + +App.DecimalCapacityInputComponent = Ember.TextField.extend({ + classNames: ['form-control'], + maxVal: null, + queue: null, + + initVal: function() { + this.set('value', parseFloat(this.get('value'))); + }.on('init'), + + keyDown: function(evt) { + var newChar, val = this.get('value') || 0; + val = val.toString(); + + if ((evt.keyCode > 64 && evt.keyCode < 91) || + (evt.keyCode > 185 && evt.keyCode < 190) || + (evt.keyCode > 190 && evt.keyCode < 193) || + (evt.keyCode > 218 && evt.keyCode < 223)) { + return false; + } + + if (evt.keyCode === 190) { + return true; + } + + if (evt.keyCode > 95 && evt.keyCode < 106) { + newChar = (evt.keyCode - 96).toString(); + } else { + newChar = String.fromCharCode(evt.keyCode); + } + + if (newChar.match(/[0-9]/)) { + val = val.substring(0, evt.target.selectionStart) + newChar + val.substring(evt.target.selectionEnd); + } + + //Restricting three decimal places, allow decimal precision=2 + if (/^\d+\.\d{3}$/.test(val)) { + return false; + } + + return parseFloat(val) <= 100; + }, + + valueDidChange: function() { + var val = this.get('value'), + maxVal = this.get('maxVal'); + if (/^\d+(\.\d{1,2})?$/.test(val)) { + this.set('value', (parseFloat(val) > maxVal)? parseFloat(maxVal) : parseFloat(val)); + } + }.observes('value').on('change') + +}); + +App.DecimalMaxcapacityInputComponent = App.DecimalCapacityInputComponent.extend({ + checkInvalid: function(c, o) { + var queue = this.get('queue'), + max_capacity = +queue.get('maximum_capacity'), + capacity = +queue.get('capacity'); + + if (queue.get('maximum_capacity') === null) { + return; + } + + if (o === 'queue.capacity' && max_capacity < capacity) { + return queue.set('maximum_capacity', capacity); + } + + if (max_capacity < capacity && queue.get('isDirty')) { + queue.get('errors').add('maximum_capacity', 'Maximum must be greater than or equal to capacity'); + } else { + queue.get('errors').remove('maximum_capacity'); + } + + }.observes('queue.capacity', 'queue.maximum_capacity') +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/displayNodeLabels.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/displayNodeLabels.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/displayNodeLabels.js index 9b31e57..d91942d 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/displayNodeLabels.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/displayNodeLabels.js @@ -30,12 +30,16 @@ App.DisplayRootLabelComponent = Ember.Component.extend({ enableRootLabel: function(label) { var store = this.get('label.store'), rootQ = store.getById('queue', 'root'); - rootQ.addQueueNodeLabel(this.get('label')); + rootQ.recursiveAddChildQueueLabels(label); + var rootLabel = store.getById('label', ['root', label.get('name')].join('.')); + rootLabel.setCapacity(100); }, disableRootLabel: function(label) { var store = this.get('label.store'), rootQ = store.getById('queue', 'root'); - rootQ.recursiveRemoveChildQueueLabels(this.get('label')); + rootQ.recursiveRemoveChildQueueLabels(label); + var rootLabel = store.getById('label', ['root', label.get('name')].join('.')); + rootLabel.setCapacity(0); } }, http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/editLabelCapacity.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/editLabelCapacity.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/editLabelCapacity.js index 7bb3f82..ed22c2b 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/editLabelCapacity.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/editLabelCapacity.js @@ -33,10 +33,9 @@ App.EditLabelCapacityComponent = Ember.Component.extend({ } }, enableAccess: function() { - this.get('queue').addQueueNodeLabel(this.get('label')); + this.get('queue').recursiveAddChildQueueLabels(this.get('label')); }, disableAccess: function() { - this.get('label').set('capacity', 0); this.get('queue').recursiveRemoveChildQueueLabels(this.get('label')); } }, http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueSummary.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueSummary.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueSummary.js index bfcd4b6..c15f100 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueSummary.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/components/queueSummary.js @@ -20,13 +20,23 @@ var _runState = 'RUNNING'; var _stopState = 'STOPPED'; - - var _notStartedState = 'NOT STARTED'; + var _notStartedState = 'NOT SAVED'; App.QueueSummaryComponent = Ember.Component.extend({ layoutName: 'components/queueSummary', queue: null, allQueues: null, + precision: 2, + queuesNeedRefresh: null, + + isQueueNeedRefreshOrRestart: function() { + var qsNeedRefresh = this.get('queuesNeedRefresh'), + qq = this.get('queue'); + if (qsNeedRefresh && qsNeedRefresh.findBy('path', qq.get('path'))) { + return true; + } + return false; + }.property('queuesNeedRefresh.[]', 'queue'), isRunningState: function() { return this.get('queue.state') === _runState || this.get('queue.state') === null; @@ -44,7 +54,7 @@ qStateColor: function() { if (this.get('queue.isNewQueue')) { - return 'text-info'; + return 'text-danger'; } else if (this.get('isRunningState')) { return 'text-success'; } else { @@ -56,6 +66,10 @@ return this.get('queue').changedAttributes().hasOwnProperty('state'); }.property('queue.state'), + isNewQueue: function() { + return this.get('queue.isNewQueue'); + }.property('queue.isNewQueue'), + effectiveCapacity: function() { var currentQ = this.get('queue'), allQueues = this.get('allQueues'), @@ -64,8 +78,9 @@ effectiveCapacityRatio *= (currentQ.get('capacity') / 100); currentQ = allQueues.findBy('id', currentQ.get('parentPath').toLowerCase()) || null; } - var effectiveCapacityPercent = effectiveCapacityRatio * 100; - this.get('queue').set('absolute_capacity', effectiveCapacityPercent || 0); - return effectiveCapacityPercent; + var effectiveCapacityPercent = effectiveCapacityRatio * 100, + absoluteCapacity = parseFloat(parseFloat(effectiveCapacityPercent).toFixed(this.get('precision'))); + this.get('queue').set('absolute_capacity', absoluteCapacity || 0); + return absoluteCapacity; }.property('queue.capacity', '[email protected]', 'allQueues.length') }); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/capsched.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/capsched.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/capsched.js index 545d348..447cae0 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/capsched.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/capsched.js @@ -50,6 +50,15 @@ App.CapschedController = Ember.Controller.extend({ sortedTags: Ember.computed.sort('tags', function(a, b){ return (+a.id > +b.id)? (+a.id < +b.id)? 0 : -1 : 1; - }) + }), + showSpinner: false, + + startSpinner: function() { + this.set('showSpinner', true); + }, + + stopSpinner: function() { + this.set('showSpinner', false); + } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/editqueue.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/editqueue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/editqueue.js index f42d2e4..dd46b2c 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/editqueue.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/editqueue.js @@ -28,6 +28,8 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ scheduler: Ember.computed.alias('controllers.capsched.content'), allQueues: Ember.computed.alias('controllers.capsched.queues'), isNodeLabelsEnabledByRM: Ember.computed.alias('store.isNodeLabelsEnabledByRM'), + isRmOffline: Ember.computed.alias('store.isRmOffline'), + precision: 2, isRangerEnabledForYarn: function() { var isRanger = this.get('controllers.capsched.isRangerEnabledForYarn'); @@ -77,7 +79,7 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ originalQName = this.get('content.name'), qParentPath = this.get('content.parentPath'), qPath = [qParentPath, qName].join('.'), - qAlreadyExists = this.store.hasRecordForId('queue', qPath.toLowerCase()); + qAlreadyExists = (this.get('allQueues').findBy('name', qName))? true : false; if (Ember.isBlank(qName)) { this.set('isInvalidQName', true); this.set('invalidQNameMessage', 'Enter queue name'); @@ -156,24 +158,41 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ /** * Returns maximum applications for a queue if defined, - * else the inherited value (for all queues) + * else the inherited value */ maximumApplications: function(key, val) { - if (arguments.length > 1) { - if (val !== this.get('scheduler.maximum_applications')) { - this.set('content.maximum_applications', val); - } else { - this.set('content.maximum_applications', null); - } - } - var schedulerMaxApps = this.get('scheduler.maximum_applications'), - absoluteCapacity = this.get('content.absolute_capacity'); + if (arguments.length > 1) { + this.set('content.maximum_applications', val); + } + var queueMaxApps = this.get('content.maximum_applications'); + if (queueMaxApps) { + return queueMaxApps; + } else { + return this.getInheritedMaximumApplications(); + } + }.property('content.maximum_applications', 'scheduler.maximum_applications'), + + isMaximumApplicationsInherited: function() { if (this.get('content.maximum_applications')) { - return this.get('content.maximum_applications'); + return false; } else { - return Math.round(schedulerMaxApps * (absoluteCapacity / 100)); + return true; } - }.property('content.maximum_applications', 'content.absolute_capacity', 'scheduler.maximum_applications'), + }.property('content.maximum_applications'), + + /** + * Returns inherited maximum applications for a queue + */ + getInheritedMaximumApplications: function() { + var parentQ = this.store.getById('queue', this.get('content.parentPath').toLowerCase()); + while (parentQ !== null) { + if (parentQ.get('maximum_applications')) { + return parentQ.get('maximum_applications'); + } + parentQ = this.store.getById('queue', parentQ.get('parentPath').toLowerCase()); + } + return this.get('scheduler.maximum_applications'); + }, /** * Returns maximum AM resource percent for a queue if defined, @@ -181,20 +200,37 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ */ maximumAMResourcePercent: function(key, val) { if (arguments.length > 1) { - if (val !== this.get('scheduler.maximum_am_resource_percent')) { - this.set('content.maximum_am_resource_percent', val); - } else { - this.set('content.maximum_am_resource_percent', null); - } + this.set('content.maximum_am_resource_percent', val); } - var schedulerResoucePercent = this.get('scheduler.maximum_am_resource_percent'), - absoluteCapacity = this.get('content.absolute_capacity'); + var qMaxAmPercent = this.get('content.maximum_am_resource_percent'); + if (qMaxAmPercent) { + return qMaxAmPercent; + } else { + return this.getInheritedMaxAmResourcePercent(); + } + }.property('content.maximum_am_resource_percent', 'scheduler.maximum_am_resource_percent'), + + isMaxAmResourcePercentInherited: function() { if (this.get('content.maximum_am_resource_percent')) { - return this.get('content.maximum_am_resource_percent') + return false; } else { - return (schedulerResoucePercent * (absoluteCapacity / 100)); + return true; + } + }.property('content.maximum_am_resource_percent'), + + /** + * Returns inherited maximum am resource percent for a queue + */ + getInheritedMaxAmResourcePercent: function() { + var parentQ = this.store.getById('queue', this.get('content.parentPath').toLowerCase()); + while (parentQ !== null) { + if (parentQ.get('maximum_am_resource_percent')) { + return parentQ.get('maximum_am_resource_percent'); + } + parentQ = this.store.getById('queue', parentQ.get('parentPath').toLowerCase()); } - }.property('content.maximum_am_resource_percent', 'content.absolute_capacity', 'scheduler.maximum_am_resource_percent'), + return this.get('scheduler.maximum_am_resource_percent'); + }, /** * Sets ACL value to '*' or ' ' and returns '*' and 'custom' respectively. @@ -344,9 +380,11 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ var childrenQs = this.get('childrenQueues'), totalCapacity = 0; childrenQs.forEach(function(currentQ){ - totalCapacity += currentQ.get('capacity'); + if (typeof currentQ.get('capacity') === 'number') { + totalCapacity += currentQ.get('capacity'); + } }); - return totalCapacity; + return parseFloat(totalCapacity.toFixed(this.get('precision'))); }.property('childrenQueues.length', '[email protected]'), widthPattern: 'width: %@%', @@ -388,11 +426,14 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ this.set('queueDirtyFields.' + queueProp, this.get('content').changedAttributes().hasOwnProperty(queueProp)); }, + //Node Labels allNodeLabels: Ember.computed.alias('store.nodeLabels.content'), queueNodeLabels: Ember.computed.alias('content.sortedLabels'), hasQueueLabels: Ember.computed.gt('content.sortedLabels.length', 0), nonAccessibleLabels: Ember.computed.alias('content.nonAccessibleLabels'), allLabelsForQueue: Ember.computed.union('queueNodeLabels', 'nonAccessibleLabels'), + sortLabelsBy: ['name'], + sortedAllLabelsForQueue: Ember.computed.sort('allLabelsForQueue', 'sortLabelsBy'), hasAnyNodeLabelsForQueue: Ember.computed.gt('allLabelsForQueue.length', 0), accessibleLabelNames: function() { @@ -435,6 +476,136 @@ App.CapschedQueuesconfEditqueueController = Ember.Controller.extend({ return chidrenQLabels; }.property('childrenQueues.length', '[email protected].[]', 'content.labels.length'), - hasChildrenQueueLabels: Ember.computed.gt('childrenQueueLabels.length', 0) + hasChildrenQueueLabels: Ember.computed.gt('childrenQueueLabels.length', 0), + + //Default Node Label + initDefaultLabelOptions: function() { + var optionsObj = [{label: 'None', value: null}], + labels = this.get('queueNodeLabels'), + len = this.get('queueNodeLabels.length'); + if (len > 0) { + labels.forEach(function(lb){ + var obj = {label: lb.get('name'), value: lb.get('name')}; + optionsObj.pushObject(obj); + }); + } + this.set('defaultNodeLabelOptions', optionsObj); + }.observes('queueNodeLabels.[]').on('init'), + + defaultNodeLabelOptions: [], + + isDefaultNodeLabelInherited: function() { + if (this.get('content.default_node_label_expression') !== null) { + return false; + } else { + return true; + } + }.property('content.default_node_label_expression'), + + queueDefaultNodeLabelExpression: function(key, value) { + if (arguments.length > 1) { + if (value !== null) { + this.set('content.default_node_label_expression', value); + } else { + this.set('content.default_node_label_expression', null); + } + } + if (this.get('content.default_node_label_expression') !== null) { + return this.get('content.default_node_label_expression'); + } else { + return this.getDefaultNodeLabelExpressionInherited(); + } + }.property('content.default_node_label_expression'), + + getDefaultNodeLabelExpressionInherited: function() { + var store = this.get('store'), + currentQ = this.get('content'), + parentQ = store.getById('queue', currentQ.get('parentPath').toLowerCase()), + dnlexpr = null; + while (parentQ !== null) { + if (parentQ.get('default_node_label_expression') !== null) { + return parentQ.get('default_node_label_expression'); + } + parentQ = store.getById('queue', parentQ.get('parentPath').toLowerCase()); + } + return dnlexpr; + }, + + selectedDefaultNodeLabel: function() { + var labels = this.get('queueNodeLabels'); + var dnle = this.get('content.default_node_label_expression') || this.getDefaultNodeLabelExpressionInherited(); + if (dnle !== null && labels.findBy('name', dnle)) { + return dnle; + } + return 'None'; + }.property('content.default_node_label_expression'), + + isValidDefaultNodeLabel: function() { + return this.get('selectedDefaultNodeLabel') !== 'None'; + }.property('selectedDefaultNodeLabel'), + + //Preemption + isPreemptionSupported: Ember.computed.alias('store.isPreemptionSupported'), + currentStack: Ember.computed.alias('store.stackId'), + + isPreemptionOverriden: function(key, value) { + if (arguments.length > 1) { + this.set('content.isPreemptionOverriden', value); + } + return this.get('content.isPreemptionOverriden'); + }.property('content.isPreemptionOverriden'), + + isPreemptionInherited: function() { + return this.get('content.isPreemptionInherited'); + }.property('content.disable_preemption', 'content.isPreemptionInherited'), + + queueDisablePreemption: function() { + if (!this.get('isPreemptionInherited')) { + return (this.get('content.disable_preemption')==='true')?true:false; + } else { + return this.getInheritedQueuePreemption(); + } + }.property('content.disable_preemption', 'content.isPreemptionInherited'), + + getInheritedQueuePreemption: function() { + var store = this.get('store'), + currentQ = this.get('content'), + parentQ = store.getById('queue', currentQ.get('parentPath').toLowerCase()), + preemption = ''; + while (parentQ !== null) { + if (!parentQ.get('isPreemptionInherited')) { + preemption = parentQ.get('disable_preemption'); + return (preemption==='true')?true:false; + } + parentQ = store.getById('queue', parentQ.get('parentPath').toLowerCase()); + } + return preemption; + }, + + isQueuePreemptionDirty: function() { + return this.get('queueDirtyFields.disable_preemption'); + }.property('content.disable_preemption', 'content.isPreemptionInherited'), + + doOverridePreemption: function(key, value) { + if (value) { + this.set('content.isPreemptionInherited', false); + this.set('content.disable_preemption', (value === 'disable')?'true':'false'); + } + if (this.get('content.isPreemptionInherited')) { + return ''; + } else { + return (this.get('content.disable_preemption')==='true')?'disable':'enable'; + } + }.property('content.disable_preemption', 'content.isPreemptionInherited'), + + preemptionOvierideWatcher: function() { + var override = this.get('content.isPreemptionOverriden'), + wasInheritedInitially = (this.get('content').changedAttributes().hasOwnProperty('isPreemptionInherited') + && this.get('content').changedAttributes()['isPreemptionInherited'][0] === true); + if (override === false && wasInheritedInitially) { + this.set('content.isPreemptionInherited', true); + this.set('content.disable_preemption', ''); + } + }.observes('content.isPreemptionOverriden') }); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queuesconf.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queuesconf.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queuesconf.js index 683bc6f..a68a2aa 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queuesconf.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/controllers/queuesconf.js @@ -26,7 +26,10 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ queues: Ember.computed.alias('controllers.capsched.queues'), isOperator: Ember.computed.alias('controllers.capsched.isOperator'), allNodeLabels: Ember.computed.alias('store.nodeLabels.content'), + isRmOffline: Ember.computed.alias('store.isRmOffline'), + isNodeLabelsEnabledByRM: Ember.computed.alias('store.isNodeLabelsEnabledByRM'), allNodeLabelRecords: [], + queuesNeedRefresh: Ember.computed.alias('store.queuesNeedRefresh'), actions: { addNewQueue: function() { @@ -68,6 +71,13 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ }); this.set('newQueue', newQueue); store.saveAndUpdateQueue(newQueue).then(Em.run.bind(this, 'saveAndUpdateQueueSuccess', newQueue)); + + if (!this.get('queuesNeedRefresh').findBy('path', queuePath)) { + this.get('queuesNeedRefresh').addObject({ + path: queuePath, + name: queueName + }); + } }, clearCreateQueue: function() { this.set('newQueueName', ''); @@ -145,8 +155,7 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ var parentPath = this.get('selectedQueue.path'), queueName = this.get('newQueueName'), queuePath = [parentPath, queueName].join('.'), - qAlreadyExists = this.store.hasRecordForId('queue', queuePath.toLowerCase()); - + qAlreadyExists = (this.get('queues').findBy('name', queueName))? true : false; if (Ember.isBlank(queueName)) { this.set('isInvalidQueueName', true); this.set('invalidQueueNameMessage', 'Enter queue name'); @@ -165,6 +174,11 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ }.observes('newQueueName', 'newQueueName.length'), initNodeLabelRecords: function() { + if (!this.get('isNodeLabelsEnabledByRM') || this.get('isRmOffline')) { + return; + } + this.setDefaultNodeLabelAccesses('root'); + this.checkNodeLabelsInvalidCapacity('root'); var allQs = this.get('queues'), allLabels = this.get('allNodeLabels'), store = this.get('store'), @@ -183,9 +197,69 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ queue.set('nonAccessibleLabels', nonAccessible); }); this.set('allNodeLabelRecords', records); - }.on('init'), + setDefaultNodeLabelAccesses: function(queuePath) { + var allLabels = this.get('allNodeLabels'), + store = this.get('store'), + ctrl = this, + queue = store.getById('queue', queuePath.toLowerCase()), + parentQ = store.getById('queue', queue.get('parentPath').toLowerCase()), + children = store.all('queue').filterBy('depth', queue.get('depth') + 1).filterBy('parentPath', queue.get('path')); + + if (Ember.isEmpty(queue.get('labels'))) { + if (parentQ && parentQ.get('labels.length') > 0) { + parentQ.get('labels').forEach(function(label) { + var qlabel = store.getById('label', [queue.get('path'), label.get('name')].join('.')); + queue.addQueueNodeLabel(qlabel); + }); + } else { + allLabels.forEach(function(label) { + var qlabel = store.getById('label', [queue.get('path'), label.name].join('.')); + queue.addQueueNodeLabel(qlabel); + }); + } + } + + if (queue.get('name') === 'root') { + queue.get('labels').setEach('capacity', 100); + } + + children.forEach(function(child) { + ctrl.setDefaultNodeLabelAccesses(child.get('path')); + }); + }, + + checkNodeLabelsInvalidCapacity: function(queuePath) { + var allLabels = this.get('allNodeLabels'), + store = this.get('store'), + ctrl = this, + queue = store.getById('queue', queuePath.toLowerCase()), + children = store.all('queue').filterBy('depth', queue.get('depth') + 1).filterBy('parentPath', queue.get('path')); + + allLabels.forEach(function(lab) { + var total = 0; + children.forEach(function(child) { + var cLabel = child.get('labels').findBy('name', lab.name); + if (cLabel) { + total += cLabel.get('capacity'); + } + }); + if (total !== 100) { + children.forEach(function(child) { + var cLabel = child.get('labels').findBy('name', lab.name); + if (cLabel) { + cLabel.set('overCapacity', true); + } + }); + } + }); + + children.forEach(function(child) { + ctrl.checkNodeLabelsInvalidCapacity(child.get('path')); + }); + }, + /** * Marks each queue in leaf with 'overCapacity' if sum if their capacity values is greater than 100. * @method capacityControl @@ -361,24 +435,23 @@ App.CapschedQueuesconfController = Ember.Controller.extend({ return this.get('selectedQueue.state') === _stopState; }.property('selectedQueue.state'), - isSelectedQLeaf: function() { return this.get('selectedQueue.queues') === null; }.property('selectedQueue.queues'), - isSelectedQLeafAndRunning: function() { - return this.get('isSelectedQLeaf') && this.get('isSelectedQRunning'); - }.property('isSelectedQRunning', 'isSelectedQLeaf'), + canAddChildrenQueues: function() { + return this.get('isRmOffline') || !this.get('isSelectedQLeaf') || this.get('isSelectedQueueRmStateNotRunning'); + }.property('isRmOffline', 'isSelectedQLeaf', 'isSelectedQueueRmStateNotRunning'), - /** - * Returns true if queue is root. - * @type {Boolean} - */ isRootQSelected: Ember.computed.match('selectedQueue.id', /^(root)$/), - isSelectedQDeletable: function() { - return !this.get('isRootQSelected') && !this.get('isSelectedQRunning'); - }.property('isRootQSelected', 'isSelectedQRunning'), + isSelectedQueueRmStateNotRunning: function() { + return this.get('selectedQueue.isNewQueue') || this.get('selectedQueue.rmQueueState') === _stopState; + }.property('selectedQueue.rmQueueState', 'selectedQueue.isNewQueue'), + + isSelectedQueueDeletable: function() { + return !this.get('isRootQSelected') && (this.get('isRmOffline') || this.get('isSelectedQueueRmStateNotRunning')); + }.property('isRootQSelected', 'isSelectedQueueRmStateNotRunning', 'isRmOffline'), configNote: Ember.computed.alias('store.configNote'), saveMode: '', http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js index e91c90f..acf6465 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/models/queue.js @@ -36,7 +36,13 @@ App.Label = DS.Model.extend({ isDefault: function () { return this.get('queue.default_node_label_expression') === this.get('name'); }.property('queue.default_node_label_expression'), - absoluteCapacity: 0 + absoluteCapacity: 0, + setCapacity: function(cap) { + this.set('capacity', cap); + }, + setMaxCapacity: function(maxCap) { + this.set('maximum_capacity', maxCap); + } }); App.Scheduler = DS.Model.extend({ @@ -155,6 +161,8 @@ App.Queue = DS.Model.extend({ if (!this.get('nonAccessibleLabels').contains(qLabel)) { this.get('nonAccessibleLabels').addObject(qLabel); } + qLabel.setCapacity(0); + qLabel.setMaxCapacity(100); this.notifyPropertyChange('labels'); this.notifyPropertyChange('nonAccessibleLabels'); } @@ -183,6 +191,15 @@ App.Queue = DS.Model.extend({ this.notifyPropertyChange('nonAccessibleLabels'); }, + recursiveAddChildQueueLabels: function(qLabel) { + qLabel = this.store.getById('label', [this.get('path'), qLabel.get('name')].join('.')); + this.addQueueNodeLabel(qLabel); + var childrenQs = this.store.all('queue').filterBy('depth', this.get('depth') + 1).filterBy('parentPath', this.get('path')); + childrenQs.forEach(function(child) { + child.recursiveAddChildQueueLabels(qLabel); + }.bind(this)); + }, + isAnyDirty: function () { return this.get('isDirty') || !Em.isEmpty(this.get('labels').findBy('isDirty',true)) || this.get('isLabelsDirty'); }.property('isDirty','[email protected]','initialLabels','isLabelsDirty'), @@ -231,6 +248,9 @@ App.Queue = DS.Model.extend({ minimum_user_limit_percent: DS.attr('number', { defaultValue: 100 }), maximum_applications: DS.attr('number', { defaultValue: null }), maximum_am_resource_percent: DS.attr('number', { defaultValue: null }), + disable_preemption: DS.attr('string', {defaultValue: ''}), + isPreemptionInherited: DS.attr('boolean', {defaultValue: true}), + isPreemptionOverriden: false, queues: DS.attr('string'), queuesArray:function (key,val) { @@ -260,6 +280,9 @@ App.Queue = DS.Model.extend({ nonAccessibleLabels: [], + //queue state from resource manager + rmQueueState: 'UNKNOWN', + //new queue flag isNewQueue:DS.attr('boolean', {defaultValue: false}), http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js index 49363d4..0b13a8a 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/router.js @@ -23,17 +23,15 @@ App.Router.map(function() { this.resource('queue', { path: '/:queue_id' }); this.resource('trace', { path: '/log' }); }); - this.route('refuse'); - this.resource('capsched', {path: '/capacity-scheduler'}, function() { this.route('scheduler', {path: '/scheduler'}); this.route('advanced', {path: '/advanced'}); this.route('trace', {path: '/log'}); - this.route('refuse', {path: '/refuse'}); this.route('queuesconf', {path: '/queues'}, function() { this.route('editqueue', {path: '/:queue_id'}); }); }); + this.route('refuse'); }); var RANGER_SITE = 'ranger-yarn-plugin-properties'; @@ -226,6 +224,7 @@ App.CapschedRoute = Ember.Route.extend({ opt = 'saveAndRefresh'; } + capschedCtrl.startSpinner(); Em.RSVP.Promise.all([labels, queues, scheduler]).then( Em.run.bind(that,'saveConfigsSuccess'), Em.run.bind(that,'saveConfigsError', 'save') @@ -233,8 +232,13 @@ App.CapschedRoute = Ember.Route.extend({ if (opt) { return store.relaunchCapSched(opt); } - }) - .catch(Em.run.bind(this,'saveConfigsError', opt)); + }).then(function(){ + return store.getRmSchedulerConfigInfo(); + }).catch( + Em.run.bind(this,'saveConfigsError', opt) + ).finally(function(){ + capschedCtrl.stopSpinner(); + }); } }, beforeModel: function(transition) { @@ -277,6 +281,11 @@ App.CapschedRoute = Ember.Route.extend({ return store.find('queue'); }).then(function(queues) { controller.set('queues', queues); + loadingController.set('model', { + message: 'loading rm info' + }); + return store.getRmSchedulerConfigInfo(); + }).then(function() { return store.find('scheduler', 'scheduler'); }).then(function(scheduler){ resolve(scheduler); @@ -326,6 +335,6 @@ App.CapschedQueuesconfEditqueueRoute = Ember.Route.extend({ App.CapschedTraceRoute = Ember.Route.extend({ model: function() { - return this.controllerFor('capsched.queuesconf').get('alertMessage'); + return this.controllerFor('capsched').get('alertMessage'); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/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 c3976b4..ee7bdd4 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 @@ -106,7 +106,9 @@ 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), + disable_preemption: props[base_path + '.disable_preemption'] || '', + isPreemptionInherited: (props[base_path + '.disable_preemption'] !== undefined)?false:true }; switch ((props.hasOwnProperty(labelsPath))?props[labelsPath]:'') { @@ -250,6 +252,11 @@ App.QueueSerializer = DS.RESTSerializer.extend(App.SerializerMixin,{ } }, this); + var isPreemptionSupported = record.get('store.isPreemptionSupported'); + if (isPreemptionSupported && !record.get('isPreemptionInherited')) { + json[this.PREFIX + "." + record.get('path') + ".disable_preemption"] = (record.get('disable_preemption')==='true')? true:false; + } + return json; }, serializeHasMany:function (record, json, relationship) { http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js index 36228a1..01a7d6a 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/store.js @@ -105,6 +105,46 @@ function _fetchTagged(adapter, store, type, sinceToken) { }, null, "DS: Extract payload of findAll " + type); } +function _fillRmQueueStateIntoQueues(data, store) { + var parsed = JSON.parse(data), + queuesNeedRefresh = store.get('queuesNeedRefresh'), + rootQInfo = parsed['scheduler']['schedulerInfo']; + var queueStates = _getRmQueueStates(rootQInfo, 'root', []); + store.all('queue').forEach(function(queue) { + var qInfo = queueStates.findBy('path', queue.get('path')); + if (qInfo && qInfo.state) { + queue.set('rmQueueState', qInfo.state); + if (queuesNeedRefresh.findBy('path', queue.get('path'))) { + queuesNeedRefresh.removeObject(queuesNeedRefresh.findBy('path', queue.get('path'))); + } + } else { + if (!queuesNeedRefresh.findBy('path', queue.get('path'))) { + queuesNeedRefresh.addObject({ + path: queue.get('path'), + name: queue.get('name') + }); + } + } + }); +} + +function _getRmQueueStates(queueInfo, qPath, qStates) { + var qObj = { + name: queueInfo.queueName, + path: qPath.toLowerCase(), + state: queueInfo.state || 'RUNNING' + }; + qStates.addObject(qObj); + if (queueInfo.queues) { + var children = queueInfo.queues.queue || []; + children.forEach(function(child) { + var cPath = [qPath, child.queueName].join('.'); + return _getRmQueueStates(child, cPath, qStates); + }); + } + return qStates; +} + App.ApplicationStore = DS.Store.extend({ adapter: App.QueueAdapter, @@ -119,10 +159,21 @@ App.ApplicationStore = DS.Store.extend({ stackId: '', + isPreemptionSupported: function() { + var stackId = this.get('stackId'), + stackVersion = stackId.substr(stackId.indexOf('-') + 1); + if (stackVersion >= 2.3) { + return true; + } + return false; + }.property('stackId'), + hasDeletedQueues:Em.computed.notEmpty('deletedQueues.[]'), deletedQueues:[], + queuesNeedRefresh: [], + buildConfig: function (fmt) { var records = [], config = '', @@ -379,5 +430,30 @@ App.ApplicationStore = DS.Store.extend({ }, checkCluster:function () { return this.get('defaultAdapter').checkCluster(this); + }, + getRmSchedulerConfigInfo: function() { + var store = this, + adapter = this.get('defaultAdapter'); + return new Ember.RSVP.Promise(function(resolve, reject) { + adapter.getRmSchedulerConfigInfo().then(function(data) { + if (data) { + _fillRmQueueStateIntoQueues(data, store); + } + store.set('isRmOffline', false); + resolve([]); + }, function() { + store.set('isRmOffline', true); + resolve([]); + }).finally(function() { + store.pollRmSchedulerConfigInfo(); + }); + }); + }, + pollRmSchedulerConfigInfo: function() { + var store = this; + //Poll getRmSchedulerConfigInfo every 1 minute. + Ember.run.later(store, function() { + store.getRmSchedulerConfigInfo(); + }, 60000); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/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 e849ad4..195b55d 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 @@ -961,6 +961,10 @@ padding: 2px 3px; } } + .tooltip-inner { + white-space: pre; + max-width: none; + } } } @@ -1059,6 +1063,9 @@ &:focus { outline: none; } + &:disabled { + cursor: not-allowed !important; + } &::-webkit-slider-runnable-track { width: 100%; height: 2px; @@ -1144,11 +1151,11 @@ margin-top: 5px; } .capacity-input-percent { - width: 25%; + width: 90px; float: left; display: inline-block; input { - width: 60%; + width: 55px; } } .capacity-input-slider { @@ -1166,6 +1173,7 @@ } .total-capacity-progress { width: 100%; + margin-bottom: 0; } } @@ -1199,9 +1207,9 @@ margin-bottom: 10px; } .label-capacity-input { - width: 35%; + width: 77px; input { - width: 60% !important; + width: 50px !important; } .input-group-addon { float: left; @@ -1218,6 +1226,7 @@ background-color: #f9f9f9!important; } .total-capacity-progress { + margin-bottom: 0; .progress-bar { color: #000; } @@ -1229,23 +1238,55 @@ width: 54%; display: inline-block; } + .default-label-options { + max-width: 170px; + } } .capsched-versions-wrapper { padding-top: 40px; padding-left: 0; + .capsched-versions-panel { + .panel-heading { + margin-bottom: -1px; + } + .panel-body { + padding: 0px 15px 5px 15px; + max-height: 428px; + overflow-y: auto; + } + .table { + margin-bottom: 0; + } + } } -.capsched-versions-panel { - .panel-heading { - margin-bottom: -1px; - } - .panel-body { - padding: 0px 15px 5px 15px; - max-height: 428px; - overflow-y: auto; +.spinner-wrapper { + .save-spinner { + position: fixed; + width: 150px; + height: 100px; + left: 45%; + background-color: #f5f5f5; + border-radius: 4px; + border: 1px solid #e4e4e4; + .fa-spinner { + margin-left: 45px; + margin-top: 20px; + } + .saving-text { + margin-left: 50px; + margin-top: 10px; + } } - .table { - margin-bottom: 0; +} + +.preemption-container { + .override-btns { + margin-top: 10px; } } + +.alert-message-dialog { + display: block; +} http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js index 6e310ad..2c9609f 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates.js @@ -59,3 +59,4 @@ require('templates/components/saveConfigDialog'); require('templates/components/labelCapacityBar'); require('templates/components/displayRootLabel'); require('templates/components/displayLeafLabel'); +require('templates/capsched/partials/preemption'); http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched.hbs index b75237f..14625c1 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched.hbs @@ -31,7 +31,7 @@ <a href="#">Queues</a> {{/link-to}} {{#link-to "capsched.advanced" tagName="li"}} - <a href="#">Advanced</a> + <a href="#">User Queue Mappings</a> {{/link-to}} </ul> <div class="tab-content"> @@ -45,7 +45,7 @@ {{!-- ALERT --}} {{#if alertMessage}} - <div class="alert alert-danger"> + <div class="alert alert-danger modal alert-message-dialog" tabindex="-1" role="dialog" aria-hidden="false"> <button {{action 'clearAlert'}} type="button" class="close" aria-hidden="false">×</button> <strong> {{alertMessage.status}} </strong> {{alertMessage.simpleMessage}} <div> @@ -55,3 +55,12 @@ {{#link-to 'capsched.trace' class="alert-link"}}Trace{{/link-to}} </div> {{/if}} + +<div {{bind-attr class=":spinner-wrapper showSpinner::hide"}}> + <div class="save-spinner"> + <i class="fa fa-spinner fa-spin fa-3x fa-fw"></i> + <div class="saving-text"> + SAVING... + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/labelCapacity.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/labelCapacity.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/labelCapacity.hbs index e659f5c..9073910 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/labelCapacity.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/labelCapacity.hbs @@ -31,6 +31,34 @@ <label>Accessible Node Labels: </label> <span>{{accessibleLabelNames}}</span> </div> + <div class="row"> + <div class="col-sm-6 col-md-6"> + <label>Default Label: </label> + <span>{{selectedDefaultNodeLabel}}</span> + {{#if isDefaultNodeLabelInherited}} + {{#unless isRoot}} + {{#if isValidDefaultNodeLabel}} + <span>(Inherited)</span> + {{/if}} + {{/unless}} + {{/if}} + </div> + <div class="col-sm-6 col-md-6"> + <label>Set Default Node Label: </label> + {{view Ember.Select + class="default-label-options" + content=defaultNodeLabelOptions + optionLabelPath="content.label" + optionValuePath="content.value" + value=queueDefaultNodeLabelExpression + }} + {{#if queueDirtyFields.default_node_label_expression}} + <div class="btn-group btn-group-xs"> + <a {{action 'rollbackProp' 'default_node_label_expression' content}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> + </div> + {{/if}} + </div> + </div> {{#if isRoot}} <div class="border-wrapper"> <div class="row"> @@ -45,7 +73,7 @@ </div> </div> {{#if hasAnyNodeLabelsForQueue}} - {{#each label in allLabelsForQueue}} + {{#each label in sortedAllLabelsForQueue}} {{display-root-label queueLabels=queueNodeLabels nonAccessibleLabels=nonAccessibleLabels label=label isRoot=isRoot}} {{/each}} {{else}} @@ -100,8 +128,12 @@ {{/if}} {{/if}} {{else}} - <div> - <small>Node labels are disabled by RM. Configure and enable labels to map the labels to queues.</small> + <div class="text-warning"> + {{#if isRmOffline}} + <small>Unable to obtain information about the node labels from the resource manager</small> + {{else}} + <small>Node labels are disabled by resource manager. Enable node labels to map the labels to queues.</small> + {{/if}} </div> {{/if}} {{else}} @@ -120,8 +152,12 @@ </div> {{/if}} {{else}} - <div> - <small>Node labels are disabled</small> + <div class="text-warning"> + {{#if isRmOffline}} + <small>Unable to obtain information about the node labels from the resource manager</small> + {{else}} + <small>Node labels are disabled by resource manager</small> + {{/if}} </div> {{/if}} {{/if}} http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/preemption.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/preemption.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/preemption.hbs new file mode 100644 index 0000000..73a9e21 --- /dev/null +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/preemption.hbs @@ -0,0 +1,75 @@ +{{! +* 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. +}} + +<div class="preemption-container"> + <div class="panel panel-default"> + <div class="panel-heading"> + <div class="panel-title"> + Preemption <a id="collapseQueuePreemptionPanelBtn" href="#collapsibleQueuePreemptionPanel" data-toggle="collapse" {{bind-attr class=":pull-right view.isPreemptionPanelCollapsed:collapsed"}}><i {{bind-attr class=":fa view.isPreemptionPanelCollapsed:fa-plus:fa-minus"}}></i></a> + </div> + </div> + <div id="collapsibleQueuePreemptionPanel" {{bind-attr class=":panel-collapse :collapse view.isPreemptionPanelCollapsed::in"}}> + <div class="panel-body"> + {{#if isPreemptionSupported}} + <div class="row"> + <div class="col-sm-10 col-md-10"> + <label>Can this queue be preempted by other queues: </label> + {{#if queueDisablePreemption}} + <span class="text-danger">Disabled </span> + {{else}} + <span class="text-success">Enabled </span> + {{/if}} + {{#if isPreemptionInherited}} + {{#if isRoot}} + <span>(Inherited from global settings)</span> + {{else}} + <span>(Inherited)</span> + {{/if}} + {{/if}} + {{#if isQueuePreemptionDirty}} + {{warning-info class="yellow-warning" tooltip="Need refresh queues/restart RM"}} + {{/if}} + </div> + </div> + <div class="row"> + <div class="col-sm-4 col-md-4"> + <div class="checkbox"> + <label> + {{input type="checkbox" name="preemptionOverride" checked=isPreemptionOverriden}} + Override Preemption + </label> + </div> + </div> + {{#if isPreemptionOverriden}} + <div class="col-sm-3 col-md-3"> + <div class="btn-group btn-group-sm override-btns" data-toggle="buttons"> + {{radio-button label="Enable" classNames="btn-small" selectionBinding="doOverridePreemption" value="enable"}} + {{radio-button label="Disable" classNames="btn-small" selectionBinding="doOverridePreemption" value="disable"}} + </div> + </div> + {{/if}} + </div> + {{else}} + <div class="text-warning"> + <span>Preemption is not supported for your current stack version {{currentStack}}</span> + </div> + {{/if}} + </div> + </div> + </div> +</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueCapacity.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueCapacity.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueCapacity.hbs index b4891cd..7b25af3 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueCapacity.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueCapacity.hbs @@ -20,7 +20,7 @@ <div class="panel panel-default"> <div class="panel-heading"> <div class="panel-title"> - Capacity <a id="collapseQueueCapacityPanelBtn" href="#collapsibleQueueCapacityPanel" data-toggle="collapse" {{bind-attr class=":pull-right view.isCapacityPanelCollapsed:collapsed"}}><i {{bind-attr class=":fa view.isCapacityPanelCollapsed:fa-plus:fa-minus"}}></i></a> + Queue Capacity <a id="collapseQueueCapacityPanelBtn" href="#collapsibleQueueCapacityPanel" data-toggle="collapse" {{bind-attr class=":pull-right view.isCapacityPanelCollapsed:collapsed"}}><i {{bind-attr class=":fa view.isCapacityPanelCollapsed:fa-plus:fa-minus"}}></i></a> </div> </div> <div id="collapsibleQueueCapacityPanel" {{bind-attr class=":panel-collapse :collapse view.isCapacityPanelCollapsed::in"}}> @@ -61,8 +61,16 @@ </div> {{#if warnInvalidCapacity}} <div class="row"> - <div class="col-sm-6 col-md-6"> - <span class="text-danger">Total capacity must be equal to 100%</span> + <div class="col-sm-6 col-md-6 col-md-offset-2 col-sm-offset-2"> + <small class="text-danger">Total capacity must be equal to 100%</small> + </div> + </div> + {{/if}} + {{#if parentQueue}} + <div class="row"> + <div class="col-md-10 col-sm-10"> + <small>To edit capacity and maximum capacity at parent queue level</small> + <small>{{#link-to 'capsched.queuesconf.editqueue' parentQueue}}Click Here{{/link-to}}</small> </div> </div> {{/if}} @@ -79,8 +87,8 @@ </div> <div class="row"> <div class="col-md-10 col-sm-10"> - <span>To edit capacity and maximum capacity at parent queue level</span> - <span>{{#link-to 'capsched.queuesconf.editqueue' parentQueue}}Click Here{{/link-to}}</span> + <small>To edit capacity and maximum capacity at parent queue level</small> + <small>{{#link-to 'capsched.queuesconf.editqueue' parentQueue}}Click Here{{/link-to}}</small> </div> </div> {{/unless}} http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueResources.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueResources.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueResources.hbs index bb82025..262f380 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueResources.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/partials/queueResources.hbs @@ -96,13 +96,15 @@ </div> {{/if}} </div> - <div class="col-sm-2 inherited-value"> - <span>(Inherited)</span> - </div> + {{#if isMaximumApplicationsInherited}} + <div class="col-sm-2 inherited-value"> + <span>(Inherited)</span> + </div> + {{/if}} {{else}} <div class="col-sm-3"> {{#if maximumApplications}} - <p class="form-control-static">{{maximumApplications}} (Inherited)</p> + <p class="form-control-static">{{maximumApplications}} {{#if isMaximumApplicationsInherited}}(Inherited){{/if}}</p> {{else}} <p class="form-control-static">Inherited</p> {{/if}} @@ -117,28 +119,30 @@ message='The maximum percentage of total capacity of this specific queue that can be utilized by application masters at any point in time.' }} {{#if isOperator}} - <div class="col-sm-3 control-value input-percent-wrap"> - <div class="input-group input-percent"> - {{int-input placeholder="Inherited" value=maximumAMResourcePercent class="input-sm" maxVal=100}} - <span class="input-group-addon">%</span> + <div class="col-sm-3 control-value input-percent-wrap"> + <div class="input-group input-percent"> + {{int-input placeholder="Inherited" value=maximumAMResourcePercent class="input-sm" maxVal=100}} + <span class="input-group-addon">%</span> + </div> + {{#if queueDirtyFields.maximum_am_resource_percent}} + <div class="btn-group btn-group-xs"> + <a {{action 'rollbackProp' 'maximum_am_resource_percent' content}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> + </div> + {{/if}} </div> - {{#if queueDirtyFields.maximum_am_resource_percent}} - <div class="btn-group btn-group-xs"> - <a {{action 'rollbackProp' 'maximum_am_resource_percent' content}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> + {{#if isMaxAmResourcePercentInherited}} + <div class="col-sm-2 inherited-value"> + <span>(Inherited)</span> </div> {{/if}} - </div> - <div class="col-sm-2 inherited-value"> - <span>(Inherited)</span> - </div> {{else}} - <div class="col-sm-2"> - {{#if maximumAMResourcePercent}} - <p class="form-control-static">{{maximumAMResourcePercent}}% (Inherited)</p> - {{else}} - <p class="form-control-static">Inherited</p> - {{/if}} - </div> + <div class="col-sm-2"> + {{#if maximumAMResourcePercent}} + <p class="form-control-static">{{maximumAMResourcePercent}}% {{#if isMaxAmResourcePercentInherited}}(Inherited){{/if}}</p> + {{else}} + <p class="form-control-static">Inherited</p> + {{/if}} + </div> {{/if}} </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf.hbs index b1a8708..1bc3380 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf.hbs @@ -28,8 +28,11 @@ {{#if selectedQueue}} <div class="queue-actions-wrapper"> <div class="pull-left"> - <button type="button" {{bind-attr class=":btn :btn-primary :btn-xs isSelectedQLeafAndRunning:disabled"}} name="addQueueBtn" {{action "addNewQueue"}}>Add Queue</button> + <button type="button" {{bind-attr class=":btn :btn-primary :btn-xs canAddChildrenQueues::disabled"}} name="addQueueBtn" {{action "addNewQueue"}}>Add Queue</button> </div> + {{#unless canAddChildrenQueues}} + {{warning-info class="yellow-warning" tooltip="Queue must be STOPPED to add children queues"}} + {{/unless}} <div class="pull-right"> {{#if isSelectedQRunning}} <button type="button" class="btn btn-primary btn-xs" name="queueStateBtn" {{action "stopQueue"}}>Stop Queue</button> @@ -38,9 +41,9 @@ <button type="button" class="btn btn-primary btn-xs" name="queueStateBtn" {{action "startQueue"}}>Start Queue</button> {{/if}} {{#unless isRootQSelected}} - <button type="button" {{bind-attr class=":btn :btn-danger :btn-xs isSelectedQDeletable::disabled"}} class="btn btn-danger btn-xs" name="deleteQueueBtn" {{action "showDeleteQueueDialog" target="view"}}>Delete Queue</button> - {{#unless isSelectedQDeletable}} - {{warning-info class="yellow-warning" tooltip="Queue should be STOPPED to delete"}} + <button type="button" {{bind-attr class=":btn :btn-danger :btn-xs isSelectedQueueDeletable::disabled"}} class="btn btn-danger btn-xs" name="deleteQueueBtn" {{action "showDeleteQueueDialog" target="view"}}>Delete Queue</button> + {{#unless isSelectedQueueDeletable}} + {{warning-info class="yellow-warning" tooltip="Queue must be STOPPED to delete"}} {{/unless}} {{/unless}} </div> @@ -66,7 +69,7 @@ {{/if}} </div> <div class="col-sm-5"> - {{queue-summary queue=selectedQueue allQueues=queues}} + {{queue-summary queue=selectedQueue allQueues=queues queuesNeedRefresh=queuesNeedRefresh}} </div> </div> <hr> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf/editqueue.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf/editqueue.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf/editqueue.hbs index 2848fed..bbdb331 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf/editqueue.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/queuesconf/editqueue.hbs @@ -68,6 +68,10 @@ {{partial "capsched/partials/labelCapacity"}} </div> + <div class="queue-preemption-wrapper"> + {{partial "capsched/partials/preemption"}} + </div> + <div class="queue-acl-wrapper"> {{partial "capsched/partials/accessControlList"}} </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/trace.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/trace.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/trace.hbs new file mode 100644 index 0000000..77e9258 --- /dev/null +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/capsched/trace.hbs @@ -0,0 +1,21 @@ +{{! +* 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. +}} + +<pre> + {{trace}} +</pre> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editLabelCapacity.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editLabelCapacity.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editLabelCapacity.hbs index 9e25cc6..8fe627e 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editLabelCapacity.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editLabelCapacity.hbs @@ -35,13 +35,15 @@ <span class="input-group-addon">%</span> </div> <div class="form-group label-capacity-slider"> - {{input-range min="0" max="100" step="1" value=label.capacity class="input-sm"}} + {{input-range min="0" max="100" step="1" value=label.capacity class="input-sm" disabled=isAccessDisabled}} </div> - {{#if isLabelCapacityDirty}} - <div class="btn-group btn-group-xs label-capacity-rollback"> - <a {{action 'rollbackProp' 'capacity' label}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> - </div> - {{/if}} + {{#unless isAccessDisabled}} + {{#if isLabelCapacityDirty}} + <div class="btn-group btn-group-xs label-capacity-rollback"> + <a {{action 'rollbackProp' 'capacity' label}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> + </div> + {{/if}} + {{/unless}} </div> <div class="col-md-4 col-sm-4"> <div {{bind-attr class=":form-group :input-group :label-capacity-input isInvalidLabelMaxCapacity:has-error"}}> @@ -49,16 +51,18 @@ <span class="input-group-addon">%</span> </div> <div class="form-group label-capacity-slider"> - {{input-range min="0" max="100" step="1" value=label.maximum_capacity class="input-sm"}} + {{input-range min="0" max="100" step="1" value=label.maximum_capacity class="input-sm" disabled=isAccessDisabled}} </div> - {{#if isLabelMaxCapacityDirty}} - <div class="btn-group btn-group-xs label-capacity-rollback"> - <a {{action 'rollbackProp' 'maximum_capacity' label}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> - </div> - {{/if}} + {{#unless isAccessDisabled}} + {{#if isLabelMaxCapacityDirty}} + <div class="btn-group btn-group-xs label-capacity-rollback"> + <a {{action 'rollbackProp' 'maximum_capacity' label}} href="#" class="btn btn-default btn-warning"><i class="fa fa-undo"></i></a> + </div> + {{/if}} + {{/unless}} {{#if isInvalidLabelMaxCapacity}} <div class="label-maxcapacity-error"> - <span class="text-danger">Maximum capacity must be greater than or equal to capacity</span> + <small class="text-danger">Maximum capacity must be greater than or equal to capacity</small> </div> {{/if}} </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editQueueCapacity.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editQueueCapacity.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editQueueCapacity.hbs index c430a44..a7901b8 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editQueueCapacity.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/editQueueCapacity.hbs @@ -18,7 +18,7 @@ <div class="row form-inline"> <div class="col-md-2 col-sm-2"> - <span class="queue-name">{{queue.name}}</span> + {{#link-to 'capsched.queuesconf.editqueue' queue}}{{queue.name}}{{/link-to}} </div> <div class="col-md-5 col-md-5"> <div {{bind-attr class=":form-group :input-group :capacity-input-percent warnInvalidTotalCapacity:has-error"}}> @@ -50,7 +50,7 @@ {{#if isInvalidQueueMaximumCapacity}} <div class="row"> <div class="col-md-12 col-sm-12"> - <span class="text-danger">Maximum capacity must be greater than or equal to capacity</span> + <small class="text-danger">Maximum capacity must be greater than or equal to capacity</small> </div> </div> {{/if}} http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/labelCapacityBar.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/labelCapacityBar.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/labelCapacityBar.hbs index 96cbe99..3bd94a0 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/labelCapacityBar.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/labelCapacityBar.hbs @@ -32,8 +32,8 @@ </div> {{#if warnInvalidLabelCapacity}} <div class="row"> - <div class="col-sm-6 col-md-6"> - <span class="text-danger">Total capacity must be equal to 100%</span> + <div class="col-sm-6 col-md-6 col-sm-offset-2 col-md-offset-2"> + <small class="text-danger">Total capacity must be equal to 100%</small> </div> </div> {{/if}} http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueMapping.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueMapping.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueMapping.hbs index bd49c50..fa35518 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueMapping.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueMapping.hbs @@ -48,17 +48,23 @@ <div class="queue-mapping-options"> <div class="row"> <div class="radio"> - <label title="Assign job to queue with same name as user"> + <label> {{view view.radioButton selectionBinding="selectedMapping" name="queueMappingType" value="u:%user:%user"}} - User %user -> queue %user + {{tooltip-label + label='Map user to queue with the same name of user' + message='User %user -> queue %user <br/> Ex: User=john will be mapped to queue=john, and user=alice will be mapped to queue=alice' + }} </label> </div> </div> <div class="row"> <div class="radio"> - <label title="Assign job to queue with same name as user's primary group"> + <label> {{view view.radioButton selectionBinding="selectedMapping" name="queueMappingType" value="u:%user:%primary_group"}} - User %user -> queue %primary_group + {{tooltip-label + label='Map user to queue with the same name of userâs primary group' + message='User %user -> queue %primary_group <br/> Ex: User=john will be mapped to queue=john\'s primary group, and user=alice will be mapped to queue=alice\'s primary group' + }} </label> </div> </div> @@ -66,7 +72,10 @@ <div class="radio"> <label> {{view view.radioButton selectionBinding="selectedMapping" name="queueMappingType" value="u:%name:%qname"}} - Assign users to queue + {{tooltip-label + label='Assign specific users to given queue' + message='Specified users will be mapped to queue specified' + }} </label> </div> {{#if isCustomUserMapping}} @@ -100,7 +109,10 @@ <div class="radio"> <label> {{view view.radioButton selectionBinding="selectedMapping" name="queueMappingType" value="g:%name:%qname"}} - Assign groups to queue + {{tooltip-label + label='Assign users from specific groups to given queue' + message='Users from specified groups will be mapped to queue specified' + }} </label> </div> {{#if isCustomGroupMapping}} @@ -144,7 +156,10 @@ <div class="checkbox"> <label> {{input type="checkbox" name="queueMappingOverride" checked=mappingsOverrideEnable}} - Allow user to override queue mapping + {{tooltip-label + label='Allow application to override user-queue mapping' + message='When checked, applications submitted with queues specified will be used other than those defined in default queue mapping' + }} </label> </div> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueSummary.hbs ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueSummary.hbs b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueSummary.hbs index ef5663d..9c3ffd0 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueSummary.hbs +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/templates/components/queueSummary.hbs @@ -43,25 +43,37 @@ <div class="col-sm-5"> <span>{{queue.capacity}}%</span> </div> + {{#if isQueueNeedRefreshOrRestart}} + <div class="col-sm-2"> + {{warning-info class="yellow-warning" tooltip="Need refresh queues/restart RM to take effect"}} + </div> + {{/if}} </div> <div class="row"> <div class="col-sm-5"> - <label>Absolute Capacity</label> + <label>Absolute Capacity of Cluster</label> </div> <div class="col-sm-5"> <span>{{effectiveCapacity}}%</span> </div> + {{#if isQueueNeedRefreshOrRestart}} + <div class="col-sm-2"> + {{warning-info class="yellow-warning" tooltip="Need refresh queues/restart RM to take effect"}} + </div> + {{/if}} </div> <div class="row"> <div class="col-sm-5"> <label>State</label> </div> - <div class="col-sm-6"> + <div class="col-sm-5"> <span {{bind-attr class="qStateColor"}}>{{queueState}}</span> - {{#if isDirtyState}} - {{warning-info class="yellow-warning" tooltip="Not Yet Saved"}} - {{/if}} </div> + {{#if isQueueNeedRefreshOrRestart}} + <div class="col-sm-2"> + {{warning-info class="yellow-warning" tooltip="Need refresh queues/restart RM to take effect"}} + </div> + {{/if}} </div> </div> </div> http://git-wip-us.apache.org/repos/asf/ambari/blob/6a89de63/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/editqueue.js ---------------------------------------------------------------------- diff --git a/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/editqueue.js b/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/editqueue.js index a5d0ef6..fc791aa 100644 --- a/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/editqueue.js +++ b/contrib/views/capacity-scheduler/src/main/resources/ui/app/views/editqueue.js @@ -19,10 +19,11 @@ var App = require('app'); App.CapschedQueuesconfEditqueueView = Ember.View.extend({ + isCapacityPanelCollapsed: true, + isLabelsPanelCollapsed: true, + isPreemptionPanelCollapsed: true, isAclPanelCollapsed: true, isResourcesPanelCollapsed: true, - isCapacityPanelCollapsed: false, - isLabelsPanelCollapsed: true, doExpandCollapsePanel: function() { var that = this; @@ -46,11 +47,18 @@ App.CapschedQueuesconfEditqueueView = Ember.View.extend({ this.toggleProperty('isLabelsPanelCollapsed'); }); }); + this.$('#collapseQueuePreemptionPanelBtn').on('click', function(e) { + Ember.run.next(that, function() { + this.toggleProperty('isPreemptionPanelCollapsed'); + }); + }); }.on('didInsertElement'), destroyEventListeners: function() { this.$('#collapseQueueAclPanelBtn').off('click'); this.$('#collapseResourcesPanelBtn').off('click'); this.$('#collapseQueueCapacityPanelBtn').off('click'); + this.$('#collapseLabelCapacityPanelBtn').off('click'); + this.$('#collapseQueuePreemptionPanelBtn').off('click'); }.on('willDestroyElement') });
