/** * 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 stringUtils = require('utils/string_utils'); var configTagFromFileNameMap = {}; App.config = Em.Object.create({ CONFIG_GROUP_NAME_MAX_LENGTH: 18, /** * filename exceptions used to support substandard sitenames which don't have "xml" extension * @type {string[]} */ filenameExceptions: ['alert_notification'], preDefinedServiceConfigs: [], /** * * Returns file name version that stored on server. * * Example: * App.config.getOriginalFileName('core-site') // returns core-site.xml * App.config.getOriginalFileName('zoo.cfg') // returns zoo.cfg * * @param {String} fileName * @method getOriginalFileName **/ getOriginalFileName: function (fileName) { if (/\.xml$/.test(fileName)) return fileName; return this.get('filenameExceptions').contains(fileName) ? fileName : fileName + '.xml'; }, /** * Check if Hive installation with new MySQL database created via Ambari is allowed * @param osFamily * @returns {boolean} */ isManagedMySQLForHiveAllowed: function (osFamily) { var osList = ['redhat5', 'suse11']; return !osList.contains(osFamily); }, /** * * Returns the configuration tagName from supplied filename * * Example: * App.config.getConfigTagFromFileName('core-site.xml') // returns core-site * App.config.getConfigTagFromFileName('zoo.cfg') // returns zoo.cfg * * @param {String} fileName * @method getConfigTagFromFileName **/ getConfigTagFromFileName: function (fileName) { if (configTagFromFileNameMap[fileName]) { return configTagFromFileNameMap[fileName]; } var ret = fileName.endsWith('.xml') ? fileName.slice(0, -4) : fileName; configTagFromFileNameMap[fileName] = ret; return ret; }, /** * * @param name * @param fileName * @returns {string} */ configId: function(name, fileName) { return name + "__" + App.config.getConfigTagFromFileName(fileName); }, setPreDefinedServiceConfigs: function (isMiscTabToBeAdded) { var configs = this.get('preDefinedSiteProperties'); var services = []; var self = this; var stackServices = App.StackService.find().filterProperty('id'); // Only include services that has configTypes related to them for service configuration page var servicesWithConfigTypes = stackServices.filter(function (service) { var configtypes = service.get('configTypes'); return configtypes && !!Object.keys(configtypes).length; }, this); var allTabs; if (isMiscTabToBeAdded) { var nonServiceTab = require('data/service_configs'); var miscService = nonServiceTab.findProperty('serviceName', 'MISC'); var tagTypes = {}; servicesWithConfigTypes.mapProperty('configTypes').forEach(function (configTypes) { for (var fileName in configTypes) { if (fileName.endsWith('-env') && !miscService.get('configTypes')[fileName]) { tagTypes[fileName] = configTypes[fileName]; } } }); miscService.set('configTypes', $.extend(miscService.get('configTypes'), tagTypes)); allTabs = servicesWithConfigTypes.concat(nonServiceTab); } else { allTabs = servicesWithConfigTypes; } allTabs.forEach(function (service) { var configTypes = Em.keys(service.get('configTypes')); // filter properties by service name and service config types var serviceConfigs = configs.filterProperty('serviceName', service.get('serviceName')).filter(function(property) { var propFilename = self.getConfigTagFromFileName(Em.getWithDefault(property, 'filename', '')); if (propFilename && service.get('serviceName') != 'MISC') { return configTypes.contains(propFilename); } return true; }); service.set('configs', serviceConfigs); services.push(service); }); this.set('preDefinedServiceConfigs', services); }, secureConfigs: require('data/HDP2/secure_mapping'), secureConfigsMap: function () { var ret = {}; this.get('secureConfigs').forEach(function (sc) { ret[sc.name] = true; }); return ret; }.property('secureConfigs.[]'), customStackMapping: require('data/custom_stack_map'), mapCustomStack: function () { var baseStackFolder = App.get('currentStackName'), singMap = { "1": ">", "-1": "<", "0": "=" }; this.get('customStackMapping').every(function (stack) { if(stack.stackName == App.get('currentStackName')){ var versionCompare = Em.compare(App.get('currentStackVersionNumber'), stack.stackVersionNumber); if(singMap[versionCompare+""] === stack.sign){ baseStackFolder = stack.baseStackFolder; return false; } } return true; }); return baseStackFolder; }, allPreDefinedSiteProperties: function() { var sitePropertiesForCurrentStack = this.preDefinedConfigFile(this.mapCustomStack(), 'site_properties'); if (sitePropertiesForCurrentStack) { return sitePropertiesForCurrentStack.configProperties; } else if (App.get('isHadoop23Stack')) { return require('data/HDP2.3/site_properties').configProperties; } else if (App.get('isHadoop22Stack')) { return require('data/HDP2.2/site_properties').configProperties; } else { return require('data/HDP2/site_properties').configProperties; } }.property('App.isHadoop22Stack', 'App.isHadoop23Stack'), preDefinedSiteProperties: function () { var serviceNames = App.StackService.find().mapProperty('serviceName').concat('MISC'); return this.get('allPreDefinedSiteProperties').filter(function(p) { return serviceNames.contains(p.serviceName); }); }.property('allPreDefinedSiteProperties'), /** * map of preDefinedSiteProperties provide search by index * @type {object} */ preDefinedSitePropertiesMap: function () { var map = {}; this.get('preDefinedSiteProperties').forEach(function (c) { map[this.configId(c.name, c.filename)] = c; }, this); return map; }.property('preDefinedSiteProperties'), preDefinedConfigFile: function(folder, file) { try { return require('data/{0}/{1}'.format(folder, file)); } catch (err) { // the file doesn't exist, which might be expected. } }, /** * get service for current config type * @param {String} configType - config fileName without xml * @return App.StackService */ getServiceByConfigType: function(configType) { return App.StackService.find().find(function(s) { return Object.keys(s.get('configTypes')).contains(configType); }); }, serviceByConfigTypeMap: function () { var ret = {}; App.StackService.find().forEach(function(s) { Object.keys(s.get('configTypes')).forEach(function (ct) { ret[ct] = s; }); }); return ret; }.property(), /** * generates config objects * @param configGroups * @param serviceName * @param selectedConfigGroup * @param canEdit * @returns {Array} */ mergePredefinedWithSaved: function (configGroups, serviceName, selectedConfigGroup, canEdit) { var configs = []; var serviceConfigProperty; var serviceByConfigTypeMap = this.get('serviceByConfigTypeMap'); configGroups.forEach(function (siteConfig) { var service = serviceByConfigTypeMap[siteConfig.type]; if (service && serviceName != 'MISC') { serviceName = service.get('serviceName'); } var filename = App.config.getOriginalFileName(siteConfig.type); var attributes = siteConfig['properties_attributes'] || {}; var finalAttributes = attributes.final || {}; var properties = siteConfig.properties || {}; var uiOnlyConfigsObj = {}; var uiOnlyConfigDerivedFromTheme = App.uiOnlyConfigDerivedFromTheme.toArray(); uiOnlyConfigDerivedFromTheme.forEach(function(item) { if (filename === item.filename) { uiOnlyConfigsObj[item.name] = item.value; } }); properties = $.extend({}, properties, uiOnlyConfigsObj); for (var index in properties) { var id = this.configId(index, siteConfig.type); var preDefinedPropertyDef = this.get('preDefinedSitePropertiesMap')[id]; var uiOnlyConfigFromTheme = uiOnlyConfigDerivedFromTheme.findProperty('name', index); var configsPropertyDef = preDefinedPropertyDef || uiOnlyConfigFromTheme; var advancedConfig = App.StackConfigProperty.find(id); var isStackProperty = !!advancedConfig.get('id') || !!preDefinedPropertyDef; var template = this.createDefaultConfig(index, serviceName, filename, isStackProperty, configsPropertyDef); var serviceConfigObj = isStackProperty ? this.mergeStaticProperties(template, advancedConfig) : template; if (serviceConfigObj.isRequiredByAgent !== false) { var formattedValue = this.formatPropertyValue(serviceConfigObj, properties[index]); serviceConfigObj.value = serviceConfigObj.savedValue = formattedValue; serviceConfigObj.isFinal = serviceConfigObj.savedIsFinal = finalAttributes[index] === "true"; serviceConfigObj.isEditable = this.getIsEditable(serviceConfigObj, selectedConfigGroup, canEdit); serviceConfigObj.isVisible = serviceConfigObj.isVisible !== false || serviceName === 'MISC'; if (serviceName!='MISC' && serviceConfigObj.category === "Users and Groups") { serviceConfigObj.category = this.getDefaultCategory(advancedConfig, filename); } serviceConfigObj.serviceName = serviceName; } var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj); serviceConfigProperty.validate(); configs.push(serviceConfigProperty); } }, this); return configs; }, /** * This method sets default values for config property * These property values has the lowest priority and can be overridden be stack/UI * config property but is used when such properties are absent in stack/UI configs * @param {string} name * @param {string} serviceName * @param {string} fileName * @param {boolean} definedInStack * @param {Object} [coreObject] * @returns {Object} */ createDefaultConfig: function(name, serviceName, fileName, definedInStack, coreObject) { var tpl = { /** core properties **/ name: name, filename: fileName, value: '', savedValue: null, isFinal: false, savedIsFinal: null, /** UI and Stack properties **/ recommendedValue: null, recommendedIsFinal: null, supportsFinal: this.shouldSupportFinal(serviceName, fileName), serviceName: serviceName, displayName: this.getDefaultDisplayName(name, fileName), displayType: this.getDefaultDisplayType(name, fileName, coreObject ? coreObject.value : '', serviceName), description: null, category: this.getDefaultCategory(definedInStack, fileName), isSecureConfig: this.getIsSecure(name), showLabel: true, isVisible: true, isUserProperty: !definedInStack, isRequired: definedInStack, group: null, isRequiredByAgent: true, isReconfigurable: true, unit: null, hasInitialValue: false, isOverridable: true, index: Infinity, dependentConfigPattern: null, options: null, radioName: null, belongsToService: [], widgetType: null }; return Object.keys(coreObject|| {}).length ? $.extend(tpl, coreObject) : tpl; }, /** * This method creates host name properties * @param serviceName * @param componentName * @param value * @param stackComponent * @returns Object */ createHostNameProperty: function(serviceName, componentName, value, stackComponent) { var hostOrHosts = stackComponent.get('isMultipleAllowed') ? 'hosts' : 'host'; return { "name": componentName.toLowerCase() + '_' + hostOrHosts, "displayName": stackComponent.get('displayName') + ' ' + (value.length > 1 ? 'hosts' : 'host'), "value": value, "recommendedValue": value, "description": "The " + hostOrHosts + " that has been assigned to run " + stackComponent.get('displayName'), "displayType": "component" + hostOrHosts.capitalize(), "isOverridable": false, "isRequiredByAgent": false, "serviceName": serviceName, "filename": serviceName.toLowerCase() + "-site.xml", "category": componentName, "index": 0 } }, /** * This method merge properties form stackConfigProperty which are taken from stack * with UIConfigProperty which are hardcoded on UI * @param coreObject * @param stackProperty * @param preDefined * @param [propertiesToSkip] */ mergeStaticProperties: function(coreObject, stackProperty, preDefined, propertiesToSkip) { propertiesToSkip = propertiesToSkip || ['name', 'filename', 'value', 'savedValue', 'isFinal', 'savedIsFinal']; for (var k in coreObject) { if (coreObject.hasOwnProperty(k)) { if (!propertiesToSkip.contains(k)) { coreObject[k] = this.getPropertyIfExists(k, coreObject[k], stackProperty, preDefined); } } } return coreObject; }, /** * This method using for merging some properties from two objects * if property exists in firstPriority result will be it's property * else if property exists in secondPriority result will be it's property * otherwise defaultValue will be returned * @param {String} propertyName * @param {*} defaultValue=null * @param {Em.Object|Object} firstPriority * @param {Em.Object|Object} [secondPriority=null] * @returns {*} */ getPropertyIfExists: function(propertyName, defaultValue, firstPriority, secondPriority) { firstPriority = firstPriority || {}; secondPriority = secondPriority || {}; var fp = Em.get(firstPriority, propertyName); if (firstPriority && !Em.isNone(fp)) { return fp; } else { var sp = Em.get(secondPriority, propertyName); if (secondPriority && !Em.isNone(sp)) { return sp; } else { return defaultValue; } } }, /** * Get displayType for properties that has not defined value * @param name * @param type * @param value * @param serviceName * @returns {string} */ getDefaultDisplayType: function(name, type, value, serviceName) { if (this.isContentProperty(name, type)) { return 'content'; } else if (serviceName && serviceName == 'FALCON' && this.getConfigTagFromFileName(type) == 'oozie-site') { /** * This specific type for 'oozie-site' configs of FALCON service. * After this type will be moved to stack definition this hard-code should be removed */ return 'custom'; } return value && !stringUtils.isSingleLine(value) ? 'multiLine' : 'string'; }, /** * Get the default value of displayName * @param name * @param fileName * @returns {*} */ getDefaultDisplayName: function(name, fileName) { return this.isContentProperty(name, fileName, ['-env']) ? this.getConfigTagFromFileName(fileName) + ' template' : name }, /** * Get category for properties that has not defined value * @param stackConfigProperty * @param fileName * @returns {string} */ getDefaultCategory: function(stackConfigProperty, fileName) { return (stackConfigProperty ? 'Advanced ' : 'Custom ') + this.getConfigTagFromFileName(fileName); }, /** * Get isSecureConfig for properties that has not defined value * @param propertyName * @returns {boolean} */ getIsSecure: function(propertyName) { return !!this.get('secureConfigsMap')[propertyName]; }, /** * Calculate isEditable rely on controller state selected group and config restriction * @param {Object} serviceConfigProperty * @param {Object} selectedConfigGroup * @param {boolean} canEdit * @returns {boolean} */ getIsEditable: function(serviceConfigProperty, selectedConfigGroup, canEdit) { return canEdit && Em.get(selectedConfigGroup, 'isDefault') && Em.get(serviceConfigProperty, 'isReconfigurable') }, /** * format property value depending on displayType * and one exception for 'kdc_type' * @param serviceConfigProperty * @param [originalValue] * @returns {*} */ formatPropertyValue: function(serviceConfigProperty, originalValue) { var value = originalValue || Em.get(serviceConfigProperty, 'value'), displayType = Em.get(serviceConfigProperty, 'displayType') || Em.get(serviceConfigProperty, 'valueAttributes.type'), category = Em.get(serviceConfigProperty, 'category'); switch (displayType) { case 'content': case 'string': case 'multiLine': return this.trimProperty({ displayType: displayType, value: value }); break; case 'directories': if (['DataNode', 'NameNode'].contains(category)) { return value.split(',').sort().join(',');//TODO check if this code is used } break; case 'directory': if (['SNameNode'].contains(category)) { return value.split(',').sort()[0];//TODO check if this code is used } break; case 'componentHosts': if (typeof(value) == 'string') { return value.replace(/\[|]|'|'/g, "").split(','); } break; case 'int': if (/\d+m$/.test(value) ) { return value.slice(0, value.length - 1); } else { var int = parseInt(value); return isNaN(int) ? "" : int.toString(); } break; case 'float': var float = parseFloat(value); return isNaN(float) ? "" : float.toString(); } if (Em.get(serviceConfigProperty, 'name') === 'kdc_type') { return App.router.get('mainAdminKerberosController.kdcTypesValues')[value]; } if ( /^\s+$/.test("" + value)) { value = " "; } return value; }, /** * defines if property with name and fileName * are special content property. By default result will be true if property name is 'content' * and tag ends on '-env' or '-log4j', but some other tag endings can be passed in tagEnds * @param {string} name * @param {string} fileName * @param {string[]} [tagEnds] * @returns {boolean} */ isContentProperty: function(name, fileName, tagEnds) { if (tagEnds && tagEnds.length) { //tagEnds = tagEnds || ['-env', '-log4j']; var type = this.getConfigTagFromFileName(fileName); return name == 'content' && tagEnds.some(function(tagEnd) { return type.endsWith(tagEnd)}); } else { return name == 'content'; } }, /** * * @param configs * @returns {Object[]} */ sortConfigs: function(configs) { return configs.sort(function(a, b) { if (Em.get(a, 'index') > Em.get(b, 'index')) return 1; if (Em.get(a, 'index') < Em.get(b, 'index')) return -1; if (Em.get(a, 'name') > Em.get(b, 'index')) return 1; if (Em.get(a, 'name') < Em.get(b, 'index')) return -1; return 0; }); }, /** * merge stored configs with pre-defined * @return {Array} */ mergePreDefinedWithStack: function (selectedServiceNames) { var mergedConfigs = []; var uiPersistentProperties = [ this.configId('oozie_hostname', 'oozie-env.xml') ]; var configTypesMap = {}; App.StackService.find().filter(function (service) { return selectedServiceNames.contains(service.get('serviceName')); }).map(function (item) { return Em.keys(item.get('configTypes')); }).reduce(function (p, c) { return p.concat(c); }).concat(['cluster-env', 'alert_notification']) .uniq().compact().filter(function (configType) { return !!configType; }).forEach(function (c) { configTypesMap[c] = true; }); var predefinedIds = Object.keys(this.get('preDefinedSitePropertiesMap')); var uiOnlyConfigDerivedFromTheme = App.uiOnlyConfigDerivedFromTheme.mapProperty('name'); // ui only required configs from theme are required to show configless widgets (widget that are not related to a config) var stackIds = []; var stackConfigPropertyMap = {}; App.StackConfigProperty.find().forEach(function (scp) { var id = scp.get('id'); if(scp.get('isValueDefined')) { stackIds.push(id); } stackConfigPropertyMap[id] = scp; }); var configIds = stackIds.concat(predefinedIds).concat(uiOnlyConfigDerivedFromTheme).uniq(); configIds.forEach(function(id) { var preDefined = this.get('preDefinedSitePropertiesMap')[id]; var isUIOnlyFromTheme = App.uiOnlyConfigDerivedFromTheme.findProperty('name',id); var advanced = stackConfigPropertyMap[id] || Em.Object.create({}); var name = preDefined ? preDefined.name : isUIOnlyFromTheme ? isUIOnlyFromTheme.get('name') : advanced.get('name'); var filename = preDefined ? preDefined.filename : isUIOnlyFromTheme ? isUIOnlyFromTheme.get('filename') : advanced.get('filename'); var isUIOnly = (Em.getWithDefault(preDefined || {}, 'isRequiredByAgent', true) === false) || isUIOnlyFromTheme; /* Take properties that: - UI specific only, marked with isRequiredByAgent: false - Present in stack definition, mapped to App.StackConfigProperty - Related to configuration for alerts notification, marked with filename: "alert_notification" - Property that filename supported by Service's config type, App.StackService:configType - Property that not defined in stack but should be saved, see uiPersistentProperties */ if (!(uiPersistentProperties.contains(id) || isUIOnly || advanced.get('id')) && filename != 'alert_notification') { return; } var serviceName = preDefined ? preDefined.serviceName : isUIOnlyFromTheme ? isUIOnlyFromTheme.get('serviceName') : advanced.get('serviceName'); if (configTypesMap[this.getConfigTagFromFileName(filename)]) { var configData = this.createDefaultConfig(name, serviceName, filename, true, preDefined || isUIOnlyFromTheme || {}); if (configData.recommendedValue) { configData.value = configData.recommendedValue; } if (advanced.get('id')) { configData = this.mergeStaticProperties(configData, advanced, null, ['name', 'filename']); configData.value = configData.recommendedValue = this.formatPropertyValue(advanced, advanced.get('value')); configData.widgetType = advanced.get('widget.type'); } mergedConfigs.push(configData); } }, this); return mergedConfigs; }, /** * * @param {string} ifStatement * @param {Array} serviceConfigs * @returns {boolean} */ calculateConfigCondition: function(ifStatement, serviceConfigs) { // Split `if` statement if it has logical operators var ifStatementRegex = /(&&|\|\|)/; var IfConditions = ifStatement.split(ifStatementRegex); var allConditionResult = []; IfConditions.forEach(function(_condition){ var condition = _condition.trim(); if (condition === '&&' || condition === '||') { allConditionResult.push(_condition); } else { var splitIfCondition = condition.split('==='); var ifCondition = splitIfCondition[0]; var result = splitIfCondition[1] || "true"; var parseIfConditionVal = ifCondition; var regex = /\$\{.*?\}/g; var configStrings = ifCondition.match(regex); configStrings.forEach(function (_configString) { var configObject = _configString.substring(2, _configString.length - 1).split("/"); var config = serviceConfigs.filterProperty('filename', configObject[0] + '.xml').findProperty('name', configObject[1]); if (config) { var configValue = config.value; parseIfConditionVal = parseIfConditionVal.replace(_configString, configValue); } }, this); var conditionResult = window.eval(JSON.stringify(parseIfConditionVal.trim())) === result.trim(); allConditionResult.push(conditionResult); } }, this); return Boolean(window.eval(allConditionResult.join(''))); }, miscConfigVisibleProperty: function (configs, serviceToShow) { configs.forEach(function (item) { if (item.get('isVisible') && item.belongsToService && item.belongsToService.length) { if (item.get('belongsToService').contains('Cluster') && item.get('displayType') == 'user') { item.set('isVisible', true); return; } item.set("isVisible", item.belongsToService.some(function (cur) { return serviceToShow.contains(cur) })); } }); return configs; }, /** * create new ServiceConfig object by service name * @param {string} serviceName * @param {App.ServiceConfigGroup[]} configGroups * @param {App.ServiceConfigProperty[]} configs * @param {Number} initConfigsLength * @return {App.ServiceConfig} * @method createServiceConfig */ createServiceConfig: function (serviceName, configGroups, configs, initConfigsLength) { var preDefinedServiceConfig = App.config.get('preDefinedServiceConfigs').findProperty('serviceName', serviceName); return App.ServiceConfig.create({ serviceName: preDefinedServiceConfig.get('serviceName'), displayName: preDefinedServiceConfig.get('displayName'), configCategories: preDefinedServiceConfig.get('configCategories'), configs: configs || [], configGroups: configGroups || [], initConfigsLength: initConfigsLength || 0 }); }, /** * GETs all cluster level sites in one call. * * @return {$.ajax} */ loadConfigsByTags: function (tags) { var urlParams = []; tags.forEach(function (_tag) { urlParams.push('(type=' + _tag.siteName + '&tag=' + _tag.tagName + ')'); }); var params = urlParams.join('|'); return App.ajax.send({ name: 'config.on_site', sender: this, data: { params: params } }); }, /** * Add additional properties to advanced property config object. * Additional logic based on `property_type`. * * @method advancedConfigIdentityData * @param {Object} config * @return {Object} */ advancedConfigIdentityData: function (config) { var propertyData = {}; var proxyUserGroupServices = ['HIVE', 'OOZIE', 'FALCON']; var checkboxProperties = ['ignore_groupsusers_create', 'override_uid']; if (Em.isArray(config.property_type)) { if (config.property_type.contains('USER') || config.property_type.contains('ADDITIONAL_USER_PROPERTY') || config.property_type.contains('GROUP')) { propertyData.category = 'Users and Groups'; propertyData.isVisible = !App.get('isHadoopWindowsStack'); propertyData.serviceName = 'MISC'; propertyData.displayType = checkboxProperties.contains(config.property_name) ? 'boolean' : 'user'; if (config.property_type.contains('ADDITIONAL_USER_PROPERTY')) { propertyData.index = 999; } else if (config.service_name) { var propertyIndex = config.service_name == 'MISC' ? 30 : App.StackService.find().mapProperty('serviceName').indexOf(config.service_name); propertyData.belongsToService = [config.service_name]; propertyData.index = propertyIndex; } else { propertyData.index = 30; } if (config.property_name == 'proxyuser_group') propertyData.belongsToService = proxyUserGroupServices; } if (config.property_type.contains('PASSWORD')) { propertyData.displayType = "password"; } } return propertyData; }, configTypesInfoMap: {}, /** * Get config types and config type attributes from stack service * * @param service * @return {object} */ getConfigTypesInfoFromService: function (service) { var configTypesInfoMap = this.get('configTypesInfoMap'); if (configTypesInfoMap[service]) { // don't recalculate return configTypesInfoMap[service]; } var configTypes = service.get('configTypes'); var configTypesInfo = { items: [], supportsFinal: [] }; if (configTypes) { for (var key in configTypes) { if (configTypes.hasOwnProperty(key)) { configTypesInfo.items.push(key); if (configTypes[key].supports && configTypes[key].supports.final === "true") { configTypesInfo.supportsFinal.push(key); } } } } configTypesInfoMap[service] = configTypesInfo; this.set('configTypesInfoMap', configTypesInfoMap); return configTypesInfo; }, /** * Get properties from server by type and tag with properties, that belong to group * push them to common {serviceConfigs} and call callback function */ loadServiceConfigGroupOverrides: function (serviceConfigs, loadedGroupToOverrideSiteToTagMap, configGroups, callback, sender) { var configKeyToConfigMap = {}; serviceConfigs.forEach(function (item) { if (!configKeyToConfigMap[item.filename]) { configKeyToConfigMap[item.filename] = {}; } configKeyToConfigMap[item.filename][item.name] = item; }); var typeTagToGroupMap = {}; var urlParams = []; for (var group in loadedGroupToOverrideSiteToTagMap) { var overrideTypeTags = loadedGroupToOverrideSiteToTagMap[group]; for (var type in overrideTypeTags) { var tag = overrideTypeTags[type]; typeTagToGroupMap[type + "///" + tag] = configGroups.findProperty('name', group); urlParams.push('(type=' + type + '&tag=' + tag + ')'); } } var params = urlParams.join('|'); if (urlParams.length) { App.ajax.send({ name: 'config.host_overrides', sender: this, data: { params: params, configKeyToConfigMap: configKeyToConfigMap, typeTagToGroupMap: typeTagToGroupMap, callback: callback, sender: sender, serviceConfigs: serviceConfigs }, success: 'loadServiceConfigGroupOverridesSuccess' }); } else { callback.call(sender, serviceConfigs); } }, loadServiceConfigGroupOverridesSuccess: function (data, opt, params) { data.items.forEach(function (config) { var group = params.typeTagToGroupMap[config.type + "///" + config.tag]; var properties = config.properties; for (var prop in properties) { var fileName = this.getOriginalFileName(config.type); var serviceConfig = !!params.configKeyToConfigMap[fileName] ? params.configKeyToConfigMap[fileName][prop] : false; var hostOverrideValue = this.formatPropertyValue(serviceConfig, properties[prop]); var hostOverrideIsFinal = !!(config.properties_attributes && config.properties_attributes.final && config.properties_attributes.final[prop]); if (serviceConfig) { // Value of this property is different for this host. if (!Em.get(serviceConfig, 'overrides')) Em.set(serviceConfig, 'overrides', []); serviceConfig.overrides.pushObject({value: hostOverrideValue, group: group, isFinal: hostOverrideIsFinal}); } else { params.serviceConfigs.push(this.createCustomGroupConfig(prop, config, group)); } } }, this); params.callback.call(params.sender, params.serviceConfigs); }, /** * Create config with non default config group. Some custom config properties * can be created and assigned to non-default config group. * * @param {String} propertyName - name of the property * @param {Object} config - config info * @param {Em.Object} group - config group to set * @param {Boolean} isEditable * @return {Object} **/ createCustomGroupConfig: function (propertyName, config, group, isEditable) { var propertyObject = this.createDefaultConfig(propertyName, group.get('service.serviceName'), this.getOriginalFileName(config.type), false, { savedValue: config.properties[propertyName], value: config.properties[propertyName], group: group, isEditable: isEditable !== false, isOverridable: false }); group.set('switchGroupTextShort', Em.I18n.t('services.service.config_groups.switchGroupTextShort').format(group.get('name'))); group.set('switchGroupTextFull', Em.I18n.t('services.service.config_groups.switchGroupTextFull').format(group.get('name'))); return App.ServiceConfigProperty.create(propertyObject); }, complexConfigsTemplate: [ { "name": "capacity-scheduler", "displayName": "Capacity Scheduler", "value": "", "description": "Capacity Scheduler properties", "displayType": "custom", "isOverridable": true, "isRequired": true, "isVisible": true, "isReconfigurable": true, "supportsFinal": false, "serviceName": "YARN", "filename": "capacity-scheduler.xml", "category": "CapacityScheduler" } ], /** * transform set of configs from file * into one config with textarea content: * name=value * @param {App.ServiceConfigProperty[]} configs * @param {String} filename * @param {App.ServiceConfigProperty[]} [configsToSkip=[]] * @return {*} */ fileConfigsIntoTextarea: function (configs, filename, configsToSkip) { var fileConfigs = configs.filterProperty('filename', filename); var value = '', savedValue = '', recommendedValue = ''; var template = this.get('complexConfigsTemplate').findProperty('filename', filename); var complexConfig = $.extend({}, template); if (complexConfig) { fileConfigs.forEach(function (_config) { if (!(configsToSkip && configsToSkip.someProperty('name', _config.name))) { value += _config.name + '=' + _config.value + '\n'; if (!Em.isNone(_config.savedValue)) { savedValue += _config.name + '=' + _config.savedValue + '\n'; } if (!Em.isNone(_config.recommendedValue)) { recommendedValue += _config.name + '=' + _config.recommendedValue + '\n'; } } }, this); var isFinal = fileConfigs.someProperty('isFinal', true); var savedIsFinal = fileConfigs.someProperty('savedIsFinal', true); var recommendedIsFinal = fileConfigs.someProperty('recommendedIsFinal', true); complexConfig.value = value; if (savedValue) { complexConfig.savedValue = savedValue; } if (recommendedValue) { complexConfig.recommendedValue = recommendedValue; } complexConfig.isFinal = isFinal; complexConfig.savedIsFinal = savedIsFinal; complexConfig.recommendedIsFinal = recommendedIsFinal; configs = configs.filter(function (_config) { return _config.filename !== filename || (configsToSkip && configsToSkip.someProperty('name', _config.name)); }); configs.push(App.ServiceConfigProperty.create(complexConfig)); } return configs; }, /** * transform one config with textarea content * into set of configs of file * @param configs * @param filename * @return {*} */ textareaIntoFileConfigs: function (configs, filename) { var complexConfigName = this.get('complexConfigsTemplate').findProperty('filename', filename).name; var configsTextarea = configs.findProperty('name', complexConfigName); if (configsTextarea && !App.get('testMode')) { var properties = configsTextarea.get('value').split('\n'); properties.forEach(function (_property) { var name, value; if (_property) { _property = _property.split('='); name = _property[0]; value = (_property[1]) ? _property[1] : ""; configs.push(Em.Object.create({ name: name, value: value, savedValue: value, serviceName: configsTextarea.get('serviceName'), filename: filename, isFinal: configsTextarea.get('isFinal'), isNotDefaultValue: configsTextarea.get('isNotDefaultValue'), isRequiredByAgent: configsTextarea.get('isRequiredByAgent'), group: null })); } }); return configs.without(configsTextarea); } return configs; }, /** * trim trailing spaces for all properties. * trim both trailing and leading spaces for host displayType and hive/oozie datebases url. * for directory or directories displayType format string for further using. * for password and values with spaces only do nothing. * @param {Object} property * @returns {*} */ trimProperty: function (property) { var displayType = Em.get(property, 'displayType'); var value = Em.get(property, 'value'); var name = Em.get(property, 'name'); var rez; switch (displayType) { case 'directories': case 'directory': rez = value.replace(/,/g, ' ').trim().split(/\s+/g).join(','); break; case 'host': rez = value.trim(); break; case 'password': break; default: if (name == 'javax.jdo.option.ConnectionURL' || name == 'oozie.service.JPAService.jdbc.url') { rez = value.trim(); } rez = (typeof value == 'string') ? value.replace(/(\s+$)/g, '') : value; } return ((rez == '') || (rez == undefined)) ? value : rez; }, /** * Generate minimal config property object used in *_properties.js files. * Example: * * var someProperties = App.config.generateConfigPropertiesByName([ * 'property_1', 'property_2', 'property_3'], { category: 'General', filename: 'myFileName'}); * // someProperties contains Object[] * [ * { * name: 'property_1', * displayName: 'property_1', * isVisible: true, * isReconfigurable: true, * category: 'General', * filename: 'myFileName' * }, * ....... * ] * * @param {string[]} names * @param {Object} properties - additional properties which will merge with base object definition * @returns {object[]} * @method generateConfigPropertiesByName */ generateConfigPropertiesByName: function (names, properties) { return names.map(function (item) { var baseObj = { name: item }; if (properties) return $.extend(baseObj, properties); else return baseObj; }); }, /** * load cluster stack configs from server and run mapper * @returns {$.ajax} * @method loadConfigsFromStack */ loadClusterConfigsFromStack: function () { return App.ajax.send({ name: 'configs.stack_configs.load.cluster_configs', sender: this, data: { stackVersionUrl: App.get('stackVersionURL') }, success: 'saveConfigsToModel' }); }, /** * load stack configs from server and run mapper * @param {String[]} [serviceNames=null] * @returns {$.ajax} * @method loadConfigsFromStack */ loadConfigsFromStack: function (serviceNames) { serviceNames = serviceNames || []; var name = serviceNames.length > 0 ? 'configs.stack_configs.load.services' : 'configs.stack_configs.load.all'; return App.ajax.send({ name: name, sender: this, data: { stackVersionUrl: App.get('stackVersionURL'), serviceList: serviceNames.join(',') }, success: 'saveConfigsToModel' }); }, /** * Runs stackConfigPropertiesMapper * @param {object} data * @method saveConfigsToModel */ saveConfigsToModel: function (data) { App.stackConfigPropertiesMapper.map(data); }, /** * Check if config filename supports final attribute * @param serviceName * @param filename * @returns {boolean} */ shouldSupportFinal: function (serviceName, filename) { var unsupportedServiceNames = ['MISC', 'Cluster']; if (!serviceName || unsupportedServiceNames.contains(serviceName) || !filename) { return false; } else { var stackService = App.StackService.find(serviceName); if (!stackService) { return false; } return !!this.getConfigTypesInfoFromService(stackService).supportsFinal.find(function (configType) { return filename.startsWith(configType); }); } }, /** * Remove all ranger-related configs, that should be available only if Ranger is installed * @param configs - stepConfigs object */ removeRangerConfigs: function (configs) { configs.forEach(function (service) { var filteredConfigs = []; service.get('configs').forEach(function (config) { if (!/^ranger-/.test(config.get('filename'))) { filteredConfigs.push(config); } }); service.set('configs', filteredConfigs); var filteredCategories = []; service.get('configCategories').forEach(function (category) { if (!/ranger-/.test(category.get('name'))) { filteredCategories.push(category); } }); service.set('configCategories', filteredCategories); }); }, /** * @param {App.ServiceConfigProperty} serviceConfigProperty * @param {Object} override - plain object with properties that is different from parent SCP * @param {App.ServiceConfigGroup} configGroup * @returns {App.ServiceConfigProperty} */ createOverride: function(serviceConfigProperty, override, configGroup) { Em.assert('serviceConfigProperty can\' be null', serviceConfigProperty); Em.assert('configGroup can\' be null', configGroup); if (Em.isNone(serviceConfigProperty.get('overrides'))) serviceConfigProperty.set('overrides', []); var newOverride = App.ServiceConfigProperty.create(serviceConfigProperty); if (!Em.isNone(override)) { for (var key in override) { newOverride.set(key, override[key]); } } newOverride.setProperties({ 'isOriginalSCP': false, 'overrides': null, 'group': configGroup, 'parentSCP': serviceConfigProperty }); serviceConfigProperty.get('overrides').pushObject(newOverride); serviceConfigProperty.set('overrideValues', serviceConfigProperty.get('overrides').mapProperty('value')); serviceConfigProperty.set('overrideIsFinalValues', serviceConfigProperty.get('overrides').mapProperty('isFinal')); newOverride.validate(); return newOverride; }, /** * Merge values in "stored" to "base" if name matches, it's a value only merge. * @param base {Array} Em.Object * @param stored {Array} Object */ mergeStoredValue: function(base, stored) { if (stored) { base.forEach(function (p) { var sp = stored.filterProperty("filename", p.filename).findProperty("name", p.name); if (sp) { p.set("value", sp.value); } }); } }, /** * Update config property value based on its current value and list of zookeeper server hosts. * Used to prevent sort order issues. * siteConfigs object formatted according server's persist format e.g. * * * { * 'yarn-site': { * 'property_name1': 'property_value1' * 'property_name2': 'property_value2' * ..... * } * } * * * @method updateHostsListValue * @param {Object} siteConfigs - prepared site config object to store * @param {String} propertyName - name of the property to update * @param {String} hostsList - list of ZooKeeper Server names to set as config property value * @return {String} - result value */ updateHostsListValue: function(siteConfigs, propertyName, hostsList) { var value = hostsList; var propertyHosts = (siteConfigs[propertyName] || '').split(','); var hostsToSet = hostsList.split(','); if (!Em.isEmpty(siteConfigs[propertyName])) { var diffLength = propertyHosts.filter(function(hostName) { return !hostsToSet.contains(hostName); }).length; if (diffLength == 0 && propertyHosts.length == hostsToSet.length) { value = siteConfigs[propertyName]; } } siteConfigs[propertyName] = value; return value; } });