blob: 8b9c673a67943a106cc8b0fa4d2f9b440a00f786 [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 blueprintUtils = require('utils/blueprint');
App.EnhancedConfigsMixin = Em.Mixin.create(App.ConfigWithOverrideRecommendationParser, {
/**
* this value is used for observing
* whether recommendations for dependent properties was received from server
* @type {number}
*/
recommendationTimeStamp: null,
/**
* this property is used to force update min/max values
* for not default config groups
* @type {boolean}
*/
forceUpdateBoundaries: false,
/**
* object with loaded recommendations
*
* @type {Object}
*/
recommendationsConfigs: null,
/**
* list of recommendations that should be applied without any config changes
*
* @type {Object[]}
*/
initialRecommendations: [],
/**
* flag is true when Ambari changes some of the dependent properties
* @type {boolean}
*/
hasChangedDependencies: Em.computed.and('isControllerSupportsEnhancedConfigs', 'changedProperties.length'),
/**
* defines is block with changed dependent configs should be shown
* rely on controller
* @type {boolean}
*/
isControllerSupportsEnhancedConfigs: Em.computed.existsIn('name', ['wizardStep7Controller','mainServiceInfoConfigsController']),
/**
* Stores name and file name of changed config
* This used only for capacity-scheduler
*
* @property {object}
*/
currentlyChangedConfig: null,
dependenciesGroupMessage: Em.I18n.t('popup.dependent.configs.dependencies.for.groups'),
/**
* message fro alert box for dependent configs
* @type {string}
*/
dependenciesMessage: function() {
var changedProperties = this.get('changedProperties').filterProperty('saveRecommended');
var changedServices = changedProperties.mapProperty('serviceName').uniq();
var cfgLenSuffix = changedProperties.length === 1 ? 'singular' : 'plural';
var sLenSuffix = changedServices.length === 1 ? 'singular' : 'plural';
return Em.I18n.t('popup.dependent.configs.dependencies.config.' + cfgLenSuffix).format(changedProperties.length)
+ Em.I18n.t('popup.dependent.configs.dependencies.service.' + sLenSuffix).format(changedServices.length);
}.property('changedProperties.length'),
/**
* dependent properties that was changed by Ambari
* @type {Object[]}
*/
changedProperties: function() {
return this.get('recommendations').filter(function(dp) {
return (this.get('selectedConfigGroup.isDefault') && Em.get(dp, 'configGroup').contains('Default'))
|| [this.get('selectedConfigGroup.name'), this.get('selectedConfigGroup.dependentConfigGroups') && this.get('selectedConfigGroup.dependentConfigGroups')[Em.get(dp, 'serviceName')]].contains(Em.get(dp, 'configGroup'));
}, this);
}.property('recommendations.@each.saveRecommended', 'recommendations.@each.recommendedValue', 'selectedConfigGroup'),
/**
* defines if change dependent group message should be shown
* @type {boolean}
*/
showSelectGroupsPopup: Em.computed.and('!selectedConfigGroup.isDefault', 'selectedService.dependentServiceNames.length'),
/**
* set default values for dependentGroups
* @method setDependentGroups
*/
setDependentGroups: function () {
if (this.get('selectedConfigGroup') && this.get('isControllerSupportsEnhancedConfigs') && !this.get('selectedConfigGroup.isDefault') && this.get('selectedService.dependentServiceNames.length')) {
this.get('selectedService.dependentServiceNames').forEach(function (serviceName) {
if (!this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) {
var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
if (stepConfig) {
stepConfig.get('configGroups').filterProperty('isDefault', false).forEach(function (configGroup) {
this.get('selectedService.configGroups').filterProperty('isDefault', false).forEach(function (currentServiceGroup) {
if (currentServiceGroup.get('dependentConfigGroups')[serviceName] != configGroup.get('name')) {
var dependentGroups = $.extend({},this.get('selectedConfigGroup.dependentConfigGroups'));
dependentGroups[serviceName] = configGroup.get('name');
this.set('selectedConfigGroup.dependentConfigGroups', dependentGroups);
}
}, this);
}, this);
}
}
}, this);
}
}.observes('selectedConfigGroup'),
/******************************METHODS THAT WORKS WITH DEPENDENT CONFIGS *************************************/
/**
* get config group object for current service
* @param serviceName
* @returns {App.ConfigGroup|null}
*/
getGroupForService: function(serviceName) {
if (!this.get('stepConfigs') || this.get('stepConfigs.length') === 0) {
return null;
}
if (this.get('selectedService.serviceName') === serviceName) {
return this.get('selectedConfigGroup');
} else {
var stepConfig = this.get('stepConfigs').findProperty('serviceName', serviceName);
if (stepConfig) {
var groups = stepConfig.get('configGroups');
if (this.get('selectedConfigGroup.isDefault')) {
return groups.length ? groups.findProperty('isDefault', true) : null;
} else {
return groups.length ? groups.findProperty('name', this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) : null;
}
} else {
return null;
}
}
},
clearRecommendationsInfo: function() {
this.set('recommendationsConfigs', null);
},
clearRecommendations: function () {
this.clearRecommendationsInfo();
this.clearAllRecommendations();
},
/**
* sends request to get values for dependent configs
* @param {{type: string, name: string}[]} changedConfigs - list of changed configs to track recommendations
* @param {Function} [onComplete]
* @returns {$.ajax|null}
*/
loadConfigRecommendations: function (changedConfigs, onComplete) {
var self = this;
var updateDependencies = Em.isArray(changedConfigs) && changedConfigs.length > 0;
var stepConfigs = this.get('stepConfigs');
if (updateDependencies || Em.isNone(this.get('recommendationsConfigs'))) {
var configGroup = this.get('selectedConfigGroup');
var recommendations = this.get('hostGroups');
var dataToSend = {
recommend: updateDependencies ? 'configuration-dependencies' : 'configurations',
hosts: this.get('hostNames'),
services: this.get('serviceNames')
};
if (updateDependencies) {
dataToSend.changed_configurations = changedConfigs;
}
if (Em.isArray(changedConfigs) && changedConfigs.mapProperty('type').uniq()[0] === 'capacity-scheduler') {
this.set('currentlyChangedConfig', {
name: 'capacity-scheduler',
fileName: 'capacity-scheduler.xml'
});
} else {
this.set('currentlyChangedConfig', null);
}
if (configGroup && !configGroup.get('isDefault') && configGroup.get('hosts.length') > 0) {
recommendations.config_groups = [this.buildConfigGroupJSON(this.get('selectedService.configs'), configGroup)];
} else {
delete recommendations.config_groups;
}
this.setUserContext(dataToSend);
if (stepConfigs.someProperty('serviceName', 'MISC')) {
recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
dataToSend.recommendations = recommendations;
return this.getRecommendationsRequest(dataToSend, onComplete);
} else {
App.config.getClusterEnvConfigs().done(function (clusterEnvConfigs) {
stepConfigs = stepConfigs.concat(Em.Object.create({
serviceName: 'MISC',
configs: clusterEnvConfigs
}));
recommendations.blueprint.configurations = blueprintUtils.buildConfigsJSON(stepConfigs);
dataToSend.recommendations = recommendations;
return self.getRecommendationsRequest(dataToSend, onComplete);
});
}
} else {
if (onComplete) {
onComplete()
}
}
return $.Deferred().resolve().promise();
},
getRecommendationsRequest: function (dataToSend, callback) {
var self = this;
this.set('recommendationsInProgress', true);
return App.ajax.send({
name: 'config.recommendations',
sender: self,
data: {
stackVersionUrl: App.get('stackVersionURL'),
dataToSend: dataToSend
},
success: 'loadRecommendationsSuccess',
error: 'loadRecommendationsError',
callback: function () {
self.set('recommendationsInProgress', false);
self.set('recommendationTimeStamp', (new Date).getTime());
if (callback) {
callback()
}
}
});
},
setUserContext: function(dataToSend) {
var controllerName = this.get('content.controllerName');
var changes = dataToSend.changed_configurations;
if (changes) {
dataToSend['user_context'] = {"operation" : "EditConfig"};
} else {
if (!controllerName) {
dataToSend['user_context'] = {"operation" : "RecommendAttribute"};
} else if (controllerName == 'addServiceController') {
dataToSend['user_context'] = {
"operation" : "AddService",
"operation_details" : (this.get('content.services')|| []).filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName').join(',')
};
} else if (controllerName == 'installerController'){
dataToSend['user_context'] = {"operation" : "ClusterCreate"};
}
}
},
/**
* Defines if there is any changes made by user.
* Check all properties except recommended properties from popup
*
* @returns {boolean}
*/
isConfigHasInitialState: function() {
return this.get('selectedConfigGroup.isDefault') && !Em.isNone(this.get('recommendationsConfigs'))
&& !this.get('stepConfigs').filter(function(stepConfig) {
return stepConfig.get('changedConfigProperties').filter(function(c) {
var changedConfigIds = this.get('changedProperties').map(function(changed) {
return App.config.configId(changed.propertyName, changed.propertyFileName);
});
return !changedConfigIds.contains(App.config.configId(c.get('name'), c.get('filename')));
}, this).length;
}, this).length;
},
/**
* generates JSON with config group info to send it for recommendations
* @param configs
* @param configGroup
* @returns {{configurations: Object[], hosts: string[]}}
*/
buildConfigGroupJSON: function(configs, configGroup) {
Em.assert('configGroup can\'t be null', configGroup);
var hosts = configGroup.get('hosts');
var configurations = {};
var overrides = configs.forEach(function(cp) {
var override = cp.get('overrides') && cp.get('overrides').findProperty('group.name', configGroup.get('name'));
if (override) {
var tag = App.config.getConfigTagFromFileName(cp.get('filename'));
if (!configurations[tag]) {
configurations[tag] = { properties: {} };
}
configurations[tag].properties[cp.get('name')] = override.get('value');
}
});
return {
configurations: [configurations],
hosts: hosts
}
},
/**
* shows popup with results for recommended value
* if case properties that was changes belongs to not default group
* user should pick to what config group from dependent service dependent properties will be saved
* @param data
* @param opt
* @param params
* @method dependenciesSuccess
*/
loadRecommendationsSuccess: function (data, opt, params) {
this._saveRecommendedValues(data, params.dataToSend.changed_configurations);
if (this.isConfigHasInitialState()) {
/** clearing all recommendations info **/
this.undoRedoRecommended(this.get('recommendations'), false);
this.clearAllRecommendations();
/**
* resetting recommendations to initial state
* this case can be present when installed services depends on new added service on ADW
**/
this.undoRedoRecommended(this.get('initialRecommendations'), true);
this.get('initialRecommendations').forEach(function (r) {
this.get('recommendations').pushObject(r);
}, this);
}
this.set("recommendationsConfigs", Em.get(data, "resources.0.recommendations.blueprint.configurations"));
},
loadRecommendationsError: Em.K,
changedDependentGroup: function () {
var dependentServices = this.get('selectedService.dependentServiceNames');
var isInstallWizard = this.get('content.controllerName') === 'installerController';
var installedServices = App.Service.find().mapProperty('serviceName');
var services = this.get('stepConfigs').filter(function (stepConfig) {
if (dependentServices.contains(stepConfig.get('serviceName'))) {
return isInstallWizard ? true : installedServices.contains(stepConfig.get('serviceName'));
}
return false;
}, this);
App.showSelectGroupsPopup(this.get('selectedService.serviceName'),
this.get('selectedService.configGroups').findProperty('name', this.get('selectedConfigGroup.name')),
services, this.get('recommendations'));
},
/**
* saves values from response for dependent config properties to <code>recommendations<code>
* @param data
* @param [changedConfigs=null]
* @method saveRecommendedValues
* @private
*/
_saveRecommendedValues: function(data, changedConfigs) {
Em.assert('invalid data - `data.resources[0].recommendations.blueprint.configurations` not defined ', data && data.resources[0] && Em.get(data.resources[0], 'recommendations.blueprint.configurations'));
var recommendations = data.resources[0].recommendations;
if (recommendations['config-groups'] && this.get('selectedConfigGroup') && !this.get('selectedConfigGroup.isDefault')) {
var configFroGroup = recommendations['config-groups'][0];
this.get('stepConfigs').forEach(function(stepConfig) {
var configGroup = this.getGroupForService(stepConfig.get('serviceName'));
if (configGroup && this.isConfigGroupAffected(configFroGroup.hosts, configGroup.get('hosts'))) {
this.updateOverridesByRecommendations(configFroGroup.configurations, stepConfig.get('configs'), changedConfigs, configGroup);
this.updateOverridesByRecommendations(configFroGroup.dependent_configurations, stepConfig.get('configs'), changedConfigs, configGroup);
this.toggleProperty('forceUpdateBoundaries');
}
}, this);
} else {
var configObject = recommendations.blueprint.configurations;
this.get('stepConfigs').forEach(function(stepConfig) {
this.updateConfigsByRecommendations(configObject, stepConfig.get('configs'), changedConfigs);
}, this);
this.addByRecommendations(configObject, changedConfigs);
}
this.cleanUpRecommendations();
},
/**
* determine whether hosts of group affected by config modifications
* @param {Array} affectedHosts
* @param {Array} groupHosts
* @returns {boolean}
*/
isConfigGroupAffected: function(affectedHosts, groupHosts) {
return _.intersection(affectedHosts, groupHosts).length > 0;
},
/**
* method to show popup with dependent configs
* @method showChangedDependentConfigs
*/
showChangedDependentConfigs: function(event, callback, secondary) {
var self = this;
var recommendations = event ? this.get('changedProperties') : this.get('recommendations'),
recommendedChanges = recommendations.filterProperty('isEditable'),
requiredChanges = this.filterRequiredChanges(recommendations);
if (recommendedChanges.length > 0 || requiredChanges.length > 0) {
App.showDependentConfigsPopup(recommendedChanges, requiredChanges, function() {
self.onSaveRecommendedPopup(recommendedChanges.concat(requiredChanges));
if (callback) callback();
}, secondary);
} else {
if (callback) callback();
}
},
/**
*
* @param {Array} recommendations
* @returns {Array}
*/
filterRequiredChanges: function(recommendations) {
return recommendations.filter(function(recommendation) {
if (recommendation.isEditable === false) {
if (!this.get('selectedConfigGroup.isDefault')) {
return App.ServiceConfigGroup.defaultGroupName !== recommendation.configGroup
} else {
return true;
}
}
}, this);
},
/**
* update configs when toggle checkbox on dependent configs popup
*/
onSaveRecommendedPopup: function(recommendations) {
var propertiesToUpdate = recommendations.filter(function(c) {
return Em.get(c, 'saveRecommendedDefault') != Em.get(c, 'saveRecommended');
}),
propertiesToUndo = propertiesToUpdate.filterProperty('saveRecommended', false),
propertiesToRedo = propertiesToUpdate.filterProperty('saveRecommended', true);
this.undoRedoRecommended(propertiesToUndo, false);
this.undoRedoRecommended(propertiesToRedo, true);
this.set('recommendationTimeStamp', (new Date).getTime());
},
/**
* run through config properties list (form dependent popup)
* and set value to default (undo) or recommended (redo)
* this happens when toggle checkbox in popup
* @param {Object[]} propertiesToUpdate
* @param {boolean} redo
*/
undoRedoRecommended: function(propertiesToUpdate, redo) {
propertiesToUpdate.forEach(function(p) {
var initial = redo ? Em.get(p, 'initialValue') : Em.get(p, 'recommendedValue');
var recommended = redo ? Em.get(p, 'recommendedValue') : Em.get(p, 'initialValue');
var stepConfig = this.get('stepConfigs').findProperty('serviceName', Em.get(p, 'serviceName'));
var config = stepConfig.get('configs').find(function(scp) {
return scp.get('name') == Em.get(p, 'propertyName') && scp.get('filename') == App.config.getOriginalFileName(Em.get(p, 'propertyFileName'));
});
var selectedGroup = App.ServiceConfigGroup.find().filterProperty('serviceName', Em.get(p, 'serviceName')).findProperty('name', Em.get(p, 'configGroup'));
if (selectedGroup.get('isDefault')) {
if (Em.isNone(recommended)) {
stepConfig.get('configs').removeObject(config);
} else if (Em.isNone(initial)) {
var stackConfigProperty = App.configsCollection.getConfigByName(Em.get(p, 'propertyName'), Em.get(p, 'propertyFileName'));
stepConfig.get('configs').pushObject(this._createNewProperty(Em.get(p, 'propertyName'), Em.get(p, 'propertyFileName'),Em.get(p, 'serviceName'),
recommended, stackConfigProperty? stackConfigProperty.propertyDependsOn : []));
} else {
Em.set(config, 'value', recommended);
}
} else {
if (Em.isNone(recommended)) {
config.get('overrides').removeObject(config.getOverride(selectedGroup.get('name')));
} else if (Em.isNone(initial)) {
this._addConfigOverrideRecommendation(config, recommended, null, selectedGroup);
} else {
var override = config.getOverride(selectedGroup.get('name'));
if (override) {
override.set('value', recommended);
}
}
}
}, this);
},
saveInitialRecommendations: function() {
this.get('recommendations').forEach(function (r) {
this.get('initialRecommendations').pushObject(r);
}, this);
},
/**
* disable saving recommended value for current config
* @param config
* @param {boolean} saveRecommended
* @method removeCurrentFromDependentList
*/
removeCurrentFromDependentList: function (config, saveRecommended) {
var recommendation = this.getRecommendation(config.get('name'), config.get('filename'), config.get('group.name'));
if (recommendation) this.saveRecommendation(recommendation, saveRecommended);
}
});