/**
* 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 batchUtils = require('utils/batch_scheduled_requests');
var databaseUtils = require('utils/configs/database');
App.MainServiceInfoConfigsController = Em.Controller.extend(App.ConfigsLoader, App.ServerValidatorMixin, App.EnhancedConfigsMixin, App.ThemesMappingMixin, App.VersionsMappingMixin, App.ConfigsSaverMixin, App.ConfigsComparator, App.ComponentActionsByConfigs, {
name: 'mainServiceInfoConfigsController',
isHostsConfigsPage: false,
isRecommendedLoaded: true,
dataIsLoaded: false,
stepConfigs: [], //contains all field properties that are viewed in this service
selectedService: null,
selectedConfigGroup: null,
requestsInProgress: [],
groupsStore: App.ServiceConfigGroup.find(),
/**
* config groups for current service
* @type {App.ConfigGroup[]}
*/
configGroups: function() {
return this.get('groupsStore').filterProperty('serviceName', this.get('content.serviceName'));
}.property('content.serviceName', 'groupsStore'),
dependentConfigGroups: function() {
if (this.get('dependentServiceNames.length') === 0) return [];
return this.get('groupsStore').filter(function(group) {
return this.get('dependentServiceNames').contains(group.get('serviceName'));
}, this);
}.property('content.serviceName', 'dependentServiceNames', 'groupsStore.length', 'groupsStore.@each.name'),
allConfigs: [],
/**
* Determines if save configs is in progress
* @type {boolean}
*/
saveInProgress: false,
isCompareMode: false,
preSelectedConfigVersion: null,
/**
* contain Service Config Property, when user proceed from Select Config Group dialog
*/
overrideToAdd: null,
/**
* version selected to view
*/
selectedVersion: null,
/**
* note passed on configs save
* @type {string}
*/
serviceConfigVersionNote: '',
versionLoaded: false,
/**
* Determines when data about config groups is loaded
* Including recommendations with information about hosts in the each group
* @type {boolean}
*/
configGroupsAreLoaded: false,
dependentServiceNames: [],
/**
* defines which service configs need to be loaded to stepConfigs
* @type {string[]}
*/
servicesToLoad: function() {
return [this.get('content.serviceName')].concat(this.get('dependentServiceNames')).uniq();
}.property('content.serviceName', 'dependentServiceNames.length'),
/**
* @type {boolean}
*/
isCurrentSelected: function () {
return App.ServiceConfigVersion.find(this.get('content.serviceName') + "_" + this.get('selectedVersion')).get('isCurrent');
}.property('selectedVersion', 'content.serviceName', 'dataIsLoaded', 'versionLoaded'),
/**
* @type {boolean}
*/
canEdit: function () {
return (this.get('selectedVersion') == this.get('currentDefaultVersion') || !this.get('selectedConfigGroup.isDefault'))
&& !this.get('isCompareMode') && App.isAuthorized('SERVICE.MODIFY_CONFIGS');
}.property('selectedVersion', 'isCompareMode', 'currentDefaultVersion', 'selectedConfigGroup.isDefault'),
serviceConfigs: Em.computed.alias('App.config.preDefinedServiceConfigs'),
/**
* Number of errors in the configs in the selected service (only for AdvancedTab if App supports Enhanced Configs)
* @type {number}
*/
errorsCount: function() {
return this.get('selectedService.configsWithErrors').filter(function(c) {
return Em.isNone(c.get('widget'));
}).length;
}.property('selectedService.configsWithErrors'),
/**
* Determines if Save-button should be disabled
* Disabled if some configs have invalid values for selected service
* or save-process currently in progress
*
* @type {boolean}
*/
isSubmitDisabled: function () {
if (!this.get('selectedService')) return true;
return this.get('selectedService').get('errorCount') !== 0 || this.get('saveInProgress');
}.property('selectedService.errorCount', 'saveInProgress'),
/**
* Determines if some config value is changed
* @type {boolean}
*/
isPropertiesChanged: Em.computed.alias('selectedService.isPropertiesChanged'),
/**
* Filter text will be located here
* @type {string}
*/
filter: '',
/**
* List of filters for config properties to populate filter combobox
* @type {{attributeName: string, attributeValue: boolean, caption: string}[]}
*/
propertyFilters: [
{
attributeName: 'isOverridden',
attributeValue: true,
caption: 'common.combobox.dropdown.overridden'
},
{
attributeName: 'isFinal',
attributeValue: true,
caption: 'common.combobox.dropdown.final'
},
{
attributeName: 'hasCompareDiffs',
attributeValue: true,
caption: 'common.combobox.dropdown.changed',
dependentOn: 'isCompareMode'
},
{
attributeName: 'hasIssues',
attributeValue: true,
caption: 'common.combobox.dropdown.issues'
}
],
/**
* Dropdown menu items in filter combobox
* @type {{attributeName: string, attributeValue: string, name: string, selected: boolean}[]}
*/
filterColumns: function () {
var filterColumns = [];
this.get('propertyFilters').forEach(function(filter) {
if (Em.isNone(filter.dependentOn) || this.get(filter.dependentOn)) {
filterColumns.push(Ember.Object.create({
attributeName: filter.attributeName,
attributeValue: filter.attributeValue,
name: this.t(filter.caption),
selected: filter.dependentOn ? this.get(filter.dependentOn) : false
}));
}
}, this);
return filterColumns;
}.property('propertyFilters', 'isCompareMode'),
/**
* Detects of some of the `password`-configs has not default value
*
* @type {boolean}
*/
passwordConfigsAreChanged: function () {
return this.get('stepConfigs')
.findProperty('serviceName', this.get('selectedService.serviceName'))
.get('configs')
.filterProperty('displayType', 'password')
.someProperty('isNotDefaultValue');
}.property('stepConfigs.[].configs', 'selectedService.serviceName'),
/**
* indicate whether service config version belongs to default config group
* @param {object} version
* @return {Boolean}
* @private
* @method isVersionDefault
*/
isVersionDefault: function(version) {
return (App.ServiceConfigVersion.find(this.get('content.serviceName') + "_" + version).get('groupId') == -1);
},
/**
* register request to view to track his progress
* @param {$.ajax} request
* @method trackRequest
*/
trackRequest: function (request) {
this.get('requestsInProgress').push(request);
},
/**
* clear and set properties to default value
* @method clearStep
*/
clearStep: function () {
this.get('requestsInProgress').forEach(function(r) {
if (r && r.readyState !== 4) {
r.abort();
}
});
this.get('requestsInProgress').clear();
this.clearLoadInfo();
this.clearSaveInfo();
this.clearRecommendationsInfo();
this.clearAllRecommendations();
this.setProperties({
saveInProgress: false,
isInit: true,
hash: null,
dataIsLoaded: false,
versionLoaded: false,
filter: '',
serviceConfigVersionNote: '',
dependentServiceNames: [],
configGroupsAreLoaded: false
});
this.get('filterColumns').setEach('selected', false);
this.clearConfigs();
},
clearConfigs: function() {
this.get('selectedConfigGroup', null);
this.get('allConfigs').invoke('destroy');
this.get('stepConfigs').invoke('destroy');
this.set('stepConfigs', []);
this.set('allConfigs', []);
this.set('selectedService', null);
},
/**
* "Finger-print" of the stepConfigs
. Filled after first configGroup selecting
* Used to determine if some changes were made (when user navigates away from this page)
* @type {String|null}
*/
hash: null,
/**
* Is this initial config group changing
* @type {Boolean}
*/
isInit: true,
/**
* On load function
* @method loadStep
*/
loadStep: function () {
var self = this;
var serviceName = this.get('content.serviceName');
this.clearStep();
this.set('dependentServiceNames', App.StackService.find(serviceName).get('dependentServiceNames'));
if (App.get('isClusterSupportsEnhancedConfigs')) {
this.loadConfigTheme(serviceName).always(function() {
App.themesMapper.generateAdvancedTabs([serviceName]);
// Theme mapper has UI only configs that needs to be merged with current service version configs
// This requires calling `loadCurrentVersions` after theme has loaded
self.loadCurrentVersions();
});
} else {
this.loadCurrentVersions();
}
this.loadServiceConfigVersions();
},
/**
* Generate "finger-print" for current stepConfigs[0]
* Used to determine, if user has some unsaved changes (comparing with hash
)
* @returns {string|null}
* @method getHash
*/
getHash: function () {
if (!this.get('selectedService.configs.length')) {
return null;
}
var hash = {};
this.get('selectedService.configs').forEach(function (config) {
hash[config.get('name')] = {value: App.config.formatPropertyValue(config), overrides: [], isFinal: config.get('isFinal')};
if (!config.get('overrides')) return;
if (!config.get('overrides.length')) return;
config.get('overrides').forEach(function (override) {
hash[config.get('name')].overrides.push(App.config.formatPropertyValue(override));
});
});
return JSON.stringify(hash);
},
parseConfigData: function(data) {
this.prepareConfigObjects(data, this.get('content.serviceName'));
var self = this;
this.loadCompareVersionConfigs(this.get('allConfigs')).done(function() {
self.addOverrides(data, self.get('allConfigs'));
self.onLoadOverrides(self.get('allConfigs'));
});
},
prepareConfigObjects: function(data, serviceName) {
this.get('stepConfigs').clear();
var configs = [];
data.items.forEach(function (version) {
if (version.group_name == 'default') {
version.configurations.forEach(function (configObject) {
configs = configs.concat(App.config.getConfigsFromJSON(configObject, true));
});
}
});
configs = App.config.sortConfigs(configs);
/**
* if property defined in stack but somehow it missed from cluster properties (can be after stack upgrade)
* ui should add this properties to step configs
*/
configs = this.mergeWithStackProperties(configs);
//put properties from capacity-scheduler.xml into one config with textarea view
if (this.get('content.serviceName') === 'YARN') {
configs = App.config.addYarnCapacityScheduler(configs);
}
if (this.get('content.serviceName') === 'KERBEROS') {
var kdc_type = configs.findProperty('name', 'kdc_type');
if (kdc_type.get('value') === 'none') {
configs.findProperty('name', 'kdc_host').set('isVisible', false);
configs.findProperty('name', 'admin_server_host').set('isVisible', false);
configs.findProperty('name', 'domains').set('isVisible', false);
} else if (kdc_type.get('value') === 'active-directory') {
configs.findProperty('name', 'container_dn').set('isVisible', true);
configs.findProperty('name', 'ldap_url').set('isVisible', true);
} else if (kdc_type.get('value') === 'ipa') {
configs.findProperty('name', 'group').set('isVisible', true);
configs.findProperty('name', 'manage_krb5_conf').set('value', false);
configs.findProperty('name', 'install_packages').set('value', false);
configs.findProperty('name', 'admin_server_host').set('isVisible', false);
configs.findProperty('name', 'domains').set('isVisible', false);
}
}
this.setPropertyIsEditable(configs);
this.set('allConfigs', configs);
},
/**
* Set isEditable proeperty based on selected group, security
* and controller restriction
* @param configs
*/
setPropertyIsEditable: function(configs) {
if (!this.get('selectedConfigGroup.isDefault') || !this.get('canEdit')) {
configs.setEach('isEditable', false);
} else if (App.get('isKerberosEnabled')) {
configs.filterProperty('isSecureConfig').setEach('isEditable', false);
}
},
/**
* adds properties form stack that doesn't belong to cluster
* to step configs
* also set recommended value if isn't exists
*
* @return {App.ServiceConfigProperty[]}
* @method mergeWithStackProperties
*/
mergeWithStackProperties: function (configs) {
App.config.getPropertiesFromTheme(this.get('content.serviceName')).forEach(function (advanced_id) {
if (!configs.someProperty('id', advanced_id)) {
var advanced = App.configsCollection.getConfig(advanced_id);
if (advanced) {
advanced.savedValue = null;
advanced.isNotSaved = true;
configs.pushObject(App.ServiceConfigProperty.create(advanced));
}
}
});
return configs;
},
addOverrides: function(data, allConfigs) {
var self = this;
data.items.forEach(function(group) {
if (group.group_name != 'default') {
var configGroup = App.ServiceConfigGroup.find().filterProperty('serviceName', group.service_name).findProperty('name', group.group_name);
group.configurations.forEach(function(config) {
for (var prop in config.properties) {
var fileName = App.config.getOriginalFileName(config.type);
var serviceConfig = allConfigs.filterProperty('name', prop).findProperty('filename', fileName);
if (serviceConfig) {
var value = App.config.formatPropertyValue(serviceConfig, config.properties[prop]);
var isFinal = !!(config.properties_attributes && config.properties_attributes.final && config.properties_attributes.final[prop]);
if (self.get('selectedConfigGroup.isDefault') || configGroup.get('name') == self.get('selectedConfigGroup.name')) {
var overridePlainObject = {
"value": value,
"savedValue": value,
"isFinal": isFinal,
"savedIsFinal": isFinal,
"isEditable": self.get('canEdit') && configGroup.get('name') == self.get('selectedConfigGroup.name')
};
App.config.createOverride(serviceConfig, overridePlainObject, configGroup);
}
} else {
var isEditable = self.get('canEdit') && configGroup.get('name') == self.get('selectedConfigGroup.name');
allConfigs.push(App.config.createCustomGroupConfig(prop, fileName, config.properties[prop], configGroup, isEditable));
}
}
});
}
});
},
/**
* @param allConfigs
* @private
* @method onLoadOverrides
*/
onLoadOverrides: function (allConfigs) {
this.get('servicesToLoad').forEach(function(serviceName) {
var configGroups = serviceName == this.get('content.serviceName') ? this.get('configGroups') : this.get('dependentConfigGroups').filterProperty('serviceName', serviceName);
var configTypes = App.StackService.find(serviceName).get('configTypeList');
var configsByService = this.get('allConfigs').filter(function (c) {
return configTypes.contains(App.config.getConfigTagFromFileName(c.get('filename')));
});
var serviceConfig = App.config.createServiceConfig(serviceName, configGroups, configsByService, configsByService.length);
this.addHostNamesToConfigs(serviceConfig);
this.get('stepConfigs').pushObject(serviceConfig);
}, this);
var selectedService = this.get('stepConfigs').findProperty('serviceName', this.get('content.serviceName'));
this.set('selectedService', selectedService);
this.checkOverrideProperty(selectedService);
if (App.Service.find().someProperty('serviceName', 'RANGER')) {
this.setVisibilityForRangerProperties(selectedService);
} else {
App.config.removeRangerConfigs(this.get('stepConfigs'));
}
this.loadConfigRecommendations(null, this._onLoadComplete.bind(this));
App.loadTimer.finish('Service Configs Page');
},
/**
* @method _getRecommendationsForDependenciesCallback
*/
_onLoadComplete: function () {
this.get('stepConfigs').forEach(function(serviceConfig){
serviceConfig.set('initConfigsLength', serviceConfig.get('configs.length'));
});
this.setProperties({
dataIsLoaded: true,
versionLoaded: true,
isInit: false,
hash: this.getHash()
});
},
/**
* hide properties from Advanced ranger category that match pattern
* if property with dependentConfigPattern is false otherwise don't hide
* @param serviceConfig
* @private
* @method setVisibilityForRangerProperties
*/
setVisibilityForRangerProperties: function(serviceConfig) {
var category = "Advanced ranger-{0}-plugin-properties".format(this.get('content.serviceName').toLowerCase());
if (serviceConfig.configCategories.findProperty('name', category)) {
var patternConfig = serviceConfig.configs.findProperty('dependentConfigPattern');
if (patternConfig) {
var value = patternConfig.get('value') === true || ["yes", "true"].contains(patternConfig.get('value').toLowerCase());
serviceConfig.configs.filter(function(c) {
if (c.get('category') === category && c.get('name').match(patternConfig.get('dependentConfigPattern')) && c.get('name') != patternConfig.get('name'))
c.set('isVisible', value);
});
}
}
},
/**
* Allow update property if recommendations
* is based on changing property
*
* @param parentProperties
* @returns {boolean}
* @override
*/
allowUpdateProperty: function(parentProperties) {
return !!(parentProperties && parentProperties.length);
},
/**
* trigger App.config.createOverride
* @param {Object[]} stepConfig
* @private
* @method checkOverrideProperty
*/
checkOverrideProperty: function (stepConfig) {
var overrideToAdd = this.get('overrideToAdd');
var value = !!this.get('overrideToAdd.widget') ? Em.get(overrideToAdd, 'value') : '';
if (overrideToAdd) {
overrideToAdd = stepConfig.configs.filter(function(c){
return c.name == overrideToAdd.name && c.filename == overrideToAdd.filename;
});
if (overrideToAdd[0]) {
App.config.createOverride(overrideToAdd[0], {"isEditable": true, "value": value}, this.get('selectedConfigGroup'));
this.set('overrideToAdd', null);
}
}
},
/**
*
* @param serviceConfig
*/
addHostNamesToConfigs: function(serviceConfig) {
serviceConfig.get('configCategories').forEach(function(c) {
if (c.showHost) {
var stackComponent = App.StackServiceComponent.find(c.name);
var component = stackComponent.get('isMaster') ? App.MasterComponent.find(c.name) : App.SlaveComponent.find(c.name);
var hProperty = App.config.createHostNameProperty(serviceConfig.get('serviceName'), c.name, component.get('hostNames') || [], stackComponent);
serviceConfig.get('configs').push(App.ServiceConfigProperty.create(hProperty));
}
}, this);
App.ConfigAction.find().forEach(function(item){
var hostComponentConfig = item.get('hostComponentConfig');
var config = serviceConfig.get('configs').filterProperty('filename', hostComponentConfig.fileName).findProperty('name', hostComponentConfig.configName);
if (config){
var componentHostName = App.HostComponent.find().findProperty('componentName', item.get('componentName')) ;
if (componentHostName) {
var setConfigValue = !config.get('value');
if (setConfigValue) {
config.set('value', componentHostName.get('hostName'));
config.set('recommendedValue', componentHostName.get('hostName'));
}
}
}
}, this);
},
/**
* Trigger loadSelectedVersion
* @method doCancel
*/
doCancel: function () {
this.set('preSelectedConfigVersion', null);
this.clearAllRecommendations();
this.loadSelectedVersion(this.get('selectedVersion'), this.get('selectedConfigGroup'));
},
/**
* trigger restartAllServiceHostComponents(batchUtils) if confirmed in popup
* @method restartAllStaleConfigComponents
* @return App.showConfirmationFeedBackPopup
*/
restartAllStaleConfigComponents: function () {
var self = this;
var serviceDisplayName = this.get('content.displayName');
var bodyMessage = Em.Object.create({
confirmMsg: Em.I18n.t('services.service.restartAll.confirmMsg').format(serviceDisplayName),
confirmButton: Em.I18n.t('services.service.restartAll.confirmButton'),
additionalWarningMsg: this.get('content.passiveState') === 'OFF' ? Em.I18n.t('services.service.restartAll.warningMsg.turnOnMM').format(serviceDisplayName) : null
});
var isNNAffected = false;
var restartRequiredHostsAndComponents = this.get('content.restartRequiredHostsAndComponents');
for (var hostName in restartRequiredHostsAndComponents) {
restartRequiredHostsAndComponents[hostName].forEach(function (hostComponent) {
if (hostComponent == 'NameNode')
isNNAffected = true;
})
}
if (this.get('content.serviceName') == 'HDFS' && isNNAffected &&
this.get('content.hostComponents').filterProperty('componentName', 'NAMENODE').someProperty('workStatus', App.HostComponentStatus.started)) {
App.router.get('mainServiceItemController').checkNnLastCheckpointTime(function () {
return App.showConfirmationFeedBackPopup(function (query) {
var selectedService = self.get('content.id');
batchUtils.restartAllServiceHostComponents(serviceDisplayName, selectedService, true, query);
}, bodyMessage);
});
} else {
return App.showConfirmationFeedBackPopup(function (query) {
var selectedService = self.get('content.id');
batchUtils.restartAllServiceHostComponents(serviceDisplayName, selectedService, true, query);
}, bodyMessage);
}
},
/**
* trigger launchHostComponentRollingRestart(batchUtils)
* @method rollingRestartStaleConfigSlaveComponents
*/
rollingRestartStaleConfigSlaveComponents: function (componentName) {
batchUtils.launchHostComponentRollingRestart(componentName.context, this.get('content.displayName'), this.get('content.passiveState') === "ON", true);
},
/**
* trigger showItemsShouldBeRestarted popup with hosts that requires restart
* @param {{context: object}} event
* @method showHostsShouldBeRestarted
*/
showHostsShouldBeRestarted: function (event) {
var restartRequiredHostsAndComponents = event.context.restartRequiredHostsAndComponents;
var hosts = [];
for (var hostName in restartRequiredHostsAndComponents) {
hosts.push(hostName);
}
var hostsText = hosts.length == 1 ? Em.I18n.t('common.host') : Em.I18n.t('common.hosts');
hosts = hosts.join(', ');
this.showItemsShouldBeRestarted(hosts, Em.I18n.t('service.service.config.restartService.shouldBeRestarted').format(hostsText));
},
/**
* trigger showItemsShouldBeRestarted popup with components that requires restart
* @param {{context: object}} event
* @method showComponentsShouldBeRestarted
*/
showComponentsShouldBeRestarted: function (event) {
var restartRequiredHostsAndComponents = event.context.restartRequiredHostsAndComponents;
var hostsComponets = [];
var componentsObject = {};
for (var hostName in restartRequiredHostsAndComponents) {
restartRequiredHostsAndComponents[hostName].forEach(function (hostComponent) {
hostsComponets.push(hostComponent);
if (componentsObject[hostComponent] != undefined) {
componentsObject[hostComponent]++;
} else {
componentsObject[hostComponent] = 1;
}
})
}
var componentsList = [];
for (var obj in componentsObject) {
var componentDisplayName = (componentsObject[obj] > 1) ? obj + 's' : obj;
componentsList.push(componentsObject[obj] + ' ' + componentDisplayName);
}
var componentsText = componentsList.length == 1 ? Em.I18n.t('common.component') : Em.I18n.t('common.components');
hostsComponets = componentsList.join(', ');
this.showItemsShouldBeRestarted(hostsComponets, Em.I18n.t('service.service.config.restartService.shouldBeRestarted').format(componentsText));
},
/**
* Show popup with selectable (@see App.SelectablePopupBodyView) list of items
* @param {string} content string with comma-separated list of hostNames or componentNames
* @param {string} header popup header
* @returns {App.ModalPopup}
* @method showItemsShouldBeRestarted
*/
showItemsShouldBeRestarted: function (content, header) {
return App.ModalPopup.show({
content: content,
header: header,
bodyClass: App.SelectablePopupBodyView,
secondary: null
});
},
/**
* trigger manageConfigurationGroups
* @method manageConfigurationGroup
*/
manageConfigurationGroup: function () {
App.router.get('manageConfigGroupsController').manageConfigurationGroups(null, this.get('content'));
},
/**
* If user changes cfg group if some configs was changed popup with propose to save changes must be shown
* @param {object} event - triggered event for selecting another config-group
* @method selectConfigGroup
*/
selectConfigGroup: function (event) {
var self = this;
function callback() {
self.doSelectConfigGroup(event);
}
if (!this.get('isInit')) {
if (this.hasUnsavedChanges()) {
this.showSavePopup(null, callback);
return;
}
}
callback();
},
/**
* switch view to selected group
* @param event
* @method selectConfigGroup
*/
doSelectConfigGroup: function (event) {
App.loadTimer.start('Service Configs Page');
var configGroupVersions = App.ServiceConfigVersion.find().filterProperty('groupId', event.context.get('configGroupId'));
//check whether config group has config versions
if (event.context.get('configGroupId') == -1) {
this.loadCurrentVersions();
} else if (configGroupVersions.length > 0) {
this.loadSelectedVersion(configGroupVersions.findProperty('isCurrent').get('version'), event.context);
} else {
this.loadSelectedVersion(null, event.context);
}
}
});