Repository: ambari Updated Branches: refs/heads/trunk b847a0c49 -> cda44fe2f
AMBARI-18848. Create common extensible mixin for host component and service configs validation (alexantonenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/cda44fe2 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/cda44fe2 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/cda44fe2 Branch: refs/heads/trunk Commit: cda44fe2fac7c16bf333ffcad405a856a630d7e2 Parents: b847a0c Author: Alex Antonenko <[email protected]> Authored: Thu Nov 10 17:28:14 2016 +0200 Committer: Alex Antonenko <[email protected]> Committed: Thu Nov 10 18:33:13 2016 +0200 ---------------------------------------------------------------------- ambari-web/app/assets/test/tests.js | 2 + .../resourceManager/step2_controller.js | 2 +- .../main/service/reassign/step2_controller.js | 2 +- .../app/controllers/wizard/step6_controller.js | 169 ++++++----------- ambari-web/app/mixins.js | 2 + ambari-web/app/mixins/common/blueprint.js | 24 ++- .../host_component_recommendation_mixin.js | 127 +++++++++++++ .../hosts/host_component_validation_mixin.js | 106 +++++++++++ ambari-web/app/mixins/common/serverValidator.js | 97 +++++++--- .../mixins/wizard/assign_master_components.js | 176 +++++++---------- ambari-web/app/utils/blueprint.js | 36 ++++ .../test/controllers/wizard/step5_test.js | 55 ------ .../test/controllers/wizard/step6_test.js | 6 +- .../host_component_recommendation_mixin_test.js | 189 +++++++++++++++++++ .../host_component_validation_mixin_test.js | 188 ++++++++++++++++++ 15 files changed, 876 insertions(+), 305 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index 5c794ea..6a71db6 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -161,6 +161,8 @@ var files = [ 'test/mixins/common/configs/configs_loader_test', 'test/mixins/common/configs/configs_saver_test', 'test/mixins/common/configs/toggle_isrequired_test', + 'test/mixins/common/hosts/host_component_recommendation_mixin_test', + 'test/mixins/common/hosts/host_component_validation_mixin_test', 'test/mixins/common/widgets/export_metrics_mixin_test', 'test/mixins/common/widgets/time_range_mixin_test', 'test/mixins/common/widgets/widget_section_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js index 8ad2a51..ec7ddd5 100644 --- a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js +++ b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js @@ -18,7 +18,7 @@ var App = require('app'); -App.RMHighAvailabilityWizardStep2Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, { +App.RMHighAvailabilityWizardStep2Controller = Em.Controller.extend(App.AssignMasterComponents, { name: "rMHighAvailabilityWizardStep2Controller", http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/controllers/main/service/reassign/step2_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/reassign/step2_controller.js b/ambari-web/app/controllers/main/service/reassign/step2_controller.js index c730771..f40508c 100644 --- a/ambari-web/app/controllers/main/service/reassign/step2_controller.js +++ b/ambari-web/app/controllers/main/service/reassign/step2_controller.js @@ -18,7 +18,7 @@ var App = require('app'); -App.ReassignMasterWizardStep2Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, { +App.ReassignMasterWizardStep2Controller = Em.Controller.extend(App.AssignMasterComponents, { name: "reassignMasterWizardStep2Controller", http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/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 f8b708c..87b0ee5 100644 --- a/ambari-web/app/controllers/wizard/step6_controller.js +++ b/ambari-web/app/controllers/wizard/step6_controller.js @@ -32,7 +32,7 @@ var validationUtils = require('utils/validator'); * slaveComponentHosts: App.db.slaveComponentHosts (slave-components-to-hosts mapping the user selected in Step 6) * */ -App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { +App.WizardStep6Controller = Em.Controller.extend(App.HostComponentValidationMixin, App.HostComponentRecommendationMixin, { name: 'wizardStep6Controller', @@ -571,13 +571,21 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { callValidation: function (successCallback) { var self = this; + var validationCallback = function() { + self.set('validationInProgress', false); + if (App.get('router.btnClickInProgress') && successCallback) { + successCallback(); + } + }; clearTimeout(this.get('timer')); if (this.get('validationInProgress')) { this.set('timer', setTimeout(function () { - self.callValidation(successCallback); + self.callValidation() + .then(validationCallback); }, 700)); } else { - this.callServerSideValidation(successCallback); + this.callServerSideValidation() + .then(validationCallback); } }, @@ -586,18 +594,37 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { * @method callServerSideValidation */ callServerSideValidation: function (successCallback) { - var self = this; this.set('validationInProgress', true); var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'); var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName'); var services = installedServices.concat(selectedServices).uniq(); + var hostNames = this.get('hosts').mapProperty('hostName'); + + var bluePrintsForValidation = this.getValidationBlueprint(); + this.set('content.recommendationsHostGroups', bluePrintsForValidation); + + return this.validateSelectedHostComponents({ + services: services, + hosts: hostNames, + blueprint: bluePrintsForValidation + }); + }, - var hostNames = self.get('hosts').mapProperty('hostName'); - var slaveBlueprint = self.getCurrentBlueprint(); + /** + * Returns blueprint passed to validation request + * @method getValidationBlueprint + */ + getValidationBlueprint: function() { + var slaveBlueprint = this.getCurrentBlueprint(); var masterBlueprint = null; - //Existing Installed but invisible masters on `Assign Masters page` should be included in host component layout for recommnedation/validation call var invisibleInstalledMasters = []; + var hostNames = this.get('hosts').mapProperty('hostName'); + var invisibleSlavesAndClients = App.StackServiceComponent.find().filter(function (component) { + return component.get("isSlave") && component.get("isShownOnInstallerSlaveClientPage") === false || + component.get("isClient") && component.get("isRequiredOnAllHosts"); + }).mapProperty("componentName"); + //Existing Installed but invisible masters on `Assign Masters page` should be included in host component layout for recommnedation/validation call if (this.get('isAddServiceWizard')) { var invisibleMasters = App.StackServiceComponent.find().filterProperty("isMaster").filterProperty("isShownOnAddServiceAssignMasterPage", false); invisibleInstalledMasters = invisibleMasters.filter(function(item){ @@ -605,56 +632,28 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { return masterComponent && !!masterComponent.get('totalCount'); }).mapProperty("componentName"); } - var invisibleSlavesAndClients = App.StackServiceComponent.find().filter(function (component) { - return component.get("isSlave") && component.get("isShownOnInstallerSlaveClientPage") === false || - component.get("isClient") && component.get("isRequiredOnAllHosts"); - }).mapProperty("componentName"); if (this.get('isInstallerWizard') || this.get('isAddServiceWizard')) { - masterBlueprint = self.getCurrentMastersBlueprint(); - var selectedClientComponents = self.get('content.clients').mapProperty('component_name'); + masterBlueprint = this.getCurrentMastersBlueprint(); + var selectedClientComponents = this.get('content.clients').mapProperty('component_name'); var alreadyInstalledClients = App.get('components.clients').reject(function (c) { return selectedClientComponents.contains(c); }); - var invisibleComponents = invisibleInstalledMasters.concat(invisibleSlavesAndClients).concat(alreadyInstalledClients); - var invisibleBlueprint = blueprintUtils.filterByComponents(this.get('content.recommendations'), invisibleComponents); masterBlueprint = blueprintUtils.mergeBlueprints(masterBlueprint, invisibleBlueprint); } else if (this.get('isAddHostWizard')) { - masterBlueprint = self.getCurrentMasterSlaveBlueprint(); + masterBlueprint = this.getCurrentMasterSlaveBlueprint(); hostNames = hostNames.concat(App.Host.find().mapProperty("hostName")).uniq(); slaveBlueprint = blueprintUtils.addComponentsToBlueprint(slaveBlueprint, invisibleSlavesAndClients); } - - var bluePrintsForValidation = blueprintUtils.mergeBlueprints(masterBlueprint, slaveBlueprint); - this.set('content.recommendationsHostGroups', bluePrintsForValidation); - - return App.ajax.send({ - name: 'config.validations', - sender: self, - data: { - stackVersionUrl: App.get('stackVersionURL'), - hosts: hostNames, - services: services, - validate: 'host_groups', - recommendations: bluePrintsForValidation - }, - success: 'updateValidationsSuccessCallback', - error: 'updateValidationsErrorCallback' - }). - then(function () { - self.set('validationInProgress', false); - if (App.get('router.btnClickInProgress') && successCallback) { - successCallback(); - } - } - ); + return blueprintUtils.mergeBlueprints(masterBlueprint, slaveBlueprint); }, /** * Success-callback for validations request * @param {object} data * @method updateValidationsSuccessCallback + * @override App.HostComponentRecommendationMixin */ updateValidationsSuccessCallback: function (data) { var self = this; @@ -738,6 +737,7 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { * @param {string} error * @param {object} opt * @method updateValidationsErrorCallback + * @override App.HostComponentRecommendationMixin */ updateValidationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) { }, @@ -746,46 +746,18 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { * Composes selected values of comboboxes into blueprint format */ getCurrentBlueprint: function () { - var self = this; - - var res = { - blueprint: { host_groups: [] }, - blueprint_cluster_binding: { host_groups: [] } - }; - - var clientComponents = self.get('content.clients').mapProperty('component_name'); - var mapping = self.get('hosts'); - - mapping.forEach(function (item, i) { - var groupName = 'host-group-' + (i+1); - - var hostGroup = { - name: groupName, - components: item.checkboxes.filterProperty('checked', true).map(function (checkbox) { - if (checkbox.component === "CLIENT") { - return clientComponents.map(function (client) { - return { name: client }; - }); - } else { - return { name: checkbox.component }; - } - }) - }; - - hostGroup.components = [].concat.apply([], hostGroup.components); - - var binding = { - name: groupName, - hosts: [ - { fqdn: item.hostName } - ] - }; - - res.blueprint.host_groups.push(hostGroup); - res.blueprint_cluster_binding.host_groups.push(binding); - }); - - return res; + var clientComponents = this.get('content.clients').mapProperty('component_name'); + var components = this.get('hosts').reduce(function(acc, host) { + var checked = host.checkboxes.filterProperty('checked', true).mapProperty('component'); + var componentNames = checked.indexOf('CLIENT') > -1 ? checked.without('CLIENT').concat(clientComponents) : checked; + return acc.concat(componentNames.map(function(componentName) { + return Em.Object.create({ + componentName: componentName, + hostName: Em.get(host, 'hostName') + }); + })); + }, []); + return this.getComponentsBlueprint(components); }, /** @@ -794,38 +766,13 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { * @method getCurrentMastersBlueprint */ getCurrentMastersBlueprint: function () { - var res = { - blueprint: { host_groups: [] }, - blueprint_cluster_binding: { host_groups: [] } - }; - var masters = this.get('content.masterComponentHosts'); - var hosts = this.get('content.hosts'); - - Em.keys(hosts).forEach(function (host, i) { - var groupName = 'host-group-' + (i + 1); - var components = []; - masters.forEach(function (master) { - if (master.hostName === host) { - components.push({ - name: master.component - }); - } - }); - res.blueprint.host_groups.push({ - name: groupName, - components: components - }); - res.blueprint_cluster_binding.host_groups.push({ - name: groupName, - hosts: [ - { - fqdn: host - } - ] - }); - }, this); - return blueprintUtils.mergeBlueprints(res, this.getCurrentSlaveBlueprint()); + var mastersBlueprint = this.getComponentsBlueprint(masters.map(function(component) { + var c = Em.Object.create(component); + c.set('componentName', c.get('component')); + return c; + }), this.get('hosts').mapProperty('hostName')); + return blueprintUtils.mergeBlueprints(mastersBlueprint, this.getCurrentSlaveBlueprint()); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/mixins.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins.js b/ambari-web/app/mixins.js index 6deec0a..20d5a93 100644 --- a/ambari-web/app/mixins.js +++ b/ambari-web/app/mixins.js @@ -60,6 +60,8 @@ require('mixins/common/configs/configs_saver'); require('mixins/common/configs/configs_loader'); require('mixins/common/configs/configs_comparator'); require('mixins/common/configs/toggle_isrequired'); +require('mixins/common/hosts/host_component_recommendation_mixin'); +require('mixins/common/hosts/host_component_validation_mixin'); require('mixins/common/widgets/export_metrics_mixin'); require('mixins/common/widgets/time_range_mixin'); require('mixins/common/widgets/widget_mixin'); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/mixins/common/blueprint.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/blueprint.js b/ambari-web/app/mixins/common/blueprint.js index a006b9c..3df3b1a 100644 --- a/ambari-web/app/mixins/common/blueprint.js +++ b/ambari-web/app/mixins/common/blueprint.js @@ -23,14 +23,16 @@ var dataManipulation = require('utils/data_manipulation'); App.BlueprintMixin = Em.Mixin.create({ /** - * returns blueprint for all currently installed master, slave and client components - * @method getCurrentMasterSlaveBlueprint + * returns blueprint for selected components + * @method getComponentsBlueprint + * @param {Em.Object[]} components components objects or model @see App.HostComponent + * required properties are <code>hostName, componentName</code> + * @param {string[]} [allHostNames=[]] host names, provide when host groups without components should be created + * @return {Object} blueprint object */ - getCurrentMasterSlaveBlueprint: function () { - var components = App.HostComponent.find(); - var hosts = components.mapProperty("hostName").uniq(); + getComponentsBlueprint: function(components, allHostNames) { + var hosts = components.mapProperty('hostName').concat(allHostNames || []).uniq(); var mappedComponents = dataManipulation.groupPropertyValues(components, 'hostName'); - var res = { blueprint: { host_groups: [] }, blueprint_cluster_binding: { host_groups: [] } @@ -57,6 +59,14 @@ App.BlueprintMixin = Em.Mixin.create({ }, /** + * returns blueprint for all currently installed master, slave and client components + * @method getCurrentMasterSlaveBlueprint + */ + getCurrentMasterSlaveBlueprint: function () { + return this.getComponentsBlueprint(App.HostComponent.find()); + }, + + /** * Returns blueprint for all currently installed slave and client components */ getCurrentSlaveBlueprint: function () { @@ -69,4 +79,4 @@ App.BlueprintMixin = Em.Mixin.create({ return blueprintUtils.filterByComponents(fullBlueprint, components); } -}); \ No newline at end of file +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/mixins/common/hosts/host_component_recommendation_mixin.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/hosts/host_component_recommendation_mixin.js b/ambari-web/app/mixins/common/hosts/host_component_recommendation_mixin.js new file mode 100644 index 0000000..b0d58e7 --- /dev/null +++ b/ambari-web/app/mixins/common/hosts/host_component_recommendation_mixin.js @@ -0,0 +1,127 @@ +/** + * 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. + */ + +require('mixins/common/blueprint'); + +var App = require('app'); + +/** + * @typedef {object} RecommendComponentObject + * @property {string} componentName name of the component + * @property {number} [size=0] desired components size + * @property {string[]} [hosts=[]] hosts assigned to component + */ + +/** + * @typedef {object} HostComponentRecommendationOptions + * @property {string[]} hosts list of host names, in most cases all available host names + * @property {RecommendComponentObject[]} components list of components + * @property {string[]} services list of service names + * @property {object} [blueprint=null] when null blueprint will be created by <code>HostComponentRecommendationOptions.components</code> attribute + */ + +/** + * @typedef {object} HostRecommendationRequestData + * @property {string} stackVersionUrl stack version url + * @property {string[]} hosts host names + * @property {string[]} services service names + * @property {string} recommend recommendation type e.g. 'host_groups' + * @property {object} recommendations blueprint object + */ + +/** + * Contains methods to get server recommendation and validation for host components + * @type {Em.Mixin} + */ +App.HostComponentRecommendationMixin = Em.Mixin.create(App.BlueprintMixin, { + + /** + * Get recommendations for selected components + * @method getRecommendedHosts + * @param {HostComponentRecommendationOptions} recommend list of the components + * @return {$.Deferred} + */ + getRecommendedHosts: function(options) { + var opts = $.extend({ + services: [], + hosts: [], + components: [], + blueprint: null + }, options || {}); + + opts.components = this.formatRecommendComponents(opts.components); + return this.loadComponentsRecommendationsFromServer(this.getRecommendationRequestData(opts)); + }, + + /** + * @method formatRecommendComponents + * @param {RecommendComponentsObject[]} components + * @returns {Em.Object[]} + */ + formatRecommendComponents: function(components) { + var res = []; + if (!components) return []; + components.forEach(function(component) { + var componentName = Em.get(component, 'componentName'); + if (Em.get(component, 'hosts.length')) { + Em.get(component, 'hosts').forEach(function(hostName) { + res.push(Em.Object.create({ + componentName: componentName, + hostName: hostName + })); + }); + } + }); + return res; + }, + + /** + * Returns request data for recommendation request + * @param {HostComponentRecommendationOptions} options + * @return {HostRecommendationRequestData} + * @method getRecommendationRequestData + */ + getRecommendationRequestData: function(options) { + return { + recommend: 'host_groups', + stackVersionUrl: App.get('stackVersionURL'), + hosts: options.hosts, + services: options.services, + recommendations: options.blueprint || this.getComponentsBlueprint(options.components) + }; + }, + + /** + * Get recommendations info from API + * @method loadComponentsRecommendationsFromServer + * @param {object} recommendationData + * @returns {$.Deferred} + */ + loadComponentsRecommendationsFromServer: function(recommendationData) { + return App.ajax.send({ + name: 'wizard.loadrecommendations', + sender: this, + data: recommendationData, + success: 'loadRecommendationsSuccessCallback', + error: 'loadRecommendationsErrorCallback' + }); + }, + + loadRecommendationsSuccessCallback: function() {}, + loadRecommendationsErrorCallback: function() {} +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/mixins/common/hosts/host_component_validation_mixin.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/common/hosts/host_component_validation_mixin.js b/ambari-web/app/mixins/common/hosts/host_component_validation_mixin.js new file mode 100644 index 0000000..538f83c --- /dev/null +++ b/ambari-web/app/mixins/common/hosts/host_component_validation_mixin.js @@ -0,0 +1,106 @@ +/** + * 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. + */ + +require('mixins/common/blueprint'); + +var App = require('app'); + +/** + * @typedef {object} HostValidationRequestData + * @property {string} stackVersionUrl stack version url + * @property {string[]} hosts host names + * @property {string[]} services service names + * @property {string} validate validation type e.g. 'host_groups' + * @property {object} recommendations blueprint object + */ + +App.HostComponentValidationMixin = Em.Mixin.create(App.BlueprintMixin, { + /** + * Validate host components + * @method validateSelectedHostComponents + * @param {HostComponentRecommendationOptions} options + * @return {$.Deferred} + */ + validateSelectedHostComponents: function(options) { + var opts = $.extend({ + services: [], + blueprint: null, + hosts: [], + components: [] + }, options || {}); + + opts.components = this.formatValidateComponents(opts.components); + return this.getHostComponentValidationRequest(this.getHostComponentValidationParams(opts)); + }, + + /** + * @method formatValidateComponents + * @param {RecommendComponentObject[]} components + * @returns {Em.Object[]} + */ + formatValidateComponents: function(components) { + var res = []; + if (!components) return []; + components.forEach(function(component) { + var componentName = Em.get(component, 'componentName'); + if (Em.get(component, 'hosts.length')) { + Em.get(component, 'hosts').forEach(function(hostName) { + res.push(Em.Object.create({ + componentName: componentName, + hostName: hostName + })); + }); + } + }); + return res; + }, + + /** + * Returns request data for validation request + * @method getHostComponentValidationParams + * @return {HostValidationRequestData} + */ + getHostComponentValidationParams: function(options) { + return { + stackVersionUrl: App.get('stackVersionURL'), + hosts: options.hosts, + services: options.services, + validate: 'host_groups', + recommendations: options.blueprint || this.getComponentsBlueprint(options.components) + }; + }, + + /** + * Performs request to validate components location + * @method getHostComponentValidationRequest + * @param {object} validationData + * @returns {$.Deferred} + */ + getHostComponentValidationRequest: function(validationData) { + return App.ajax.send({ + name: 'config.validations', + sender: this, + data: validationData, + success: 'updateValidationsSuccessCallback', + error: 'updateValidationsErrorCallback' + }); + }, + + updateValidationsSuccessCallback: function() {}, + updateValidationsErrorCallback: function() {} +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/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 index b251d84..319c281 100644 --- a/ambari-web/app/mixins/common/serverValidator.js +++ b/ambari-web/app/mixins/common/serverValidator.js @@ -19,6 +19,21 @@ var App = require('app'); var blueprintUtils = require('utils/blueprint'); +/** + * @typedef {object} ConfigsValidationRequestData + * @property {string} stackVersionUrl stack version url + * @property {string[]} hosts host names + * @property {string[]} services service names + * @property {string} validate validation type e.g. 'configurations' + * @property {object} recommendations blueprint object + */ + +/** + * @typedef {object} ConfigsValidationOptions + * @property {string[]} hosts host names + * @property {string[]} services service names + * @property {object} blueprint service configurations blueprint + */ App.ServerValidatorMixin = Em.Mixin.create({ /** @@ -124,41 +139,79 @@ App.ServerValidatorMixin = Em.Mixin.create({ var stepConfigs = this.get('stepConfigs'); var dfd = $.Deferred(); - this.getBlueprintConfigurations().done(function(blueprintConfigurations) { - recommendations.blueprint.configurations = blueprintConfigurations; - App.ajax.send({ - name: 'config.validations', - sender: self, - data: { - stackVersionUrl: App.get('stackVersionURL'), + this.getBlueprintConfigurations(this.get('stepConfigs')) + .done(function(blueprintConfigurations) { + recommendations.blueprint.configurations = blueprintConfigurations; + self.validateSelectedConfigs({ hosts: self.get('hostNames'), services: self.get('serviceNames'), - validate: 'configurations', - recommendations: recommendations - }, - success: 'validationSuccess', - error: 'validationError' - }).done(dfd.resolve).fail(dfd.reject); - }); + blueprint: recommendations + }).done(dfd.resolve) + .fail(dfd.reject); + }); return dfd.promise(); }, /** + * Perform service config validation + * @param validateSelectedConfigs + * @param {ConfigsValidationOptions} options + * @returns {$.Deferred} + */ + validateSelectedConfigs: function(options) { + var opts = $.extend({ + services: [], + hosts: [], + blueprint: null + }, options || {}); + + return this.getServiceConfigsValidationRequest(this.getServiceConfigsValidationParams(opts)); + }, + + /** + * @method getServiceConfigsValidationRequest + * @param {ConfigsValidationRequestData} validationData + * @returns {$.Deferred} + */ + getServiceConfigsValidationRequest: function(validationData) { + return App.ajax.send({ + name: 'config.validations', + sender: this, + data: validationData, + success: 'validationSuccess', + error: 'validationError' + }); + }, + + /** + * @method getServiceConfigsValidationParams + * @param {ConfigsValidationOptions} options + * @returns {ConfigsValidationRequestData} + */ + getServiceConfigsValidationParams: function(options) { + return { + stackVersionUrl: App.get('stackVersionURL'), + hosts: options.hosts, + services: options.services, + validate: 'configurations', + recommendations: options.blueprint + }; + }, + + /** * Return JSON for blueprint configurations + * @param {App.ServiceConfigs[]} serviceConfigs * @returns {*} */ - getBlueprintConfigurations: function () { + getBlueprintConfigurations: function (serviceConfigs) { var dfd = $.Deferred(); - var stepConfigs = this.get('stepConfigs'); - // check if we have configs from 'cluster-env', if not, then load them, as they are mandatory for validation request - if (!stepConfigs.findProperty('serviceName', 'MISC')) { - App.config.getConfigsByTypes([{site: 'cluster-env', serviceName: 'MISC'}]).done(function (configs) { - stepConfigs = stepConfigs.concat(configs); - dfd.resolve(blueprintUtils.buildConfigsJSON(stepConfigs)); + if (!serviceConfigs.findProperty('serviceName', 'MISC')) { + App.config.getConfigsByTypes([{site: 'cluster-env', serviceName: 'MISC'}]).done(function(configs) { + dfd.resolve(blueprintUtils.buildConfigsJSON(serviceConfigs.concat(configs))); }); } else { - dfd.resolve(blueprintUtils.buildConfigsJSON(stepConfigs)); + dfd.resolve(blueprintUtils.buildConfigsJSON(serviceConfigs)); } return dfd.promise(); }, http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/mixins/wizard/assign_master_components.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mixins/wizard/assign_master_components.js b/ambari-web/app/mixins/wizard/assign_master_components.js index 46409b2..70f1638 100644 --- a/ambari-web/app/mixins/wizard/assign_master_components.js +++ b/ambari-web/app/mixins/wizard/assign_master_components.js @@ -16,6 +16,9 @@ * limitations under the License. */ +require('mixins/common/hosts/host_component_recommendation_mixin'); +require('mixins/common/hosts/host_component_validation_mixin'); + var App = require('app'); var blueprintUtils = require('utils/blueprint'); var numberUtils = require('utils/number_utils'); @@ -27,7 +30,7 @@ var validationUtils = require('utils/validator'); * Should be used with controller linked with App.AssignMasterComponentsView * @type {Ember.Mixin} */ -App.AssignMasterComponents = Em.Mixin.create({ +App.AssignMasterComponents = Em.Mixin.create(App.HostComponentValidationMixin, App.HostComponentRecommendationMixin, { /** * Array of master component names to show on the page @@ -390,39 +393,6 @@ App.AssignMasterComponents = Em.Mixin.create({ }, /** - * Send AJAX request to validate current host layout - * @param blueprint - blueprint for validation (can be with/withour slave/client components) - */ - validate: function(blueprint, callback) { - var self = this; - - var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'); - var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName'); - var services = installedServices.concat(selectedServices).uniq(); - - var hostNames = self.get('hosts').mapProperty('host_name'); - - App.ajax.send({ - name: 'config.validations', - sender: self, - data: { - stackVersionUrl: App.get('stackVersionURL'), - hosts: hostNames, - services: services, - validate: 'host_groups', - recommendations: blueprint - }, - success: 'updateValidationsSuccessCallback', - error: 'updateValidationsErrorCallback' - }).then(function() { - if (callback) { - callback(); - } - } - ); - }, - - /** * Success-callback for validations request * @param {object} data * @method updateValidationsSuccessCallback @@ -471,38 +441,34 @@ App.AssignMasterComponents = Em.Mixin.create({ }, /** - * Composes selected values of comboboxes into master blueprint + merge it with currently installed slave blueprint + * @override App.HostComponentRecommendationMixin */ - getCurrentBlueprint: function() { - var self = this; - - var res = { - blueprint: { host_groups: [] }, - blueprint_cluster_binding: { host_groups: [] } - }; - - var mapping = self.get('masterHostMapping'); - - mapping.forEach(function(item, i) { - var group_name = 'host-group-' + (i+1); - - var host_group = { - name: group_name, - components: item.masterServices.map(function(master) { - return { name: master.component_name }; - }) - }; - - var binding = { - name: group_name, - hosts: [ { fqdn: item.host_name } ] - }; + getRecommendationRequestData: function(options) { + var res = this._super(options); + res.services = this.getCurrentServiceNames(); + if (!this.get('isInstallerWizard')) { + res.recommendations = this.getCurrentMasterSlaveBlueprint(); + } + return res; + }, - res.blueprint.host_groups.push(host_group); - res.blueprint_cluster_binding.host_groups.push(binding); - }); + /** + * @override App.HostComponentRecommendationMixin + */ + getHostComponentValidationParams: function(options) { + var res = this._super(options); + res.services = this.getCurrentServiceNames(); + return res; + }, - return blueprintUtils.mergeBlueprints(res, self.getCurrentSlaveBlueprint()); + /** + * Returns selected and installed service names + * @return {string[]} + */ + getCurrentServiceNames: function() { + return App.StackService.find().filter(function(i) { + return i.get('isSelected') || i.get('isInstalled'); + }).mapProperty('serviceName').uniq(); }, /** @@ -519,7 +485,6 @@ App.AssignMasterComponents = Em.Mixin.create({ App.StackServiceComponent.find().forEach(function (stackComponent) { stackComponent.set('serviceComponentId', 1); }, this); - }, /** @@ -527,12 +492,17 @@ App.AssignMasterComponents = Em.Mixin.create({ * @method loadStep */ loadStep: function () { + var self = this; this.clearStep(); if (this._additionalClearSteps) { this._additionalClearSteps(); } this.renderHostInfo(); - this.loadComponentsRecommendationsFromServer(this.loadStepCallback); + this.getRecommendedHosts({ + hosts: this.get('hosts').mapProperty('host_name') + }).then(function() { + self.loadStepCallback(self.createComponentInstallationObjects(), self); + }); }, /** @@ -636,50 +606,19 @@ App.AssignMasterComponents = Em.Mixin.create({ /** * Get recommendations info from API - * @param {function}callback - * @param {boolean} includeMasters + * @param {object} recommendationBlueprint * @method loadComponentsRecommendationsFromServer + * @override App.HostComponentRecommendationMixin */ - loadComponentsRecommendationsFromServer: function(callback, includeMasters) { + loadComponentsRecommendationsFromServer: function(recommendationBlueprint) { var self = this; if (this.get('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); - } - else { - var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName'); - var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName'); - var services = installedServices.concat(selectedServices).uniq(); - - var hostNames = self.get('hosts').mapProperty('host_name'); - - var data = { - stackVersionUrl: App.get('stackVersionURL'), - hosts: hostNames, - services: services, - recommend: 'host_groups' - }; - - if (includeMasters) { - // Made partial recommendation request for reflect in blueprint host-layout changes which were made by user in UI - data.recommendations = self.getCurrentBlueprint(); - } - else - if (!self.get('isInstallerWizard')) { - data.recommendations = self.getCurrentMasterSlaveBlueprint(); - } - - return App.ajax.send({ - name: 'wizard.loadrecommendations', - sender: self, - data: data, - success: 'loadRecommendationsSuccessCallback', - error: 'loadRecommendationsErrorCallback' - }).then(function () { - callback(self.createComponentInstallationObjects(), self); - }); + return $.Deferred().resolve().promise(); + } else { + return this._super(recommendationBlueprint); } }, @@ -1159,9 +1098,14 @@ App.AssignMasterComponents = Em.Mixin.create({ this.set('validationInProgress', true); // load recommendations with partial request - self.loadComponentsRecommendationsFromServer(function() { - // For validation use latest received recommendations because it contains current master layout and recommended slave/client layout - self.validate(self.get('recommendations'), function() { + this.getRecommendedHosts({ + hosts: this.get('hosts').mapProperty('host_name'), + components: this.getCurrentComponentHostMap() + }).then(function() { + self.validateSelectedHostComponents({ + hosts: self.get('hosts').mapProperty('host_name'), + blueprint: self.get('recommendations') + }).then(function() { if (callback) { callback(); } @@ -1174,6 +1118,28 @@ App.AssignMasterComponents = Em.Mixin.create({ }, true); }, + getCurrentComponentHostMap: function() { + return this.get('masterHostMapping').reduce(function(acc, i) { + var components = Em.getWithDefault(i, 'masterServices', []); + var hostName = Em.get(i, 'host_name'); + components.forEach(function(i) { + var componentName = Em.get(i, 'component_name'); + var component = acc.findProperty('componentName', componentName); + if (component) { + Em.set(component, 'hosts', Em.getWithDefault(component, 'hosts', []).concat(hostName).uniq()); + Em.set(component, 'size', Em.getWithDefault(component, 'hosts.length', 0)); + } else { + acc.push({ + componentName: componentName, + hosts: [hostName], + size: 1 + }); + } + }); + return acc; + }, []); + }, + _goNextStepIfValid: function () { if (!this.get('submitDisabled')) { App.router.send('next'); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/app/utils/blueprint.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/blueprint.js b/ambari-web/app/utils/blueprint.js index 1148d3d..1216c3f 100644 --- a/ambari-web/app/utils/blueprint.js +++ b/ambari-web/app/utils/blueprint.js @@ -16,8 +16,44 @@ * limitations under the License. */ +/** + * @typedef {object} BlueprintObject + * @property {BlueprintMappings} blueprint + * @property {BlueprintClusterBindings} blueprint_cluster_bindings + */ +/** + * @typedef {object} BlueprintMappings + * @property {BlueprintMappingsHostGroup[]} host_groups + */ +/** + * @typedef {object[]} BlueprintMappingsHostGroup + * @property {BlueprintHostGroupComponent[]} components + * @property {string} name host group name + */ +/** + * @typedef {object} BlueprintHostGroupComponent + * @property {string} name component name + */ +/** + * @typedef {object} BlueprintClusterBindings + * @property {BlueprintClusterBindingsHostGroup[]} host_groups + */ +/** + * @typedef {object} BlueprintClusterBindingsHostGroup + * @property {BlueprintClusterBindingsHostGroupHosts[]} hosts + * @property {string} name host group name + */ +/** + * @typedef {object} BlueprintClusterBindingsHostGroupHosts + * @property {string} fqdn host fqdn + */ module.exports = { + /** + * @param {BlueprintObject} masterBlueprint + * @param {BlueprintObject} slaveBlueprint + * @return {BlueprintObject} + */ mergeBlueprints: function(masterBlueprint, slaveBlueprint) { console.time('mergeBlueprints'); var self = this; http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/test/controllers/wizard/step5_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step5_test.js b/ambari-web/test/controllers/wizard/step5_test.js index 821a709..3c9048d 100644 --- a/ambari-web/test/controllers/wizard/step5_test.js +++ b/ambari-web/test/controllers/wizard/step5_test.js @@ -1149,61 +1149,6 @@ describe('App.WizardStep5Controller', function () { }); - describe('#getCurrentBlueprint', function () { - - beforeEach(function() { - sinon.stub(c, 'getCurrentSlaveBlueprint', function() { - return { - blueprint_cluster_binding: { - host_groups: [] - }, - blueprint: { - host_groups: [] - } - }; - }); - }); - - afterEach(function() { - c.getCurrentSlaveBlueprint.restore(); - }); - - it('should map masterHostMapping', function () { - - c.reopen({masterHostMapping: [ - {host_name: 'h1', hostInfo:{}, masterServices: [ - {serviceId: 's1', component_name: 'c1'}, - {serviceId: 's2', component_name: 'c2'} - ]}, - {host_name: 'h2', hostInfo:{}, masterServices: [ - {serviceId: 's1', component_name: 'c1'}, - {serviceId: 's3', component_name: 'c3'} - ]} - ]}); - - var r = c.getCurrentBlueprint(); - expect(r).to.eql({"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": "h1"} - ]}, - {"name": "host-group-2", "hosts": [ - {"fqdn": "h2"} - ]} - ]}} - ); - }); - - }); - describe('#updateValidationsSuccessCallback', function() { beforeEach(function() { http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/test/controllers/wizard/step6_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step6_test.js b/ambari-web/test/controllers/wizard/step6_test.js index ed8f16a..4cf155c 100644 --- a/ambari-web/test/controllers/wizard/step6_test.js +++ b/ambari-web/test/controllers/wizard/step6_test.js @@ -1123,7 +1123,7 @@ describe('App.WizardStep6Controller', function () { masterComponentHosts: Em.A([ {hostName: 'h1', component: 'c1'} ]), - hosts: {'h1': {}}, + hosts: [{hostName: 'h1'}], m: 'one host and one component', e:{ blueprint: { @@ -1154,7 +1154,7 @@ describe('App.WizardStep6Controller', function () { {hostName: 'h2', component: 'c2'}, {hostName: 'h2', component: 'c3'} ]), - hosts: {'h1': {}, 'h2': {}, 'h3': {}}, + hosts: [{hostName: 'h1'}, {hostName: 'h2'}, {hostName: 'h3'}], m: 'multiple hosts and multiple components', e: { blueprint: { @@ -1206,7 +1206,7 @@ describe('App.WizardStep6Controller', function () { tests.forEach(function (test) { it(test.m, function () { controller.set('content.masterComponentHosts', test.masterComponentHosts); - controller.set('content.hosts', test.hosts); + controller.set('hosts', test.hosts); var r = controller.getCurrentMastersBlueprint(); expect(r).to.eql(test.e); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/test/mixins/common/hosts/host_component_recommendation_mixin_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/hosts/host_component_recommendation_mixin_test.js b/ambari-web/test/mixins/common/hosts/host_component_recommendation_mixin_test.js new file mode 100644 index 0000000..2e95a06 --- /dev/null +++ b/ambari-web/test/mixins/common/hosts/host_component_recommendation_mixin_test.js @@ -0,0 +1,189 @@ +/** + * 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'); + +describe('App.HostComponentRecommendationMixin', function() { + var mixedObject; + beforeEach(function() { + mixedObject = Em.Object.create(App.HostComponentRecommendationMixin, {}); + }); + + describe('#formatRecommendComponents', function() { + var cases = [ + { + components: undefined, + hosts: [], + m: 'when undefined components passed should return empty array', + e: [] + }, + { + components: [], + hosts: [], + m: 'when empty array passed should return empty array', + e: [] + }, + { + components: [ + { componentName: 'C1', hosts: ['h1', 'h2']}, + { componentName: 'C2', hosts: ['h3']}, + ], + hosts: [], + m: 'when components passed along with hosts location should return correct objects', + e: [ + Em.Object.create({ componentName: 'C1', hostName: 'h1'}), + Em.Object.create({ componentName: 'C1', hostName: 'h2'}), + Em.Object.create({ componentName: 'C2', hostName: 'h3'}), + ] + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + expect(mixedObject.formatRecommendComponents(test.components, test.hosts)).to.be.eql(test.e); + }); + }); + }); + + describe('#getRecommendedHosts', function() { + var cases; + beforeEach(function() { + sinon.stub(mixedObject, 'loadComponentsRecommendationsFromServer'); + sinon.stub(mixedObject, 'getRecommendationRequestData'); + sinon.stub(mixedObject, 'formatRecommendComponents', function(c) { return c;}); + }); + + afterEach(function() { + mixedObject.loadComponentsRecommendationsFromServer.restore(); + mixedObject.getRecommendationRequestData.restore(); + mixedObject.formatRecommendComponents.restore(); + }); + + cases = [ + { + opts: null, + e: { + services: [], + hosts: [], + components: [], + blueprint: null + }, + m: 'when no args passed defaults should be used and no error thrown' + }, + { + opts: { + services: ['s1'], + hosts: ['h1'], + components: [{componentName: 'c1'}], + blueprint: {recommend:{}} + }, + e: { + services: ['s1'], + hosts: ['h1'], + components: [{componentName: 'c1'}], + blueprint: {recommend:{}} + }, + m: 'should normaly merge defaults with passed options' + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + mixedObject.getRecommendedHosts(test.opts); + expect(mixedObject.getRecommendationRequestData.args[0][0]).to.be.eql(test.e); + }); + }); + }); + + describe('#loadComponentsRecommedationsFromServer', function() { + it('default request options checking', function() { + mixedObject.loadComponentsRecommendationsFromServer('someData'); + expect(App.ajax.send.args[0][0]).to.be.eql({ + name: 'wizard.loadrecommendations', + sender: mixedObject, + data: 'someData', + success: 'loadRecommendationsSuccessCallback', + error: 'loadRecommendationsErrorCallback' + }); + }); + }); + + describe('#getRecommendationRequestData', function() { + var cases; + beforeEach(function() { + sinon.stub(App, 'get').withArgs('stackVersionURL').returns('/stack/url'); + }); + afterEach(function() { + App.get.restore(); + }); + cases = [ + { + options: { + hosts: ['h1'], + services: ['s1'], + components: [Em.Object.create({componentName: 'c1', hostName: 'h1'})], + blueprint: null + }, + e: { + recommend: 'host_groups', + stackVersionUrl: '/stack/url', + hosts: ['h1'], + services: ['s1'], + recommendations: { + blueprint: { + host_groups: [ + {name: 'host-group-1',components: [{name: 'c1'}]} + ] + }, + blueprint_cluster_binding: { + host_groups: [ + {name: 'host-group-1', hosts: [{fqdn: 'h1'}]} + ] + } + } + }, + m: 'when blueprint not passed it should be generated from components list' + }, + { + options: { + hosts: ['h1'], + services: ['s1'], + components: [Em.Object.create({componentName: 'c1', hostName: 'h1'})], + blueprint: { blueprint: {}} + }, + e: { + recommend: 'host_groups', + stackVersionUrl: '/stack/url', + hosts: ['h1'], + services: ['s1'], + recommendations: { + blueprint: {} + } + }, + m: 'when blueprint passed it should be used instead of generated blueprint' + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + expect(mixedObject.getRecommendationRequestData(test.options)).to.be.eql(test.e); + }); + }); + }); + +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/cda44fe2/ambari-web/test/mixins/common/hosts/host_component_validation_mixin_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mixins/common/hosts/host_component_validation_mixin_test.js b/ambari-web/test/mixins/common/hosts/host_component_validation_mixin_test.js new file mode 100644 index 0000000..7cbe232 --- /dev/null +++ b/ambari-web/test/mixins/common/hosts/host_component_validation_mixin_test.js @@ -0,0 +1,188 @@ +/** + * 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'); + +describe('App.HostComponentValidationMixin', function() { + var mixedObject; + beforeEach(function() { + mixedObject = Em.Object.create(App.HostComponentValidationMixin, {}); + }); + + describe('#formatValidateComponents', function() { + var cases = [ + { + components: undefined, + hosts: [], + m: 'when undefined components passed should return empty array', + e: [] + }, + { + components: [], + hosts: [], + m: 'when empty array passed should return empty array', + e: [] + }, + { + components: [ + { componentName: 'C1', hosts: ['h1', 'h2']}, + { componentName: 'C2', hosts: ['h3']}, + ], + hosts: [], + m: 'when components passed along with hosts location should return correct objects', + e: [ + Em.Object.create({ componentName: 'C1', hostName: 'h1'}), + Em.Object.create({ componentName: 'C1', hostName: 'h2'}), + Em.Object.create({ componentName: 'C2', hostName: 'h3'}), + ] + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + expect(mixedObject.formatValidateComponents(test.components, test.hosts)).to.be.eql(test.e); + }); + }); + }); + + describe('#validateSelectedHostComponents', function() { + var cases; + beforeEach(function() { + sinon.stub(mixedObject, 'getHostComponentValidationRequest'); + sinon.stub(mixedObject, 'getHostComponentValidationParams'); + sinon.stub(mixedObject, 'formatValidateComponents', function(c) { return c;}); + }); + + afterEach(function() { + mixedObject.getHostComponentValidationRequest.restore(); + mixedObject.getHostComponentValidationParams.restore(); + mixedObject.formatValidateComponents.restore(); + }); + + cases = [ + { + opts: null, + e: { + services: [], + hosts: [], + components: [], + blueprint: null + }, + m: 'when no args passed defaults should be used and no error thrown' + }, + { + opts: { + services: ['s1'], + hosts: ['h1'], + components: [{componentName: 'c1'}], + blueprint: {recommend:{}} + }, + e: { + services: ['s1'], + hosts: ['h1'], + components: [{componentName: 'c1'}], + blueprint: {recommend:{}} + }, + m: 'should normaly merge defaults with passed options' + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + mixedObject.validateSelectedHostComponents(test.opts); + expect(mixedObject.getHostComponentValidationParams.args[0][0]).to.be.eql(test.e); + }); + }); + }); + + describe('#getHostComponentValidationRequest', function() { + it('default request options checking', function() { + mixedObject.getHostComponentValidationRequest('someData'); + expect(App.ajax.send.args[0][0]).to.be.eql({ + name: 'config.validations', + sender: mixedObject, + data: 'someData', + success: 'updateValidationsSuccessCallback', + error: 'updateValidationsErrorCallback' + }); + }); + }); + + describe('#getHostComponentValidationParams', function() { + var cases; + beforeEach(function() { + sinon.stub(App, 'get').withArgs('stackVersionURL').returns('/stack/url'); + }); + afterEach(function() { + App.get.restore(); + }); + cases = [ + { + options: { + hosts: ['h1'], + services: ['s1'], + components: [Em.Object.create({componentName: 'c1', hostName: 'h1'})], + blueprint: null + }, + e: { + validate: 'host_groups', + stackVersionUrl: '/stack/url', + hosts: ['h1'], + services: ['s1'], + recommendations: { + blueprint: { + host_groups: [ + {name: 'host-group-1',components: [{name: 'c1'}]} + ] + }, + blueprint_cluster_binding: { + host_groups: [ + {name: 'host-group-1', hosts: [{fqdn: 'h1'}]} + ] + } + } + }, + m: 'when blueprint not passed it should be generated from components list' + }, + { + options: { + hosts: ['h1'], + services: ['s1'], + components: [Em.Object.create({componentName: 'c1', hostName: 'h1'})], + blueprint: { blueprint: {}} + }, + e: { + validate: 'host_groups', + stackVersionUrl: '/stack/url', + hosts: ['h1'], + services: ['s1'], + recommendations: { + blueprint: {} + } + }, + m: 'when blueprint passed it should be used instead of generated blueprint' + } + ]; + + cases.forEach(function(test) { + it(test.m, function() { + expect(mixedObject.getHostComponentValidationParams(test.options)).to.be.eql(test.e); + }); + }); + }); +});
