blob: 3cffa68a494a6502f1550dc11b828e458c8e43a5 [file] [log] [blame]
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
var App = require('app');
var validator = require('utils/validator');
var stringUtils = require('utils/string_utils');
var hostsUtils = require('utils/hosts');
App.ServicesConfigView = Em.View.extend({
templateName: require('templates/common/configs/services_config'),
didInsertElement: function () {
var controller = this.get('controller');
controller.loadStep();
}
});
App.ServiceConfigView = Em.View.extend({
templateName: require('templates/common/configs/service_config'),
isRestartMessageCollapsed: false,
filter: '', //from template
columns: [], //from template
canEdit: true, // View is editable or read-only?
supportsHostOverrides: function () {
switch (this.get('controller.name')) {
case 'wizardStep7Controller':
return App.supports.hostOverridesInstaller;
case 'mainServiceInfoConfigsController':
return App.supports.hostOverrides;
case 'mainHostServiceConfigsController':
return App.supports.hostOverridesHost;
default:
return false;
}
}.property('controller.name'),
toggleRestartMessageView: function () {
this.$('.service-body').toggle('blind', 200);
this.set('isRestartMessageCollapsed', !this.get('isRestartMessageCollapsed'));
},
didInsertElement: function () {
if(this.get('isNotEditable') === true) {
this.set('canEdit', false);
}
this.$('.service-body').hide();
$(".restart-required-property").tooltip({html: true});
$(".icon-lock").tooltip({placement: 'right'});
}
});
App.ServiceConfigsByCategoryView = Ember.View.extend({
classNames: ['accordion-group', 'common-config-category'],
classNameBindings: ['category.name', 'isShowBlock::hidden'],
content: null,
category: null,
service: null,
canEdit: true, // View is editable or read-only?
serviceConfigs: null, // General, Advanced, NameNode, SNameNode, DataNode, etc.
// total number of
// hosts (by
// default,
// cacheable )
categoryConfigs: function () {
return this.get('serviceConfigs').filterProperty('category', this.get('category.name')).filterProperty('isVisible', true);
}.property('serviceConfigs.@each', 'categoryConfigsAll.@each.isVisible').cacheable(),
/**
* This method provides all the properties which apply
* to this category, irrespective of visibility. This
* is helpful in Oozie/Hive database configuration, where
* MySQL etc. database options don't show up, because
* they were not visible initially.
*/
categoryConfigsAll: function () {
return this.get('serviceConfigs').filterProperty('category', this.get('category.name'));
}.property('serviceConfigs.@each').cacheable(),
/**
* Warn/prompt user to adjust Service props when changing user/groups in Misc
* Is triggered when user ended editing text field
*/
miscConfigChange: function (manuallyChangedProperty) {
var changedProperty;
if(manuallyChangedProperty.get("id")){
changedProperty = [manuallyChangedProperty];
}else{
changedProperty = this.get("serviceConfigs").filterProperty("editDone", true);
}
if (changedProperty.length > 0) {
changedProperty = changedProperty.objectAt(0);
} else {
return;
}
if (this.get('controller.selectedService.serviceName') == 'MISC') {
var newValue = changedProperty.get("value");
var stepConfigs = this.get("controller.stepConfigs");
this.affectedProperties = [];
var curConfigs = "";
var affectedPropertyName = App.get('isHadoop2Stack') ? "dfs.permissions.superusergroup" : "dfs.permissions.supergroup";
if (changedProperty.get("name") == "hdfs_user") {
curConfigs = stepConfigs.findProperty("serviceName", "HDFS").get("configs");
if (newValue != curConfigs.findProperty("name", affectedPropertyName).get("value")) {
this.affectedProperties.push(
{
serviceName: "HDFS",
propertyName: affectedPropertyName,
propertyDisplayName: affectedPropertyName,
newValue: newValue,
curValue: curConfigs.findProperty("name", affectedPropertyName).get("value"),
changedPropertyName: "hdfs_user"
}
);
}
if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "dfs.cluster.administrators").get("value"))) {
this.affectedProperties.push(
{
serviceName: "HDFS",
propertyName: "dfs.cluster.administrators",
propertyDisplayName: "dfs.cluster.administrators",
newValue: " " + $.trim(newValue),
curValue: curConfigs.findProperty("name", "dfs.cluster.administrators").get("value"),
changedPropertyName: "hdfs_user"
}
);
}
} else if (changedProperty.get("name") == "hbase_user" && !App.get('isHadoop2Stack')) {
curConfigs = stepConfigs.findProperty("serviceName", "HDFS").get("configs");
if (newValue != curConfigs.findProperty("name", "dfs.block.local-path-access.user").get("value")) {
this.affectedProperties.push(
{
serviceName: "HDFS",
propertyName: "dfs.block.local-path-access.user",
propertyDisplayName: "dfs.block.local-path-access.user",
newValue: newValue,
curValue: curConfigs.findProperty("name", "dfs.block.local-path-access.user").get("value"),
changedPropertyName: "hbase_user"
}
);
}
var hbaseCurConfigs = stepConfigs.findProperty("serviceName", "HBASE").get("configs");
if (newValue != hbaseCurConfigs.findProperty("name", "hbase.superuser").get("value")) {
this.affectedProperties.push(
{
serviceName: "HBASE",
propertyName: "hbase.superuser",
propertyDisplayName: "hbase.superuser",
newValue: newValue,
curValue: hbaseCurConfigs.findProperty("name", "hbase.superuser").get("value"),
changedPropertyName: "hbase_user"
}
);
}
} else if (changedProperty.get("name") == "user_group") {
if (!((this.get("controller.selectedServiceNames").indexOf("MAPREDUCE") >= 0) || (this.get("controller.selectedServiceNames").indexOf("YARN") >= 0))) {
return;
}
if(this.get("controller.selectedServiceNames").indexOf("MAPREDUCE") >= 0) {
curConfigs = stepConfigs.findProperty("serviceName", "MAPREDUCE").get("configs");
if (newValue != curConfigs.findProperty("name", "mapreduce.tasktracker.group").get("value")) {
this.affectedProperties.push(
{
serviceName: "MAPREDUCE",
propertyName: "mapreduce.tasktracker.group",
propertyDisplayName: "mapreduce.tasktracker.group",
newValue: newValue,
curValue: curConfigs.findProperty("name", "mapreduce.tasktracker.group").get("value"),
changedPropertyName: "user_group"
}
)
}
if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"))) {
this.affectedProperties.push(
{
serviceName: "MAPREDUCE",
propertyName: "mapreduce.cluster.administrators",
propertyDisplayName: "mapreduce.cluster.administrators",
newValue: " " + $.trim(newValue),
curValue: curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"),
changedPropertyName: "user_group"
}
);
}
}
if(this.get("controller.selectedServiceNames").indexOf("MAPREDUCE2") >= 0) {
curConfigs = stepConfigs.findProperty("serviceName", "MAPREDUCE2").get("configs");
if ($.trim(newValue) != $.trim(curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"))) {
this.affectedProperties.push(
{
serviceName: "MAPREDUCE2",
propertyName: "mapreduce.cluster.administrators",
propertyDisplayName: "mapreduce.cluster.administrators",
newValue: " " + $.trim(newValue),
curValue: curConfigs.findProperty("name", "mapreduce.cluster.administrators").get("value"),
changedPropertyName: "user_group"
}
);
}
}
if (this.get("controller.selectedServiceNames").indexOf("YARN") >= 0) {
curConfigs = stepConfigs.findProperty("serviceName", "YARN").get("configs");
if (newValue != curConfigs.findProperty("name", "yarn.nodemanager.linux-container-executor.group").get("value")) {
this.affectedProperties.push(
{
serviceName: "YARN",
propertyName: "yarn.nodemanager.linux-container-executor.group",
propertyDisplayName: "yarn.nodemanager.linux-container-executor.group",
newValue: newValue,
curValue: curConfigs.findProperty("name", "yarn.nodemanager.linux-container-executor.group").get("value"),
changedPropertyName: "user_group"
}
)
}
}
}
if (this.affectedProperties.length > 0 && !this.get("controller.miscModalVisible")) {
this.newAffectedProperties = this.affectedProperties;
var self = this;
return App.ModalPopup.show({
classNames: ['modal-690px-width'],
showCloseButton: false,
header: "Warning: you must also change these Service properties",
onApply: function () {
self.get("newAffectedProperties").forEach(function (item) {
self.get("controller.stepConfigs").findProperty("serviceName", item.serviceName).get("configs")
.findProperty("name", item.propertyName).set("value", item.newValue);
});
self.get("controller").set("miscModalVisible", false);
this.hide();
},
onIgnore: function () {
self.get("controller").set("miscModalVisible", false);
this.hide();
},
onUndo: function () {
var affected = self.get("newAffectedProperties").objectAt(0);
self.get("controller.stepConfigs").findProperty("serviceName", "MISC").get("configs")
.findProperty("name", affected.changedPropertyName).set("value", $.trim(affected.curValue));
self.get("controller").set("miscModalVisible", false);
this.hide();
},
footerClass: Ember.View.extend({
classNames: ['modal-footer'],
template: Ember.Handlebars.compile([
'<div class="pull-right">',
'<button class="btn" {{action onUndo target="view.parentView"}}>Cancel</button>',
'<button class="btn btn-warning" {{action onIgnore target="view.parentView"}}>Ignore</button>',
'<button class="btn btn-success" {{action onApply target="view.parentView"}}>Apply</button>',
'</div>'
].join(''))
}),
bodyClass: Ember.View.extend({
templateName: require('templates/common/configs/propertyDependence'),
controller: this,
propertyChange: self.get("newAffectedProperties"),
didInsertElement: function () {
self.get("controller").set("miscModalVisible", true);
}
})
});
}
}
}.observes('categoryConfigs.@each.editDone'),
/**
* When the view is in read-only mode, it marks
* the properties as read-only.
*/
updateReadOnlyFlags: function () {
var configs = this.get('serviceConfigs');
var canEdit = this.get('canEdit');
if (!canEdit && configs) {
configs.forEach(function(c){
c.set('isEditable', false);
var overrides = c.get('overrides');
if (overrides!=null) {
overrides.setEach('isEditable', false);
}
});
}
},
/**
* Filtered <code>categoryConfigs</code> array. Used to show filtered result
*/
filteredCategoryConfigs: function () {
var filter = this.get('parentView.filter').toLowerCase();
//var isOnlyModified = this.get('parentView.columns').length && this.get('parentView.columns')[1].get('selected');
var isOnlyOverridden = this.get('parentView.columns').length && this.get('parentView.columns')[0].get('selected');
//var isOnlyRestartRequired = this.get('parentView.columns').length && this.get('parentView.columns')[2].get('selected');
var filteredResult = this.get('categoryConfigs').filter(function (config) {
/* if (isOnlyModified && !config.get('isNotDefaultValue')) {
return false;
}
if (isOnlyRestartRequired && !config.get('isRestartRequired')) {
return false;
}*/
if (isOnlyOverridden && !config.get('isOverridden')) {
return false;
}
var searchString = config.get('defaultValue') + config.get('description') +
config.get('displayName') + config.get('name');
if (config.get('overrides')) {
config.get('overrides').forEach(function(overriddenConf){
searchString += overriddenConf.get('value') + overriddenConf.get('group.name');
});
}
return searchString.toLowerCase().indexOf(filter) > -1;
});
filteredResult = this.sortByIndex(filteredResult);
return filteredResult;
}.property('categoryConfigs', 'parentView.filter', 'parentView.columns.@each.selected'),
/**
* sort configs in current category by index
* @param configs
* @return {*}
*/
sortByIndex: function (configs) {
var sortedConfigs = [];
var unSorted = [];
if (!configs.someProperty('index')) {
return configs;
}
configs.forEach(function (config) {
var index = config.get('index');
if ((index !== null) && isFinite(index)) {
sortedConfigs[index] ? sortedConfigs.splice(index, 0, config) : sortedConfigs[index] = config;
} else {
unSorted.push(config);
}
});
// remove undefined elements from array
sortedConfigs = sortedConfigs.filter(function (config) {
if (config !== undefined) return true;
});
return sortedConfigs.concat(unSorted);
},
/**
* Onclick handler for Config Group Header. Used to show/hide block
*/
onToggleBlock: function () {
this.$('.accordion-body').toggle('blind', 500);
this.set('category.isCollapsed', !this.get('category.isCollapsed'));
},
/**
* Should we show config group or not
*/
isShowBlock: function () {
return this.get('category.canAddProperty') || this.get('filteredCategoryConfigs').length > 0;
}.property('category.canAddProperty', 'filteredCategoryConfigs.length'),
didInsertElement: function () {
var isCollapsed = (this.get('category.name').indexOf('Advanced') != -1);
var self = this;
this.set('category.isCollapsed', isCollapsed);
if (isCollapsed) {
this.$('.accordion-body').hide();
}
this.updateReadOnlyFlags();
Em.run.next(function() {
self.updateReadOnlyFlags();
});
},
childView: App.ServiceConfigsOverridesView,
changeFlag: Ember.Object.create({
val: 1
}),
isOneOfAdvancedSections: function () {
var category = this.get('category');
return category.indexOf("Advanced") != -1;
},
showAddPropertyWindow: function (event) {
var configsOfFile = this.get('service.configs').filterProperty('filename', this.get('category.siteFileName'));
var self =this;
var serviceConfigObj = Ember.Object.create({
name: '',
value: '',
defaultValue: null,
filename: '',
isUserProperty: true,
isKeyError: false,
errorMessage: "",
observeAddPropertyValue: function () {
var name = this.get('name');
if (name.trim() != "") {
if (validator.isValidConfigKey(name)) {
var configMappingProperty = App.config.get('configMapping').all().filterProperty('filename',self.get('category.siteFileName')).findProperty('name', name);
if ((configMappingProperty == null) && (!configsOfFile.findProperty('name', name))) {
this.set("isKeyError", false);
this.set("errorMessage", "");
} else {
this.set("isKeyError", true);
this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.error.derivedKey'));
}
} else {
this.set("isKeyError", true);
this.set("errorMessage", Em.I18n.t('form.validator.configKey'));
}
} else {
this.set("isKeyError", true);
this.set("errorMessage", Em.I18n.t('services.service.config.addPropertyWindow.errorMessage'));
}
}.observes("name")
});
var category = this.get('category');
serviceConfigObj.displayType = "advanced";
serviceConfigObj.category = category.get('name');
var serviceName = this.get('service.serviceName');
var serviceConfigsMetaData = App.config.get('preDefinedServiceConfigs');
var serviceConfigMetaData = serviceConfigsMetaData.findProperty('serviceName', serviceName);
var categoryMetaData = serviceConfigMetaData == null ? null : serviceConfigMetaData.configCategories.findProperty('name', category.get('name'));
if (categoryMetaData != null) {
serviceConfigObj.filename = categoryMetaData.siteFileName;
}
var self = this;
App.ModalPopup.show({
// classNames: ['big-modal'],
classNames: [ 'sixty-percent-width-modal'],
header: "Add Property",
primary: 'Add',
secondary: 'Cancel',
onPrimary: function () {
serviceConfigObj.observeAddPropertyValue();
/**
* For the first entrance use this if (serviceConfigObj.name.trim() != "")
*/
if (!serviceConfigObj.isKeyError) {
serviceConfigObj.displayName = serviceConfigObj.name;
serviceConfigObj.id = 'site property';
serviceConfigObj.serviceName = serviceName;
serviceConfigObj.displayType = stringUtils.isSingleLine(serviceConfigObj.get('value')) ? 'advanced' : 'multiLine';
var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj);
self.get('controller.secureConfigs').filterProperty('filename', self.get('category.siteFileName')).forEach(function (_secureConfig) {
if (_secureConfig.name === serviceConfigProperty.get('name')) {
serviceConfigProperty.set('isSecureConfig', true);
}
}, this);
self.get('serviceConfigs').pushObject(serviceConfigProperty);
this.hide();
}
},
onSecondary: function () {
this.hide();
},
bodyClass: Ember.View.extend({
templateName: require('templates/common/configs/addPropertyWindow'),
controllerBinding: 'App.router.mainServiceInfoConfigsController',
serviceConfigProperty: serviceConfigObj
})
});
},
/**
* Removes the top-level property from list of properties.
* Should be only called on user properties.
*/
removeProperty: function (event) {
var serviceConfigProperty = event.contexts[0];
this.get('serviceConfigs').removeObject(serviceConfigProperty);
},
/**
* Restores given property's value to be its default value.
* Does not update if there is no default value.
*/
doRestoreDefaultValue: function (event) {
var serviceConfigProperty = event.contexts[0];
var value = serviceConfigProperty.get('value');
var dValue = serviceConfigProperty.get('defaultValue');
if (dValue != null) {
if (serviceConfigProperty.get('displayType') === 'password') {
serviceConfigProperty.set('retypedPassword', dValue);
}
serviceConfigProperty.set('value', dValue);
}
this.miscConfigChange(serviceConfigProperty);
},
createOverrideProperty: function (event) {
var serviceConfigProperty = event.contexts[0];
var serviceConfigController = this.get('controller');
var selectedConfigGroup = serviceConfigController.get('selectedConfigGroup');
if (selectedConfigGroup.get('isDefault')) {
// Launch dialog to pick/create Config-group
App.config.launchConfigGroupSelectionCreationDialog(this.get('service.serviceName'),
serviceConfigController.get('configGroups'), serviceConfigProperty, function (selectedGroupInPopup) {
console.log("launchConfigGroupSelectionCreationDialog(): Selected/Created:", selectedGroupInPopup);
if (selectedGroupInPopup) {
serviceConfigController.set('overrideToAdd', serviceConfigProperty);
serviceConfigController.set('selectedConfigGroup', selectedGroupInPopup);
}
});
} else {
serviceConfigController.addOverrideProperty(serviceConfigProperty);
}
},
showOverrideWindow: function (event) {
// argument 1
var serviceConfigProperty = event.contexts[0];
var parentServiceConfigProperty = serviceConfigProperty.get('parentSCP');
var alreadyOverriddenHosts = [];
parentServiceConfigProperty.get('overrides').forEach(function (override) {
if (override != null && override != serviceConfigProperty && override.get('selectedHostOptions') != null) {
alreadyOverriddenHosts = alreadyOverriddenHosts.concat(override.get('selectedHostOptions'))
}
});
var selectedHosts = serviceConfigProperty.get('selectedHostOptions');
/**
* Get all the hosts available for selection. Since data is dependent on
* controller, we ask it, instead of doing regular Ember's App.Host.find().
* This should be an array of App.Host.
*/
var allHosts = this.get('controller.getAllHosts');
var availableHosts = Ember.A([]);
allHosts.forEach(function (host) {
var hostId = host.get('id');
if (alreadyOverriddenHosts.indexOf(hostId) < 0) {
availableHosts.pushObject(Ember.Object.create({
selected: selectedHosts.indexOf(hostId) > -1,
host: host
}));
}
});
/**
* From the currently selected service we want the service-components.
* We only need an array of objects which have the 'componentName' and
* 'displayName' properties. Since each controller has its own objects,
* we ask for a normalized array back.
*/
var validComponents = this.get('controller.getCurrentServiceComponents');
hostsUtils.launchHostsSelectionDialog(availableHosts, selectedHosts,
false, validComponents, function(newSelectedHosts){
if (newSelectedHosts!=null) {
serviceConfigProperty.set('selectedHostOptions', newSelectedHosts);
serviceConfigProperty.validate();
} else {
// Dialog cancelled
// If property has no hosts already, then remove it from the parent.
var hostCount = serviceConfigProperty.get('selectedHostOptions.length');
if (hostCount < 1) {
var parentSCP = serviceConfigProperty.get('parentSCP');
var overrides = parentSCP.get('overrides');
overrides.removeObject(serviceConfigProperty);
}
}
});
}
});
App.ServiceConfigTab = Ember.View.extend({
tagName: 'li',
selectService: function (event) {
this.set('controller.selectedService', event.context);
},
didInsertElement: function () {
var serviceName = this.get('controller.selectedService.serviceName');
this.$('a[href="#' + serviceName + '"]').tab('show');
}
});
/**
* custom view for capacity scheduler category
* @type {*}
*/
App.ServiceConfigCapacityScheduler = App.ServiceConfigsByCategoryView.extend({
templateName: require('templates/common/configs/capacity_scheduler'),
category: null,
service: null,
serviceConfigs: null,
customConfigs: function(){
return App.config.get('preDefinedCustomConfigs');
}.property('App.config.preDefinedCustomConfigs'),
/**
* configs filtered by capacity-scheduler category
*/
categoryConfigs: function () {
return this.get('serviceConfigs').filterProperty('category', this.get('category.name'));
}.property('queueObserver', 'serviceConfigs.@each'),
/**
* rewrote method to avoid incompatibility with parent
*/
filteredCategoryConfigs: function () {
return this.get('categoryConfigs');
}.property(),
advancedConfigs: function () {
return this.get('categoryConfigs').filterProperty('isQueue', undefined) || [];
}.property('categoryConfigs.@each'),
didInsertElement: function () {
this._super();
this.createEmptyQueue(this.get('customConfigs').filterProperty('isQueue'));
},
//list of fields which will be populated by default in a new queue
fieldsToPopulate: function(){
if(App.get('isHadoop2Stack')){
return ["yarn.scheduler.capacity.root.<queue-name>.user-limit-factor",
"yarn.scheduler.capacity.root.<queue-name>.state"];
}
return [
"mapred.capacity-scheduler.queue.<queue-name>.minimum-user-limit-percent",
"mapred.capacity-scheduler.queue.<queue-name>.user-limit-factor",
"mapred.capacity-scheduler.queue.<queue-name>.supports-priority",
"mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks",
"mapred.capacity-scheduler.queue.<queue-name>.maximum-initialized-active-tasks-per-user",
"mapred.capacity-scheduler.queue.<queue-name>.init-accept-jobs-factor"
];
}.property('App.isHadoop2Stack'),
/**
* create empty queue
* take some queue then copy it and set all config values to null
* @param customConfigs
*/
createEmptyQueue: function (customConfigs) {
var emptyQueue = {
name: '<queue-name>',
configs: []
};
var fieldsToPopulate = this.get('fieldsToPopulate');
customConfigs.forEach(function (config) {
var newConfig = $.extend({}, config);
if (fieldsToPopulate.contains(config.name)) {
newConfig.value = config.defaultValue;
}
newConfig = App.ServiceConfigProperty.create(newConfig);
newConfig.validate();
emptyQueue.configs.push(newConfig);
});
this.set('emptyQueue', emptyQueue);
},
deriveQueueNames: function(configs){
var queueNames = [];
configs.mapProperty('name').forEach(function(name){
var queueName;
if(App.get('isHadoop2Stack')){
queueName = /^yarn\.scheduler\.capacity\.root\.(.*?)\./.exec(name);
} else {
queueName = /^mapred\.capacity-scheduler\.queue\.(.*?)\./.exec(name);
}
if(queueName){
queueNames.push(queueName[1]);
}
});
return queueNames.uniq();
},
queues: function(){
var configs = this.get('categoryConfigs').filterProperty('isQueue', true);
var queueNames = this.deriveQueueNames(configs);
var queues = [];
queueNames.forEach(function(queueName){
queues.push({
name: queueName,
color: this.generateColor(queueName),
configs: this.groupConfigsByQueue(queueName, configs)
})
}, this);
return queues;
}.property('queueObserver'),
/**
* group configs by queue
* @param queueName
* @param configs
*/
groupConfigsByQueue: function (queueName, configs) {
var customConfigs = [];
var queue = [];
this.get('customConfigs').forEach(function(_config){
var copy = $.extend({}, _config);
copy.name = _config.name.replace('<queue-name>', queueName);
customConfigs.push(copy);
});
configs.forEach(function (config) {
var customConfig = customConfigs.findProperty('name', config.get('name'));
if (customConfig) {
config.set('description', customConfig.description);
config.set('displayName', customConfig.displayName);
config.set('isRequired', customConfig.isRequired);
config.set('unit', customConfig.unit);
config.set('displayType', customConfig.displayType);
config.set('valueRange', customConfig.valueRange);
config.set('isVisible', customConfig.isVisible);
config.set('inTable', customConfig.inTable);
config.set('index', customConfig.index);
queue.push(config);
}
});
if(queue.length < customConfigs.length){
this.addMissingProperties(queue, customConfigs);
}
return queue;
},
/**
* add missing properties to queue when they don't come from server
* @param queue
* @param customConfigs
*/
addMissingProperties: function(queue, customConfigs){
customConfigs.forEach(function(_config){
var serviceConfigProperty;
if(!queue.someProperty('name', _config.name)){
_config.value = _config.defaultValue;
serviceConfigProperty = App.ServiceConfigProperty.create(_config);
serviceConfigProperty.validate();
queue.push(serviceConfigProperty);
}
}, this);
},
/**
* convert queues to table content
*/
tableContent: function () {
var result = [];
this.get('queues').forEach(function (queue) {
var usersAndGroups = queue.configs.findProperty('name', this.getUserAndGroupNames(queue.name)[0]).get('value');
usersAndGroups = (usersAndGroups) ? usersAndGroups.split(' ') : [''];
if (usersAndGroups.length == 1) {
usersAndGroups.push('');
}
var queueObject = {
name: queue.name,
color: 'background-color:' + queue.color + ';',
configs: this.sortByIndex(queue.configs.filterProperty('inTable'))
};
//push acl_submit_jobs users
queueObject.configs.unshift({
value: usersAndGroups[1],
inTable: true,
displayName: Em.I18n.t('common.users')
});
//push acl_submit_jobs groups
queueObject.configs.unshift({
value: usersAndGroups[0],
inTable: true,
displayName: Em.I18n.t('services.mapReduce.config.queue.groups')
});
result.push(queueObject);
}, this);
return result;
}.property('queues'),
/**
* create headers depending on existed properties in queue
*/
tableHeaders: function(){
var headers = [
Em.I18n.t('services.mapReduce.config.queue.name')
];
return (this.get('tableContent').length) ?
headers.concat(this.get('tableContent').objectAt(0).configs.filterProperty('inTable').mapProperty('displayName')):
headers;
}.property('tableContent'),
queueObserver: null,
/**
* uses as template for adding new queue
*/
emptyQueue: {},
/**
* get capacities sum of queues except of current
* @param queueName
* @return {Number}
*/
getQueuesCapacitySum: function(queueName){
var capacitySum = 0;
this.get('queues').filter(function(queue){
return queue.name !== queueName;
}).forEach(function(queue){
capacitySum = capacitySum + window.parseInt(queue.configs.find(function(config){
return config.get('name').substr(-9, 9) === '.capacity';
}).get('value'));
});
return capacitySum;
},
/**
* get names of configs, for users and groups, which have different names in HDP1 and HDP2
* @param queueName
* @return {Array}
*/
getUserAndGroupNames: function(queueName){
queueName = queueName || '<queue-name>';
if(App.config.get('isHDP2')){
return ['yarn.scheduler.capacity.root.' + queueName + '.acl_submit_jobs',
'yarn.scheduler.capacity.root.' + queueName + '.acl_administer_jobs']
}
return ['mapred.queue.' + queueName + '.acl-submit-job',
'mapred.queue.' + queueName + '.acl-administer-jobs']
},
generateColor: function (str) {
var hash = 0;
for (var i = 0; i < str.length; i++) {
hash = str.charCodeAt(i) + ((hash << 5) - hash);
}
return '#' + Number(Math.abs(hash)).toString(16).concat('00000').substr(0, 6);
},
/**
* add new queue
* add created configs to serviceConfigs with current queue name
* @param queue
*/
addQueue: function (queue) {
var serviceConfigs = this.get('serviceConfigs');
var admin = [];
var submit = [];
var submitConfig;
var adminConfig;
queue.name = queue.configs.findProperty('name', 'queueName').get('value');
queue.configs.forEach(function (config) {
var adminName = this.getUserAndGroupNames()[1];
var submitName = this.getUserAndGroupNames()[0];
if(config.name == adminName){
if (config.type == 'USERS') {
admin[0] = config.value;
}
if (config.type == 'GROUPS') {
admin[1] = config.value;
}
if (config.isQueue) {
adminConfig = config;
}
}
if(config.name == submitName){
if (config.type == 'USERS') {
submit[0] = config.value;
}
if (config.type == 'GROUPS') {
submit[1] = config.value;
}
if (config.isQueue) {
submitConfig = config;
}
}
config.set('name', config.get('name').replace('<queue-name>', queue.name));
config.set('value', config.get('value').toString());
if (config.isQueue) {
serviceConfigs.push(config);
}
}, this);
adminConfig.set('value', admin.join(' '));
submitConfig.set('value', submit.join(' '));
this.set('queueObserver', new Date().getTime());
},
/**
* delete queue
* delete configs from serviceConfigs which have current queue name
* @param queue
*/
deleteQueue: function (queue) {
var serviceConfigs = this.get('serviceConfigs');
var configNames = queue.configs.filterProperty('isQueue').mapProperty('name');
for (var i = 0, l = serviceConfigs.length; i < l; i++) {
if (configNames.contains(serviceConfigs[i].name)) {
serviceConfigs.splice(i, 1);
l--;
i--;
}
}
this.set('queueObserver', new Date().getTime());
},
/**
* save changes that was made to queue
* edit configs from serviceConfigs which have current queue name
* @param queue
*/
editQueue: function (queue) {
var serviceConfigs = this.get('serviceConfigs');
var configNames = queue.configs.filterProperty('isQueue').mapProperty('name');
serviceConfigs.forEach(function (_config) {
var configName = _config.get('name');
var admin = [];
var submit = [];
//comparison executes including 'queue.<queue-name>' to avoid false matches
var queueNamePrefix = App.config.get('isHDP2') ? 'root.' : 'queue.';
if (configNames.contains(_config.get('name'))) {
if(configName == this.getUserAndGroupNames(queue.name)[0]){
submit = queue.configs.filterProperty('name', configName);
submit = submit.findProperty('type', 'USERS').get('value') + ' ' + submit.findProperty('type', 'GROUPS').get('value');
_config.set('value', submit);
} else if(configName == this.getUserAndGroupNames(queue.name)[1]){
admin = queue.configs.filterProperty('name', configName);
admin = admin.findProperty('type', 'USERS').get('value') + ' ' + admin.findProperty('type', 'GROUPS').get('value');
_config.set('value', admin);
} else {
_config.set('value', queue.configs.findProperty('name', _config.get('name')).get('value').toString());
}
_config.set('name', configName.replace(queueNamePrefix + queue.name, queueNamePrefix + queue.configs.findProperty('name', 'queueName').get('value')));
}
}, this);
this.set('queueObserver', new Date().getTime());
},
pieChart: App.ChartPieView.extend({
w: 200,
h: 200,
queues: null,
didInsertElement: function () {
this.update();
},
data: [{"label":"default", "value":100}],
update: function () {
var self = this;
var data = [];
var queues = this.get('queues');
var capacitiesSum = 0;
queues.forEach(function (queue) {
var value = window.parseInt(queue.configs.find(function(_config){
return _config.get('name').substr(-9, 9) === '.capacity';
}).get('value'));
data.push({
label: queue.name,
value: value,
color: queue.color
})
});
data.mapProperty('value').forEach(function (capacity) {
capacitiesSum += capacity;
});
if (capacitiesSum < 100) {
data.push({
label: Em.I18n.t('common.empty'),
value: (100 - capacitiesSum),
color: 'transparent',
isEmpty: true
})
}
$(d3.select(this.get('selector'))[0]).children().remove();
this.set('data', data);
this.set('palette', new Rickshaw.Color.Palette({
scheme: data.mapProperty('color')
}));
this.appendSvg();
this.get('arcs')
.on("click",function (d, i) {
var event = {context: d.data.label};
if (d.data.isEmpty !== true) self.get('parentView').queuePopup(event);
}).on('mouseover', function (d, i) {
var position = d3.svg.mouse(this);
var label = $('#section_label');
label.css('left', position[0] + 100);
label.css('top', position[1] + 100);
label.text(d.data.label);
label.show();
})
.on('mouseout', function (d, i) {
$('#section_label').hide();
})
}.observes('queues'),
donut: d3.layout.pie().sort(null).value(function (d) {
return d.value;
})
}),
/**
* open popup with chosen queue
* @param event
*/
queuePopup: function (event) {
//if queueName was handed that means "Edit" mode, otherwise "Add" mode
var queueName = event.context || null;
var self = this;
App.ModalPopup.show({
didInsertElement: function () {
if (queueName) {
this.set('header', Em.I18n.t('services.mapReduce.config.editQueue'));
this.set('secondary', Em.I18n.t('common.save'));
if (self.get('queues').length > 1 && self.get('canEdit')) {
this.set('delete', Em.I18n.t('common.delete'));
}
}
},
header: Em.I18n.t('services.mapReduce.config.addQueue'),
secondary: Em.I18n.t('common.add'),
primary: Em.I18n.t('common.cancel'),
delete: null,
isError: function () {
if (!self.get('canEdit')) {
return true;
}
var content = this.get('content');
var configs = content.configs.filter(function (config) {
return !(config.name == self.getUserAndGroupNames(content.name)[0] ||
config.name == self.getUserAndGroupNames(content.name)[1] &&
config.isQueue);
});
return configs.someProperty('isValid', false);
}.property('content.configs.@each.isValid'),
onDelete: function () {
var view = this;
App.ModalPopup.show({
header: Em.I18n.t('popup.confirmation.commonHeader'),
body: Em.I18n.t('hosts.delete.popup.body'),
primary: Em.I18n.t('yes'),
onPrimary: function () {
self.deleteQueue(view.get('content'));
view.hide();
this.hide();
}
});
},
onSecondary: function () {
if (queueName) {
self.editQueue(this.get('content'));
} else {
self.addQueue(this.get('content'));
}
this.hide();
},
/**
* Queue properties order:
* 1. Queue Name
* 2. Capacity
* 3. Max Capacity
* 4. Users
* 5. Groups
* 6. Admin Users
* 7. Admin Groups
* 8. Support Priority
* ...
*/
content: function () {
var content = (queueName) ? self.get('queues').findProperty('name', queueName) : self.get('emptyQueue');
var configs = [];
// copy of queue configs
content.configs.forEach(function (config, index) {
if(config.get('name').substr(-9, 9) === '.capacity') {
//added validation function for capacity property
config.reopen({
validate: function () {
var value = this.get('value');
var isError = false;
var capacitySum = self.getQueuesCapacitySum(content.name);
if (value == '') {
if (this.get('isRequired')) {
this.set('errorMessage', 'This is required');
isError = true;
} else {
return;
}
}
if (!isError) {
if (!validator.isValidInt(value)) {
this.set('errorMessage', 'Must contain digits only');
isError = true;
}
}
if (!isError) {
if ((capacitySum + parseInt(value)) > 100) {
isError = true;
this.set('errorMessage', 'The sum of capacities more than 100');
}
if (!isError) {
this.set('errorMessage', '');
}
}
}.observes('value')
});
}
if (config.name == 'mapred.capacity-scheduler.queue.' + content.name + '.supports-priority') {
if (config.get('value') == 'true' || config.get('value') === true) {
config.set('value', true);
} else {
config.set('value', false);
}
}
if(config.name === 'yarn.scheduler.capacity.root.' + content.name + '.state'){
config.reopen({
validate: function(){
var value = this.get('value');
this._super();
if(!this.get('errorMessage')){
if(!(value === 'STOPPED' || value === 'RUNNING')){
this.set('errorMessage', 'State value should be RUNNING or STOPPED');
}
}
}.observes('value')
})
}
configs[index] = App.ServiceConfigProperty.create(config);
});
content = {
name: content.name,
configs: configs
};
content = this.insertExtraConfigs(content);
content.configs = self.sortByIndex(content.configs);
return content;
}.property(),
footerClass: Ember.View.extend({
classNames: ['modal-footer', 'host-checks-update'],
template: Ember.Handlebars.compile([
'{{#if view.parentView.delete}}<div class="pull-left">',
'<button class="btn btn-danger" {{action onDelete target="view.parentView"}}>',
'{{view.parentView.delete}}</button></div>{{/if}}',
'<p class="pull-right">',
'{{#if view.parentView.primary}}<button type="button" class="btn" {{action onPrimary target="view.parentView"}}>',
'{{view.parentView.primary}}</button>{{/if}}',
'{{#if view.parentView.secondary}}',
'<button type="button" {{bindAttr disabled="view.parentView.isError"}} class="btn btn-success" {{action onSecondary target="view.parentView"}}>',
'{{view.parentView.secondary}}</button>{{/if}}',
'</p>'
].join(''))
}),
bodyClass: Ember.View.extend({
template: Ember.Handlebars.compile([
'<form class="form-horizontal pre-scrollable" autocomplete="off">{{#each view.parentView.content.configs}}',
'{{#if isVisible}}',
'<div class="row-fluid control-group">',
' <div {{bindAttr class="errorMessage:error :control-label-span :span4"}}>',
' <label>{{displayName}}</label>',
' </div>',
' <div {{bindAttr class="errorMessage:error :control-group :span8"}}>',
' {{view viewClass serviceConfigBinding="this" categoryConfigsBinding="view.categoryConfigs" }}',
' <span class="help-inline">{{errorMessage}}</span>',
' </div>',
'</div>',
'{{/if}}',
'{{/each}}</form>'
].join(''))
}),
/**
* Insert extra config in popup according to queue
*
* the mapred.queue.default.acl-administer-jobs turns into two implicit configs:
* "Admin Users" field and "Admin Groups" field
* the mapred.queue.default.acl-submit-job turns into two implicit configs:
* "Users" field and "Groups" field
* Add implicit config that contain "Queue Name"
* @param content
* @return {*}
*/
insertExtraConfigs: function (content) {
var that = this;
var admin = content.configs.findProperty('name', self.getUserAndGroupNames(content.name)[1]).get('value');
var submit = content.configs.findProperty('name', self.getUserAndGroupNames(content.name)[0]).get('value');
admin = (admin) ? admin.split(' ') : [''];
submit = (submit) ? submit.split(' ') : [''];
if (admin.length < 2) {
admin.push('');
}
if (submit.length < 2) {
submit.push('');
}
var nameField = App.ServiceConfigProperty.create({
name: 'queueName',
displayName: Em.I18n.t('services.mapReduce.extraConfig.queue.name'),
description: Em.I18n.t('services.mapReduce.description.queue.name'),
value: (content.name == '<queue-name>') ? '' : content.name,
validate: function () {
var queueNames = self.get('queues').mapProperty('name');
var value = this.get('value');
var isError = false;
var regExp = /^[a-z]([\_\-a-z0-9]{0,50})\$?$/i;
if (value == '') {
if (this.get('isRequired')) {
this.set('errorMessage', 'This is required');
isError = true;
} else {
return;
}
}
if (!isError) {
if ((queueNames.indexOf(value) !== -1) && (value != content.name)) {
this.set('errorMessage', 'Queue name is already used');
isError = true;
}
}
if (!isError) {
if (!regExp.test(value)) {
this.set('errorMessage', 'Incorrect input');
isError = true;
}
}
if (!isError) {
this.set('errorMessage', '');
}
}.observes('value'),
isRequired: true,
isVisible: true,
isEditable: self.get('canEdit'),
index: 0
});
nameField.validate();
content.configs.unshift(nameField);
var submitUser = App.ServiceConfigProperty.create({
name: self.getUserAndGroupNames(content.name)[0],
displayName: Em.I18n.t('common.users'),
value: submit[0],
description: Em.I18n.t('services.mapReduce.description.queue.submit.user'),
isRequired: true,
isVisible: true,
type: 'USERS',
displayType: "UNIXList",
isEditable: self.get('canEdit'),
index: 3
});
var submitGroup = App.ServiceConfigProperty.create({
name: self.getUserAndGroupNames(content.name)[0],
displayName: Em.I18n.t('services.mapReduce.config.queue.groups'),
description: Em.I18n.t('services.mapReduce.description.queue.submit.group'),
value: submit[1],
isRequired: true,
isVisible: true,
"displayType": "UNIXList",
type: 'GROUPS',
isEditable: self.get('canEdit'),
index: 4
});
var adminUser = App.ServiceConfigProperty.create({
name: self.getUserAndGroupNames(content.name)[1],
displayName: Em.I18n.t('services.mapReduce.config.queue.adminUsers'),
description: Em.I18n.t('services.mapReduce.description.queue.admin.user'),
value: admin[0],
isRequired: true,
isVisible: true,
type: 'USERS',
displayType: "UNIXList",
isEditable: self.get('canEdit'),
index: 5
});
var adminGroup = App.ServiceConfigProperty.create({
name: self.getUserAndGroupNames(content.name)[1],
displayName: Em.I18n.t('services.mapReduce.config.queue.adminGroups'),
value: admin[1],
description: Em.I18n.t('services.mapReduce.description.queue.admin.group'),
isRequired: true,
isVisible: true,
"displayType": "UNIXList",
type: 'GROUPS',
isEditable: self.get('canEdit'),
index: 6
});
submitUser.reopen({
validate: function () {
that.userGroupValidation(this, submitGroup);
}.observes('value')
});
submitGroup.reopen({
validate: function () {
that.userGroupValidation(this, submitUser);
}.observes('value')
});
adminUser.reopen({
validate: function () {
that.userGroupValidation(this, adminGroup);
}.observes('value')
});
adminGroup.reopen({
validate: function () {
that.userGroupValidation(this, adminUser);
}.observes('value')
});
submitUser.validate();
adminUser.validate();
content.configs.push(submitUser);
content.configs.push(submitGroup);
content.configs.push(adminUser);
content.configs.push(adminGroup);
return content;
},
/**
* Validate by follow rules:
* Users can be blank. If it is blank, Groups must not be blank.
* Groups can be blank. If it is blank, Users must not be blank.
* @param context
* @param boundConfig
*/
userGroupValidation: function (context, boundConfig) {
if (context.get('value') == '') {
if (boundConfig.get('value') == '') {
context._super();
} else {
boundConfig.validate();
}
} else {
if (boundConfig.get('value') == '') {
boundConfig.set('errorMessage', '');
}
context._super();
}
}
})
}
});