You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2016/11/14 18:27:40 UTC
[27/50] [abbrv] ambari git commit: AMBARI-18848. Create common
extensible mixin for host component and service configs validation
(alexantonenko)
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/branch-dev-patch-upgrade
Commit: cda44fe2fac7c16bf333ffcad405a856a630d7e2
Parents: b847a0c
Author: Alex Antonenko <hi...@gmail.com>
Authored: Thu Nov 10 17:28:14 2016 +0200
Committer: Alex Antonenko <hi...@gmail.com>
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);
+ });
+ });
+ });
+});