AMBARI-6922 FE: Ambari installer and service config page should validate configs by calling /validations. (ababiichuk)
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/818dc161 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/818dc161 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/818dc161 Branch: refs/heads/branch-alerts-dev Commit: 818dc161fe41b7f9b92e67042b3840e4a8158737 Parents: 4644a82 Author: aBabiichuk <ababiic...@cybervisiontech.com> Authored: Tue Aug 19 22:03:23 2014 +0300 Committer: aBabiichuk <ababiic...@cybervisiontech.com> Committed: Tue Aug 19 22:10:08 2014 +0300 ---------------------------------------------------------------------- ambari-web/app/controllers/installer.js | 32 ++- .../controllers/main/service/info/configs.js | 20 +- .../app/controllers/wizard/step5_controller.js | 18 +- .../app/controllers/wizard/step6_controller.js | 13 +- .../app/controllers/wizard/step7_controller.js | 56 +---- ambari-web/app/messages.js | 4 + ambari-web/app/mixins.js | 1 + ambari-web/app/mixins/common/serverValidator.js | 239 +++++++++++++++++++ ambari-web/app/models/service_config.js | 15 +- ambari-web/app/models/stack_service.js | 3 + ambari-web/app/routes/installer.js | 11 +- ambari-web/app/utils/blueprint.js | 81 +++++++ ambari-web/app/utils/config.js | 20 +- ambari-web/test/utils/blueprint_test.js | 137 +++++++++++ 14 files changed, 560 insertions(+), 90 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/controllers/installer.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/installer.js b/ambari-web/app/controllers/installer.js index 03c5afb..8b1fa1f 100644 --- a/ambari-web/app/controllers/installer.js +++ b/ambari-web/app/controllers/installer.js @@ -38,6 +38,15 @@ App.InstallerController = App.WizardController.extend({ slaveGroupProperties: null, stacks: null, clients:[], + /** + * recommendations for host groups loaded from server + */ + recommendations: null, + /** + * recommendationsHostGroups - current component assignment after 5 and 6 steps + * (uses for host groups validation and to load recommended configs) + */ + recommendationsHostGroups: null, controllerName: 'installerController' }), @@ -64,7 +73,10 @@ App.InstallerController = App.WizardController.extend({ 'stacksVersions', 'currentStep', 'serviceInfo', - 'hostInfo' + 'hostInfo', + 'recommendations', + 'recommendationsHostGroups', + 'recommendationsConfigs' ], init: function () { @@ -467,6 +479,18 @@ App.InstallerController = App.WizardController.extend({ this.set("content.masterComponentHosts", masterComponentHosts); }, + loadRecommendations: function() { + this.set("content.recommendations", this.getDBProperty('recommendations')); + }, + + loadCurrentHostGroups: function() { + this.set("content.recommendationsHostGroups", this.getDBProperty('recommendationsHostGroups')); + }, + + loadRecommendationsConfigs: function() { + App.router.set("wizardStep7Controller.recommendationsConfigs", this.getDBProperty('recommendationsConfigs')); + }, + /** * Load master component hosts data for using in required step controllers */ @@ -685,6 +709,7 @@ App.InstallerController = App.WizardController.extend({ callback: function () { this.loadMasterComponentHosts(); this.loadConfirmedHosts(); + this.loadRecommendations(); } } ], @@ -694,6 +719,7 @@ App.InstallerController = App.WizardController.extend({ callback: function () { this.loadSlaveComponentHosts(); this.loadClients(); + this.loadRecommendations(); } } ], @@ -703,6 +729,8 @@ App.InstallerController = App.WizardController.extend({ callback: function () { this.loadServiceConfigGroups(); this.loadServiceConfigProperties(); + this.loadCurrentHostGroups(); + this.loadRecommendationsConfigs(); } } ] @@ -739,7 +767,7 @@ App.InstallerController = App.WizardController.extend({ * Clear loaded recommendations */ clearRecommendations: function() { - this.set('recommendations', undefined) + this.set('content.recommendations', undefined) } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/controllers/main/service/info/configs.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/info/configs.js b/ambari-web/app/controllers/main/service/info/configs.js index a856d1e..a1b3677 100644 --- a/ambari-web/app/controllers/main/service/info/configs.js +++ b/ambari-web/app/controllers/main/service/info/configs.js @@ -21,7 +21,7 @@ require('controllers/wizard/slave_component_groups_controller'); var batchUtils = require('utils/batch_scheduled_requests'); var lazyLoading = require('utils/lazy_loading'); -App.MainServiceInfoConfigsController = Em.Controller.extend({ +App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorMixin, { name: 'mainServiceInfoConfigsController', isHostsConfigsPage: false, forceTransition: false, @@ -1091,15 +1091,17 @@ App.MainServiceInfoConfigsController = Em.Controller.extend({ (serviceName !== 'HDFS' && this.get('content.isStopped') === true) || ((serviceName === 'HDFS') && this.get('content.isStopped') === true && (!App.Service.find().someProperty('id', 'MAPREDUCE') || App.Service.find('MAPREDUCE').get('isStopped')))) { - if (this.isDirChanged()) { - App.showConfirmationPopup(function () { + this.serverSideValidation().done(function() { + if (self.isDirChanged()) { + App.showConfirmationPopup(function () { + self.saveConfigs(); + }, Em.I18n.t('services.service.config.confirmDirectoryChange').format(displayName), function () { + self.set('isApplyingChanges', false) + }); + } else { self.saveConfigs(); - }, Em.I18n.t('services.service.config.confirmDirectoryChange').format(displayName), function () { - self.set('isApplyingChanges', false) - }); - } else { - this.saveConfigs(); - } + } + }); } else { status = 'started'; if (this.get('content.serviceName') !== 'HDFS' || (this.get('content.serviceName') === 'HDFS' && !App.Service.find().someProperty('id', 'MAPREDUCE'))) { http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/controllers/wizard/step5_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step5_controller.js b/ambari-web/app/controllers/wizard/step5_controller.js index b82fa1c..7df1b8b 100644 --- a/ambari-web/app/controllers/wizard/step5_controller.js +++ b/ambari-web/app/controllers/wizard/step5_controller.js @@ -212,11 +212,11 @@ App.WizardStep5Controller = Em.Controller.extend({ return false; } - if (App.supports.serverRecommendValidate) { + if (App.get('supports.serverRecommendValidate')) { self.set('submitDisabled', true); // reset previous recommendations - App.router.set('installerController.recommendations', null); + this.set('content.recommendations', null); if (self.get('servicesMasters').length === 0) { return; @@ -375,7 +375,7 @@ App.WizardStep5Controller = Em.Controller.extend({ console.log("WizardStep5Controller: Loading step5: Assign Masters"); this.clearStep(); this.renderHostInfo(); - if (App.supports.serverRecommendValidate ) { + if (App.get('supports.serverRecommendValidate')) { this.loadComponentsRecommendationsFromServer(this.loadStepCallback); } else { this.loadComponentsRecommendationsLocally(this.loadStepCallback); @@ -478,7 +478,7 @@ App.WizardStep5Controller = Em.Controller.extend({ loadComponentsRecommendationsFromServer: function(callback, includeMasters) { var self = this; - if (App.router.get('installerController.recommendations')) { + if (this.get('content.recommendations')) { // Don't do AJAX call if recommendations has been already received // But if user returns to previous step (selecting services), stored recommendations will be cleared in routers' next handler and AJAX call will be made again callback(self.createComponentInstallationObjects(), self); @@ -523,7 +523,7 @@ App.WizardStep5Controller = Em.Controller.extend({ /** * Create components for displaying component-host comboboxes in UI assign dialog - * expects installerController.recommendations will be filled with recommendations API call result + * expects content.recommendations will be filled with recommendations API call result * @return {Object[]} */ createComponentInstallationObjects: function() { @@ -538,7 +538,7 @@ App.WizardStep5Controller = Em.Controller.extend({ var masterHosts = self.get('content.masterComponentHosts'); //saved to local storage info var selectedNotInstalledServices = self.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'); - var recommendations = App.router.get('installerController.recommendations'); + var recommendations = this.get('content.recommendations'); var resultComponents = []; var multipleComponentHasBeenAdded = {}; @@ -616,7 +616,7 @@ App.WizardStep5Controller = Em.Controller.extend({ * @method loadRecommendationsSuccessCallback */ loadRecommendationsSuccessCallback: function (data) { - App.router.set('installerController.recommendations', data.resources[0].recommendations); + this.set('content.recommendations', data.resources[0].recommendations); }, /** @@ -1025,7 +1025,7 @@ App.WizardStep5Controller = Em.Controller.extend({ // load recommendations with partial request self.loadComponentsRecommendationsFromServer(function() { // For validation use latest received recommendations because ir contains current master layout and recommended slave/client layout - self.validate(App.router.get('installerController.recommendations'), function() { + self.validate(self.get('content.recommendations'), function() { if (callback) { callback(); } @@ -1047,7 +1047,7 @@ App.WizardStep5Controller = Em.Controller.extend({ } }; - if (App.supports.serverRecommendValidate ) { + if (App.get('supports.serverRecommendValidate')) { self.recommendAndValidate(function() { goNextStepIfValid(); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/controllers/wizard/step6_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step6_controller.js b/ambari-web/app/controllers/wizard/step6_controller.js index a74f020..78c1254 100644 --- a/ambari-web/app/controllers/wizard/step6_controller.js +++ b/ambari-web/app/controllers/wizard/step6_controller.js @@ -377,7 +377,7 @@ App.WizardStep6Controller = Em.Controller.extend({ var clientHeaders = headers.findProperty('name', 'CLIENT'); var slaveComponents = this.get('content.slaveComponentHosts'); if (!slaveComponents) { // we are at this page for the first time - if (!App.supports.serverRecommendValidate) { + if (!App.get('supports.serverRecommendValidate')) { hostsObj.forEach(function (host) { var checkboxes = host.get('checkboxes'); checkboxes.setEach('checked', !host.hasMaster); @@ -393,7 +393,7 @@ App.WizardStep6Controller = Em.Controller.extend({ lastHost.get('checkboxes').setEach('checked', true); } } else { - var recommendations = App.router.get('installerController.recommendations'); + var recommendations = this.get('content.recommendations'); // Get all host-component pairs from recommendations var componentHostPairs = recommendations.blueprint.host_groups.map(function (group) { return group.components.map(function (component) { @@ -495,7 +495,7 @@ App.WizardStep6Controller = Em.Controller.extend({ callValidation: function (successCallback) { var self = this; - if (App.supports.serverRecommendValidate) { + if (App.get('supports.serverRecommendValidate')) { self.callServerSideValidation(successCallback); } else { var res = self.callClientSideValidation(); @@ -540,7 +540,7 @@ App.WizardStep6Controller = Em.Controller.extend({ var invisibleComponents = invisibleMasters.concat(invisibleSlaves).concat(alreadyInstalledClients); - var invisibleBlueprint = blueprintUtils.filterByComponents(App.router.get('installerController.recommendations'), invisibleComponents); + var invisibleBlueprint = blueprintUtils.filterByComponents(this.get('content.recommendations'), invisibleComponents); masterBlueprint = blueprintUtils.mergeBlueprints(masterBlueprint, invisibleBlueprint); } else if (this.get('isAddHostWizard')) { masterBlueprint = self.getMasterSlaveBlueprintForAddHostWizard(); @@ -548,6 +548,9 @@ App.WizardStep6Controller = Em.Controller.extend({ slaveBlueprint = blueprintUtils.addComponentsToBlueprint(slaveBlueprint, invisibleSlaves); } + var bluePrintsForValidation = blueprintUtils.mergeBlueprints(masterBlueprint, slaveBlueprint); + this.set('content.recommendationsHostGroups', bluePrintsForValidation); + App.ajax.send({ name: 'config.validations', sender: self, @@ -556,7 +559,7 @@ App.WizardStep6Controller = Em.Controller.extend({ hosts: hostNames, services: services, validate: 'host_groups', - recommendations: blueprintUtils.mergeBlueprints(masterBlueprint, slaveBlueprint) + recommendations: bluePrintsForValidation }, success: 'updateValidationsSuccessCallback' }). http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/controllers/wizard/step7_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step7_controller.js b/ambari-web/app/controllers/wizard/step7_controller.js index bb222b5..6bf145c 100644 --- a/ambari-web/app/controllers/wizard/step7_controller.js +++ b/ambari-web/app/controllers/wizard/step7_controller.js @@ -29,7 +29,7 @@ var stringUtils = require('utils/string_utils'); * */ -App.WizardStep7Controller = Em.Controller.extend({ +App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, { name: 'wizardStep7Controller', @@ -113,8 +113,6 @@ App.WizardStep7Controller = Em.Controller.extend({ serviceConfigsData: require('data/service_configs'), - recommendedConfigs: null, - /** * Are advanced configs loaded * @type {bool} @@ -600,7 +598,7 @@ App.WizardStep7Controller = Em.Controller.extend({ var s = App.StackService.find(component.get('serviceName')), defaultGroupSelected = component.get('selectedConfigGroup.isDefault'); - if(!App.supports.serverRecommendValidate) { + if(!App.get('supports.serverRecommendValidate')) { if (s && s.get('configsValidator')) { var recommendedDefaults = this._getRecommendedDefaultsForComponent(component.get('serviceName')); s.get('configsValidator').set('recommendedDefaults', recommendedDefaults); @@ -723,8 +721,8 @@ App.WizardStep7Controller = Em.Controller.extend({ } //STEP 6: Distribute configs by service and wrap each one in App.ServiceConfigProperty (configs -> serviceConfigs) var self = this; - if (App.supports.serverRecommendValidate) { - this.loadDefaultConfigs(function() { + if (App.get('supports.serverRecommendValidate')) { + this.loadServerSideConfigsRecommendations().complete(function() { self.setStepConfigs(configs, storedConfigs); self.checkHostOverrideInstaller(); self.activateSpecialConfigs(); @@ -771,7 +769,7 @@ App.WizardStep7Controller = Em.Controller.extend({ masterComponentHosts: this.get('wizardController.content.masterComponentHosts'), slaveComponentHosts: this.get('wizardController.content.slaveComponentHosts') }; - var serviceConfigs = App.config.renderConfigs(configs, storedConfigs, this.get('allSelectedServiceNames'), this.get('installedServiceNames'), localDB, this.get('recommendedConfigs')); + var serviceConfigs = App.config.renderConfigs(configs, storedConfigs, this.get('allSelectedServiceNames'), this.get('installedServiceNames'), localDB, this.get('recommendationsConfigs')); if (this.get('wizardController.name') === 'addServiceController') { serviceConfigs.setEach('showConfig', true); serviceConfigs.setEach('selected', false); @@ -930,41 +928,6 @@ App.WizardStep7Controller = Em.Controller.extend({ } }, - loadDefaultConfigs: function(callback) { - var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'); - var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName'); - var services = installedServices.concat(selectedServices).uniq(); - this.set('isDefaultsLoaded', false); - var hostNames = Object.keys(this.get('content.hosts')); - App.ajax.send({ - 'name': 'wizard.step7.loadrecommendations.configs', - 'sender': this, - 'data': { - stackVersionUrl: App.get('stackVersionURL'), - hosts: hostNames, - services: services, - recommendations: App.router.get('installerController.recommendations') - }, - 'success': 'loadDefaultConfigsSuccess' - }) - .retry({ - times: App.maxRetries, - timeout: App.timeout - }) - .then(function () { - callback(); - }, function () { - App.showReloadPopup(); - console.log('Load recommendations failed'); - }); - }, - - loadDefaultConfigsSuccess: function(data) { - if (!data) { - console.warn('error while loading default config values'); - } - this.set("recommendedConfigs", Em.get(data.resources[0] , "recommendations.blueprint.configurations")); - }, /** * Check if Oozie or Hive use existing database then need * to restore missed properties @@ -1360,12 +1323,15 @@ App.WizardStep7Controller = Em.Controller.extend({ * @method submit */ submit: function () { + if (this.get('isSubmitDisabled')) { + return; + } var _this = this; - if (!this.get('isSubmitDisabled')) { - this.checkDatabaseConnectionTest().done(function () { + this.serverSideValidation().done(function () { + _this.checkDatabaseConnectionTest().done(function () { _this.resolveHiveMysqlDatabase(); }); - } + }); } }); http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index cd25878..989a657 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -648,6 +648,10 @@ Em.I18n.translations = { 'installer.step7.popup.mySQLWarning.confirmation.body': 'You will be brought back to the \"Assign Masters\" step and will lose all your current customizations. Are you sure?', 'installer.step7.popup.database.connection.header': 'Database Connectivity Warning', 'installer.step7.popup.database.connection.body': 'You have not run or passed the database connection test for: {0}. It is highly recommended that you pass the connection test before proceeding to prevent failures during deployment.', + 'installer.step7.popup.validation.failed.header': 'Validation failed.', + 'installer.step7.popup.validation.failed.body': 'Some services are not properly configured. You have to change the highlighted configs according to the recommended values.', + 'installer.step7.popup.validation.request.failed.body': 'Config validaition failed.', + 'installer.step7.popup.validation.warning.body': 'Some services are not properly configured. Recommended to change the highlighted configs. Are you sure you want to proceed witout changing configs?', 'installer.step7.oozie.database.new': 'New Derby Database', 'installer.step7.hive.database.new': 'New MySQL Database', http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/mixins.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js index 3384391..f087569 100644 --- a/ambari-web/app/mixins.js +++ b/ambari-web/app/mixins.js @@ -21,6 +21,7 @@ require('mixins/common/localStorage'); require('mixins/common/userPref'); +require('mixins/common/serverValidator'); require('mixins/models/service_mixin'); require('mixins/common/tableServerProvider'); require('mixins/common/table_server_mixin'); http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/mixins/common/serverValidator.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/serverValidator.js b/ambari-web/app/mixins/common/serverValidator.js new file mode 100644 index 0000000..a4be042 --- /dev/null +++ b/ambari-web/app/mixins/common/serverValidator.js @@ -0,0 +1,239 @@ +/** + * 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.ServerValidatorMixin = Em.Mixin.create({ + + /** + * @type {bool} set true if at leasst one config has error + */ + configValidationError: false, + + /** + * @type {bool} set true if at leasst one config has warning + */ + configValidationWarning: false, + + /** + * @type {bool} set true if at leasst one config has warning + */ + configValidationFailed: false, + + /** + * recommendation configs loaded from server + * (used only during install) + * @type {Object} + */ + recommendationsConfigs: null, + + /** + * by default loads data from model otherwise must be overridden as computed property + * refer to \assets\data\stacks\HDP-2.1\recommendations_configs.json to learn structure + * (shouldn't contain configurations filed) + * @type {Object} + */ + hostNames: function() { + return this.get('content.hosts') + ? Object.keys(this.get('content.hosts')) + : App.HostComponent.find().mapProperty('hostName').uniq(); + }.property('content.hosts'), + + + /** + * by default loads data from model otherwise must be overridden as computed property + * @type {Array} - of strings (serviceNames) + */ + serviceNames: function() { + return this.get('content.serviceName') + ? [this.get('content.serviceName')] + : App.StackService.find().filter(function(s){return s.get('isSelected') || s.get('isInstalled')}).mapProperty('serviceName'); + }.property('content.serviceName'), + + /** + * by default loads data from model otherwise must be overridden as computed property + * filter services that support server validation and concat with misc configs if Installer or current service + * @type {Array} - of objects (services) + */ + services: function() { + return this.get('content.serviceName') + ? [App.StackService.find(this.get('content.serviceName'))] + : App.StackService.find().filter(function(s){ + return s.get('allowServerValidator') && (s.get('isSelected') || s.get('isInsalled')) + }).concat(require("data/service_configs")); + }.property('content.serviceName'), + + /** + * by default loads data from model otherwise must be overridden as computed property + * can be used for service|host configs pages + * @type {Array} of strings (hostNames) + */ + hostGroups: function() { + return this.get('content.recommendationsHostGroups') || blueprintUtils.generateHostGroups(this.get('hostNames'), App.HostComponent.find()); + }.property('content.recommendationsHostGroups', 'hostNames'), + + /** + * controller that is child of this mixis has to contain stepConfigs + * @type {Array} + */ + stepConfigs: null, + + /** + * @method loadServerSideConfigsRecommendations + * laod recommendations from server + * (used only during install) + * @returns {*} + */ + loadServerSideConfigsRecommendations: function() { + if (this.get('recommendationsConfigs') || !App.get('supports.serverRecommendValidate')) { + return $.Deferred().resolve(); + } + return App.ajax.send({ + 'name': 'wizard.step7.loadrecommendations.configs', + 'sender': this, + 'data': { + stackVersionUrl: App.get('stackVersionURL'), + hosts: this.get('hostNames'), + services: this.get('serviceNames'), + recommendations: this.get('hostGroups') + }, + 'success': 'loadRecommendationsSuccess', + 'error': 'loadRecommendationsError' + }); + }, + + /** + * @method loadRecommendationsSuccess + * success callback after loading recommendations + * (used only during install) + * @param data + */ + loadRecommendationsSuccess: function(data) { + if (!data) { + console.warn('error while loading default config values'); + } + this.set("recommendationsConfigs", Em.get(data.resources[0] , "recommendations.blueprint.configurations")); + }, + + loadRecommendationsError: function() { + console.error('Load recommendations failed'); + }, + + /** + * @method serverSideValidation + * send request to validate configs + * @returns {*} + */ + serverSideValidation: function() { + var self = this; + var deferred = $.Deferred(); + if (!App.get('supports.serverRecommendValidate')) { + deferred.resolve(); + return deferred; + } + var recommendations = this.get('hostGroups'); + recommendations.blueprint.configurations = blueprintUtils.buildConfisJSON(this.get('services'), this.get('stepConfigs')); + App.ajax.send({ + name: 'config.validations', + sender: this, + data: { + stackVersionUrl: App.get('stackVersionURL'), + hosts: this.get('hostNames'), + services: this.get('serviceNames'), + validate: 'configurations', + recommendations: recommendations + }, + success: 'validationSuccess', + error: 'validationError' + }).complete(function() { + self.warnUser(deferred); + }); + return deferred; + }, + + + /** + * @method validationSuccess + * success callback after getting responce from server + * go through the step configs and set warn and error messages + * @param data + */ + validationSuccess: function(data) { + var self = this; + self.set('configValidationError', false); + self.set('configValidationWarning', false); + self.set('configValidationFailed', false); + data.resources.forEach(function(r) { + r.items.forEach(function(item){ + if (item.type == "configuration") { + self.get('stepConfigs').forEach(function(service) { + service.get('configs').forEach(function(property) { + if ((property.get('filename') == item['config-type'] + '.xml') && (property.get('name') == item['config-name'])) { + if (item.level == "ERROR") { + self.set('configValidationError', true); + property.set('errorMessage', item.message); + property.set('error', true); + } else if (item.level == "ERROR") { + self.set('configValidationWarning', true); + property.set('warnMessage', item.message); + property.set('warn', true); + } + } + }); + }) + } + }); + }); + }, + + validationError: function() { + this.set('configValidationFailed', true); + console.error('config validation failed'); + }, + + + /** + * warn user if some errors or warning were + * in seting up configs otherwise go to the nex operation + * @param deferred + * @returns {*} + */ + warnUser: function(deferred) { + var self = this; + if (this.get('configValidationFailed')) { + this.set('isSubmitDisabled', false); + this.set("isApplyingChanges", false); + return App.showAlertPopup(Em.I18n.t('installer.step7.popup.validation.failed.header'), Em.I18n.t('installer.step7.popup.validation.request.failed.body')); + } else if (this.get('configValidationError')) { + this.set("isApplyingChanges", false); + this.set('isSubmitDisabled', true); + return App.showAlertPopup(Em.I18n.t('installer.step7.popup.validation.failed.header'), Em.I18n.t('installer.step7.popup.validation.failed.body')); + } else if (this.get('configValidationWarning')) { + this.set('isSubmitDisabled', true); + this.set("isApplyingChanges", false); + return App.showConfirmationPopup(function () { + self.set('isSubmitDisabled', false); + self.set("isApplyingChanges", true); + deferred.resolve(); + }, Em.I18n.t('installer.step7.popup.validation.warning.body')); + } else { + deferred.resolve(); + } + } +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/models/service_config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/service_config.js b/ambari-web/app/models/service_config.js index 7ef67f3..2df2ad7 100644 --- a/ambari-web/app/models/service_config.js +++ b/ambari-web/app/models/service_config.js @@ -844,13 +844,14 @@ App.ServiceConfigProperty = Ember.Object.extend({ } } } - - var serviceValidator = this.get('serviceValidator'); - if (serviceValidator!=null) { - var validationIssue = serviceValidator.validateConfig(this); - if (validationIssue) { - this.set('warnMessage', validationIssue); - isWarn = true; + if (!App.get('supports.serverRecommendValidate')) { + var serviceValidator = this.get('serviceValidator'); + if (serviceValidator!=null) { + var validationIssue = serviceValidator.validateConfig(this); + if (validationIssue) { + this.set('warnMessage', validationIssue); + isWarn = true; + } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/models/stack_service.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/stack_service.js b/ambari-web/app/models/stack_service.js index c599c33..0b489f8 100644 --- a/ambari-web/app/models/stack_service.js +++ b/ambari-web/app/models/stack_service.js @@ -171,6 +171,9 @@ App.StackService = DS.Model.extend(App.ServiceModelMixin, { return defaultConfigsHandler && defaultConfigsHandler.configsValidator; }.property('serviceName'), + allowServerValidator: function() { + return ["YARN", "STORM", "MAPREDUCE2", "HIVE", "TEZ"].contains(this.get('serviceName')); + }.property('serviceName'), /** * configCategories are fetched from App.StackService.configCategories. * Also configCategories that does not match any serviceComponent of a service and not included in the permissible default pattern are omitted http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/routes/installer.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/routes/installer.js b/ambari-web/app/routes/installer.js index 7abfe96..0a5e46e 100644 --- a/ambari-web/app/routes/installer.js +++ b/ambari-web/app/routes/installer.js @@ -250,6 +250,7 @@ module.exports = Em.Route.extend({ controller.saveClients(wizardStep4Controller); controller.clearRecommendations(); // Force reload recommendation between steps 4 and 5 + controller.setDBProperty('recommendations', undefined); controller.setDBProperty('masterComponentHosts', undefined); router.transitionTo('step5'); } @@ -273,6 +274,7 @@ module.exports = Em.Route.extend({ var wizardStep5Controller = router.get('wizardStep5Controller'); controller.saveMasterComponentHosts(wizardStep5Controller); controller.setDBProperty('slaveComponentHosts', undefined); + controller.setDBProperty('recommendations', wizardStep5Controller.get('content.recommendations')); router.transitionTo('step6'); } }), @@ -302,6 +304,8 @@ module.exports = Em.Route.extend({ controller.setDBProperty('serviceConfigProperties', null); controller.setDBProperty('advancedServiceConfig', null); controller.setDBProperty('serviceConfigGroups', null); + controller.setDBProperty('recommendationsHostGroups', wizardStep6Controller.get('content.recommendationsHostGroups')); + controller.setDBProperty('recommendationsConfigs', null); controller.loadAdvancedConfigs(wizardStep7Controller); router.transitionTo('step7'); } @@ -326,12 +330,13 @@ module.exports = Em.Route.extend({ }, back: Em.Router.transitionTo('step6'), next: function (router) { - var installerController = router.get('installerController'); + var controller = router.get('installerController'); var wizardStep7Controller = router.get('wizardStep7Controller'); - installerController.saveServiceConfigProperties(wizardStep7Controller); + controller.saveServiceConfigProperties(wizardStep7Controller); if (App.supports.hostOverridesInstaller) { - installerController.saveServiceConfigGroups(wizardStep7Controller); + controller.saveServiceConfigGroups(wizardStep7Controller); } + controller.setDBProperty('recommendationsConfigs', wizardStep7Controller.get('recommendationsConfigs')); router.transitionTo('step8'); } }), http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/utils/blueprint.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/blueprint.js b/ambari-web/app/utils/blueprint.js index b56ceca..f8ec9c2 100644 --- a/ambari-web/app/utils/blueprint.js +++ b/ambari-web/app/utils/blueprint.js @@ -185,5 +185,86 @@ module.exports = { }); return res; + }, + + /** + * @method buildConfisJSON - generet JSON according to blueprint format + * @param {Em.Array} stepConfigs - array of Ember Objects + * @param {Array} services + * @returns {Object} + * Example: + * { + * "yarn-env": { + * "properties": { + * "content": "some value", + * "yarn_heapsize": "1024", + * "resourcemanager_heapsize": "1024", + * } + * }, + * "yarn-log4j": { + * "properties": { + * "content": "some other value" + * } + * } + * } + */ + buildConfisJSON: function(services, stepConfigs) { + var configurations = {}; + services.forEach(function(service) { + var config = stepConfigs.findProperty('serviceName', service.get('serviceName')); + if (config && service.get('configTypes')) { + Object.keys(service.get('configTypes')).forEach(function(type) { + configurations[type] = { + properties: {} + } + }); + config.get('configs').forEach(function(property){ + if (configurations[property.get('filename').replace('.xml','')]){ + configurations[property.get('filename').replace('.xml','')]['properties'][property.get('name')] = property.get('value'); + } else { + console.warn(property.get('name') + " from " + property.get('filename') + " can't be validate"); + } + }); + } + }); + return configurations; + }, + + /** + * @method generateHostGroups + * @param {Array} hostNames - list of all hostNames + * @param {Array} hostComponents - list of all hostComponents + * @returns {{blueprint: {host_groups: Array}, blueprint_cluster_binding: {host_groups: Array}}} + */ + generateHostGroups: function(hostNames, hostComponents) { + var recommendations = { + blueprint: { + host_groups: [] + }, + blueprint_cluster_binding: { + host_groups: [] + } + }; + + for (var i = 1; i <= hostNames.length; i++) { + var host_group = { + name: "host-group-" + i, + components: [] + }; + var hcFiltered = hostComponents.filterProperty('hostName', hostNames[i-1]).mapProperty('componentName'); + for (var j = 0; j < hcFiltered.length; j++) { + host_group.components.push({ + name: hcFiltered[j] + }); + } + recommendations.blueprint.host_groups.push(host_group); + recommendations.blueprint_cluster_binding.host_groups.push({ + name: "host-group-" + i, + hosts: [{ + fqdn: hostNames[i-1] + }] + }); + } + return recommendations; } }; http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/app/utils/config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/config.js b/ambari-web/app/utils/config.js index f576f38..62db148 100644 --- a/ambari-web/app/utils/config.js +++ b/ambari-web/app/utils/config.js @@ -690,8 +690,8 @@ App.config = Em.Object.create({ // Use calculated default values for some configs var recommendedDefaults = {}; - if (App.supports.serverRecommendValidate) { - if (!storedConfigs && service.get('configTypes')) { + if (App.get('supports.serverRecommendValidate')) { + if (!storedConfigs && service.get('configTypes') && service.get('allowServerValidator')) { Object.keys(service.get('configTypes')).forEach(function (type) { if (!recommended || !recommended[type]) { return; @@ -727,14 +727,14 @@ App.config = Em.Object.create({ } }); } - } - if (service.get('configsValidator')) { - service.get('configsValidator').set('recommendedDefaults', recommendedDefaults); - var validators = service.get('configsValidator').get('configValidators'); - for (var validatorName in validators) { - var c = configsByService.findProperty('name', validatorName); - if (c) { - c.set('serviceValidator', service.get('configsValidator')); + if (service.get('configsValidator')) { + service.get('configsValidator').set('recommendedDefaults', recommendedDefaults); + var validators = service.get('configsValidator').get('configValidators'); + for (var validatorName in validators) { + var c = configsByService.findProperty('name', validatorName); + if (c) { + c.set('serviceValidator', service.get('configsValidator')); + } } } } http://git-wip-us.apache.org/repos/asf/ambari/blob/818dc161/ambari-web/test/utils/blueprint_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/utils/blueprint_test.js b/ambari-web/test/utils/blueprint_test.js index e527a09..1f8d803 100644 --- a/ambari-web/test/utils/blueprint_test.js +++ b/ambari-web/test/utils/blueprint_test.js @@ -276,4 +276,141 @@ describe('utils/blueprint', function() { ); }); }); + + describe('#buildConfisJSON', function () { + var tests = [ + { + "services": [ + Em.Object.create({ + serviceName: "YARN", + configTypes: { + "yarn-site": {}, + "yarn-env": {} + }, + allowServerValidator: true, + isInstalled: true + }) + ], + "stepConfigs": [ + Em.Object.create({ + serviceName: "YARN", + configs: [ + Em.Object.create({ + name: "p1", + value: "v1", + filename: "yarn-site.xml" + }), + Em.Object.create({ + name: "p2", + value: "v2", + filename: "yarn-site.xml" + }), + Em.Object.create({ + name: "p3", + value: "v3", + filename: "yarn-env.xml" + }) + ] + }) + ], + "configurations": { + "yarn-site": { + "properties": { + "p1": "v1", + "p2": "v2" + } + }, + "yarn-env": { + "properties": { + "p3": "v3" + } + } + } + } + ]; + tests.forEach(function (test) { + it("generate configs for request (use in validation)", function () { + expect(blueprintUtils.buildConfisJSON(test.services, test.stepConfigs)).to.eql(test.configurations); + }); + }); + }); + + describe('#generateHostGroups', function () { + var tests = [ + { + "hostNames": ["host1", "host2"], + "hostComponents": [ + Em.Object.create({ + componentName: "C1", + hostName: "host1" + }), + Em.Object.create({ + componentName: "C2", + hostName: "host1" + }), + Em.Object.create({ + componentName: "C1", + hostName: "host2" + }), + Em.Object.create({ + componentName: "C3", + hostName: "host2" + }) + ], + result: { + blueprint: { + host_groups: [ + { + name: "host-group-1", + "components": [ + { + "name": "C1" + }, + { + "name": "C2" + } + ] + }, + { + name: "host-group-2", + "components": [ + { + "name": "C1" + }, + { + "name": "C3" + } + ] + } + ] + }, + blueprint_cluster_binding: { + host_groups: [ + { + "name": "host-group-1", + "hosts": [ + { + "fqdn": "host1" + } + ] + }, + { + "name": "host-group-2", + "hosts": [ + { + "fqdn": "host2" + } + ] + }, + ] + } + } + } + ]; + tests.forEach(function (test) { + it("generate host groups", function () { + expect(blueprintUtils.generateHostGroups(test.hostNames, test.hostComponents)).to.eql(test.result); + }); + }); + }); }); \ No newline at end of file