Selaa lähdekoodia

AMBARI-10404. Make assignMasters controller more reusable and add support of cardinality. (akovalenko)

Aleksandr Kovalenko 10 vuotta sitten
vanhempi
commit
8d576ebdc1
36 muutettua tiedostoa jossa 1462 lisäystä ja 1899 poistoa
  1. 3 3
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js
  2. 6 108
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step2_controller.js
  3. 2 2
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js
  4. 1 1
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step4_controller.js
  5. 1 1
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step5_controller.js
  6. 1 1
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step7_controller.js
  7. 1 1
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js
  8. 1 3
      ambari-web/app/controllers/main/admin/highAvailability/nameNode/wizard_controller.js
  9. 11 28
      ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js
  10. 34 92
      ambari-web/app/controllers/main/service/reassign/step2_controller.js
  11. 2 948
      ambari-web/app/controllers/wizard/step5_controller.js
  12. 1 0
      ambari-web/app/mixins.js
  13. 1071 0
      ambari-web/app/mixins/wizard/assign_master_components.js
  14. 9 4
      ambari-web/app/models/stack_service_component.js
  15. 1 1
      ambari-web/app/routes/high_availability_routes.js
  16. 2 2
      ambari-web/app/routes/rm_high_availability_routes.js
  17. 8 7
      ambari-web/app/styles/application.less
  18. 25 10
      ambari-web/app/templates/common/assign_master_components.hbs
  19. 0 83
      ambari-web/app/templates/main/admin/highAvailability/nameNode/step2.hbs
  20. 0 93
      ambari-web/app/templates/main/admin/highAvailability/resourceManager/step2.hbs
  21. 1 0
      ambari-web/app/views.js
  22. 201 0
      ambari-web/app/views/common/assign_master_components_view.js
  23. 4 2
      ambari-web/app/views/main/admin/highAvailability/nameNode/step2_view.js
  24. 2 2
      ambari-web/app/views/main/admin/highAvailability/nameNode/step3_view.js
  25. 1 1
      ambari-web/app/views/main/admin/highAvailability/nameNode/step4_view.js
  26. 1 1
      ambari-web/app/views/main/admin/highAvailability/nameNode/step6_view.js
  27. 2 2
      ambari-web/app/views/main/admin/highAvailability/nameNode/step8_view.js
  28. 4 2
      ambari-web/app/views/main/admin/highAvailability/resourceManager/step2_view.js
  29. 5 2
      ambari-web/app/views/main/service/reassign/step2_view.js
  30. 15 164
      ambari-web/app/views/wizard/step5_view.js
  31. 7 219
      ambari-web/test/controllers/main/service/reassign/step2_controller_test.js
  32. 27 110
      ambari-web/test/controllers/wizard/step5_test.js
  33. 4 2
      ambari-web/test/views/main/admin/highAvailability/nameNode/step3_view_test.js
  34. 2 1
      ambari-web/test/views/main/admin/highAvailability/nameNode/step4_view_test.js
  35. 2 1
      ambari-web/test/views/main/admin/highAvailability/nameNode/step6_view_test.js
  36. 4 2
      ambari-web/test/views/main/admin/highAvailability/nameNode/step8_view_test.js

+ 3 - 3
ambari-web/app/controllers/main/admin/highAvailability/nameNode/rollback_controller.js

@@ -247,12 +247,12 @@ App.HighAvailabilityRollbackController = App.HighAvailabilityProgressPageControl
   },
   stopStandbyNameNode: function(){
     console.warn('func: stopStandbyNameNode');
-    var hostName = this.get('content.masterComponentHosts').findProperty('isAddNameNode', true).hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).hostName;
     this.updateComponent('NAMENODE', hostName, "HDFS", "Stop");
   },
   stopNameNode: function(){
     console.warn('func: stopNameNode');
-    var hostName = this.get('content.masterComponentHosts').findProperty('isCurNameNode').hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true).hostName;
     this.updateComponent('NAMENODE', hostName, "HDFS", "Stop");
   },
   restoreHDFSConfigs: function(){
@@ -276,7 +276,7 @@ App.HighAvailabilityRollbackController = App.HighAvailabilityProgressPageControl
   },
   deleteAdditionalNameNode: function(){
     console.warn('func: deleteAdditionalNameNode');
-    var hostNames = this.get('content.masterComponentHosts').filterProperty('isAddNameNode', true).mapProperty('hostName');
+    var hostNames = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).mapProperty('hostName');
     this.unInstallComponent('NAMENODE', hostNames);
   },
   startAllServices: function(){

+ 6 - 108
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step2_controller.js

@@ -20,121 +20,19 @@ var App = require('app');
 
 require('controllers/wizard/step5_controller');
 
-App.HighAvailabilityWizardStep2Controller = App.WizardStep5Controller.extend({
+App.HighAvailabilityWizardStep2Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, {
 
   name:"highAvailabilityWizardStep2Controller",
 
-  /**
-   * master components which could be assigned to multiple hosts in HA wizard
-   */
-  multipleComponentsHaWizard: ['NAMENODE', 'JOURNALNODE'],
+  useServerValidation: false,
 
-  /**
-   * master components supported by Ambari
-   */
+  mastersToShow: ['NAMENODE', 'JOURNALNODE'],
 
-  multipleComponents: ['NAMENODE', 'JOURNALNODE','ZOOKEEPER_SERVER','HBASE_MASTER','RESOURCEMANAGER'],
+  mastersToAdd: ['NAMENODE', 'JOURNALNODE', 'JOURNALNODE', 'JOURNALNODE'],
 
-  /**
-   * overrides method in wizardStep5Controller
-   * it must be empty as it shouldn't be run
-   */
-  masterHostMappingObserver: function() {},
+  showCurrentPrefix: ['NAMENODE'],
 
-  /**
-   * Load services info to appropriate variable and return masterComponentHosts
-   * @return Array
-   */
-  masterHostMapping:function () {
-    var mapping = [], mappingObject, self = this, mappedHosts, hostObj, hostInfo;
-    //get the unique assigned hosts and find the master services assigned to them
-
-
-    mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq();
-
-    mappedHosts.forEach(function (item) {
-
-      hostObj = self.get("hosts").findProperty("host_name", item);
-      console.log("Name of the host is: " + hostObj.host_name);
-
-      var masterServices = self.get("selectedServicesMasters").filterProperty("selectedHost", item);
-      masterServices.forEach(function(item){
-        if(this.get('multipleComponentsHaWizard').contains(item.component_name)){
-          item.set('color','green');
-        }else{
-          item.set('color','grey');
-        }
-      }, this);
-
-      mappingObject = Ember.Object.create({
-        host_name:item,
-        hostInfo:hostObj.host_info,
-        masterServices:masterServices
-      });
-
-      mapping.pushObject(mappingObject);
-    }, this);
-
-    return mapping.sortProperty('host_name');
-  }.property("selectedServicesMasters.@each.selectedHost"),
-
-  /**
-   * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
-   * @param masterComponents
-   */
-  renderComponents:function (masterComponents) {
-    var services = this.get('content.services').filterProperty('isInstalled', true).mapProperty('serviceName'); //list of shown services
-
-    var result = [];
-
-    var curNameNode = masterComponents.findProperty('component_name',"NAMENODE");
-    curNameNode.isCurNameNode = true;
-    curNameNode.serviceComponentId = 0;
-
-    //Create JOURNALNODE
-    for (var index = 0; index < 3; index++) {
-      masterComponents.push(
-        {
-          component_name: "JOURNALNODE",
-          display_name: "JournalNode",
-          isServiceCoHost: false,
-          isInstalled: false,
-          selectedHost: this.get("hosts")[index].get("host_name"),
-          serviceId: "HDFS",
-          serviceComponentId: index
-        }
-      )
-    }
-    //Create Additional NameNode
-    masterComponents.push(
-      {
-        component_name: "NAMENODE",
-        display_name: "NameNode",
-        isServiceCoHost: false,
-        isInstalled: false,
-        selectedHost: this.get("hosts").mapProperty('host_name').without(curNameNode.selectedHost)[0],
-        serviceId: "HDFS",
-        isAddNameNode: true,
-        serviceComponentId: 1
-      }
-    );
-
-    masterComponents.forEach(function (item) {
-      var componentObj = Ember.Object.create(item);
-      result.push(componentObj);
-    }, this);
-
-    this.set("selectedServicesMasters", result);
-
-    var components = result.filterProperty('component_name',"NAMENODE");
-    components.push.apply(components, result.filterProperty('component_name',"JOURNALNODE"));
-
-    this.set('servicesMasters', components);
-    this.set('componentToRebalance', "NAMENODE");
-    this.incrementProperty('rebalanceComponentHostsCounter');
-    this.set('componentToRebalance', "JOURNALNODE");
-    this.incrementProperty('rebalanceComponentHostsCounter');
-  }
+  showAdditionalPrefix: ['NAMENODE']
 
 });
 

+ 2 - 2
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js

@@ -111,8 +111,8 @@ App.HighAvailabilityWizardStep3Controller = Em.Controller.extend({
   },
 
   tweakServiceConfigValues: function(configs,nameServiceId) {
-    var currentNameNodeHost = this.get('content.masterComponentHosts').findProperty('isCurNameNode').hostName;
-    var newNameNodeHost = this.get('content.masterComponentHosts').findProperty('isAddNameNode').hostName;
+    var currentNameNodeHost = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true).hostName;
+    var newNameNodeHost = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).hostName;
     var journalNodeHosts = this.get('content.masterComponentHosts').filterProperty('component', 'JOURNALNODE').mapProperty('hostName');
     var zooKeeperHosts = this.get('content.masterComponentHosts').filterProperty('component', 'ZOOKEEPER_SERVER').mapProperty('hostName');
     var config = configs.findProperty('name','dfs.namenode.rpc-address.' + nameServiceId + '.nn1');

+ 1 - 1
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step4_controller.js

@@ -31,7 +31,7 @@ App.HighAvailabilityWizardStep4Controller = Em.Controller.extend({
   isNameNodeStarted: true,
 
   pullCheckPointStatus: function () {
-    var hostName = this.get('content.masterComponentHosts').findProperty('isCurNameNode', true).hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true).hostName;
     App.ajax.send({
       name: 'admin.high_availability.getNnCheckPointStatus',
       sender: this,

+ 1 - 1
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step5_controller.js

@@ -45,7 +45,7 @@ App.HighAvailabilityWizardStep5Controller = App.HighAvailabilityProgressPageCont
   },
 
   installNameNode: function () {
-    var hostName = this.get('content.masterComponentHosts').findProperty('isAddNameNode').hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).hostName;
     this.createComponent('NAMENODE', hostName, "HDFS");
   },
 

+ 1 - 1
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step7_controller.js

@@ -32,7 +32,7 @@ App.HighAvailabilityWizardStep7Controller = App.HighAvailabilityProgressPageCont
   },
 
   startNameNode: function () {
-    var hostName = this.get('content.masterComponentHosts').findProperty('isCurNameNode').hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true).hostName;
     this.updateComponent('NAMENODE', hostName, "HDFS", "Start");
   }
 });

+ 1 - 1
ambari-web/app/controllers/main/admin/highAvailability/nameNode/step9_controller.js

@@ -36,7 +36,7 @@ App.HighAvailabilityWizardStep9Controller = App.HighAvailabilityProgressPageCont
   },
 
   startSecondNameNode: function () {
-    var hostName = this.get('content.masterComponentHosts').findProperty('isAddNameNode', true).hostName;
+    var hostName = this.get('content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).hostName;
     this.updateComponent('NAMENODE', hostName, "HDFS", "Start");
   },
 

+ 1 - 3
ambari-web/app/controllers/main/admin/highAvailability/nameNode/wizard_controller.js

@@ -94,9 +94,7 @@ App.HighAvailabilityWizardController = App.WizardController.extend({
         component: _component.get('component_name'),
         hostName: _component.get('selectedHost'),
         serviceId: _component.get('serviceId'),
-        isCurNameNode: _component.get('isCurNameNode'),
-        isAddNameNode: _component.get('isAddNameNode'),
-        isInstalled: true
+        isInstalled:  _component.get('isInstalled')
       });
     });
     this.setDBProperty('masterComponentHosts', masterComponentHosts);

+ 11 - 28
ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step2_controller.js

@@ -18,35 +18,18 @@
 
 var App = require('app');
 
-App.RMHighAvailabilityWizardStep2Controller = App.WizardStep5Controller.extend({
+App.RMHighAvailabilityWizardStep2Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, {
+
   name: "rMHighAvailabilityWizardStep2Controller",
 
-  loadStepCallback: function (components, self) {
-    this._super(components, self);
-    self.hideUnusedComponents();
-  },
-
-  renderComponents: function (masterComponents) {
-    var existedRM = masterComponents.findProperty('component_name', 'RESOURCEMANAGER');
-    existedRM.isAdditional = false;
-    var additionalRMSelectedHost = this.get('content.rmHosts.additionalRM') ||
-        this.get('hosts').mapProperty('host_name').without(existedRM.selectedHost)[0];
-    var additionalRM = $.extend({}, existedRM, {
-      isInstalled: false,
-      isAdditional: true,
-      selectedHost: additionalRMSelectedHost
-    });
-    masterComponents.push(additionalRM);
-    this._super(masterComponents);
-  },
-
-  /**
-   * Remove service masters, that should be hidden in this wizard
-   */
-  hideUnusedComponents: function () {
-    var servicesMasters = this.get('servicesMasters');
-    servicesMasters = servicesMasters.filterProperty('component_name', 'RESOURCEMANAGER');
-    this.set('servicesMasters', servicesMasters);
-  }
+  useServerValidation: false,
+
+  mastersToShow: ['RESOURCEMANAGER'],
+
+  mastersToAdd: ['RESOURCEMANAGER'],
+
+  showCurrentPrefix: ['RESOURCEMANAGER'],
+
+  showAdditionalPrefix: ['RESOURCEMANAGER']
 });
 

+ 34 - 92
ambari-web/app/controllers/main/service/reassign/step2_controller.js

@@ -18,110 +18,52 @@
 
 var App = require('app');
 
-App.ReassignMasterWizardStep2Controller = App.WizardStep5Controller.extend({
+App.ReassignMasterWizardStep2Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, {
 
-  currentHostId: null,
-  showCurrentHost: true,
-  useServerValidation: false,
+  name: "reassignMasterWizardStep2Controller",
 
-  loadStep: function() {
-    // If High Availability is enabled NameNode became a multiple component
-    if (App.get('isHaEnabled')) {
-      this.get('multipleComponents').push('NAMENODE');
-    }
-    this.clearStep();
-    this.renderHostInfo();
-    this.loadStepCallback(this.loadComponents(), this);
+  useServerValidation: false,
 
-    // if moving NameNode with HA enabled
-    if (this.get('content.reassign.component_name') === "NAMENODE" && App.get('isHaEnabled')) {
-      this.set('showCurrentHost', false);
-      this.set('componentToRebalance', 'NAMENODE');
-      this.incrementProperty('rebalanceComponentHostsCounter');
+  mastersToShow: function () {
+    return [this.get('content.reassign.component_name')];
+  }.property('content.reassign.component_name'),
 
-    // if moving ResourceManager with HA enabled
-    } else if (this.get('content.reassign.component_name') === "RESOURCEMANAGER" && App.get('isRMHaEnabled')) {
-      this.set('showCurrentHost', false);
-      this.set('componentToRebalance', 'RESOURCEMANAGER');
-      this.incrementProperty('rebalanceComponentHostsCounter');
-    } else {
-      this.set('showCurrentHost', true);
-      this.rebalanceSingleComponentHosts(this.get('content.reassign.component_name'));
-    }
-  },
+  mastersToMove: function () {
+    return [this.get('content.reassign.component_name')];
+  }.property('content.reassign.component_name'),
 
   /**
-   * load master components
-   * @return {Array}
+   * Show 'Current: <host>' for masters with single instance
    */
-  loadComponents: function () {
-    var masterComponents = this.get('content.masterComponentHosts');
-    this.set('currentHostId', this.get('content').get('reassign').host_id);
-    var componentNameToReassign = this.get('content').get('reassign').component_name;
-    var result = [];
-
-    masterComponents.forEach(function (master) {
-      var color = "grey";
-      if (master.component == componentNameToReassign) {
-        color = 'green';
-      }
-      result.push({
-        component_name: master.component,
-        display_name: App.format.role(master.component),
-        selectedHost: master.hostName,
-        isInstalled: true,
-        serviceId: App.HostComponent.find().findProperty('componentName', master.component).get('serviceName'),
-        isServiceCoHost: ['HIVE_METASTORE', 'WEBHCAT_SERVER'].contains(master.component) && !this.get('this.isReassignWizard'),
-        color: color
-      });
-    }, this);
-    return result;
-  },
+  additionalHostsList: function () {
+    if (this.get('servicesMastersToShow.length') === 1) {
+      return [
+        {
+          label: Em.I18n.t('services.reassign.step2.currentHost'),
+          host: App.HostComponent.find().findProperty('componentName', this.get('content.reassign.component_name')).get('hostName')
+        }
+      ];
+    }
+    return [];
+  }.property('content.reassign.component_name', 'servicesMastersToShow.length'),
 
   /**
-   * rebalance single component among available hosts
-   * @param componentName
-   * @return {Boolean}
+   * Assignment is valid only if for one master component host was changed
+   * @returns {boolean}
    */
-  rebalanceSingleComponentHosts: function (componentName) {
-    var currentComponents = this.get("selectedServicesMasters").filterProperty("component_name", componentName),
-      availableComponentHosts = [];
-
-    this.get("hosts").forEach(function (item) {
-      if ( (this.get('currentHostId') !== item.get('host_name')) && !(currentComponents.mapProperty("selectedHost").contains(item.get('host_name')))) {
-        availableComponentHosts.pushObject(item);
+  customClientSideValidation: function () {
+    var reassigned = 0;
+    var existedComponents = App.HostComponent.find().filterProperty('componentName', this.get('content.reassign.component_name')).mapProperty('hostName');
+    console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', existedComponents);
+    var newComponents = this.get('servicesMasters').filterProperty('component_name', this.get('content.reassign.component_name')).mapProperty('selectedHost');
+    console.log('>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>', newComponents);
+    existedComponents.forEach(function (host) {
+      if (!newComponents.contains(host)) {
+        reassigned++;
       }
     }, this);
+    return reassigned === 1;
+  }
 
-    if (availableComponentHosts.length > 0) {
-      var preparedAvailableHosts = availableComponentHosts.slice(0);
-
-      currentComponents.forEach(function (item) {
-        if (item.get('selectedHost') === this.get('currentHostId') && item.get('component_name') === componentName) {
-          item.set('selectedHost', preparedAvailableHosts.objectAt(0).host_name);
-        }
-        item.set("availableHosts", preparedAvailableHosts.sortProperty('host_name'));
-      }, this);
-      return true;
-    }
-    return false;
-  },
-
-  updateIsSubmitDisabled: function () {
-    var isSubmitDisabled = this._super();
-    if (!isSubmitDisabled) {
-      var reassigned = 0;
-      var existedComponents = App.HostComponent.find().filterProperty('componentName', this.get('content.reassign.component_name')).mapProperty('hostName');
-      var newComponents = this.get('servicesMasters').mapProperty('selectedHost');
-      existedComponents.forEach(function (host) {
-        if (!newComponents.contains(host)) {
-          reassigned++;
-        }
-      }, this);
-      isSubmitDisabled = reassigned !== 1;
-    }
-    this.set('submitDisabled', isSubmitDisabled);
-    return isSubmitDisabled;
-  }.observes('servicesMasters.@each.selectedHost', 'servicesMasters.@each.isHostNameValid')
 });
 

+ 2 - 948
ambari-web/app/controllers/wizard/step5_controller.js

@@ -17,954 +17,8 @@
  */
 
 var App = require('app');
-var blueprintUtils = require('utils/blueprint');
-var numberUtils = require('utils/number_utils');
-var validationUtils = require('utils/validator');
 
-App.WizardStep5Controller = Em.Controller.extend(App.BlueprintMixin, {
+App.WizardStep5Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, {
 
-  name: "wizardStep5Controller",
-
-  /**
-   * Step title
-   * Custom if <code>App.ReassignMasterController</code> is used
-   * @type {string}
-   */
-  title: function () {
-    if (this.get('content.controllerName') == 'reassignMasterController') {
-      return Em.I18n.t('installer.step5.reassign.header');
-    }
-    return Em.I18n.t('installer.step5.header');
-  }.property('content.controllerName'),
-
-  /**
-   * Is ReassignWizard used
-   * @type {bool}
-   */
-  isReassignWizard: function () {
-    return this.get('content.controllerName') == 'reassignMasterController';
-  }.property('content.controllerName'),
-
-  /**
-   * Is isHighAvailabilityWizard used
-   * @type {bool}
-   */
-  isHighAvailabilityWizard: function () {
-    return this.get('content.controllerName') == 'highAvailabilityWizardController';
-  }.property('content.controllerName'),
-
-  /**
-   * Check if <code>installerWizard</code> used
-   * @type {bool}
-   */
-  isInstallerWizard: function () {
-    return this.get('content.controllerName') === 'installerController';
-  }.property('content.controllerName'),
-
-  /**
-   * Is AddServiceWizard used
-   * @type {bool}
-   */
-  isAddServiceWizard: function () {
-    return this.get('content.controllerName') == 'addServiceController';
-  }.property('content.controllerName'),
-
-  /**
-   * Master components which could be assigned to multiple hosts
-   * @type {string[]}
-   */
-  multipleComponents: function () {
-    return App.get('components.multipleMasters');
-  }.property('App.components.multipleMasters'),
-
-  /**
-   * Master components which could be assigned to multiple hosts
-   * @type {string[]}
-   */
-  addableComponents: function () {
-    return App.get('components.addableMasterInstallerWizard');
-  }.property('App.components.addableMasterInstallerWizard'),
-
-  /**
-   * Define state for submit button
-   * @type {bool}
-   */
-  submitDisabled: false,
-
-  /**
-   * Is Submit-click processing now
-   * @type {bool}
-   */
-  submitButtonClicked: false,
-
-  /**
-   * Either use or not use server validation in this controller
-   * @type {bool}
-   */
-  useServerValidation: true,
-
-  /**
-   * Trigger for executing host names check for components
-   * Should de "triggered" when host changed for some component and when new multiple component is added/removed
-   * @type {bool}
-   */
-  hostNameCheckTrigger: false,
-
-  /**
-   * List of hosts
-   * @type {Array}
-   */
-  hosts: [],
-
-  /**
-   * Name of multiple component which host name was changed last
-   * @type {Object|null}
-   */
-  componentToRebalance: null,
-
-  /**
-   * Name of component which host was changed last
-   * @type {string}
-   */
-  lastChangedComponent: null,
-
-  /**
-   * Flag for rebalance multiple components
-   * @type {number}
-   */
-  rebalanceComponentHostsCounter: 0,
-
-  /**
-   * @type {Ember.Enumerable}
-   */
-  servicesMasters: [],
-
-  /**
-   * @type {Ember.Enumerable}
-   */
-  selectedServicesMasters: [],
-
-
-  /**
-   * Is data for current step loaded
-   * @type {bool}
-   */
-  isLoaded: false,
-
-  /**
-   * Validation error messages which don't related with any master
-   */
-  generalErrorMessages: [],
-
-  /**
-   * Validation warning messages which don't related with any master
-   */
-  generalWarningMessages: [],
-
-  /**
-   * Is masters-hosts layout initial one
-   * @type {bool}
-   */
-  isInitialLayout: true,
-
-  /**
-   * true if any error exists
-   */
-  anyError: function() {
-    return this.get('servicesMasters').some(function(m) { return m.get('errorMessage'); }) || this.get('generalErrorMessages').some(function(m) { return m; });
-  }.property('servicesMasters.@each.errorMessage', 'generalErrorMessages'),
-
-  /**
-   * true if any warning exists
-   */
-  anyWarning: function() {
-    return this.get('servicesMasters').some(function(m) { return m.get('warnMessage'); }) || this.get('generalWarningMessages').some(function(m) { return m; });
-  }.property('servicesMasters.@each.warnMessage', 'generalWarningMessages'),
-
-    /**
-   * Clear loaded recommendations
-   */
-  clearRecommendations: function() {
-    if (this.get('content.recommendations')) {
-      this.set('content.recommendations', null);
-    }
-  },
-
-  /**
-   * List of host with assigned masters
-   * Format:
-   * <code>
-   *   [
-   *     {
-   *       host_name: '',
-   *       hostInfo: {},
-   *       masterServices: [],
-   *       masterServicesToDisplay: [] // used only in template
-   *    },
-   *    ....
-   *   ]
-   * </code>
-   * @type {Ember.Enumerable}
-   */
-  masterHostMapping: function () {
-    var mapping = [], mappingObject, mappedHosts, hostObj;
-    //get the unique assigned hosts and find the master services assigned to them
-    mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq();
-    mappedHosts.forEach(function (item) {
-      hostObj = this.get("hosts").findProperty("host_name", item);
-      // User may input invalid host name (this is handled in hostname checker). Here we just skip it
-      if (!hostObj) return;
-      var masterServices = this.get("selectedServicesMasters").filterProperty("selectedHost", item),
-        masterServicesToDisplay = [];
-      masterServices.mapProperty('display_name').uniq().forEach(function (n) {
-        masterServicesToDisplay.pushObject(masterServices.findProperty('display_name', n));
-      });
-      mappingObject = Em.Object.create({
-        host_name: item,
-        hostInfo: hostObj.host_info,
-        masterServices: masterServices,
-        masterServicesToDisplay: masterServicesToDisplay
-      });
-
-      mapping.pushObject(mappingObject);
-    }, this);
-
-    return mapping.sortProperty('host_name');
-  }.property("selectedServicesMasters.@each.selectedHost", 'selectedServicesMasters.@each.isHostNameValid'),
-
-  /**
-   * Count of hosts without masters
-   * @type {number}
-   */
-  remainingHosts: function () {
-    if (this.get('content.controllerName') === 'installerController') {
-      return 0;
-    } else {
-      return (this.get("hosts.length") - this.get("masterHostMapping.length"));
-    }
-  }.property('masterHostMapping.length', 'selectedServicesMasters.@each.selectedHost'),
-
-  /**
-   * Update submit button status
-   * @metohd updateIsSubmitDisabled
-   */
-  updateIsSubmitDisabled: function () {
-
-    if (this.thereIsNoMasters()) {
-      return false;
-    }
-
-    var isSubmitDisabled = this.get('servicesMasters').someProperty('isHostNameValid', false);
-
-    if (this.get('useServerValidation')) {
-      this.set('submitDisabled', true);
-
-      if (this.get('servicesMasters').length === 0) {
-        return;
-      }
-
-      if (!isSubmitDisabled) {
-        if (!this.get('isInitialLayout')) {
-          this.clearRecommendations(); // reset previous recommendations
-        } else {
-          this.set('isInitialLayout', false);
-        }
-        this.recommendAndValidate();
-      }
-    } else {
-      this.set('submitDisabled', isSubmitDisabled);
-      return isSubmitDisabled;
-    }
-  }.observes('servicesMasters.@each.selectedHost'),
-
-  /**
-   * 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
-  */
-  updateValidationsSuccessCallback: function (data) {
-    var self = this;
-
-    var generalErrorMessages = [];
-    var generalWarningMessages = [];
-    this.get('servicesMasters').setEach('warnMessage', null);
-    this.get('servicesMasters').setEach('errorMessage', null);
-    var anyErrors = false;
-
-    var validationData = validationUtils.filterNotInstalledComponents(data);
-    validationData.filterProperty('type', 'host-component').forEach(function(item) {
-      var master = self.get('servicesMasters').find(function(m) {
-        return m.component_name === item['component-name'] && m.selectedHost === item.host;
-      });
-      if (master) {
-        if (item.level === 'ERROR') {
-          anyErrors = true;
-          master.set('errorMessage', item.message);
-        } else if (item.level === 'WARN') {
-          master.set('warnMessage', item.message);
-        }
-      }
-    });
-
-    this.set('generalErrorMessages', generalErrorMessages);
-    this.set('generalWarningMessages', generalWarningMessages);
-
-    // use this.set('submitDisabled', anyErrors); is validation results should block next button
-    // It's because showValidationIssuesAcceptBox allow use accept validation issues and continue
-    this.set('submitDisabled', false); //this.set('submitDisabled', anyErrors);
-  },
-
-  /**
-   * Error-callback for validations request
-   * @param {object} jqXHR
-   * @param {object} ajaxOptions
-   * @param {string} error
-   * @param {object} opt
-   * @method updateValidationsErrorCallback
-   */
-  updateValidationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
-    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
-    console.log('Load validations failed');
-  },
-
-  /**
-   * Composes selected values of comboboxes into master blueprint + merge it with currenlty installed slave blueprint
-   */
-  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 } ]
-      };
-
-      res.blueprint.host_groups.push(host_group);
-      res.blueprint_cluster_binding.host_groups.push(binding);
-    });
-
-    return blueprintUtils.mergeBlueprints(res, self.getCurrentSlaveBlueprint());
-  },
-
-/**
-   * Clear controller data (hosts, masters etc)
-   * @method clearStep
-   */
-  clearStep: function () {
-    this.set('hosts', []);
-    this.set('selectedServicesMasters', []);
-    this.set('servicesMasters', []);
-    App.StackServiceComponent.find().forEach(function (stackComponent) {
-      stackComponent.set('serviceComponentId', 1);
-    }, this);
-
-  },
-
-  /**
-   * Load controller data (hosts, host components etc)
-   * @method loadStep
-   */
-  loadStep: function () {
-    console.log("WizardStep5Controller: Loading step5: Assign Masters");
-    this.clearStep();
-    this.renderHostInfo();
-    this.loadComponentsRecommendationsFromServer(this.loadStepCallback);
-  },
-
-  /**
-   * Callback after load controller data (hosts, host components etc)
-   * @method loadStepCallback
-   */
-  loadStepCallback: function(components, self) {
-    self.renderComponents(components);
-
-    self.get('addableComponents').forEach(function (componentName) {
-      self.updateComponent(componentName);
-    }, self);
-    if (self.thereIsNoMasters()) {
-      console.log('no master components to add');
-      App.router.send('next');
-    }
-  },
-
-  /**
-  * Returns true if there is no new master components which need assigment to host
-  */
-  thereIsNoMasters: function() {
-    return !this.get("selectedServicesMasters").filterProperty('isInstalled', false).length;
-  },
-
-  /**
-   * Used to set showAddControl flag for installer wizard
-   * @method updateComponent
-   */
-  updateComponent: function (componentName) {
-    var component = this.last(componentName);
-    if (!component) {
-      return;
-    }
-
-    var showControl = !App.StackServiceComponent.find().findProperty('componentName', componentName).get('stackService').get('isInstalled');
-
-    if (showControl) {
-      var mastersLength = this.get("selectedServicesMasters").filterProperty("component_name", componentName).length;
-      if (mastersLength < this.get("hosts.length") && !this.get('isReassignWizard') && !this.get('isHighAvailabilityWizard')) {
-        component.set('showAddControl', true);
-      } else if (mastersLength == 1 || this.get('isReassignWizard') || this.get('isHighAvailabilityWizard')) {
-        component.set('showRemoveControl', false);
-      }
-    }
-  },
-
-  /**
-   * Load active host list to <code>hosts</code> variable
-   * @method renderHostInfo
-   */
-  renderHostInfo: function () {
-    var hostInfo = this.get('content.hosts');
-    var result = [];
-
-    for (var index in hostInfo) {
-      var _host = hostInfo[index];
-      if (_host.bootStatus === 'REGISTERED') {
-        result.push(Em.Object.create({
-          host_name: _host.name,
-          cpu: _host.cpu,
-          memory: _host.memory,
-          disk_info: _host.disk_info,
-          host_info: Em.I18n.t('installer.step5.hostInfo').fmt(_host.name, numberUtils.bytesToSize(_host.memory, 1, 'parseFloat', 1024), _host.cpu)
-        }));
-      }
-    }
-    this.set("hosts", result);
-    this.sortHosts(this.get('hosts'));
-    this.set('isLoaded', true);
-  },
-
-  /**
-   * Sort list of host-objects by properties (memory - desc, cpu - desc, hostname - asc)
-   * @param {object[]} hosts
-   */
-  sortHosts: function (hosts) {
-    hosts.sort(function (a, b) {
-      if (a.get('memory') == b.get('memory')) {
-        if (a.get('cpu') == b.get('cpu')) {
-          return a.get('host_name').localeCompare(b.get('host_name')); // hostname asc
-        }
-        return b.get('cpu') - a.get('cpu'); // cores desc
-      }
-      return b.get('memory') - a.get('memory'); // ram desc
-    });
-  },
-
-  /**
-   * Get recommendations info from API
-   * @return {undefined}
-   * @param function(componentInstallationobjects, this) callback
-   * @param bool includeMasters
-   */
-  loadComponentsRecommendationsFromServer: function(callback, includeMasters) {
-    var self = this;
-
-    if (this.get('content.recommendations')) {
-      // Don't do AJAX call if recommendations has been already received
-      // But if user returns to previous step (selecting services), stored recommendations will be cleared in routers' next handler and AJAX call will be made again
-      callback(self.createComponentInstallationObjects(), self);
-    } 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);
-        });
-    }
-  },
-
-  /**
-   * Create components for displaying component-host comboboxes in UI assign dialog
-   * expects content.recommendations will be filled with recommendations API call result
-   * @return {Object[]}
-   */
-  createComponentInstallationObjects: function() {
-    var self = this;
-
-    var masterComponents = [];
-    if (self.get('isAddServiceWizard')) {
-      masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnAddServiceAssignMasterPage');
-    } else {
-      masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnInstallerAssignMasterPage');
-    }
-
-    var masterHosts = self.get('content.masterComponentHosts'); //saved to local storage info
-    var selectedNotInstalledServices = self.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName');
-    var recommendations = this.get('content.recommendations');
-
-    var resultComponents = [];
-    var multipleComponentHasBeenAdded = {};
-
-    recommendations.blueprint.host_groups.forEach(function(host_group) {
-      var hosts = recommendations.blueprint_cluster_binding.host_groups.findProperty('name', host_group.name).hosts;
-
-      hosts.forEach(function(host) {
-        host_group.components.forEach(function(component) {
-          var willBeAdded = true;
-          var fullComponent = masterComponents.findProperty('componentName', component.name);
-          // If it's master component which should be shown
-          if (fullComponent) {
-            // If service is already installed and not being added as a new service then render on UI only those master components
-            // that have already installed hostComponents.
-            // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
-            var isNotSelectedService = !selectedNotInstalledServices.contains(fullComponent.get('serviceName'));
-            if (isNotSelectedService) {
-              willBeAdded = App.HostComponent.find().someProperty('componentName', component.name);
-            }
-
-            if (willBeAdded) {
-              var savedComponents = masterHosts.filterProperty('component', component.name);
-
-              if (self.get('multipleComponents').contains(component.name) && savedComponents.length > 0) {
-                if (!multipleComponentHasBeenAdded[component.name]) {
-                  multipleComponentHasBeenAdded[component.name] = true;
-
-                  savedComponents.forEach(function(saved) {
-                    resultComponents.push(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), saved));
-                  });
-                }
-              } else {
-                var savedComponent = masterHosts.findProperty('component', component.name);
-                resultComponents.push(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), savedComponent));
-              }
-            }
-          }
-        });
-      });
-    });
-    return resultComponents;
-  },
-
-  /**
-   * Create component for displaying component-host comboboxes in UI assign dialog
-   * @param fullComponent - full component description
-   * @param hostName - host fqdn where component will be installed
-   * @param savedComponent - the same object which function returns but created before
-   * @return {Object}
-   */
-  createComponentInstallationObject: function(fullComponent, hostName, savedComponent) {
-    var componentName = fullComponent.get('componentName');
-
-    var componentObj = {};
-    componentObj.component_name = componentName;
-    componentObj.display_name = App.format.role(fullComponent.get('componentName'));
-    componentObj.serviceId = fullComponent.get('serviceName');
-    componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', componentName).get('isCoHostedComponent') && !this.get('isReassignWizard');
-
-    if (savedComponent) {
-      componentObj.selectedHost = savedComponent.hostName;
-      componentObj.isInstalled = savedComponent.isInstalled;
-    } else {
-      componentObj.selectedHost = hostName;
-      componentObj.isInstalled = false;
-    }
-
-    return componentObj;
-  },
-
-  /**
-   * Success-callback for recommendations request
-   * @param {object} data
-   * @method loadRecommendationsSuccessCallback
-   */
-  loadRecommendationsSuccessCallback: function (data) {
-    this.set('content.recommendations', data.resources[0].recommendations);
-  },
-
-  /**
-   * Error-callback for recommendations request
-   * @param {object} jqXHR
-   * @param {object} ajaxOptions
-   * @param {string} error
-   * @param {object} opt
-   * @method loadRecommendationsErrorCallback
-   */
-  loadRecommendationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
-    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
-    console.log('Load recommendations failed');
-  },
-
-  /**
-   * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
-   * @param {Ember.Enumerable} masterComponents
-   * @method renderComponents
-   */
-  renderComponents: function (masterComponents) {
-    var installedServices = App.StackService.find().filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'); //list of shown services
-    var result = [];
-    var serviceComponentId, previousComponentName;
-
-    masterComponents.forEach(function (item) {
-      var serviceComponent = App.StackServiceComponent.find().findProperty('componentName', item.component_name);
-      var showRemoveControl = installedServices.contains(serviceComponent.get('stackService.serviceName')) &&
-        (masterComponents.filterProperty('component_name', item.component_name).length > 1);
-      var componentObj = Em.Object.create(item);
-      console.log("TRACE: render master component name is: " + item.component_name);
-      var masterComponent = App.StackServiceComponent.find().findProperty('componentName', item.component_name);
-      if (masterComponent.get('isMasterWithMultipleInstances')) {
-        previousComponentName = item.component_name;
-        componentObj.set('serviceComponentId', result.filterProperty('component_name', item.component_name).length + 1);
-        componentObj.set("showRemoveControl", showRemoveControl);
-      }
-      componentObj.set('isHostNameValid', true);
-
-      result.push(componentObj);
-    }, this);
-    result = this.sortComponentsByServiceName(result);
-    this.set("selectedServicesMasters", result);
-    if (this.get('isReassignWizard')) {
-      var components = result.filterProperty('component_name', this.get('content.reassign.component_name'));
-      components.setEach('isInstalled', false);
-      this.set('servicesMasters', components);
-    } else {
-      this.set('servicesMasters', result);
-    }
-  },
-
-  sortComponentsByServiceName: function(components) {
-    var displayOrder = App.StackService.displayOrder;
-    return components.sort(function (a, b) {
-      var aValue = displayOrder.indexOf(a.serviceId) != -1 ? displayOrder.indexOf(a.serviceId) : components.length;
-      var bValue = displayOrder.indexOf(b.serviceId) != -1 ? displayOrder.indexOf(b.serviceId) : components.length;
-      return aValue - bValue;
-    });
-  },
-  /**
-   * Update dependent co-hosted components according to the change in the component host
-   * @method updateCoHosts
-   */
-  updateCoHosts: function () {
-    // reassign wizard has no co-host constraints
-    if (this.get('isReassignWizard')) {
-      return false;
-    }
-
-    var components = App.StackServiceComponent.find().filterProperty('isOtherComponentCoHosted');
-    var selectedServicesMasters = this.get('selectedServicesMasters');
-    components.forEach(function (component) {
-      var componentName = component.get('componentName');
-      var hostComponent = selectedServicesMasters.findProperty('component_name', componentName);
-      var dependentCoHosts = component.get('coHostedComponents');
-      dependentCoHosts.forEach(function (coHostedComponent) {
-        var dependentHostComponent = selectedServicesMasters.findProperty('component_name', coHostedComponent);
-        if (hostComponent && dependentHostComponent) dependentHostComponent.set('selectedHost', hostComponent.get('selectedHost'));
-      }, this);
-    }, this);
-  }.observes('selectedServicesMasters.@each.selectedHost'),
-
-
-  /**
-   * On change callback for inputs
-   * @param {string} componentName
-   * @param {string} selectedHost
-   * @param {number} serviceComponentId
-   * @method assignHostToMaster
-   */
-  assignHostToMaster: function (componentName, selectedHost, serviceComponentId) {
-    var flag = this.isHostNameValid(componentName, selectedHost);
-    this.updateIsHostNameValidFlag(componentName, serviceComponentId, flag);
-    if (serviceComponentId) {
-      this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId).set("selectedHost", selectedHost);
-    }
-    else {
-      this.get('selectedServicesMasters').findProperty("component_name", componentName).set("selectedHost", selectedHost);
-    }
-  },
-
-  /**
-   * Determines if hostName is valid for component:
-   * <ul>
-   *  <li>host name shouldn't be empty</li>
-   *  <li>host should exist</li>
-   *  <li>host should have only one component with <code>componentName</code></li>
-   * </ul>
-   * @param {string} componentName
-   * @param {string} selectedHost
-   * @returns {boolean} true - valid, false - invalid
-   * @method isHostNameValid
-   */
-  isHostNameValid: function (componentName, selectedHost) {
-    return (selectedHost.trim() !== '') &&
-      this.get('hosts').mapProperty('host_name').contains(selectedHost) &&
-      (this.get('selectedServicesMasters').
-        filterProperty('component_name', componentName).
-        mapProperty('selectedHost').
-        filter(function (h) {
-          return h === selectedHost;
-        }).length <= 1);
-  },
-
-  /**
-   * Update <code>isHostNameValid</code> property with <code>flag</code> value
-   * for component with name <code>componentName</code> and
-   * <code>serviceComponentId</code>-property equal to <code>serviceComponentId</code>-parameter value
-   * @param {string} componentName
-   * @param {number} serviceComponentId
-   * @param {bool} flag
-   * @method updateIsHostNameValidFlag
-   */
-  updateIsHostNameValidFlag: function (componentName, serviceComponentId, flag) {
-    if (componentName) {
-      if (serviceComponentId) {
-        this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId).set("isHostNameValid", flag);
-      } else {
-        this.get('selectedServicesMasters').findProperty("component_name", componentName).set("isHostNameValid", flag);
-      }
-    }
-  },
-
-  /**
-   * Returns last component of selected type
-   * @param {string} componentName
-   * @return {Em.Object|null}
-   * @method last
-   */
-  last: function (componentName) {
-    return this.get("selectedServicesMasters").filterProperty("component_name", componentName).get("lastObject");
-  },
-
-  /**
-   * Add new component to ZooKeeper Server and Hbase master
-   * @param {string} componentName
-   * @return {bool} true - added, false - not added
-   * @method addComponent
-   */
-  addComponent: function (componentName) {
-    /*
-     * Logic: If ZooKeeper or Hbase service is selected then there can be
-     * minimum 1 ZooKeeper or Hbase master in total, and
-     * maximum 1 ZooKeeper or Hbase on every host
-     */
-
-    var maxNumMasters = this.get("hosts.length"),
-      currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName),
-      newMaster = null,
-      masterHosts = null,
-      suggestedHost = null,
-      i = 0,
-      lastMaster = null;
-
-    if (!currentMasters.length) {
-      console.log('ALERT: Zookeeper service was not selected');
-      return false;
-    }
-
-    if (currentMasters.get("length") < maxNumMasters) {
-
-      currentMasters.set("lastObject.showAddControl", false);
-      currentMasters.set("lastObject.showRemoveControl", true);
-
-      //create a new master component host based on an existing one
-      newMaster = Em.Object.create({});
-      lastMaster = currentMasters.get("lastObject");
-      newMaster.set("display_name", lastMaster.get("display_name"));
-      newMaster.set("component_name", lastMaster.get("component_name"));
-      newMaster.set("selectedHost", lastMaster.get("selectedHost"));
-      newMaster.set("serviceId", lastMaster.get("serviceId"));
-      newMaster.set("isInstalled", false);
-
-      if (currentMasters.get("length") === (maxNumMasters - 1)) {
-        newMaster.set("showAddControl", false);
-      } else {
-        newMaster.set("showAddControl", true);
-      }
-      newMaster.set("showRemoveControl", true);
-
-      //get recommended host for the new Zookeeper server
-      masterHosts = currentMasters.mapProperty("selectedHost").uniq();
-
-      for (i = 0; i < this.get("hosts.length"); i++) {
-        if (!(masterHosts.contains(this.get("hosts")[i].get("host_name")))) {
-          suggestedHost = this.get("hosts")[i].get("host_name");
-          break;
-        }
-      }
-
-      newMaster.set("selectedHost", suggestedHost);
-      newMaster.set("serviceComponentId", (currentMasters.get("lastObject.serviceComponentId") + 1));
-
-      this.get("selectedServicesMasters").insertAt(this.get("selectedServicesMasters").indexOf(lastMaster) + 1, newMaster);
-
-      this.set('componentToRebalance', componentName);
-      this.incrementProperty('rebalanceComponentHostsCounter');
-      this.toggleProperty('hostNameCheckTrigger');
-      return true;
-    }
-    return false;//if no more zookeepers can be added
-  },
-
-  /**
-   * Remove component from ZooKeeper server or Hbase Master
-   * @param {string} componentName
-   * @param {number} serviceComponentId
-   * @return {bool} true - removed, false - no
-   * @method removeComponent
-   */
-  removeComponent: function (componentName, serviceComponentId) {
-    var currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
-
-    //work only if the multiple master service is selected in previous step
-    if (currentMasters.length <= 1) {
-      return false;
-    }
-
-    this.get("selectedServicesMasters").removeAt(this.get("selectedServicesMasters").indexOf(currentMasters.findProperty("serviceComponentId", serviceComponentId)));
-
-    currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
-    if (currentMasters.get("length") < this.get("hosts.length")) {
-      currentMasters.set("lastObject.showAddControl", true);
-    }
-
-    if (currentMasters.get("length") === 1) {
-      currentMasters.set("lastObject.showRemoveControl", false);
-    }
-
-    this.set('componentToRebalance', componentName);
-    this.incrementProperty('rebalanceComponentHostsCounter');
-    this.toggleProperty('hostNameCheckTrigger');
-    return true;
-  },
-
-  recommendAndValidate: function(callback) {
-    var self = this;
-
-    // load recommendations with partial request
-    self.loadComponentsRecommendationsFromServer(function() {
-      // For validation use latest received recommendations because ir contains current master layout and recommended slave/client layout
-      self.validate(self.get('content.recommendations'), function() {
-        if (callback) {
-          callback();
-        }
-      });
-    }, true);
-  },
-
-  /**
-   * Submit button click handler
-   * @method submit
-   */
-  submit: function () {
-    var self = this;
-    if (!this.get('submitButtonClicked')) {
-      this.set('submitButtonClicked', true);
-
-      var goNextStepIfValid = function () {
-        if (!self.get('submitDisabled')) {
-          App.router.send('next');
-        }
-        self.set('submitButtonClicked', false);
-      };
-
-      if (this.get('useServerValidation')) {
-        self.recommendAndValidate(function () {
-          self.showValidationIssuesAcceptBox(goNextStepIfValid);
-        });
-      } else {
-        self.updateIsSubmitDisabled();
-        goNextStepIfValid();
-      }
-    }
-  },
-
-  /**
-   * In case of any validation issues shows accept dialog box for user which allow cancel and fix issues or continue anyway
-   * @method showValidationIssuesAcceptBox
-   */
-  showValidationIssuesAcceptBox: function(callback) {
-    var self = this;
-    if (self.get('anyWarning') || self.get('anyError')) {
-      App.ModalPopup.show({
-        primary: Em.I18n.t('common.continueAnyway'),
-        header: Em.I18n.t('installer.step5.validationIssuesAttention.header'),
-        body: Em.I18n.t('installer.step5.validationIssuesAttention'),
-        onPrimary: function () {
-          this.hide();
-          callback();
-        }
-      });
-    } else {
-      callback();
-    }
-  }
+  name: "wizardStep5Controller"
 });

+ 1 - 0
ambari-web/app/mixins.js

@@ -42,6 +42,7 @@ require('mixins/wizard/wizardEnableDone');
 require('mixins/wizard/selectHost');
 require('mixins/wizard/addSecurityConfigs');
 require('mixins/wizard/wizard_menu_view');
+require('mixins/wizard/assign_master_components');
 require('mixins/common/configs/enhanced_configs');
 require('mixins/common/configs/configs_saver');
 require('mixins/common/widget_mixin');

+ 1071 - 0
ambari-web/app/mixins/wizard/assign_master_components.js

@@ -0,0 +1,1071 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+var blueprintUtils = require('utils/blueprint');
+var numberUtils = require('utils/number_utils');
+var validationUtils = require('utils/validator');
+
+/**
+ * Mixin for assign master-to-host step in wizards
+ * Implements basic logic of assign masters page
+ * Should be used with controller linked with App.AssignMasterComponentsView
+ * @type {Ember.Mixin}
+ */
+App.AssignMasterComponents = Em.Mixin.create({
+
+  /**
+   * Array of master component names to show on the page
+   * By default is empty, this means that masters of all selected services should be shown
+   * @type {Array}
+   */
+  mastersToShow: [],
+
+  /**
+   * Array of master component names to add for install
+   * @type {Array}
+   */
+  mastersToAdd: [],
+
+  /**
+   * Array of master component names, that are already installed, but should have ability to change host
+   * @type {Array}
+   */
+  mastersToMove: [],
+
+  /**
+   * Array of master component names to show 'Current' prefix in label before component name
+   * Prefix will be shown only for installed instances
+   * @type {Array}
+   */
+  showCurrentPrefix: [],
+
+  /**
+   * Array of master component names to show 'Additional' prefix in label before component name
+   * Prefix will be shown only for not installed instances
+   * @type {Array}
+   */
+  showAdditionalPrefix: [],
+
+  /**
+   * Array of objects with label and host keys to show specific hosts on the page
+   * @type {Array}
+   * format:
+   * [
+   *   {
+   *     label: 'Current',
+   *     host: 'c6401.ambari.apache.org'
+   *   },
+   *   {
+   *     label: 'Additional',
+   *     host: function () {
+   *       return 'c6402.ambari.apache.org';
+   *     }.property()
+   *   }
+   * ]
+   */
+  additionalHostsList: [],
+
+  /**
+   * Array of <code>servicesMasters</code> objects, that will be shown on the page
+   * Are filtered using <code>mastersToShow</code>
+   * @type {Array}
+   */
+  servicesMastersToShow: function () {
+    var mastersToShow = this.get('mastersToShow');
+    var servicesMasters = this.get('servicesMasters');
+    if (!mastersToShow.length) {
+      return servicesMasters;
+    } else {
+      var result = [];
+      mastersToShow.forEach(function(master){
+        result = result.concat(servicesMasters.filterProperty('component_name', master));
+      });
+      return result;
+    }
+  }.property('servicesMasters.length', 'mastersToShow.length'),
+
+  /**
+   * Check if <code>installerWizard</code> used
+   * @type {bool}
+   */
+  isInstallerWizard: function () {
+    return this.get('content.controllerName') === 'installerController';
+  }.property('content.controllerName'),
+
+  /**
+   * Master components which could be assigned to multiple hosts
+   * @type {string[]}
+   */
+  multipleComponents: function () {
+    return App.get('components.multipleMasters');
+  }.property('App.components.multipleMasters'),
+
+  /**
+   * Master components which could be assigned to multiple hosts
+   * @type {string[]}
+   */
+  addableComponents: function () {
+    return App.get('components.addableMasterInstallerWizard');
+  }.property('App.components.addableMasterInstallerWizard'),
+
+  /**
+   * Define state for submit button
+   * @type {bool}
+   */
+  submitDisabled: false,
+
+  /**
+   * Is Submit-click processing now
+   * @type {bool}
+   */
+  submitButtonClicked: false,
+
+  /**
+   * Either use or not use server validation in this controller
+   * @type {bool}
+   */
+  useServerValidation: true,
+
+  /**
+   * Trigger for executing host names check for components
+   * Should de "triggered" when host changed for some component and when new multiple component is added/removed
+   * @type {bool}
+   */
+  hostNameCheckTrigger: false,
+
+  /**
+   * List of hosts
+   * @type {Array}
+   */
+  hosts: [],
+
+  /**
+   * Name of multiple component which host name was changed last
+   * @type {Object|null}
+   */
+  componentToRebalance: null,
+
+  /**
+   * Name of component which host was changed last
+   * @type {string}
+   */
+  lastChangedComponent: null,
+
+  /**
+   * Flag for rebalance multiple components
+   * @type {number}
+   */
+  rebalanceComponentHostsCounter: 0,
+
+  /**
+   * @type {Ember.Enumerable}
+   */
+  servicesMasters: [],
+
+  /**
+   * @type {Ember.Enumerable}
+   */
+  selectedServicesMasters: [],
+
+  /**
+   * Is data for current step loaded
+   * @type {bool}
+   */
+  isLoaded: false,
+
+  /**
+   * Validation error messages which don't related with any master
+   */
+  generalErrorMessages: [],
+
+  /**
+   * Validation warning messages which don't related with any master
+   */
+  generalWarningMessages: [],
+
+  /**
+   * Is masters-hosts layout initial one
+   * @type {bool}
+   */
+  isInitialLayout: true,
+
+  /**
+   * true if any error exists
+   */
+  anyError: function() {
+    return this.get('servicesMasters').some(function(m) { return m.get('errorMessage'); }) || this.get('generalErrorMessages').some(function(m) { return m; });
+  }.property('servicesMasters.@each.errorMessage', 'generalErrorMessages'),
+
+  /**
+   * true if any warning exists
+   */
+  anyWarning: function() {
+    return this.get('servicesMasters').some(function(m) { return m.get('warnMessage'); }) || this.get('generalWarningMessages').some(function(m) { return m; });
+  }.property('servicesMasters.@each.warnMessage', 'generalWarningMessages'),
+
+  /**
+   * Clear loaded recommendations
+   */
+  clearRecommendations: function() {
+    if (this.get('content.recommendations')) {
+      this.set('content.recommendations', null);
+    }
+  },
+
+  /**
+   * List of host with assigned masters
+   * Format:
+   * <code>
+   *   [
+   *     {
+   *       host_name: '',
+   *       hostInfo: {},
+   *       masterServices: [],
+   *       masterServicesToDisplay: [] // used only in template
+   *    },
+   *    ....
+   *   ]
+   * </code>
+   * @type {Ember.Enumerable}
+   */
+  masterHostMapping: function () {
+    var mapping = [], mappingObject, mappedHosts, hostObj;
+    //get the unique assigned hosts and find the master services assigned to them
+    mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq();
+    mappedHosts.forEach(function (item) {
+      hostObj = this.get("hosts").findProperty("host_name", item);
+      // User may input invalid host name (this is handled in hostname checker). Here we just skip it
+      if (!hostObj) return;
+      var masterServices = this.get("selectedServicesMasters").filterProperty("selectedHost", item),
+          masterServicesToDisplay = [];
+      masterServices.mapProperty('display_name').uniq().forEach(function (n) {
+        masterServicesToDisplay.pushObject(masterServices.findProperty('display_name', n));
+      });
+      mappingObject = Em.Object.create({
+        host_name: item,
+        hostInfo: hostObj.host_info,
+        masterServices: masterServices,
+        masterServicesToDisplay: masterServicesToDisplay
+      });
+
+      mapping.pushObject(mappingObject);
+    }, this);
+
+    return mapping.sortProperty('host_name');
+  }.property("selectedServicesMasters.@each.selectedHost", 'selectedServicesMasters.@each.isHostNameValid'),
+
+  /**
+   * Count of hosts without masters
+   * @type {number}
+   */
+  remainingHosts: function () {
+    if (this.get('content.controllerName') === 'installerController') {
+      return 0;
+    } else {
+      return (this.get("hosts.length") - this.get("masterHostMapping.length"));
+    }
+  }.property('masterHostMapping.length', 'selectedServicesMasters.@each.selectedHost'),
+
+  /**
+   * Update submit button status
+   * @metohd updateIsSubmitDisabled
+   */
+  updateIsSubmitDisabled: function () {
+
+    if (this.thereIsNoMasters()) {
+      return false;
+    }
+
+    var isSubmitDisabled = this.get('servicesMasters').someProperty('isHostNameValid', false);
+
+    if (this.get('useServerValidation')) {
+      this.set('submitDisabled', true);
+
+      if (this.get('servicesMasters').length === 0) {
+        return;
+      }
+
+      if (!isSubmitDisabled) {
+        if (!this.get('isInitialLayout')) {
+          this.clearRecommendations(); // reset previous recommendations
+        } else {
+          this.set('isInitialLayout', false);
+        }
+        this.recommendAndValidate();
+      }
+    } else {
+      isSubmitDisabled = isSubmitDisabled || !this.customClientSideValidation();
+      this.set('submitDisabled', isSubmitDisabled);
+      return isSubmitDisabled;
+    }
+  }.observes('servicesMasters.@each.selectedHost'),
+
+  /**
+   * Function to validate master-to-host assignments
+   * Should be defined in controller
+   * @returns {boolean}
+   */
+  customClientSideValidation: function () {
+    return true;
+  },
+
+  /**
+   * 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
+   */
+  updateValidationsSuccessCallback: function (data) {
+    var self = this;
+
+    var generalErrorMessages = [];
+    var generalWarningMessages = [];
+    this.get('servicesMasters').setEach('warnMessage', null);
+    this.get('servicesMasters').setEach('errorMessage', null);
+    var anyErrors = false;
+
+    var validationData = validationUtils.filterNotInstalledComponents(data);
+    validationData.filterProperty('type', 'host-component').forEach(function(item) {
+      var master = self.get('servicesMasters').find(function(m) {
+        return m.component_name === item['component-name'] && m.selectedHost === item.host;
+      });
+      if (master) {
+        if (item.level === 'ERROR') {
+          anyErrors = true;
+          master.set('errorMessage', item.message);
+        } else if (item.level === 'WARN') {
+          master.set('warnMessage', item.message);
+        }
+      }
+    });
+
+    this.set('generalErrorMessages', generalErrorMessages);
+    this.set('generalWarningMessages', generalWarningMessages);
+
+    // use this.set('submitDisabled', anyErrors); is validation results should block next button
+    // It's because showValidationIssuesAcceptBox allow use accept validation issues and continue
+    this.set('submitDisabled', false); //this.set('submitDisabled', anyErrors);
+  },
+
+  /**
+   * Error-callback for validations request
+   * @param {object} jqXHR
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @param {object} opt
+   * @method updateValidationsErrorCallback
+   */
+  updateValidationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
+    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
+    console.log('Load validations failed');
+  },
+
+  /**
+   * Composes selected values of comboboxes into master blueprint + merge it with currenlty installed slave blueprint
+   */
+  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 } ]
+      };
+
+      res.blueprint.host_groups.push(host_group);
+      res.blueprint_cluster_binding.host_groups.push(binding);
+    });
+
+    return blueprintUtils.mergeBlueprints(res, self.getCurrentSlaveBlueprint());
+  },
+
+  /**
+   * Clear controller data (hosts, masters etc)
+   * @method clearStep
+   */
+  clearStep: function () {
+    this.set('hosts', []);
+    this.set('selectedServicesMasters', []);
+    this.set('servicesMasters', []);
+    App.StackServiceComponent.find().forEach(function (stackComponent) {
+      stackComponent.set('serviceComponentId', 1);
+    }, this);
+
+  },
+
+  /**
+   * Load controller data (hosts, host components etc)
+   * @method loadStep
+   */
+  loadStep: function () {
+    console.log("WizardStep5Controller: Loading step5: Assign Masters");
+    this.clearStep();
+    this.renderHostInfo();
+    this.loadComponentsRecommendationsFromServer(this.loadStepCallback);
+  },
+
+  /**
+   * Callback after load controller data (hosts, host components etc)
+   * @method loadStepCallback
+   */
+  loadStepCallback: function(components, self) {
+    self.renderComponents(components);
+
+    self.get('addableComponents').forEach(function (componentName) {
+      self.updateComponent(componentName);
+    }, self);
+    if (self.thereIsNoMasters()) {
+      console.log('no master components to add');
+      App.router.send('next');
+    }
+  },
+
+  /**
+   * Returns true if there is no new master components which need assigment to host
+   */
+  thereIsNoMasters: function() {
+    return !this.get("selectedServicesMasters").filterProperty('isInstalled', false).length;
+  },
+
+  /**
+   * Used to set showAddControl flag for installer wizard
+   * @method updateComponent
+   */
+  updateComponent: function (componentName) {
+    var component = this.last(componentName);
+    if (!component) {
+      return;
+    }
+
+    var showControl = !App.StackServiceComponent.find().findProperty('componentName', componentName).get('stackService').get('isInstalled');
+
+    if (showControl) {
+      var mastersLength = this.get("selectedServicesMasters").filterProperty("component_name", componentName).length;
+      if (mastersLength < this.getMaxNumberOfMasters(componentName)) {
+        component.set('showAddControl', true);
+      } else if (mastersLength == 1) {
+        component.set('showRemoveControl', false);
+      }
+    }
+  },
+
+  /**
+   * Count max number of instances for masters <code>componentName</code>, according to their cardinality and number of hosts
+   * @param componentName
+   * @returns {Number}
+   */
+  getMaxNumberOfMasters: function (componentName) {
+    var maxByCardinality = App.StackServiceComponent.find().findProperty('componentName', componentName).get('maxToInstall');
+    var hostsNumber = this.get("hosts.length");
+    return Math.min(maxByCardinality, hostsNumber);
+  },
+
+  /**
+   * Load active host list to <code>hosts</code> variable
+   * @method renderHostInfo
+   */
+  renderHostInfo: function () {
+    var hostInfo = this.get('content.hosts');
+    var result = [];
+
+    for (var index in hostInfo) {
+      var _host = hostInfo[index];
+      if (_host.bootStatus === 'REGISTERED') {
+        result.push(Em.Object.create({
+          host_name: _host.name,
+          cpu: _host.cpu,
+          memory: _host.memory,
+          disk_info: _host.disk_info,
+          host_info: Em.I18n.t('installer.step5.hostInfo').fmt(_host.name, numberUtils.bytesToSize(_host.memory, 1, 'parseFloat', 1024), _host.cpu)
+        }));
+      }
+    }
+    this.set("hosts", result);
+    this.sortHosts(this.get('hosts'));
+    this.set('isLoaded', true);
+  },
+
+  /**
+   * Sort list of host-objects by properties (memory - desc, cpu - desc, hostname - asc)
+   * @param {object[]} hosts
+   */
+  sortHosts: function (hosts) {
+    hosts.sort(function (a, b) {
+      if (a.get('memory') == b.get('memory')) {
+        if (a.get('cpu') == b.get('cpu')) {
+          return a.get('host_name').localeCompare(b.get('host_name')); // hostname asc
+        }
+        return b.get('cpu') - a.get('cpu'); // cores desc
+      }
+      return b.get('memory') - a.get('memory'); // ram desc
+    });
+  },
+
+  /**
+   * Get recommendations info from API
+   * @return {undefined}
+   * @param function(componentInstallationobjects, this) callback
+   * @param bool includeMasters
+   */
+  loadComponentsRecommendationsFromServer: function(callback, includeMasters) {
+    var self = this;
+
+    if (this.get('content.recommendations')) {
+      // Don't do AJAX call if recommendations has been already received
+      // But if user returns to previous step (selecting services), stored recommendations will be cleared in routers' next handler and AJAX call will be made again
+      callback(self.createComponentInstallationObjects(), self);
+    } 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);
+          });
+    }
+  },
+
+  /**
+   * Create components for displaying component-host comboboxes in UI assign dialog
+   * expects content.recommendations will be filled with recommendations API call result
+   * @return {Object[]}
+   */
+  createComponentInstallationObjects: function() {
+    var self = this;
+
+    var masterComponents = [];
+    if (self.get('isInstallerWizard')) {
+      masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnInstallerAssignMasterPage');
+    } else {
+      masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnAddServiceAssignMasterPage');
+      masterComponents = App.StackServiceComponent.find().filter(function(component){
+        return component.get('isShownOnAddServiceAssignMasterPage') || self.get('mastersToShow').contains(component.get('componentName'));
+      });
+    }
+
+    var masterHosts = self.get('content.masterComponentHosts'); //saved to local storage info
+    var selectedNotInstalledServices = self.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName');
+    var recommendations = this.get('content.recommendations');
+
+    var resultComponents = [];
+    var multipleComponentHasBeenAdded = {};
+
+    recommendations.blueprint.host_groups.forEach(function(host_group) {
+      var hosts = recommendations.blueprint_cluster_binding.host_groups.findProperty('name', host_group.name).hosts;
+
+      hosts.forEach(function(host) {
+        host_group.components.forEach(function(component) {
+          var willBeAdded = true;
+          var fullComponent = masterComponents.findProperty('componentName', component.name);
+          // If it's master component which should be shown
+          if (fullComponent) {
+            // If service is already installed and not being added as a new service then render on UI only those master components
+            // that have already installed hostComponents.
+            // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
+            var isNotSelectedService = !selectedNotInstalledServices.contains(fullComponent.get('serviceName'));
+            if (isNotSelectedService) {
+              willBeAdded = App.HostComponent.find().someProperty('componentName', component.name);
+            }
+
+            if (willBeAdded) {
+              var savedComponents = masterHosts.filterProperty('component', component.name);
+
+              if (self.get('multipleComponents').contains(component.name) && savedComponents.length > 0) {
+                if (!multipleComponentHasBeenAdded[component.name]) {
+                  multipleComponentHasBeenAdded[component.name] = true;
+
+                  savedComponents.forEach(function(saved) {
+                    resultComponents.push(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), saved));
+                  });
+                }
+              } else {
+                var savedComponent = masterHosts.findProperty('component', component.name);
+                resultComponents.push(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), savedComponent));
+              }
+            }
+          }
+        });
+      });
+    });
+    return resultComponents;
+  },
+
+  /**
+   * Create component for displaying component-host comboboxes in UI assign dialog
+   * @param fullComponent - full component description
+   * @param hostName - host fqdn where component will be installed
+   * @param savedComponent - the same object which function returns but created before
+   * @return {Object}
+   */
+  createComponentInstallationObject: function(fullComponent, hostName, savedComponent) {
+    var componentName = fullComponent.get('componentName');
+
+    var componentObj = {};
+    componentObj.component_name = componentName;
+    componentObj.display_name = App.format.role(fullComponent.get('componentName'));
+    componentObj.serviceId = fullComponent.get('serviceName');
+    componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', componentName).get('isCoHostedComponent') && !this.get('mastersToMove').contains(componentName);
+    if (savedComponent) {
+      componentObj.selectedHost = savedComponent.hostName;
+      componentObj.isInstalled = savedComponent.isInstalled;
+    } else {
+      componentObj.selectedHost = hostName;
+      componentObj.isInstalled = false;
+    }
+
+    return componentObj;
+  },
+
+  /**
+   * Success-callback for recommendations request
+   * @param {object} data
+   * @method loadRecommendationsSuccessCallback
+   */
+  loadRecommendationsSuccessCallback: function (data) {
+    this.set('content.recommendations', data.resources[0].recommendations);
+  },
+
+  /**
+   * Error-callback for recommendations request
+   * @param {object} jqXHR
+   * @param {object} ajaxOptions
+   * @param {string} error
+   * @param {object} opt
+   * @method loadRecommendationsErrorCallback
+   */
+  loadRecommendationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
+    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
+    console.log('Load recommendations failed');
+  },
+
+  /**
+   * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
+   * @param {Ember.Enumerable} masterComponents
+   * @method renderComponents
+   */
+  renderComponents: function (masterComponents) {
+    var installedServices = App.StackService.find().filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'); //list of shown services
+    var result = [];
+    var serviceComponentId, previousComponentName;
+
+    this.addNewMasters(masterComponents);
+
+    masterComponents.forEach(function (item) {
+      var masterComponent = App.StackServiceComponent.find().findProperty('componentName', item.component_name);
+      var componentObj = Em.Object.create(item);
+      console.log("TRACE: render master component name is: " + item.component_name);
+      if (masterComponent.get('isMasterWithMultipleInstances')) {
+        var showRemoveControl = installedServices.contains(masterComponent.get('stackService.serviceName')) &&
+            (masterComponents.filterProperty('component_name', item.component_name).length > 1);
+        previousComponentName = item.component_name;
+        componentObj.set('serviceComponentId', result.filterProperty('component_name', item.component_name).length + 1);
+        componentObj.set("showRemoveControl", showRemoveControl);
+      }
+      componentObj.set('isHostNameValid', true);
+      componentObj.set('showCurrentPrefix', this.get('showCurrentPrefix').contains(item.component_name) && item.isInstalled);
+      componentObj.set('showAdditionalPrefix', this.get('showAdditionalPrefix').contains(item.component_name) && !item.isInstalled);
+      if (this.get('mastersToMove').contains(item.component_name)) {
+        componentObj.set('isInstalled', false);
+      }
+
+      result.push(componentObj);
+    }, this);
+    result = this.sortComponentsByServiceName(result);
+    this.set("selectedServicesMasters", result);
+    this.set('servicesMasters', result);
+  },
+
+  /**
+   * Add new master components from <code>mastersToAdd</code> list
+   * @param masterComponents
+   * @returns {masterComponents[]}
+   */
+  addNewMasters: function (masterComponents) {
+    this.get('mastersToAdd').forEach(function(masterName){
+      var hostName = this.getHostForMaster(masterName, masterComponents);
+      var serviceName = this.getServiceByMaster(masterName);
+      masterComponents.push(this.createComponentInstallationObject(
+          Em.Object.create({
+            componentName: masterName,
+            serviceName: serviceName
+          }),
+          hostName
+      ));
+    }, this);
+    return masterComponents;
+  },
+
+  /**
+   * Find available host for master and return it
+   * If there is no available hosts returns false
+   * @param master
+   * @param allMasters
+   * @returns {*}
+   */
+  getHostForMaster: function (master, allMasters) {
+    var usedHosts = allMasters.filterProperty('component_name', master).mapProperty('selectedHost');
+    var allHosts = this.get('hosts');
+    for (var i = 0; i < allHosts.length; i++) {
+      if (!usedHosts.contains(allHosts[i].get('host_name'))) {
+        return allHosts[i].get('host_name');
+      }
+    }
+    return false;
+  },
+
+  /**
+   * Find serviceName for master by it's componentName
+   * @param master
+   * @returns {*}
+   */
+  getServiceByMaster: function (master) {
+    return App.StackServiceComponent.find().findProperty('componentName', master).get('serviceName');
+  },
+
+  sortComponentsByServiceName: function(components) {
+    var displayOrder = App.StackService.displayOrder;
+    return components.sort(function (a, b) {
+      var aValue = displayOrder.indexOf(a.serviceId) != -1 ? displayOrder.indexOf(a.serviceId) : components.length;
+      var bValue = displayOrder.indexOf(b.serviceId) != -1 ? displayOrder.indexOf(b.serviceId) : components.length;
+      return aValue - bValue;
+    });
+  },
+  /**
+   * Update dependent co-hosted components according to the change in the component host
+   * @method updateCoHosts
+   */
+  updateCoHosts: function () {
+    var components = App.StackServiceComponent.find().filterProperty('isOtherComponentCoHosted');
+    var selectedServicesMasters = this.get('selectedServicesMasters');
+    components.forEach(function (component) {
+      var componentName = component.get('componentName')
+      var hostComponent = selectedServicesMasters.findProperty('component_name', componentName);
+      var dependentCoHosts = component.get('coHostedComponents');
+      dependentCoHosts.forEach(function (coHostedComponent) {
+        var dependentHostComponent = selectedServicesMasters.findProperty('component_name', coHostedComponent);
+        if (!this.get('mastersToMove').contains(coHostedComponent) && hostComponent && dependentHostComponent) dependentHostComponent.set('selectedHost', hostComponent.get('selectedHost'));
+      }, this);
+    }, this);
+  }.observes('selectedServicesMasters.@each.selectedHost'),
+
+
+  /**
+   * On change callback for inputs
+   * @param {string} componentName
+   * @param {string} selectedHost
+   * @param {number} serviceComponentId
+   * @method assignHostToMaster
+   */
+  assignHostToMaster: function (componentName, selectedHost, serviceComponentId) {
+    var flag = this.isHostNameValid(componentName, selectedHost);
+    this.updateIsHostNameValidFlag(componentName, serviceComponentId, flag);
+    if (serviceComponentId) {
+      this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId).set("selectedHost", selectedHost);
+    }
+    else {
+      this.get('selectedServicesMasters').findProperty("component_name", componentName).set("selectedHost", selectedHost);
+    }
+  },
+
+  /**
+   * Determines if hostName is valid for component:
+   * <ul>
+   *  <li>host name shouldn't be empty</li>
+   *  <li>host should exist</li>
+   *  <li>host should have only one component with <code>componentName</code></li>
+   * </ul>
+   * @param {string} componentName
+   * @param {string} selectedHost
+   * @returns {boolean} true - valid, false - invalid
+   * @method isHostNameValid
+   */
+  isHostNameValid: function (componentName, selectedHost) {
+    return (selectedHost.trim() !== '') &&
+    this.get('hosts').mapProperty('host_name').contains(selectedHost) &&
+    (this.get('selectedServicesMasters').
+        filterProperty('component_name', componentName).
+        mapProperty('selectedHost').
+        filter(function (h) {
+          return h === selectedHost;
+        }).length <= 1);
+  },
+
+  /**
+   * Update <code>isHostNameValid</code> property with <code>flag</code> value
+   * for component with name <code>componentName</code> and
+   * <code>serviceComponentId</code>-property equal to <code>serviceComponentId</code>-parameter value
+   * @param {string} componentName
+   * @param {number} serviceComponentId
+   * @param {bool} flag
+   * @method updateIsHostNameValidFlag
+   */
+  updateIsHostNameValidFlag: function (componentName, serviceComponentId, flag) {
+    if (componentName) {
+      if (serviceComponentId) {
+        this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId).set("isHostNameValid", flag);
+      } else {
+        this.get('selectedServicesMasters').findProperty("component_name", componentName).set("isHostNameValid", flag);
+      }
+    }
+  },
+
+  /**
+   * Returns last component of selected type
+   * @param {string} componentName
+   * @return {Em.Object|null}
+   * @method last
+   */
+  last: function (componentName) {
+    return this.get("selectedServicesMasters").filterProperty("component_name", componentName).get("lastObject");
+  },
+
+  /**
+   * Add new component to ZooKeeper Server and Hbase master
+   * @param {string} componentName
+   * @return {bool} true - added, false - not added
+   * @method addComponent
+   */
+  addComponent: function (componentName) {
+    /*
+     * Logic: If ZooKeeper or Hbase service is selected then there can be
+     * minimum 1 ZooKeeper or Hbase master in total, and
+     * maximum 1 ZooKeeper or Hbase on every host
+     */
+
+    var maxNumMasters = this.getMaxNumberOfMasters(componentName),
+        currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName),
+        newMaster = null,
+        masterHosts = null,
+        suggestedHost = null,
+        i = 0,
+        lastMaster = null;
+
+    if (!currentMasters.length) {
+      console.log('ALERT: Zookeeper service was not selected');
+      return false;
+    }
+
+    if (currentMasters.get("length") < maxNumMasters) {
+
+      currentMasters.set("lastObject.showAddControl", false);
+      currentMasters.set("lastObject.showRemoveControl", true);
+
+      //create a new master component host based on an existing one
+      newMaster = Em.Object.create({});
+      lastMaster = currentMasters.get("lastObject");
+      newMaster.set("display_name", lastMaster.get("display_name"));
+      newMaster.set("component_name", lastMaster.get("component_name"));
+      newMaster.set("selectedHost", lastMaster.get("selectedHost"));
+      newMaster.set("serviceId", lastMaster.get("serviceId"));
+      newMaster.set("isInstalled", false);
+
+      if (currentMasters.get("length") === (maxNumMasters - 1)) {
+        newMaster.set("showAddControl", false);
+      } else {
+        newMaster.set("showAddControl", true);
+      }
+      newMaster.set("showRemoveControl", true);
+
+      //get recommended host for the new Zookeeper server
+      masterHosts = currentMasters.mapProperty("selectedHost").uniq();
+
+      for (i = 0; i < this.get("hosts.length"); i++) {
+        if (!(masterHosts.contains(this.get("hosts")[i].get("host_name")))) {
+          suggestedHost = this.get("hosts")[i].get("host_name");
+          break;
+        }
+      }
+
+      newMaster.set("selectedHost", suggestedHost);
+      newMaster.set("serviceComponentId", (currentMasters.get("lastObject.serviceComponentId") + 1));
+
+      this.get("selectedServicesMasters").insertAt(this.get("selectedServicesMasters").indexOf(lastMaster) + 1, newMaster);
+
+      this.set('componentToRebalance', componentName);
+      this.incrementProperty('rebalanceComponentHostsCounter');
+      this.toggleProperty('hostNameCheckTrigger');
+      return true;
+    }
+    return false;//if no more zookeepers can be added
+  },
+
+  /**
+   * Remove component from ZooKeeper server or Hbase Master
+   * @param {string} componentName
+   * @param {number} serviceComponentId
+   * @return {bool} true - removed, false - no
+   * @method removeComponent
+   */
+  removeComponent: function (componentName, serviceComponentId) {
+    var currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
+
+    //work only if the multiple master service is selected in previous step
+    if (currentMasters.length <= 1) {
+      return false;
+    }
+
+    this.get("selectedServicesMasters").removeAt(this.get("selectedServicesMasters").indexOf(currentMasters.findProperty("serviceComponentId", serviceComponentId)));
+
+    currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
+    if (currentMasters.get("length") < this.getMaxNumberOfMasters(componentName)) {
+      currentMasters.set("lastObject.showAddControl", true);
+    }
+
+    if (currentMasters.get("length") === 1) {
+      currentMasters.set("lastObject.showRemoveControl", false);
+    }
+
+    this.set('componentToRebalance', componentName);
+    this.incrementProperty('rebalanceComponentHostsCounter');
+    this.toggleProperty('hostNameCheckTrigger');
+    return true;
+  },
+
+  recommendAndValidate: function(callback) {
+    var self = this;
+
+    // load recommendations with partial request
+    self.loadComponentsRecommendationsFromServer(function() {
+      // For validation use latest received recommendations because ir contains current master layout and recommended slave/client layout
+      self.validate(self.get('content.recommendations'), function() {
+        if (callback) {
+          callback();
+        }
+      });
+    }, true);
+  },
+
+  /**
+   * Submit button click handler
+   * @method submit
+   */
+  submit: function () {
+    var self = this;
+    if (!this.get('submitButtonClicked')) {
+      this.set('submitButtonClicked', true);
+
+      var goNextStepIfValid = function () {
+        if (!self.get('submitDisabled')) {
+          App.router.send('next');
+        }
+        self.set('submitButtonClicked', false);
+      };
+
+      if (this.get('useServerValidation')) {
+        self.recommendAndValidate(function () {
+          self.showValidationIssuesAcceptBox(goNextStepIfValid);
+        });
+      } else {
+        self.updateIsSubmitDisabled();
+        goNextStepIfValid();
+      }
+    }
+  },
+
+  /**
+   * In case of any validation issues shows accept dialog box for user which allow cancel and fix issues or continue anyway
+   * @method showValidationIssuesAcceptBox
+   */
+  showValidationIssuesAcceptBox: function(callback) {
+    var self = this;
+    if (self.get('anyWarning') || self.get('anyError')) {
+      App.ModalPopup.show({
+        primary: Em.I18n.t('common.continueAnyway'),
+        header: Em.I18n.t('installer.step5.validationIssuesAttention.header'),
+        body: Em.I18n.t('installer.step5.validationIssuesAttention'),
+        onPrimary: function () {
+          this.hide();
+          callback();
+        }
+      });
+    } else {
+      callback();
+    }
+  }
+});

+ 9 - 4
ambari-web/app/models/stack_service_component.js

@@ -137,16 +137,21 @@ App.StackServiceComponent = DS.Model.extend({
 
   /**
    * Master component list that could be assigned for more than 1 host.
-   * Some components like NameNode and ResourceManager have range cardinality value
-   * like 1-2. We can assign only components with cardinality 1+/0+. Strict range value
-   * show that this components will be assigned for 2 hosts only if HA mode activated.
+   * Some components like NameNode and ResourceManager have range cardinality value, so they are excluded using isMasterAddableOnlyOnHA property
    *
    * @property {Boolean} isMasterAddableInstallerWizard
    **/
   isMasterAddableInstallerWizard: function() {
-    return this.get('isMaster') && this.get('isMultipleAllowed') && this.get('maxToInstall') === Infinity;
+    return this.get('isMaster') && this.get('isMultipleAllowed') && this.get('maxToInstall') > 1 && !this.get('isMasterAddableOnlyOnHA').contains(this.get('componentName'));
   }.property('componentName'),
 
+  /**
+   * Master components with cardinality more than 1 (n+ or n-n) that could not be added in wizards
+   * New instances of these components are added in appropriate HA wizards
+   * @property {Boolean} isMasterAddableOnlyOnHA
+   */
+  isMasterAddableOnlyOnHA: ['NAMENODE', 'RESOURCEMANAGER', 'HIVE_METASTORE', 'HIVE_SERVER', 'RANGER_ADMIN'],
+
   /** @property {Boolean} isHAComponentOnly - Components that can be installed only if HA enabled **/
   isHAComponentOnly: function() {
     var HAComponentNames = ['ZKFC','JOURNALNODE'];

+ 1 - 1
ambari-web/app/routes/high_availability_routes.js

@@ -145,7 +145,7 @@ module.exports = App.WizardRoute.extend({
     next: function (router) {
       var controller = router.get('highAvailabilityWizardController');
       var highAvailabilityWizardStep2Controller = router.get('highAvailabilityWizardStep2Controller');
-      var addNN = highAvailabilityWizardStep2Controller.get('selectedServicesMasters').findProperty('isAddNameNode', true).get('selectedHost');
+      var addNN = highAvailabilityWizardStep2Controller.get('selectedServicesMasters').filterProperty('component_name', 'NAMENODE').findProperty('isInstalled', false).get('selectedHost');
       var sNN = highAvailabilityWizardStep2Controller.get('selectedServicesMasters').findProperty('component_name','SECONDARY_NAMENODE').get('selectedHost');
       if(addNN){
         App.db.setRollBackHighAvailabilityWizardAddNNHost(addNN);

+ 2 - 2
ambari-web/app/routes/rm_high_availability_routes.js

@@ -131,8 +131,8 @@ module.exports = App.WizardRoute.extend({
     next: function (router) {
       var wizardController = router.get('rMHighAvailabilityWizardController');
       var stepController = router.get('rMHighAvailabilityWizardStep2Controller');
-      var currentRM = stepController.get('servicesMasters').findProperty('isAdditional', false);
-      var additionalRM = stepController.get('servicesMasters').findProperty('isAdditional', true);
+      var currentRM = stepController.get('servicesMasters').filterProperty('component_name', 'RESOURCEMANAGER').findProperty('isInstalled', true);
+      var additionalRM = stepController.get('servicesMasters').filterProperty('component_name', 'RESOURCEMANAGER').findProperty('isInstalled', false);
       var rmHost = {
         currentRM: currentRM.get('selectedHost'),
         additionalRM: additionalRM.get('selectedHost')

+ 8 - 7
ambari-web/app/styles/application.less

@@ -3712,7 +3712,7 @@ table.graphs {
   }
 
   .form-horizontal .control-group select {
-    width: 75%;
+    width: 85%;
     min-width: 100px;
     max-width: 250px;
   }
@@ -3747,6 +3747,13 @@ table.graphs {
     font-weight: bold;
     vertical-align: middle;
   }
+
+  .additional-hosts-list {
+    margin-bottom: 15px;
+    .host-cell {
+      line-height: 30px;
+    }
+  }
 }
 
 /*end assign masters*/
@@ -5549,12 +5556,6 @@ input[type="checkbox"].align-checkbox {
   }
 }
 
-#rm-ha-wizard {
-  .rm-host-select {
-    width: 95%;
-  }
-}
-
 .table td.no-borders { border-top: none; }
 .table td.error { background-color: #f2dede; }
 .table td.warning { background-color: #fcf8e3; }

+ 25 - 10
ambari-web/app/templates/wizard/step5.hbs → ambari-web/app/templates/common/assign_master_components.hbs

@@ -16,12 +16,9 @@
 * limitations under the License.
 }}
 <div id="assign-masters">
-  <h2>{{title}}</h2>
+  <h2>{{view.title}}</h2>
   <div class="alert alert-info">
-    {{t installer.step5.body}}
-    {{#if view.coHostedComponentText}}
-      {{{view.coHostedComponentText}}}
-    {{/if}}
+    {{{view.alertMessage}}}
   </div>
   {{#each msg in controller.generalErrorMessages}}
     <div class="alert alert-error">{{msg}}</div>
@@ -47,16 +44,34 @@
             <div class="span12 control-group">
               <form class="form-horizontal" autocomplete="off">
                 <!-- View for array controller -->
-                {{#each servicesMasters}}
+                {{#each controller.additionalHostsList}}
+                  <div class="row-fluid additional-hosts-list">
+                    <div class="span5">
+                      <label class="pts pull-right">
+                        {{label}}
+                      </label>
+                    </div>
+                    <div class="span7 host-cell">
+                      {{host}}
+                    </div>
+                  </div>
+                {{/each}}
+                {{#each servicesMastersToShow}}
                   <div class="row-fluid">
-                    <div class="span4">
+                    <div class="span5">
                       <div class="control-group">
                         <label class="pts pull-right">
+                          {{#if showCurrentPrefix}}
+                            {{t common.current}}
+                          {{/if}}
+                          {{#if showAdditionalPrefix}}
+                            {{t common.additional}}
+                          {{/if}}
                           {{display_name}}:
                         </label>
                       </div>
                     </div>
-                    <div class="span8">
+                    <div class="span7">
                       {{#if isServiceCoHost}}
                         <div class="hostName">
                           {{selectedHost}}<i class="icon-asterisks">&#10037;</i>
@@ -123,7 +138,7 @@
     <div class="spinner"></div>
   {{/if}}
   <div class="btn-area">
-    <a class="btn pull-left installer-back-btn" {{action back href="true"}}>&larr; {{t common.back}}</a>
+    <a class="btn pull-left installer-back-btn" {{action back}}>&larr; {{t common.back}}</a>
     <a class="btn btn-success pull-right" {{bindAttr disabled="submitDisabled"}} {{action submit target="controller"}}>{{t common.next}} &rarr;</a>
   </div>
-</div>
+</div>

+ 0 - 83
ambari-web/app/templates/main/admin/highAvailability/nameNode/step2.hbs

@@ -1,83 +0,0 @@
-{{!
-* 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.
-}}
-
-<h2>{{t admin.highAvailability.wizard.step2.header}}</h2>
-<div class="alert alert-info">
-  {{t admin.highAvailability.wizard.step2.body}}
-</div>
-<div class="assign-masters">
-  <div class="select-hosts span7">
-    <form class="form-horizontal" autocomplete="off">
-      <!-- View for array controller -->
-      {{#each servicesMasters}}
-        <div class="control-group">
-          <label class="control-label">
-            {{#if isCurNameNode}}
-              {{t common.current}}
-            {{/if}}
-            {{#if isAddNameNode}}
-              {{t common.additional}}
-            {{/if}}
-            {{display_name}}:
-          </label>
-
-          <div class="controls">
-            {{#if view.shouldUseInputs}}
-              {{view App.InputHostView
-              componentBinding="this"
-              disabledBinding="isInstalled" }}
-            {{else}}
-              {{view App.SelectHostView
-              componentBinding="this"
-              disabledBinding="isInstalled"
-              optionValuePath="content.host_name"
-              optionLabelPath="content.host_info" }}
-            {{/if}}
-            {{#if showAddControl}}
-              {{view App.AddControlView componentNameBinding="component_name"}}
-            {{/if}}
-            {{#if showRemoveControl}}
-              {{view App.RemoveControlView componentNameBinding="component_name" serviceComponentIddBinding="serviceComponentId"}}
-            {{/if}}
-          </div>
-        </div>
-      {{/each}}
-    </form>
-  </div>
-
-  <div class="host-assignments span5">
-    {{#each masterHostMapping}}
-      <div class="mapping-box round-corners well">
-        <div class="hostString"><span>{{hostInfo}}</span></div>
-        {{#each masterServices}}
-          <span {{bindAttr class="isInstalled:assignedService:newService :round-corners"}}>{{display_name}}</span>
-        {{/each}}
-      </div>
-    {{/each}}
-
-    {{#if remainingHosts}}
-      <div class="remaining-hosts round-corners well">
-        <span><strong>{{remainingHosts}}</strong> {{t installer.step5.attention}}</span></div>
-    {{/if}}
-  </div>
-  <div style="clear: both;"></div>
-</div>
-<div class="btn-area">
-  <a class="btn" {{action back}}>&larr; {{t common.back}}</a>
-  <a class="btn btn-success pull-right" {{bindAttr disabled="submitDisabled"}} {{action submit target="controller"}}>{{t common.next}} &rarr;</a>
-</div>

+ 0 - 93
ambari-web/app/templates/main/admin/highAvailability/resourceManager/step2.hbs

@@ -1,93 +0,0 @@
-{{!
-* 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.
-}}
-<h2>{{t admin.rm_highAvailability.wizard.step2.header}}</h2>
-<div class="alert alert-info">
-  {{t admin.rm_highAvailability.wizard.step2.body}}
-</div>
-{{#if controller.isLoaded}}
-  <div class="assign-masters row-fluid">
-    <div class="select-hosts span7">
-      <div class="row-fluid">
-        <div class="clearfix"></div>
-        <div class="row-fluid">
-          <div class="span12 control-group">
-            <form class="form-horizontal" autocomplete="off">
-              <!-- View for array controller -->
-              {{#each servicesMasters}}
-                <div class="row-fluid">
-                  <div class="span5">
-                    <div class="control-group">
-                      <label class="pts pull-right">
-                        {{#if isAdditional}}
-                          {{t common.additional}}
-                        {{else}}
-                          {{t common.current}}
-                        {{/if}}
-                        {{display_name}}:
-                      </label>
-                    </div>
-                  </div>
-                  <div class="span7">
-                    <div class="control-group">
-                      {{#if view.shouldUseInputs}}
-                        {{view App.InputHostView
-                        componentBinding="this"
-                        disabledBinding="isInstalled" }}
-                      {{else}}
-                        {{view App.SelectHostView
-                        componentBinding="this"
-                        class="rm-host-select"
-                        disabledBinding="isInstalled"
-                        optionValuePath="content.host_name"
-                        optionLabelPath="content.host_info" }}
-                      {{/if}}
-                    </div>
-                  </div>
-                </div>
-              {{/each}}
-            </form>
-          </div>
-        </div>
-      </div>
-    </div>
-
-    <div class="host-assignments span5">
-      {{#each masterHostMapping}}
-        <div class="mapping-box round-corners well">
-          <div class="hostString"><span>{{hostInfo}}</span></div>
-          {{#each masterServicesToDisplay}}
-            <span {{bindAttr class="isInstalled:assignedService:newService :round-corners"}}>{{display_name}}</span>
-          {{/each}}
-        </div>
-      {{/each}}
-
-      {{#if remainingHosts}}
-        <div class="remaining-hosts round-corners well">
-          <span><strong>{{remainingHosts}}</strong> {{t installer.step5.attention}}</span></div>
-      {{/if}}
-    </div>
-    <div class="clearfix"></div>
-  </div>
-{{else}}
-  <div class="spinner"></div>
-{{/if}}
-<div class="btn-area">
-  <a class="btn" {{action back}}>&larr; {{t common.back}}</a>
-  <a class="btn btn-success pull-right" {{bindAttr disabled="submitDisabled"}} {{action submit target="controller"}}>{{t common.next}} &rarr;</a>
-</div>
-

+ 1 - 0
ambari-web/app/views.js

@@ -77,6 +77,7 @@ require('views/common/widget/graph_widget_view');
 require('views/common/widget/template_widget_view');
 require('views/common/widget/gauge_widget_view');
 require('views/common/widget/number_widget_view');
+require('views/common/assign_master_components_view');
 require('views/login');
 require('views/main');
 require('views/main/menu');

+ 201 - 0
ambari-web/app/views/common/assign_master_components_view.js

@@ -0,0 +1,201 @@
+/**
+ * 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');
+
+App.AssignMasterComponentsView = Em.View.extend({
+
+  templateName: require('templates/common/assign_master_components'),
+
+  /**
+   * Title to be shown on the page
+   * @type {String}
+   */
+  title: '',
+
+  /**
+   * Alert message to be shown on the page
+   * @type {String}
+   */
+  alertMessage: '',
+
+  /**
+   * If install more than 25 hosts, should use App.InputHostView for hosts selection
+   * Otherwise - App.SelectHostView
+   * @type {bool}
+   */
+  shouldUseInputs: function() {
+    return this.get('controller.hosts.length') > 25;
+  }.property('controller.hosts.length'),
+
+  didInsertElement: function () {
+    this.get('controller').loadStep();
+  }
+});
+
+App.InputHostView = Em.TextField.extend(App.SelectHost, {
+
+  attributeBindings: ['disabled'],
+  /**
+   * Saved typeahead component
+   * @type {$}
+   */
+  typeahead: null,
+
+  /**
+   * When <code>value</code> (host_info) is changed this method is triggered
+   * If new hostname is valid, this host is assigned to master component
+   * @method changeHandler
+   */
+  changeHandler: function() {
+    if (!this.shouldChangeHandlerBeCalled()) return;
+    var host = this.get('controller.hosts').findProperty('host_name', this.get('value'));
+    if (Em.isNone(host)) {
+      this.get('controller').updateIsHostNameValidFlag(this.get("component.component_name"), this.get("component.serviceComponentId"), false);
+      return;
+    }
+    this.get('controller').assignHostToMaster(this.get("component.component_name"), host.get('host_name'), this.get("component.serviceComponentId"));
+    this.tryTriggerRebalanceForMultipleComponents();
+  }.observes('controller.hostNameCheckTrigger'),
+
+  didInsertElement: function () {
+    this.initContent();
+    var value = this.get('content').findProperty('host_name', this.get('component.selectedHost')).get('host_name');
+    this.set("value", value);
+    var content = this.get('content').mapProperty('host_info'),
+        self = this,
+        updater = function (item) {
+          return self.get('content').findProperty('host_info', item).get('host_name');
+        },
+        typeahead = this.$().typeahead({items: 10, source: content, updater: updater, minLength: 0});
+    typeahead.on('blur', function() {
+      self.change();
+    }).on('keyup', function(e) {
+      self.set('value', $(e.currentTarget).val());
+      self.change();
+    });
+    this.set('typeahead', typeahead);
+  },
+
+  /**
+   * Extract hosts from controller,
+   * filter out available to selection and
+   * push them into Em.Select content
+   * @method initContent
+   */
+  initContent: function () {
+    this._super();
+    this.updateTypeaheadData(this.get('content').mapProperty('host_info'));
+  },
+
+  /**
+   * Update <code>source</code> property of <code>typeahead</code> with a new list of hosts
+   * @param {string[]} hosts
+   * @method updateTypeaheadData
+   */
+  updateTypeaheadData: function(hosts) {
+    if (this.get('typeahead')) {
+      this.get('typeahead').data('typeahead').source = hosts;
+    }
+  }
+
+});
+
+App.SelectHostView = Em.Select.extend(App.SelectHost, {
+
+  attributeBindings: ['disabled'],
+
+  didInsertElement: function () {
+    this.initContent();
+    this.set("value", this.get("component.selectedHost"));
+    App.popover($("[rel=popover]"), {'placement': 'right', 'trigger': 'hover'});
+  },
+
+  /**
+   * Handler for selected value change
+   * @method change
+   */
+  changeHandler: function () {
+    if (!this.shouldChangeHandlerBeCalled()) return;
+    this.get('controller').assignHostToMaster(this.get("component.component_name"), this.get("value"), this.get("component.serviceComponentId"));
+    this.tryTriggerRebalanceForMultipleComponents();
+  }.observes('controller.hostNameCheckTrigger'),
+
+  /**
+   * On click handler
+   * @method click
+   */
+  click: function () {
+    this.initContent();
+  }
+
+});
+
+App.AddControlView = Em.View.extend({
+
+  /**
+   * Current component name
+   * @type {string}
+   */
+  componentName: null,
+
+  tagName: "span",
+
+  classNames: ["badge", "badge-important"],
+
+  template: Em.Handlebars.compile('+'),
+
+  /**
+   * Onclick handler
+   * Add selected component
+   * @method click
+   */
+  click: function () {
+    this.get('controller').addComponent(this.get('componentName'));
+  }
+});
+
+App.RemoveControlView = Em.View.extend({
+
+  /**
+   * Index for multiple component
+   * @type {number}
+   */
+  serviceComponentId: null,
+
+  /**
+   * Current component name
+   * @type {string}
+   */
+  componentName: null,
+
+  tagName: "span",
+
+  classNames: ["badge", "badge-important"],
+
+  template: Em.Handlebars.compile('-'),
+
+  /**
+   * Onclick handler
+   * Remove current component
+   * @method click
+   */
+  click: function () {
+    this.get('controller').removeComponent(this.get('componentName'), this.get("serviceComponentId"));
+  }
+});

+ 4 - 2
ambari-web/app/views/main/admin/highAvailability/nameNode/step2_view.js

@@ -21,8 +21,10 @@ var App = require('app');
 
 require('views/wizard/step5_view');
 
-App.HighAvailabilityWizardStep2View = App.WizardStep5View.extend({
+App.HighAvailabilityWizardStep2View = App.AssignMasterComponentsView.extend({
 
-  templateName: require('templates/main/admin/highAvailability/nameNode/step2')
+  title: Em.I18n.t('admin.highAvailability.wizard.step2.header'),
+
+  alertMessage: Em.I18n.t('admin.highAvailability.wizard.step2.body')
 
 });

+ 2 - 2
ambari-web/app/views/main/admin/highAvailability/nameNode/step3_view.js

@@ -26,10 +26,10 @@ App.HighAvailabilityWizardStep3View = Em.View.extend({
     this.get('controller').loadStep();
   },
   curNameNode: function () {
-    return this.get('controller.content.masterComponentHosts').findProperty('isCurNameNode', true).hostName;
+    return this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true).hostName;
   }.property('controller.content.masterComponentHosts'),
   addNameNode: function () {
-    return this.get('controller.content.masterComponentHosts').findProperty('isAddNameNode', true).hostName;
+    return this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false).hostName;
   }.property('controller.content.masterComponentHosts'),
   secondaryNameNode: function () {
     return this.get('controller.content.masterComponentHosts').findProperty('component', "SECONDARY_NAMENODE").hostName;

+ 1 - 1
ambari-web/app/views/main/admin/highAvailability/nameNode/step4_view.js

@@ -28,7 +28,7 @@ App.HighAvailabilityWizardStep4View = Em.View.extend({
   },
 
   step4BodyText: function () {
-    var nN = this.get('controller.content.masterComponentHosts').findProperty('isCurNameNode', true);
+    var nN = this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true);
     return Em.I18n.t('admin.highAvailability.wizard.step4.body').format(this.get('controller.content.hdfsUser'), nN.hostName);
   }.property('controller.content.masterComponentHosts'),
 

+ 1 - 1
ambari-web/app/views/main/admin/highAvailability/nameNode/step6_view.js

@@ -28,7 +28,7 @@ App.HighAvailabilityWizardStep6View = Em.View.extend({
   },
 
   step6BodyText: function () {
-    var nN = this.get('controller.content.masterComponentHosts').findProperty('isCurNameNode', true);
+    var nN = this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true);
     return Em.I18n.t('admin.highAvailability.wizard.step6.body').format(this.get('controller.content.hdfsUser'), nN.hostName);
   }.property('controller.content.masterComponentHosts'),
 

+ 2 - 2
ambari-web/app/views/main/admin/highAvailability/nameNode/step8_view.js

@@ -24,8 +24,8 @@ App.HighAvailabilityWizardStep8View = Em.View.extend({
   templateName: require('templates/main/admin/highAvailability/nameNode/step8'),
 
   step8BodyText: function () {
-    var nN = this.get('controller.content.masterComponentHosts').findProperty('isCurNameNode', true);
-    var addNN = this.get('controller.content.masterComponentHosts').findProperty('isAddNameNode', true);
+    var nN = this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', true);
+    var addNN = this.get('controller.content.masterComponentHosts').filterProperty('component', 'NAMENODE').findProperty('isInstalled', false);
     return Em.I18n.t('admin.highAvailability.wizard.step8.body').format(this.get('controller.content.hdfsUser'), nN.hostName, addNN.hostName);
   }.property('controller.content.masterComponentHosts')
 

+ 4 - 2
ambari-web/app/views/main/admin/highAvailability/resourceManager/step2_view.js

@@ -19,8 +19,10 @@
 
 var App = require('app');
 
-App.RMHighAvailabilityWizardStep2View = App.WizardStep5View.extend({
+App.RMHighAvailabilityWizardStep2View = App.AssignMasterComponentsView.extend({
 
-  templateName: require('templates/main/admin/highAvailability/resourceManager/step2')
+  title: Em.I18n.t('admin.rm_highAvailability.wizard.step2.header'),
+
+  alertMessage: Em.I18n.t('admin.rm_highAvailability.wizard.step2.body')
 
 });

+ 5 - 2
ambari-web/app/views/main/service/reassign/step2_view.js

@@ -19,8 +19,11 @@
 
 var App = require('app');
 
-App.ReassignMasterWizardStep2View = App.WizardStep5View.extend({
-  body: function () {
+App.ReassignMasterWizardStep2View = App.AssignMasterComponentsView.extend({
+
+  title: Em.I18n.t('installer.step5.reassign.header'),
+
+  alertMessage: function () {
     if (this.get('controller.content.reassign.component_name') === 'NAMENODE' && App.get('isHaEnabled')) {
       return Em.I18n.t('services.reassign.step2.body.namenodeHA').format(this.get('controller.content.reassign.display_name'));
     }

+ 15 - 164
ambari-web/app/views/wizard/step5_view.js

@@ -19,26 +19,28 @@
 var App = require('app');
 var stringUtils = require('utils/string_utils');
 
-App.WizardStep5View = Em.View.extend({
+App.WizardStep5View = App.AssignMasterComponentsView.extend({
 
-  templateName: require('templates/wizard/step5'),
+  title: function () {
+    if (this.get('controller.content.controllerName') == 'reassignMasterController') {
+      return Em.I18n.t('installer.step5.reassign.header');
+    }
+    return Em.I18n.t('installer.step5.header');
+  }.property('controller.content.controllerName'),
+
+  alertMessage: function () {
+    var result = Em.I18n.t('installer.step5.body');
+    result += this.get('coHostedComponentText') ? '\n' + this.get('coHostedComponentText') : '';
+    return result;
+  }.property('coHostedComponentText'),
 
-  /**
-   * If install more than 25 hosts, should use App.InputHostView for hosts selection
-   * Otherwise - App.SelectHostView
-   * @type {bool}
-   */
-  shouldUseInputs: function() {
-    return this.get('controller.hosts.length') > 25;
-  }.property('controller.hosts.length'),
+  coHostedComponentText: '',
 
   didInsertElement: function () {
-    this.get('controller').loadStep();
+    this._super();
     this.setCoHostedComponentText();
   },
 
-  coHostedComponentText: '',
-
   setCoHostedComponentText: function () {
     var coHostedComponents = App.StackServiceComponent.find().filterProperty('isOtherComponentCoHosted').filterProperty('stackService.isSelected');
     var coHostedComponentsText = '';
@@ -55,156 +57,5 @@ App.WizardStep5View = Em.View.extend({
 
     this.set('coHostedComponentText', coHostedComponentsText);
   }
-});
-
-App.InputHostView = Em.TextField.extend(App.SelectHost, {
-
-  attributeBindings: ['disabled'],
-  /**
-   * Saved typeahead component
-   * @type {$}
-   */
-  typeahead: null,
-
-  /**
-   * When <code>value</code> (host_info) is changed this method is triggered
-   * If new hostname is valid, this host is assigned to master component
-   * @method changeHandler
-   */
-  changeHandler: function() {
-    if (!this.shouldChangeHandlerBeCalled()) return;
-    var host = this.get('controller.hosts').findProperty('host_name', this.get('value'));
-    if (Em.isNone(host)) {
-      this.get('controller').updateIsHostNameValidFlag(this.get("component.component_name"), this.get("component.serviceComponentId"), false);
-      return;
-    }
-    this.get('controller').assignHostToMaster(this.get("component.component_name"), host.get('host_name'), this.get("component.serviceComponentId"));
-    this.tryTriggerRebalanceForMultipleComponents();
-  }.observes('controller.hostNameCheckTrigger'),
-
-  didInsertElement: function () {
-    this.initContent();
-    var value = this.get('content').findProperty('host_name', this.get('component.selectedHost')).get('host_name');
-    this.set("value", value);
-    var content = this.get('content').mapProperty('host_info'),
-      self = this,
-      updater = function (item) {
-        return self.get('content').findProperty('host_info', item).get('host_name');
-      },
-      typeahead = this.$().typeahead({items: 10, source: content, updater: updater, minLength: 0});
-    typeahead.on('blur', function() {
-      self.change();
-    }).on('keyup', function(e) {
-        self.set('value', $(e.currentTarget).val());
-        self.change();
-      });
-    this.set('typeahead', typeahead);
-  },
-
-  /**
-   * Extract hosts from controller,
-   * filter out available to selection and
-   * push them into Em.Select content
-   * @method initContent
-   */
-  initContent: function () {
-    this._super();
-    this.updateTypeaheadData(this.get('content').mapProperty('host_info'));
-  },
-
-  /**
-   * Update <code>source</code> property of <code>typeahead</code> with a new list of hosts
-   * @param {string[]} hosts
-   * @method updateTypeaheadData
-   */
-  updateTypeaheadData: function(hosts) {
-    if (this.get('typeahead')) {
-      this.get('typeahead').data('typeahead').source = hosts;
-    }
-  }
-
-});
-
-App.SelectHostView = Em.Select.extend(App.SelectHost, {
-
-  attributeBindings: ['disabled'],
 
-  didInsertElement: function () {
-    this.initContent();
-    this.set("value", this.get("component.selectedHost"));
-    App.popover($("[rel=popover]"), {'placement': 'right', 'trigger': 'hover'});
-  },
-
-  /**
-   * Handler for selected value change
-   * @method change
-   */
-  changeHandler: function () {
-    if (!this.shouldChangeHandlerBeCalled()) return;
-    this.get('controller').assignHostToMaster(this.get("component.component_name"), this.get("value"), this.get("component.serviceComponentId"));
-    this.tryTriggerRebalanceForMultipleComponents();
-  }.observes('controller.hostNameCheckTrigger'),
-
-  /**
-   * On click handler
-   * @method click
-   */
-  click: function () {
-    this.initContent();
-  }
-
-});
-
-App.AddControlView = Em.View.extend({
-
-  /**
-   * Current component name
-   * @type {string}
-   */
-  componentName: null,
-
-  tagName: "span",
-
-  classNames: ["badge", "badge-important"],
-
-  template: Em.Handlebars.compile('+'),
-
-  /**
-   * Onclick handler
-   * Add selected component
-   * @method click
-   */
-  click: function () {
-    this.get('controller').addComponent(this.get('componentName'));
-  }
-});
-
-App.RemoveControlView = Em.View.extend({
-
-  /**
-   * Index for multiple component
-   * @type {number}
-   */
-  serviceComponentId: null,
-
-  /**
-   * Current component name
-   * @type {string}
-   */
-  componentName: null,
-
-  tagName: "span",
-
-  classNames: ["badge", "badge-important"],
-
-  template: Em.Handlebars.compile('-'),
-
-  /**
-   * Onclick handler
-   * Remove current component
-   * @method click
-   */
-  click: function () {
-    this.get('controller').removeComponent(this.get('componentName'), this.get("serviceComponentId"));
-  }
 });

+ 7 - 219
ambari-web/test/controllers/main/service/reassign/step2_controller_test.js

@@ -32,212 +32,8 @@ describe('App.ReassignMasterWizardStep2Controller', function () {
     renderComponents: Em.K,
     multipleComponents: []
   });
-  controller.set('_super', Em.K);
 
-  describe('#loadStep', function () {
-
-    beforeEach(function () {
-      sinon.stub(App.router, 'send', Em.K);
-      sinon.stub(controller, 'clearStep', Em.K);
-      sinon.stub(controller, 'loadComponents', Em.K);
-      sinon.stub(controller, 'loadStepCallback', Em.K);
-      sinon.stub(controller, 'rebalanceSingleComponentHosts', Em.K);
-    });
-
-    afterEach(function () {
-      App.router.send.restore();
-      controller.clearStep.restore();
-      controller.loadStepCallback.restore();
-      controller.loadComponents.restore();
-      controller.rebalanceSingleComponentHosts.restore();
-    });
-
-    it('SECONDARY_NAMENODE is absent, reassign component is NAMENODE', function () {
-      sinon.stub(App, 'get', function (k) {
-        if (k === 'isHaEnabled') return true;
-        return Em.get(App, k);
-      });
-      controller.set('content.reassign.component_name', 'NAMENODE');
-      controller.set('content.masterComponentHosts', []);
-
-      controller.loadStep();
-      expect(controller.get('showCurrentHost')).to.be.false;
-      expect(controller.get('componentToRebalance')).to.equal('NAMENODE');
-      expect(controller.get('rebalanceComponentHostsCounter')).to.equal(1);
-      App.get.restore();
-    });
-    it('SECONDARY_NAMENODE is present, reassign component is NAMENODE', function () {
-      sinon.stub(App, 'get', function (k) {
-        if (k === 'isHaEnabled') return false;
-        return Em.get(App, k);
-      });
-      controller.set('content.reassign.component_name', 'NAMENODE');
-      controller.set('content.masterComponentHosts', [
-        {
-          component: 'SECONDARY_NAMENODE'
-        }
-      ]);
-
-      controller.loadStep();
-      expect(controller.get('showCurrentHost')).to.be.true;
-      expect(controller.rebalanceSingleComponentHosts.calledWith('NAMENODE'));
-      App.get.restore();
-    });
-    it('SECONDARY_NAMENODE is absent, reassign component is not NAMENODE', function () {
-      controller.set('content.reassign.component_name', 'COMP');
-      controller.set('content.masterComponentHosts', []);
-
-      controller.loadStep();
-      expect(controller.get('showCurrentHost')).to.be.true;
-      expect(controller.rebalanceSingleComponentHosts.calledWith('COMP'));
-    });
-    it('if HA is enabled then multipleComponents should contain NAMENODE', function () {
-      controller.get('multipleComponents').clear();
-      sinon.stub(App, 'get', function (k) {
-        if (k === 'isHaEnabled') return true;
-        return Em.get(App, k);
-      });
-
-      controller.loadStep();
-      expect(controller.get('multipleComponents')).to.contain('NAMENODE');
-      expect(controller.get('multipleComponents')).to.have.length(1);
-      App.get.restore();
-    });
-  });
-
-  describe('#loadComponents', function () {
-    it('masterComponentHosts is empty', function () {
-      controller.set('content.masterComponentHosts', []);
-      controller.set('content.reassign.host_id', 1);
-
-      expect(controller.loadComponents()).to.be.empty;
-      expect(controller.get('currentHostId')).to.equal(1);
-    });
-    it('masterComponentHosts does not contain reassign component', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return [Em.Object.create({
-          componentName: 'COMP1',
-          serviceName: 'SERVICE'
-        })];
-      });
-      controller.set('content.masterComponentHosts', [{
-        component: 'COMP1',
-        hostName: 'host1'
-      }]);
-      controller.set('content.reassign.host_id', 1);
-      controller.set('content.reassign.component_name', 'COMP2');
-
-      expect(controller.loadComponents()).to.eql([
-        {
-          "component_name": "COMP1",
-          "display_name": "Comp1",
-          "selectedHost": "host1",
-          "isInstalled": true,
-          "serviceId": "SERVICE",
-          "isServiceCoHost": false,
-          "color": "grey"
-        }
-      ]);
-      expect(controller.get('currentHostId')).to.equal(1);
-
-      App.HostComponent.find.restore();
-    });
-    it('masterComponentHosts contains reassign component', function () {
-      sinon.stub(App.HostComponent, 'find', function () {
-        return [Em.Object.create({
-          componentName: 'COMP1',
-          serviceName: 'SERVICE'
-        })];
-      });
-      controller.set('content.masterComponentHosts', [{
-        component: 'COMP1',
-        hostName: 'host1'
-      }]);
-      controller.set('content.reassign.host_id', 1);
-      controller.set('content.reassign.component_name', 'COMP1');
-
-      expect(controller.loadComponents()).to.eql([
-        {
-          "component_name": "COMP1",
-          "display_name": "Comp1",
-          "selectedHost": "host1",
-          "isInstalled": true,
-          "serviceId": "SERVICE",
-          "isServiceCoHost": false,
-          "color": "green"
-        }
-      ]);
-      expect(controller.get('currentHostId')).to.equal(1);
-
-      App.HostComponent.find.restore();
-    });
-  });
-
-  describe('#rebalanceSingleComponentHosts', function () {
-    it('hosts is empty', function () {
-      controller.set('hosts', []);
-
-      expect(controller.rebalanceSingleComponentHosts()).to.be.false;
-    });
-    it('currentHostId matches one available host', function () {
-      controller.set('hosts', [Em.Object.create({
-        host_name: 'host1'
-      })]);
-      controller.set('currentHostId', 'host1');
-
-      expect(controller.rebalanceSingleComponentHosts()).to.be.false;
-    });
-
-    var testCases = [
-      {
-        title: 'selectedHost = currentHostId',
-        arguments: {
-          selectedHost: 'host1'
-        },
-        result: {
-          selectedHost: 'host3',
-          availableHosts: ['host2', 'host3']
-        }
-      },
-      {
-        title: 'selectedHost not equal to currentHostId',
-        arguments: {
-          selectedHost: 'host2'
-        },
-        result: {
-          selectedHost: 'host2',
-          availableHosts: ['host3']
-        }
-      }
-    ];
-
-    testCases.forEach(function (test) {
-      it(test.title, function () {
-        controller.set('hosts', [
-          Em.Object.create({
-            host_name: 'host1'
-          }),
-          Em.Object.create({
-            host_name: 'host3'
-          }),
-          Em.Object.create({
-            host_name: 'host2'
-          })
-        ]);
-        controller.set('currentHostId', 'host1');
-        controller.set('selectedServicesMasters', [Em.Object.create({
-          component_name: 'COMP1',
-          selectedHost: test.arguments.selectedHost
-        })]);
-
-        expect(controller.rebalanceSingleComponentHosts('COMP1')).to.be.true;
-        expect(controller.get('selectedServicesMasters')[0].get('selectedHost')).to.equal(test.result.selectedHost);
-        expect(controller.get('selectedServicesMasters')[0].get('availableHosts').mapProperty('host_name')).to.eql(test.result.availableHosts);
-      });
-    });
-  });
-
-  describe('#updateIsSubmitDisabled', function () {
+  describe('#customClientSideValidation', function () {
     var hostComponents = [];
     var isSubmitDisabled = false;
 
@@ -245,17 +41,12 @@ describe('App.ReassignMasterWizardStep2Controller', function () {
       sinon.stub(App.HostComponent, 'find', function () {
         return hostComponents;
       });
-      sinon.stub(controller, '_super', function() {
-        return isSubmitDisabled;
-      });
     });
     afterEach(function () {
       App.HostComponent.find.restore();
-      controller._super.restore();
     });
     it('No host-components, reassigned equal 0', function () {
-      expect(controller.updateIsSubmitDisabled()).to.be.true;
-      expect(controller.get('submitDisabled')).to.be.true;
+      expect(controller.customClientSideValidation()).to.be.false;
     });
     it('Reassign component match existed components, reassigned equal 0', function () {
       controller.set('content.reassign.component_name', 'COMP1');
@@ -264,11 +55,11 @@ describe('App.ReassignMasterWizardStep2Controller', function () {
         hostName: 'host1'
       })];
       controller.set('servicesMasters', [{
+        component_name: 'COMP1',
         selectedHost: 'host1'
       }]);
 
-      expect(controller.updateIsSubmitDisabled()).to.be.true;
-      expect(controller.get('submitDisabled')).to.be.true;
+      expect(controller.customClientSideValidation()).to.be.false;
     });
     it('Reassign component do not match existed components, reassigned equal 1', function () {
       controller.set('content.reassign.component_name', 'COMP1');
@@ -278,8 +69,7 @@ describe('App.ReassignMasterWizardStep2Controller', function () {
       })];
       controller.set('servicesMasters', []);
 
-      expect(controller.updateIsSubmitDisabled()).to.be.false;
-      expect(controller.get('submitDisabled')).to.be.false;
+      expect(controller.customClientSideValidation()).to.be.true;
     });
     it('Reassign component do not match existed components, reassigned equal 2', function () {
       controller.set('content.reassign.component_name', 'COMP1');
@@ -295,15 +85,13 @@ describe('App.ReassignMasterWizardStep2Controller', function () {
       ];
       controller.set('servicesMasters', []);
 
-      expect(controller.updateIsSubmitDisabled()).to.be.true;
-      expect(controller.get('submitDisabled')).to.be.true;
+      expect(controller.customClientSideValidation()).to.be.false;
     });
 
     it('submitDisabled is already true', function () {
       isSubmitDisabled = true;
 
-      expect(controller.updateIsSubmitDisabled()).to.be.true;
-      expect(controller.get('submitDisabled')).to.be.true;
+      expect(controller.customClientSideValidation()).to.be.false;
     });
   });
 });

+ 27 - 110
ambari-web/test/controllers/wizard/step5_test.js

@@ -34,28 +34,6 @@ describe('App.WizardStep5Controller', function () {
 
   controller.set('content', {});
 
-  describe('#isReassignWizard', function () {
-    it('true if content.controllerName is reassignMasterController', function () {
-      controller.set('content.controllerName', 'reassignMasterController');
-      expect(controller.get('isReassignWizard')).to.equal(true);
-    });
-    it('false if content.controllerName is not reassignMasterController', function () {
-      controller.set('content.controllerName', 'mainController');
-      expect(controller.get('isReassignWizard')).to.equal(false);
-    });
-  });
-
-  describe('#isAddServiceWizard', function () {
-    it('true if content.controllerName is addServiceController', function () {
-      controller.set('content.controllerName', 'addServiceController');
-      expect(controller.get('isAddServiceWizard')).to.equal(true);
-    });
-    it('false if content.controllerName is not addServiceController', function () {
-      controller.set('content.controllerName', 'mainController');
-      expect(controller.get('isAddServiceWizard')).to.equal(false);
-    });
-  });
-
   describe('#sortHosts', function () {
 
     var tests = Em.A([
@@ -290,28 +268,6 @@ describe('App.WizardStep5Controller', function () {
           showRemoveControl: false
         }
       },
-      {
-        componentName: 'HBASE_SERVER',
-        serviceComponents: [
-          Em.Object.create({
-            componentName: 'HBASE_SERVER',
-            stackService: Em.Object.create({isInstalled: false, serviceName: 'HBASE'})
-          })
-        ],
-        selectedServicesMasters: Em.A([
-          Em.Object.create({showAddControl: false, showRemoveControl: true, component_name: 'HBASE_SERVER'})
-        ]),
-        hosts: Em.A([
-          Em.Object.create({}),
-          Em.Object.create({})
-        ]),
-        controllerName: 'addServiceController',
-        m: 'service not installed, not all host already have provided component',
-        e: {
-          showAddControl: true,
-          showRemoveControl: true
-        }
-      },
       {
         componentName: 'HBASE_SERVER',
         serviceComponents: [
@@ -496,6 +452,17 @@ describe('App.WizardStep5Controller', function () {
   });
 
   describe('#removeComponent', function () {
+
+    beforeEach(function () {
+      sinon.stub(c, 'getMaxNumberOfMasters', function () {
+        return Infinity;
+      });
+    });
+
+    afterEach(function(){
+      c.getMaxNumberOfMasters.restore();
+    });
+
     var tests = Em.A([
       {
         componentName: 'c1',
@@ -541,38 +508,6 @@ describe('App.WizardStep5Controller', function () {
         showAddControl: true,
         showRemoveControl: false
       },
-      {
-        componentName: 'ZOOKEPEER_SERVER',
-        serviceComponentId: 2,
-        selectedServicesMasters: Em.A([
-          Em.Object.create({serviceComponentId: 1, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false}),
-          Em.Object.create({serviceComponentId: 2, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false})
-        ]),
-        hosts: [
-          {}
-        ],
-        m: 'two components, add not allowed, remove not allowed',
-        e: true,
-        showAddControl: false,
-        showRemoveControl: false
-      },
-      {
-        componentName: 'ZOOKEPEER_SERVER',
-        serviceComponentId: 2,
-        selectedServicesMasters: Em.A([
-          Em.Object.create({serviceComponentId: 1, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false}),
-          Em.Object.create({serviceComponentId: 2, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false}),
-          Em.Object.create({serviceComponentId: 3, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: true})
-        ]),
-        hosts: [
-          {},
-          {}
-        ],
-        m: 'three components, add not allowed, remove allowed',
-        e: true,
-        showAddControl: false,
-        showRemoveControl: true
-      },
       {
         componentName: 'ZOOKEPEER_SERVER',
         serviceComponentId: 2,
@@ -606,6 +541,17 @@ describe('App.WizardStep5Controller', function () {
   });
 
   describe('#addComponent', function () {
+
+    beforeEach(function () {
+      sinon.stub(c, 'getMaxNumberOfMasters', function () {
+        return Infinity;
+      });
+    });
+
+    afterEach(function(){
+      c.getMaxNumberOfMasters.restore();
+    });
+
     var tests = Em.A([
       {
         componentName: 'c1',
@@ -623,25 +569,6 @@ describe('App.WizardStep5Controller', function () {
         m: 'no such components',
         e: false
       },
-      {
-        componentName: 'ZOOKEPEER_SERVER',
-        selectedServicesMasters: Em.A([
-          Em.Object.create({serviceComponentId: 1, component_name: 'ZOOKEPEER_SERVER'})
-        ]),
-        hosts: [],
-        m: 'one component, 0 hosts',
-        e: false
-      },
-      {
-        componentName: 'ZOOKEPEER_SERVER',
-        selectedServicesMasters: Em.A([
-          Em.Object.create({serviceComponentId: 1, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false}),
-          Em.Object.create({serviceComponentId: 2, component_name: 'ZOOKEPEER_SERVER', showAddControl: false, showRemoveControl: false})
-        ]),
-        hosts: [Em.Object.create({}), Em.Object.create({})],
-        m: 'two components, two hosts',
-        e: false
-      },
       {
         componentName: 'ZOOKEPEER_SERVER',
         selectedServicesMasters: Em.A([
@@ -662,17 +589,6 @@ describe('App.WizardStep5Controller', function () {
     });
   });
 
-  describe('#title', function () {
-    it('should be custom title for reassignMasterController', function () {
-      c.set('content', {controllerName: 'reassignMasterController'});
-      expect(c.get('title')).to.equal(Em.I18n.t('installer.step5.reassign.header'));
-    });
-    it('should be default for other', function () {
-      c.set('content', {controllerName: 'notReassignMasterController'});
-      expect(c.get('title')).to.equal(Em.I18n.t('installer.step5.header'));
-    });
-  });
-
   describe('#masterHostMapping', function () {
     Em.A([
         {
@@ -987,7 +903,7 @@ describe('App.WizardStep5Controller', function () {
             serviceName: 's1'
           }),
           hostName: 'h1',
-          controllerName: 'reassignMasterController',
+          mastersToMove: ['c1'],
           savedComponent: {
             hostName: 'h2',
             isInstalled: true
@@ -1008,7 +924,7 @@ describe('App.WizardStep5Controller', function () {
             serviceName: 's1'
           }),
           hostName: 'h1',
-          controllerName: 'installerController',
+          mastersToMove: [],
           stackServiceComponents: [Em.Object.create({componentName: 'c1', isCoHostedComponent: false})],
           e: {
             component_name: 'c1',
@@ -1025,7 +941,7 @@ describe('App.WizardStep5Controller', function () {
             serviceName: 's1'
           }),
           hostName: 'h1',
-          controllerName: 'installerController',
+          mastersToMove: [],
           stackServiceComponents: [Em.Object.create({componentName: 'c1', isCoHostedComponent: true})],
           e: {
             component_name: 'c1',
@@ -1041,6 +957,7 @@ describe('App.WizardStep5Controller', function () {
           sinon.stub(App.StackServiceComponent, 'find', function () {
             return test.stackServiceComponents;
           });
+          c.set('mastersToMove', test.mastersToMove);
           c.set('content', {controllerName: test.controllerName});
           expect(c.createComponentInstallationObject(test.fullComponent, test.hostName, test.savedComponent)).to.eql(test.e);
         });
@@ -1248,4 +1165,4 @@ describe('App.WizardStep5Controller', function () {
 
   });
 
-});
+});

+ 4 - 2
ambari-web/test/views/main/admin/highAvailability/nameNode/step3_view_test.js

@@ -44,7 +44,8 @@ describe('App.HighAvailabilityWizardStep3View', function () {
   describe("#curNameNode", function() {
     it("", function() {
       view.set('controller.content.masterComponentHosts', [{
-        isCurNameNode: true,
+        component: 'NAMENODE',
+        isInstalled: true,
         hostName: 'host1'
       }]);
       view.propertyDidChange('curNameNode');
@@ -55,7 +56,8 @@ describe('App.HighAvailabilityWizardStep3View', function () {
   describe("#addNameNode", function() {
     it("", function() {
       view.set('controller.content.masterComponentHosts', [{
-        isAddNameNode: true,
+        component: 'NAMENODE',
+        isInstalled: false,
         hostName: 'host1'
       }]);
       view.propertyDidChange('addNameNode');

+ 2 - 1
ambari-web/test/views/main/admin/highAvailability/nameNode/step4_view_test.js

@@ -44,7 +44,8 @@ describe('App.HighAvailabilityWizardStep4View', function () {
   describe("#step4BodyText", function() {
     it("", function() {
       view.set('controller.content.masterComponentHosts', [{
-        isCurNameNode: true,
+        component: 'NAMENODE',
+        isInstalled: true,
         hostName: 'host1'
       }]);
       view.set('controller.content.hdfsUser', 'user');

+ 2 - 1
ambari-web/test/views/main/admin/highAvailability/nameNode/step6_view_test.js

@@ -44,7 +44,8 @@ describe('App.HighAvailabilityWizardStep6View', function () {
   describe("#step6BodyText", function() {
     it("", function() {
       view.set('controller.content.masterComponentHosts', [{
-        isCurNameNode: true,
+        component: 'NAMENODE',
+        isInstalled: true,
         hostName: 'host1'
       }]);
       view.set('controller.content.hdfsUser', 'user');

+ 4 - 2
ambari-web/test/views/main/admin/highAvailability/nameNode/step8_view_test.js

@@ -32,11 +32,13 @@ describe('App.HighAvailabilityWizardStep8View', function () {
     it("", function() {
       view.set('controller.content.masterComponentHosts', [
         {
-          isCurNameNode: true,
+          component: 'NAMENODE',
+          isInstalled: true,
           hostName: 'host1'
         },
         {
-          isAddNameNode: true,
+          component: 'NAMENODE',
+          isInstalled: false,
           hostName: 'host2'
         }
       ]);