You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by nc...@apache.org on 2017/01/05 00:05:24 UTC

[43/50] [abbrv] ambari git commit: AMBARI-19361 Cover JournalNode HA wizard with unit tests. (atkach)

AMBARI-19361 Cover JournalNode HA wizard with unit tests. (atkach)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/8f16d643
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/8f16d643
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/8f16d643

Branch: refs/heads/branch-dev-patch-upgrade
Commit: 8f16d643867772ba491f85477ee03454e7177b62
Parents: 4a4a16a
Author: Andrii Tkach <at...@apache.org>
Authored: Wed Jan 4 17:27:10 2017 +0200
Committer: Andrii Tkach <at...@apache.org>
Committed: Wed Jan 4 17:27:10 2017 +0200

----------------------------------------------------------------------
 ambari-web/app/assets/test/tests.js             |   8 +
 .../journalNode/progress_controller.js          |   3 +-
 .../journalNode/step1_controller.js             |  25 +-
 .../journalNode/step2_controller.js             |  15 +-
 .../journalNode/step4_controller.js             |  17 +-
 .../journalNode/step6_controller.js             |   4 +-
 .../journalNode/step7_controller.js             |   2 +-
 .../journalNode/wizard_controller.js            |  44 +-
 .../global/background_operations_test.js        |   9 -
 .../journalNode/progress_controller_test.js     |  67 +++
 .../journalNode/step1_controller_test.js        | 245 +++++++++++
 .../journalNode/step2_controller_test.js        | 281 +++++++++++++
 .../journalNode/step4_controller_test.js        | 220 ++++++++++
 .../journalNode/step6_controller_test.js        |  74 ++++
 .../journalNode/step7_controller_test.js        |  46 +++
 .../journalNode/step8_controller_test.js        |  61 +++
 .../journalNode/wizard_controller_test.js       | 404 +++++++++++++++++++
 17 files changed, 1463 insertions(+), 62 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/assets/test/tests.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js
index 4263016..604d96b 100644
--- a/ambari-web/app/assets/test/tests.js
+++ b/ambari-web/app/assets/test/tests.js
@@ -81,6 +81,14 @@ var files = [
   'test/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller_test',
   'test/controllers/main/admin/highAvailability/hawq/removeStandby/step2_controller_test',
   'test/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/progress_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step1_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step2_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step4_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step6_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step7_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/step8_controller_test',
+  'test/controllers/main/admin/highAvailability/journalNode/wizard_controller_test',
   'test/controllers/main/dashboard/config_history_controller_test',
   'test/controllers/main/charts/heatmap_test',
   'test/controllers/main/charts/heatmap_metrics/heatmap_metric_test',

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/progress_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/progress_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/progress_controller.js
index 436a8bd..2959d67 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/progress_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/progress_controller.js
@@ -32,7 +32,8 @@ App.ManageJournalNodeProgressPageController = App.ManageJournalNodeWizardControl
    * @param note String
    */
   reconfigureSites: function(siteNames, data, note) {
-    var tagName = App.get('testMode') ? 'version1' : 'version' + (new Date).getTime();
+    var tagName = 'version' + App.dateTime();
+
     return siteNames.map(function(_siteName) {
       var config = data.items.findProperty('type', _siteName);
       var configToSave = {

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step1_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step1_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step1_controller.js
index e1252b7..b54986b 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step1_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step1_controller.js
@@ -22,7 +22,7 @@ require('controllers/wizard/step5_controller');
 
 App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintMixin, App.AssignMasterComponents, {
 
-  name:"manageJournalNodeWizardStep1Controller",
+  name: "manageJournalNodeWizardStep1Controller",
 
   useServerValidation: false,
 
@@ -38,7 +38,7 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
    * On initial rendering, load equivalent number of existing JournalNodes to masterToShow
    * @param masterComponents
    */
-  renderComponents: function(masterComponents) {
+  renderComponents: function (masterComponents) {
     //check if we are restoring components assignment by checking existing of JOURNALNODE component in array
     var restoringComponents = masterComponents.someProperty('component_name', 'JOURNALNODE');
     masterComponents = restoringComponents ? masterComponents : masterComponents.concat(this.generateJournalNodeComponents());
@@ -54,7 +54,10 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
   generateJournalNodeComponents: function () {
     var journalNodes = [];
     App.HostComponent.find().filterProperty('componentName', 'JOURNALNODE').forEach(function (jn) {
-      var jnComponent = this.createComponentInstallationObject(Em.Object.create({serviceName: jn.get('service.serviceName'), componentName: jn.get('componentName')}), jn.get('hostName'));
+      var jnComponent = this.createComponentInstallationObject(Em.Object.create({
+        serviceName: jn.get('service.serviceName'),
+        componentName: jn.get('componentName')
+      }), jn.get('hostName'));
       jnComponent.isInstalled = true;
       journalNodes.push(jnComponent);
     }, this);
@@ -64,13 +67,13 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
   /**
    * Enable/Disable show/hide operation for each JournalNode
    */
-  showHideJournalNodesAddRemoveControl: function() {
+  showHideJournalNodesAddRemoveControl: function () {
     var masterComponents = this.get('selectedServicesMasters');
     var jns = masterComponents.filterProperty('component_name', 'JOURNALNODE');
-    var maxNumMasters = this.getMaxNumberOfMasters('JOURNALNODE')
+    var maxNumMasters = this.getMaxNumberOfMasters('JOURNALNODE');
     var showRemoveControl = jns.get('length') > this.get('JOURNALNODES_COUNT_MINIMUM');
     var showAddControl = jns.get('length') < maxNumMasters;
-    jns.forEach(function(item) {
+    jns.forEach(function (item) {
       item.set('showAddControl', false);
       item.set('showRemoveControl', showRemoveControl);
     });
@@ -80,10 +83,10 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
   /**
    * Mark existing JournalNodes 'isInstalled' and 'showCurrentPrefix'
    */
-  updateJournalNodeInfo: function() {
+  updateJournalNodeInfo: function () {
     var jns = this.get('selectedServicesMasters').filterProperty('component_name', 'JOURNALNODE');
     var hosts = App.HostComponent.find().filterProperty('componentName', 'JOURNALNODE').mapProperty('hostName');
-    hosts.forEach(function(host) {
+    hosts.forEach(function (host) {
       var jn = jns.findProperty('selectedHost', host);
       if (jn) {
         jn.set('isInstalled', true);
@@ -96,7 +99,7 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
    * Callback after load controller data (hosts, host components etc)
    * @method loadStepCallback
    */
-  loadStepCallback: function(components, self) {
+  loadStepCallback: function (components, self) {
     self.renderComponents(components);
 
     self.get('addableComponents').forEach(function (componentName) {
@@ -108,10 +111,10 @@ App.ManageJournalNodeWizardStep1Controller = Em.Controller.extend(App.BlueprintM
   /**
    * Next button is disabled when there is any change to the original JournalNode hosts
    */
-  nextButtonDisabled: function() {
+  nextButtonDisabled: function () {
     var currentHosts = this.get('selectedServicesMasters').filterProperty('component_name', 'JOURNALNODE').mapProperty('selectedHost');
     var originalHosts = App.HostComponent.find().filterProperty('componentName', 'JOURNALNODE').mapProperty('hostName');
-    return currentHosts.sort().join() == originalHosts.sort().join();
+    return currentHosts.sort().join() === originalHosts.sort().join();
   }.property('hostNameCheckTrigger', 'nextButtonCheckTrigger')
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js
index d1cb198..108c97a 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step2_controller.js
@@ -41,6 +41,8 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
   versionLoaded: true,
   hideDependenciesInfoBar: true,
 
+  isNextDisabled: Em.computed.not('isLoaded'),
+
   clearStep: function () {
     this.get('stepConfigs').clear();
     this.set('serverConfigData', {});
@@ -65,7 +67,7 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
     var urlParams = [];
     var hdfsSiteTag = data.Clusters.desired_configs['hdfs-site'].tag;
     urlParams.push('(type=hdfs-site&tag=' + hdfsSiteTag + ')');
-    this.set("hdfsSiteTag", {name : "hdfsSiteTag", value : hdfsSiteTag});
+    this.set("hdfsSiteTag", {name: "hdfsSiteTag", value: hdfsSiteTag});
 
     App.ajax.send({
       name: 'admin.get.all_configurations',
@@ -79,7 +81,7 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
   },
 
   onLoadConfigs: function (data) {
-    this.set('serverConfigData',data);
+    this.set('serverConfigData', data);
     this.set('content.nameServiceId', data.items[0].properties['dfs.nameservices']);
     this.tweakServiceConfigs(this.get('moveJNConfig.configs'));
     this.renderServiceConfigs(this.get('moveJNConfig'));
@@ -111,7 +113,7 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
     return localDB;
   },
 
-  tweakServiceConfigs: function(configs) {
+  tweakServiceConfigs: function (configs) {
     var localDB = this._prepareLocalDB();
     var dependencies = this._prepareDependencies();
 
@@ -142,7 +144,7 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
 
     this.get('stepConfigs').pushObject(serviceConfig);
     this.set('selectedService', this.get('stepConfigs').objectAt(0));
-    this.once = true;
+    this.set('once', true);
   },
 
   /**
@@ -156,8 +158,5 @@ App.ManageJournalNodeWizardStep2Controller = Em.Controller.extend({
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable'));
     }, this);
-  },
-
-  isNextDisabled: Em.computed.not('isLoaded')
-
+  }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js
index 1938527..5bcf78a 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step4_controller.js
@@ -25,12 +25,12 @@ App.ManageJournalNodeWizardStep4Controller = App.ManageJournalNodeProgressPageCo
 
   commands: ['stopStandbyNameNode', 'stopServices', 'installJournalNodes', 'deleteJournalNodes', 'startJournalNodes', 'reconfigureHDFS'],
 
-  hdfsSiteTag : "",
+  hdfsSiteTag: "",
 
-  stopStandbyNameNode: function() {
+  stopStandbyNameNode: function () {
     // save who's active and who's standby at this point in time
-    var sbNN = this.get('content.standByNN');
-    this.updateComponent('NAMENODE', sbNN.host_name, 'HDFS',  'INSTALLED');
+    var hostName = this.get('content.standByNN.host_name');
+    this.updateComponent('NAMENODE', hostName, 'HDFS',  'INSTALLED');
   },
 
   installJournalNodes: function () {
@@ -45,7 +45,7 @@ App.ManageJournalNodeWizardStep4Controller = App.ManageJournalNodeProgressPageCo
   deleteJournalNodes: function () {
     var hosts = App.router.get('manageJournalNodeWizardController').getJournalNodesToDelete();
     if (hosts && hosts.length > 0) {
-      hosts.forEach(function(host) {
+      hosts.forEach(function (host) {
         this.deleteComponent('JOURNALNODE', host);
       }, this);
     } else {
@@ -59,15 +59,14 @@ App.ManageJournalNodeWizardStep4Controller = App.ManageJournalNodeProgressPageCo
   },
 
   reconfigureHDFS: function () {
-    var data = this.get('content.serviceConfigProperties');
-    this.updateConfigProperties(data);
+    this.updateConfigProperties(this.get('content.serviceConfigProperties'));
   },
 
   /**
    * Update service configurations
    * @param {Object} data - config object to update
    */
-  updateConfigProperties: function(data) {
+  updateConfigProperties: function (data) {
     var siteNames = ['hdfs-site'];
     var configData = this.reconfigureSites(siteNames, data, Em.I18n.t('admin.manageJournalNode.step4.save.configuration.note').format(App.format.role('NAMENODE', false)));
     App.ajax.send({
@@ -77,7 +76,7 @@ App.ManageJournalNodeWizardStep4Controller = App.ManageJournalNodeProgressPageCo
         desired_config: configData
       },
       success: 'installHDFSClients',
-      error: 'onTaskError',
+      error: 'onTaskError'
     });
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step6_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step6_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step6_controller.js
index 262fe93..288d1c6 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step6_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step6_controller.js
@@ -31,7 +31,7 @@ App.ManageJournalNodeWizardStep6Controller = App.ManageJournalNodeProgressPageCo
   },
 
   startActiveNameNode: function () {
-    var activeNN = this.get('content.activeNN');
-    this.updateComponent('NAMENODE', activeNN.host_name, "HDFS", "Start");
+    var hostName = this.get('content.activeNN.host_name');
+    this.updateComponent('NAMENODE', hostName, "HDFS", "Start");
   }
 });
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step7_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step7_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step7_controller.js
index add2070..0467ce9 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step7_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/step7_controller.js
@@ -20,7 +20,7 @@ var App = require('app');
 
 App.ManageJournalNodeWizardStep7Controller = Em.Controller.extend({
 
-  name:"manageJournalNodeWizardStep7Controller",
+  name: "manageJournalNodeWizardStep7Controller",
 
   done: function () {
     App.router.send("next");

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/app/controllers/main/admin/highAvailability/journalNode/wizard_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/wizard_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/wizard_controller.js
index fe9a15b..77472bc 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/journalNode/wizard_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/journalNode/wizard_controller.js
@@ -39,9 +39,9 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
     masterComponentHosts: null,
     serviceConfigProperties: [],
     serviceName: 'MISC',
-    hdfsUser:"hdfs",
+    hdfsUser: "hdfs",
     nameServiceId: '',
-    failedTask : null,
+    failedTask: null,
     requestIds: null
   }),
 
@@ -59,7 +59,7 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
    * return new object extended from clusterStatusTemplate
    * @return Object
    */
-  getCluster: function(){
+  getCluster: function () {
     return jQuery.extend({}, this.get('clusterStatusTemplate'), {name: App.router.getClusterName()});
   },
 
@@ -98,7 +98,7 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
         }
       }
     ],
-    2 : [
+    2: [
       {
         type: 'sync',
         callback: function () {
@@ -123,7 +123,10 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
     var result = [];
     var masterComponentHosts = this.get('content.masterComponentHosts');
     if (masterComponentHosts) {
-      result = masterComponentHosts.filterProperty('component', 'JOURNALNODE').filterProperty('isInstalled', false).mapProperty('hostName');
+      result = masterComponentHosts
+        .filterProperty('component', 'JOURNALNODE')
+        .filterProperty('isInstalled', false)
+        .mapProperty('hostName');
     }
     return result;
   },
@@ -134,22 +137,22 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
     if (masterComponentHosts) {
       var currentJNs = masterComponentHosts.filterProperty('component', 'JOURNALNODE');
       var existingHosts = App.HostComponent.find().filterProperty('componentName', 'JOURNALNODE').mapProperty('hostName');
-      result = existingHosts.filter(function(host) {
-        return currentJNs.filterProperty('hostName', host).length == 0;
+      result = existingHosts.filter(function (host) {
+        return currentJNs.filterProperty('hostName', host).length === 0;
       });
     }
     return result;
   },
 
   isDeleteOnly: function () {
-    return this.get('currentStep') > 1 && this.getJournalNodesToAdd().length == 0 && this.getJournalNodesToDelete().length > 0;
+    return this.get('currentStep') > 1 && this.getJournalNodesToAdd().length === 0 && this.getJournalNodesToDelete().length > 0;
   }.property('content.masterComponentHosts', 'App.router.clusterController.isHostsLoaded', 'currentStep'),
 
   /**
    * Save config properties
    * @param stepController ManageJournalNodeWizardStep3Controller
    */
-  saveServiceConfigProperties: function(stepController) {
+  saveServiceConfigProperties: function (stepController) {
     var serviceConfigProperties = [];
     var data = stepController.get('serverConfigData');
 
@@ -168,12 +171,11 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
    * Load serviceConfigProperties to model
    */
   loadServiceConfigProperties: function () {
-    var serviceConfigProperties = this.getDBProperty('serviceConfigProperties');
-    this.set('content.serviceConfigProperties', serviceConfigProperties);
+    this.set('content.serviceConfigProperties', this.getDBProperty('serviceConfigProperties'));
   },
 
 
-  saveNNs: function(activeNN, standByNN) {
+  saveNNs: function () {
     var activeNN = App.HostComponent.find().findProperty('displayNameAdvanced', 'Active NameNode');
     var standByNN = App.HostComponent.find().findProperty('displayNameAdvanced', 'Standby NameNode');
     this.set('content.activeNN', activeNN);
@@ -182,7 +184,7 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
     this.setDBProperty('standByNN', standByNN);
   },
 
-  loadNNs: function() {
+  loadNNs: function () {
     var activeNN = this.getDBProperty('activeNN');
     var standByNN = this.getDBProperty('standByNN');
     this.set('content.activeNN', activeNN);
@@ -190,23 +192,23 @@ App.ManageJournalNodeWizardController = App.WizardController.extend({
   },
 
 
-  saveConfigTag: function(tag){
+  saveConfigTag: function (tag) {
     App.db.setManageJournalNodeWizardConfigTag(tag);
-    this.set('content.'+[tag.name], tag.value);
+    this.set('content.' + tag.name, tag.value);
   },
-  
-  
-  loadConfigTag: function(tag){
+
+
+  loadConfigTag: function (tag) {
     var tagVal = App.db.getManageJournalNodeWizardConfigTag(tag);
-    this.set('content.'+tag, tagVal);
+    this.set('content.' + tag, tagVal);
   },
 
-  saveNameServiceId: function(nameServiceId){
+  saveNameServiceId: function (nameServiceId) {
     this.setDBProperty('nameServiceId', nameServiceId);
     this.set('content.nameServiceId', nameServiceId);
   },
 
-  loadNameServiceId: function(){
+  loadNameServiceId: function () {
     var nameServiceId = this.getDBProperty('nameServiceId');
     this.set('content.nameServiceId', nameServiceId);
   },

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/global/background_operations_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/global/background_operations_test.js b/ambari-web/test/controllers/global/background_operations_test.js
index 5517b1e..8d982bb 100644
--- a/ambari-web/test/controllers/global/background_operations_test.js
+++ b/ambari-web/test/controllers/global/background_operations_test.js
@@ -730,15 +730,6 @@ describe('App.BackgroundOperationsController', function () {
       expect(controller.isInitLoading()).to.be.false;
     });
 
-    it("should return false when no request found", function() {
-      controller.set('levelInfo', Em.Object.create({
-        name: 'HOSTS_LIST',
-        requestId: 1
-      }));
-      controller.set('services', []);
-      expect(controller.isInitLoading()).to.be.false;
-    });
-
     it("should return false when request has hosts", function() {
       controller.set('levelInfo', Em.Object.create({
         name: 'HOSTS_LIST',

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/progress_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/progress_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/progress_controller_test.js
new file mode 100644
index 0000000..868f832
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/progress_controller_test.js
@@ -0,0 +1,67 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/progress_controller');
+
+
+describe('App.ManageJournalNodeProgressPageController', function () {
+  var controller;
+
+  beforeEach(function() {
+    controller = App.ManageJournalNodeProgressPageController.create();
+  });
+
+  describe('#reconfigureSites', function() {
+
+    beforeEach(function() {
+      sinon.stub(App, 'dateTime').returns(1);
+    });
+
+    afterEach(function() {
+      App.dateTime.restore();
+    });
+
+    it('should return array of sites', function() {
+      var siteNames = ['site1', 'site2'];
+      var data = {items: [
+        {
+          type: 'site1',
+          properties: {},
+          properties_attributes: {}
+        }
+      ]};
+      expect(controller.reconfigureSites(siteNames, data, 'note')).to.be.eql([
+        {
+          "properties": {},
+          "properties_attributes": {},
+          "service_config_version_note": "note",
+          "tag": "version1",
+          "type": "site1"
+        },
+        {
+          "properties": undefined,
+          "service_config_version_note": "note",
+          "tag": "version1",
+          "type": "site2"
+        }
+      ]);
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step1_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step1_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step1_controller_test.js
new file mode 100644
index 0000000..17f5ed2
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step1_controller_test.js
@@ -0,0 +1,245 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step1_controller');
+
+
+describe('App.ManageJournalNodeWizardStep1Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep1Controller.create({
+      getMaxNumberOfMasters: function () {
+        return 3;
+      },
+      updateComponent: Em.K,
+      addableComponents: ['C1']
+    });
+  });
+
+  describe('#renderComponents', function () {
+
+    beforeEach(function () {
+      sinon.stub(controller, 'updateJournalNodeInfo');
+      sinon.stub(controller, 'showHideJournalNodesAddRemoveControl');
+    });
+
+    afterEach(function () {
+      controller.updateJournalNodeInfo.restore();
+      controller.showHideJournalNodesAddRemoveControl.restore();
+    });
+
+    it('updateJournalNodeInfo should be called', function () {
+      controller.renderComponents([]);
+      expect(controller.updateJournalNodeInfo.calledOnce).to.be.true;
+    });
+
+    it('showHideJournalNodesAddRemoveControl should be called', function () {
+      controller.renderComponents([]);
+      expect(controller.showHideJournalNodesAddRemoveControl.calledOnce).to.be.true;
+    });
+
+    it('nextButtonCheckTrigger should be false', function () {
+      controller.set('nextButtonCheckTrigger', true);
+      controller.renderComponents([]);
+      expect(controller.get('nextButtonCheckTrigger')).to.be.false;
+    });
+  });
+
+  describe('#generateJournalNodeComponents', function () {
+
+    beforeEach(function () {
+      sinon.stub(App.HostComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          service: {
+            serviceName: 'S1'
+          },
+          hostName: 'host1'
+        })
+      ]);
+      sinon.stub(controller, 'createComponentInstallationObject', function (obj) {
+        return obj;
+      });
+    });
+
+    afterEach(function () {
+      App.HostComponent.find.restore();
+      controller.createComponentInstallationObject.restore();
+    });
+
+    it('should return array with JOURNALNODE components', function () {
+      expect(controller.generateJournalNodeComponents()).to.be.eql([
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          serviceName: 'S1',
+          isInstalled: true
+        })
+      ]);
+    });
+  });
+
+  describe('#showHideJournalNodesAddRemoveControl', function () {
+
+    it('showAddControl should be false and showRemoveControl should be false for first journalnode', function () {
+      var journalNodes = [
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'})
+      ];
+      controller.set('selectedServicesMasters', journalNodes);
+      controller.showHideJournalNodesAddRemoveControl();
+      expect(journalNodes[0].get('showAddControl')).to.be.false;
+      expect(journalNodes[0].get('showRemoveControl')).to.be.false;
+    });
+
+    it('showAddControl should be false and showRemoveControl should be true for first journalnode', function () {
+      var journalNodes = [
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'})
+      ];
+      controller.set('selectedServicesMasters', journalNodes);
+      controller.showHideJournalNodesAddRemoveControl();
+      expect(journalNodes[0].get('showAddControl')).to.be.false;
+      expect(journalNodes[0].get('showRemoveControl')).to.be.true;
+    });
+
+    it('showAddControl should be true and showRemoveControl should be false for second journalnode', function () {
+      var journalNodes = [
+        Em.Object.create({component_name: 'JOURNALNODE'}),
+        Em.Object.create({component_name: 'JOURNALNODE'})
+      ];
+      controller.set('selectedServicesMasters', journalNodes);
+      controller.showHideJournalNodesAddRemoveControl();
+      expect(journalNodes[1].get('showAddControl')).to.be.true;
+      expect(journalNodes[1].get('showRemoveControl')).to.be.false;
+    });
+  });
+
+  describe('#updateJournalNodeInfo', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          hostName: 'host1'
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.HostComponent.find.restore();
+    });
+
+    it('isInstalled and showCurrentPrefix should be true for installed hosts', function() {
+      var journalNodes = [
+        Em.Object.create({
+          component_name: 'JOURNALNODE',
+          selectedHost: 'host1'
+        }),
+        Em.Object.create({
+          component_name: 'JOURNALNODE',
+          selectedHost: 'host2'
+        })
+      ];
+      controller.set('selectedServicesMasters', journalNodes);
+      controller.updateJournalNodeInfo();
+      expect(journalNodes[0]).to.be.eql(Em.Object.create({
+        "component_name": "JOURNALNODE",
+        "isInstalled": true,
+        "selectedHost": "host1",
+        "showCurrentPrefix": true
+      }));
+      expect(journalNodes[1]).to.be.eql(Em.Object.create({
+        component_name: 'JOURNALNODE',
+        selectedHost: 'host2'
+      }));
+    });
+  });
+
+  describe('#loadStepCallback', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'renderComponents');
+      sinon.stub(controller, 'updateComponent');
+    });
+
+    afterEach(function() {
+      controller.renderComponents.restore();
+      controller.updateComponent.restore();
+    });
+
+    it('renderComponents should be called', function() {
+      controller.loadStepCallback([], controller);
+      expect(controller.renderComponents.calledWith([])).to.be.true;
+    });
+
+    it('updateComponent should be called', function() {
+      controller.set('addableComponents', ['C1']);
+      controller.loadStepCallback([], controller);
+      expect(controller.updateComponent.calledWith('C1')).to.be.true;
+    });
+
+    it('isLoaded should be true', function() {
+      controller.loadStepCallback([], controller);
+      expect(controller.get('isLoaded')).to.be.true;
+    });
+  });
+
+  describe('#nextButtonDisabled', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          hostName: 'host1'
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.HostComponent.find.restore();
+    });
+
+    it('should return true when hosts identical', function() {
+      controller.set('selectedServicesMasters', [
+        Em.Object.create({
+          component_name: 'JOURNALNODE',
+          selectedHost: 'host1'
+        })
+      ]);
+      controller.propertyDidChange('nextButtonDisabled');
+      expect(controller.get('nextButtonDisabled')).to.be.true;
+    });
+
+    it('should return false when hosts have been changed', function() {
+      controller.set('selectedServicesMasters', [
+        Em.Object.create({
+          component_name: 'JOURNALNODE',
+          selectedHost: 'host2'
+        })
+      ]);
+      controller.propertyDidChange('nextButtonDisabled');
+      expect(controller.get('nextButtonDisabled')).to.be.false;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step2_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step2_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step2_controller_test.js
new file mode 100644
index 0000000..17230bb
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step2_controller_test.js
@@ -0,0 +1,281 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step1_controller');
+var testHelpers = require('test/helpers');
+
+describe('App.ManageJournalNodeWizardStep2Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep2Controller.create({
+      content: Em.Object.create()
+    });
+  });
+
+  describe('#clearStep', function() {
+
+    it('stepConfigs should be empty', function() {
+      controller.clearStep();
+      expect(controller.get('stepConfigs')).to.be.empty;
+    });
+
+    it('serverConfigData should be empty', function() {
+      controller.clearStep();
+      expect(controller.get('serverConfigData')).to.be.empty;
+    });
+  });
+
+  describe('#loadStep', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'clearStep');
+      sinon.stub(controller, 'loadConfigsTags');
+    });
+
+    afterEach(function() {
+      controller.clearStep.restore();
+      controller.loadConfigsTags.restore();
+    });
+
+    it('loadConfigsTags should be called', function() {
+      controller.loadStep();
+      expect(controller.loadConfigsTags.calledOnce).to.be.true;
+    });
+
+    it('clearStep should be called', function() {
+      controller.loadStep();
+      expect(controller.clearStep.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#loadConfigsTags', function() {
+
+    it('App.ajax.send should be called', function() {
+      controller.loadConfigsTags();
+      var args = testHelpers.findAjaxRequest('name', 'config.tags');
+      expect(args[0]).to.exists;
+    });
+  });
+
+  describe('#onLoadConfigsTags', function() {
+    var data = {
+      Clusters: {
+        desired_configs: {
+          'hdfs-site': {
+            tag: 'tag1'
+          }
+        }
+      }
+    };
+
+    it('App.ajax.send should be called', function() {
+      controller.onLoadConfigsTags(data);
+      var args = testHelpers.findAjaxRequest('name', 'admin.get.all_configurations');
+      expect(args[0]).to.be.eql({
+        name: 'admin.get.all_configurations',
+        sender: controller,
+        data: {
+          urlParams: '(type=hdfs-site&tag=tag1)'
+        },
+        success: 'onLoadConfigs',
+        error: 'onTaskError'
+      });
+    });
+
+    it('hdfsSiteTag should be set', function() {
+      controller.onLoadConfigsTags(data);
+      expect(controller.get('hdfsSiteTag')).to.be.eql({name: "hdfsSiteTag", value: 'tag1'});
+    });
+  });
+
+  describe('#onLoadConfigs', function() {
+    var data = {
+      items: [
+        {
+          properties: {
+            'dfs.nameservices': 'id'
+          }
+        }
+      ]
+    };
+
+    beforeEach(function() {
+      sinon.stub(controller, 'tweakServiceConfigs');
+      sinon.stub(controller, 'renderServiceConfigs');
+    });
+
+    afterEach(function() {
+      controller.tweakServiceConfigs.restore();
+      controller.renderServiceConfigs.restore();
+    });
+
+    it('renderServiceConfigs should be called', function() {
+      controller.onLoadConfigs(data);
+      expect(controller.renderServiceConfigs.calledOnce).to.be.true;
+    });
+
+    it('tweakServiceConfigs should be called', function() {
+      controller.onLoadConfigs(data);
+      expect(controller.tweakServiceConfigs.calledOnce).to.be.true;
+    });
+
+    it('serviceConfigData should be an object', function() {
+      controller.onLoadConfigs(data);
+      expect(controller.get('serverConfigData')).to.be.eql(data);
+    });
+
+    it('nameServiceId should be "id"', function() {
+      controller.onLoadConfigs(data);
+      expect(controller.get('content.nameServiceId')).to.be.equal('id');
+    });
+
+    it('isLoaded should be true', function() {
+      controller.onLoadConfigs(data);
+      expect(controller.get('isLoaded')).to.be.true;
+    });
+  });
+
+  describe('#_prepareDependencies', function() {
+
+    it('should return configs object', function() {
+      controller.set('serverConfigData', {items: []});
+      controller.set('content.nameServiceId', 'id1');
+      expect(controller._prepareDependencies()).to.be.eql({
+        namespaceId: 'id1',
+        serverConfigs: []
+      });
+    });
+  });
+
+  describe('#_prepareLocalDB', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.Service, 'find').returns([
+        Em.Object.create({
+          serviceName: 'S1'
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.Service.find.restore();
+    });
+
+    it('should return localDB object', function() {
+      controller.set('content', Em.Object.create({
+        masterComponentHosts: [],
+        slaveComponentHosts: [],
+        hosts: []
+      }));
+      expect(controller._prepareLocalDB()).to.be.eql({
+        masterComponentHosts: [],
+        slaveComponentHosts: [],
+        hosts: [],
+        installedServices: ['S1']
+      });
+    });
+  });
+
+  describe('#tweakServiceConfigs', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, '_prepareLocalDB').returns({});
+      sinon.stub(controller, '_prepareDependencies').returns({});
+      sinon.stub(App.NnHaConfigInitializer, 'initialValue');
+    });
+
+    afterEach(function() {
+      controller._prepareLocalDB.restore();
+      controller._prepareDependencies.restore();
+      App.NnHaConfigInitializer.initialValue.restore();
+    });
+
+    it('App.NnHaConfigInitializer.initialValue should be called', function() {
+      controller.tweakServiceConfigs([{}]);
+      expect(App.NnHaConfigInitializer.initialValue.calledOnce).to.be.true;
+    });
+
+    it('should return array of configs', function() {
+      expect(controller.tweakServiceConfigs([{}])).to.be.eql([{
+        isOverridable: false
+      }]);
+    });
+  });
+
+  describe('#renderServiceConfigs', function() {
+    var _serviceConfig = {
+      configCategories: [
+        {
+          name: 'S1'
+        }
+      ]
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.Service, 'find').returns([
+        {
+          serviceName: 'S1'
+        }
+      ]);
+      sinon.stub(controller, 'loadComponentConfigs');
+      controller.set('stepConfigs', []);
+      controller.renderServiceConfigs(_serviceConfig);
+    });
+
+    afterEach(function() {
+      controller.loadComponentConfigs.restore();
+      App.Service.find.restore();
+    });
+
+    it('stepConfigs should not be empty', function() {
+      expect(controller.get('stepConfigs')).to.not.be.empty;
+    });
+
+    it('selectedService should be object', function() {
+      expect(controller.get('selectedService')).to.be.an.object;
+    });
+
+    it('once should be true', function() {
+      expect(controller.get('once')).to.be.true;
+    });
+
+    it('loadComponentConfigs should be called', function() {
+      expect(controller.loadComponentConfigs.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#loadComponentConfigs', function() {
+    var componentConfig = {
+      configs: []
+    };
+
+    it('configs should not be empty', function() {
+      controller.loadComponentConfigs({configs: [{}]}, componentConfig);
+      expect(componentConfig.configs).to.not.be.empty;
+    });
+
+    it('isEditable should be true', function() {
+      controller.loadComponentConfigs({configs: [{isReconfigurable: true}]}, componentConfig);
+      expect(componentConfig.configs[0].get('isEditable')).to.be.true;
+    });
+  });
+});
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step4_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step4_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step4_controller_test.js
new file mode 100644
index 0000000..ba92b54
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step4_controller_test.js
@@ -0,0 +1,220 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step1_controller');
+var testHelpers = require('test/helpers');
+
+describe('App.ManageJournalNodeWizardStep4Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep4Controller.create({
+      content: Em.Object.create()
+    });
+  });
+
+  describe('#stopStandbyNameNode', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'updateComponent');
+    });
+
+    afterEach(function() {
+      controller.updateComponent.restore();
+    });
+
+    it('updateComponent should be called', function() {
+      controller.set('content.standByNN', {
+        host_name: 'host1'
+      });
+      controller.stopStandbyNameNode();
+      expect(controller.updateComponent.calledWith('NAMENODE', 'host1', 'HDFS',  'INSTALLED')).to.be.true;
+    });
+  });
+
+  describe('#installJournalNodes', function() {
+    var wizardController = {
+      getJournalNodesToAdd: Em.K
+    };
+
+    beforeEach(function() {
+      this.mock = sinon.stub(wizardController, 'getJournalNodesToAdd');
+      sinon.stub(App.router, 'get').returns(wizardController);
+      sinon.stub(controller, 'createInstallComponentTask');
+      sinon.stub(controller, 'onTaskCompleted');
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+      this.mock.restore();
+      controller.createInstallComponentTask.restore();
+      controller.onTaskCompleted.restore();
+    });
+
+    it('onTaskCompleted should be called when no journalnodes to add', function() {
+      this.mock.returns([]);
+      controller.installJournalNodes();
+      expect(controller.onTaskCompleted.calledOnce).to.be.true;
+      expect(controller.createInstallComponentTask.called).to.be.false;
+    });
+
+    it('createInstallComponentTask should be called when there are journalnodes to add', function() {
+      this.mock.returns(['host1']);
+      controller.installJournalNodes();
+      expect(controller.onTaskCompleted.called).to.be.false;
+      expect(controller.createInstallComponentTask.calledWith('JOURNALNODE', ['host1'], "HDFS")).to.be.true;
+    });
+  });
+
+  describe('#deleteJournalNodes', function() {
+    var wizardController = {
+      getJournalNodesToDelete: Em.K
+    };
+
+    beforeEach(function() {
+      this.mock = sinon.stub(wizardController, 'getJournalNodesToDelete');
+      sinon.stub(App.router, 'get').returns(wizardController);
+      sinon.stub(controller, 'deleteComponent');
+      sinon.stub(controller, 'onTaskCompleted');
+    });
+
+    afterEach(function() {
+      App.router.get.restore();
+      this.mock.restore();
+      controller.deleteComponent.restore();
+      controller.onTaskCompleted.restore();
+    });
+
+    it('onTaskCompleted should be called when no journalnodes to delete', function() {
+      this.mock.returns([]);
+      controller.deleteJournalNodes();
+      expect(controller.onTaskCompleted.calledOnce).to.be.true;
+      expect(controller.deleteComponent.called).to.be.false;
+    });
+
+    it('deleteComponent should be called when there are journalnodes to delete', function() {
+      this.mock.returns(['host1']);
+      controller.deleteJournalNodes();
+      expect(controller.onTaskCompleted.called).to.be.false;
+      expect(controller.deleteComponent.calledWith('JOURNALNODE', 'host1')).to.be.true;
+    });
+  });
+
+  describe('#startJournalNodes', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'updateComponent');
+    });
+
+    afterEach(function() {
+      controller.updateComponent.restore();
+    });
+
+    it('updateComponent should be called', function() {
+      controller.set('content.masterComponentHosts', [
+        {
+          component: 'JOURNALNODE',
+          hostName: 'host1'
+        }
+      ]);
+      controller.startJournalNodes();
+      expect(controller.updateComponent.calledWith('JOURNALNODE', ['host1'], "HDFS", "Start")).to.be.true;
+    });
+  });
+
+  describe('#reconfigureHDFS', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'updateConfigProperties');
+    });
+
+    afterEach(function() {
+      controller.updateConfigProperties.restore();
+    });
+
+    it('updateConfigProperties should be called', function() {
+      controller.set('content.serviceConfigProperties', [
+        {}
+      ]);
+      controller.reconfigureHDFS();
+      expect(controller.updateConfigProperties.calledWith([{}])).to.be.true;
+    });
+  });
+
+  describe('#updateConfigProperties', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'reconfigureSites').returns({});
+    });
+
+    afterEach(function() {
+      controller.reconfigureSites.restore();
+    });
+
+    it('App.ajax.send should be called', function() {
+      controller.updateConfigProperties();
+      var args = testHelpers.findAjaxRequest('name', 'common.service.configurations');
+      expect(args[0]).to.be.eql({
+        name: 'common.service.configurations',
+        sender: controller,
+        data: {
+          desired_config: {}
+        },
+        success: 'installHDFSClients',
+        error: 'onTaskError'
+      });
+    });
+  });
+
+  describe('#installHDFSClients', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'createInstallComponentTask');
+      sinon.stub(App.clusterStatus, 'setClusterStatus');
+      controller.set('content.masterComponentHosts', [
+        {
+          component: 'NAMENODE',
+          hostName: 'host1'
+        },
+        {
+          component: 'JOURNALNODE',
+          hostName: 'host2'
+        }
+      ]);
+    });
+
+    afterEach(function() {
+      controller.createInstallComponentTask.restore();
+      App.clusterStatus.setClusterStatus.restore();
+    });
+
+    it('App.clusterStatus.setClusterStatus should be called', function() {
+      controller.installHDFSClients();
+      expect(App.clusterStatus.setClusterStatus.calledOnce).to.be.true;
+    });
+
+    it('createInstallComponentTask should be called', function() {
+      controller.installHDFSClients();
+      expect(controller.createInstallComponentTask.calledWith('HDFS_CLIENT', ['host1', 'host2'], 'HDFS')).to.be.true;
+    });
+  });
+
+});
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step6_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step6_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step6_controller_test.js
new file mode 100644
index 0000000..8351628
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step6_controller_test.js
@@ -0,0 +1,74 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step1_controller');
+
+describe('App.ManageJournalNodeWizardStep6Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep6Controller.create({
+      content: Em.Object.create()
+    });
+  });
+
+  describe('#startZooKeeperServers', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'updateComponent');
+    });
+
+    afterEach(function() {
+      controller.updateComponent.restore();
+    });
+
+    it('updateComponent should be called', function() {
+      controller.set('content.masterComponentHosts', [
+        {
+          component: 'ZOOKEEPER_SERVER',
+          hostName: 'host1'
+        }
+      ]);
+      controller.startZooKeeperServers();
+      expect(controller.updateComponent.calledWith('ZOOKEEPER_SERVER', ['host1'], "ZOOKEEPER", "Start")).to.be.true;
+    });
+  });
+
+  describe('#startActiveNameNode', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'updateComponent');
+    });
+
+    afterEach(function() {
+      controller.updateComponent.restore();
+    });
+
+    it('updateComponent should be called', function() {
+      controller.set('content.activeNN', {
+        host_name: 'host1'
+      });
+      controller.startActiveNameNode();
+      expect(controller.updateComponent.calledWith('NAMENODE', 'host1', "HDFS", "Start")).to.be.true;
+    });
+  });
+
+});
+

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step7_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step7_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step7_controller_test.js
new file mode 100644
index 0000000..386d64e
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step7_controller_test.js
@@ -0,0 +1,46 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step7_controller');
+
+
+describe('App.ManageJournalNodeWizardStep7Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep7Controller.create();
+  });
+
+  describe('#done', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'send');
+    });
+
+    afterEach(function() {
+      App.router.send.restore();
+    });
+
+    it('App.router.send should be called', function() {
+      controller.done();
+      expect(App.router.send.calledWith('next')).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step8_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step8_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step8_controller_test.js
new file mode 100644
index 0000000..9642d4a
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/step8_controller_test.js
@@ -0,0 +1,61 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/step8_controller');
+
+describe('App.ManageJournalNodeWizardStep8Controller', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardStep8Controller.create();
+  });
+
+  describe('#stopHDFS', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'stopServices');
+    });
+
+    afterEach(function() {
+      controller.stopServices.restore();
+    });
+
+    it('stopServices should be called', function() {
+      controller.stopHDFS();
+      expect(controller.stopServices.calledWith(['HDFS'], true)).to.be.true;
+    });
+  });
+
+  describe('#startAllServices', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'startServices');
+    });
+
+    afterEach(function() {
+      controller.startServices.restore();
+    });
+
+    it('stopServices should be called', function() {
+      controller.startAllServices();
+      expect(controller.startServices.calledWith(false)).to.be.true;
+    });
+  });
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/8f16d643/ambari-web/test/controllers/main/admin/highAvailability/journalNode/wizard_controller_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/controllers/main/admin/highAvailability/journalNode/wizard_controller_test.js b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/wizard_controller_test.js
new file mode 100644
index 0000000..fe8778f
--- /dev/null
+++ b/ambari-web/test/controllers/main/admin/highAvailability/journalNode/wizard_controller_test.js
@@ -0,0 +1,404 @@
+/**
+ * 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');
+require('controllers/main/admin/highAvailability/journalNode/wizard_controller');
+
+
+describe('App.ManageJournalNodeWizardController', function () {
+  var controller;
+
+  beforeEach(function () {
+    controller = App.ManageJournalNodeWizardController.create({
+      content: Em.Object.create(),
+      currentStep: 0
+    });
+  });
+
+  describe('#setCurrentStep', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.clusterStatus, 'setClusterStatus');
+    });
+
+    afterEach(function() {
+      App.clusterStatus.setClusterStatus.restore();
+    });
+
+    it('setCurrentStep should be called', function() {
+      controller.setCurrentStep(1, true);
+      expect(App.clusterStatus.setClusterStatus.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#getCluster', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'getClusterName').returns('c1');
+    });
+    afterEach(function() {
+      App.router.getClusterName.restore();
+    });
+
+    it('should return cluster object', function() {
+      controller.set('clusterStatusTemplate', {});
+      expect(controller.getCluster()).to.be.eql({
+        name: 'c1'
+      });
+    });
+  });
+
+  describe('#getJournalNodesToAdd', function() {
+
+    it('should return journalnodes hosts array', function() {
+      controller.set('content.masterComponentHosts', [
+        Em.Object.create({
+          component: 'JOURNALNODE',
+          isInstalled: false,
+          hostName: 'host1'
+        })
+      ]);
+      expect(controller.getJournalNodesToAdd()).to.be.eql(['host1']);
+    });
+
+    it('should return empty array', function() {
+      controller.set('content.masterComponentHosts', null);
+      expect(controller.getJournalNodesToAdd()).to.be.empty;
+    });
+  });
+
+  describe('#getJournalNodesToDelete', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponent, 'find').returns([
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          hostName: 'host2'
+        }),
+        Em.Object.create({
+          componentName: 'JOURNALNODE',
+          hostName: 'host1'
+        })
+      ]);
+    });
+
+    afterEach(function() {
+      App.HostComponent.find.restore();
+    });
+
+    it('should return empty array', function() {
+      controller.set('content.masterComponentHosts', null);
+      expect(controller.getJournalNodesToDelete()).to.be.empty;
+    });
+
+    it('should return journalnodes hosts array', function() {
+      controller.set('content.masterComponentHosts', [
+        Em.Object.create({
+          component: 'JOURNALNODE',
+          isInstalled: false,
+          hostName: 'host1'
+        })
+      ]);
+      expect(controller.getJournalNodesToDelete()).to.be.eql([
+        'host2'
+      ]);
+    });
+  });
+
+  describe('#isDeleteOnly', function() {
+
+    beforeEach(function() {
+      this.mockAdd = sinon.stub(controller, 'getJournalNodesToAdd').returns([]);
+      this.mockDelete = sinon.stub(controller, 'getJournalNodesToDelete').returns([]);
+    });
+    afterEach(function() {
+      this.mockDelete.restore();
+      this.mockAdd.restore();
+    });
+
+    it('should return false when currentStep less than 2nd', function() {
+      controller.set('currentStep', 1);
+      expect(controller.get('isDeleteOnly')).to.be.false;
+    });
+
+    it('should return false when there are nodes to add', function() {
+      this.mockAdd.returns(['host1']);
+      controller.set('currentStep', 2);
+      controller.propertyDidChange('isDeleteOnly');
+      expect(controller.get('isDeleteOnly')).to.be.false;
+    });
+
+    it('should return false when there are no nodes to delete', function() {
+      this.mockAdd.returns([]);
+      this.mockDelete.returns([]);
+      controller.set('currentStep', 2);
+      controller.propertyDidChange('isDeleteOnly');
+      expect(controller.get('isDeleteOnly')).to.be.false;
+    });
+
+    it('should return true when there are nodes to delete', function() {
+      this.mockAdd.returns([]);
+      this.mockDelete.returns(['host1']);
+      controller.set('currentStep', 2);
+      controller.propertyDidChange('isDeleteOnly');
+      expect(controller.get('isDeleteOnly')).to.be.true;
+    });
+  });
+
+  describe('#saveServiceConfigProperties', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'setDBProperty');
+    });
+
+    afterEach(function() {
+      controller.setDBProperty.restore();
+    });
+
+    it('serviceConfigProperties should be set', function() {
+      var stepCtrl = Em.Object.create({
+        serverConfigData: {
+          items: [{
+            type: 'file1',
+            properties: {
+              c1: 'val1'
+            }
+          }]
+        },
+        stepConfigs: [
+          Em.Object.create({
+            configs: [
+              Em.Object.create({
+                name: 'c1',
+                value: 'val1',
+                filename: 'file1'
+              })
+            ]
+          })
+        ]
+      });
+      controller.saveServiceConfigProperties(stepCtrl);
+      expect(JSON.stringify(controller.get('content.serviceConfigProperties'))).to.be.eql(JSON.stringify({
+        "items": [
+          {
+            "type": "file1",
+            "properties": {
+              "c1": "val1"
+            }
+          }
+        ]
+      }));
+      expect(controller.setDBProperty.calledWith('serviceConfigProperties', {
+        "items": [
+          {
+            "type": "file1",
+            "properties": {
+              "c1": "val1"
+            }
+          }
+        ]
+      })).to.be.true;
+    });
+  });
+
+  describe('#loadServiceConfigProperties', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'getDBProperty').returns({});
+    });
+
+    it('serviceConfigProperties should be set', function() {
+      controller.loadServiceConfigProperties();
+      expect(controller.get('content.serviceConfigProperties')).to.be.eql({});
+    });
+  });
+
+  describe('#saveNNs', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.HostComponent, 'find').returns([
+        {
+          displayNameAdvanced: 'Active NameNode'
+        },
+        {
+          displayNameAdvanced: 'Standby NameNode'
+        }
+      ]);
+      sinon.stub(controller, 'setDBProperty');
+    });
+
+    afterEach(function() {
+      controller.setDBProperty.restore();
+      App.HostComponent.find.restore();
+    });
+
+    it('NameNodes should be set', function() {
+      controller.saveNNs();
+      expect(controller.get('content.activeNN')).to.be.eql({
+        displayNameAdvanced: 'Active NameNode'
+      });
+      expect(controller.get('content.standByNN')).to.be.eql({
+        displayNameAdvanced: 'Standby NameNode'
+      });
+    });
+
+    it('setDBProperty should be called', function() {
+      controller.saveNNs();
+      expect(controller.setDBProperty.calledWith('activeNN', {
+        displayNameAdvanced: 'Active NameNode'
+      })).to.be.true;
+      expect(controller.setDBProperty.calledWith('standByNN', {
+        displayNameAdvanced: 'Standby NameNode'
+      })).to.be.true;
+    });
+  });
+
+  describe('#loadNNs', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'getDBProperty').returns({});
+    });
+
+    it('NameNodes should be set', function() {
+      controller.loadNNs();
+      expect(controller.get('content.activeNN')).to.be.eql({});
+      expect(controller.get('content.standByNN')).to.be.eql({});
+    });
+  });
+
+  describe('#loadConfigTag', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.db, 'getManageJournalNodeWizardConfigTag').returns('val1');
+    });
+
+    it('should load config tag', function() {
+      controller.loadConfigTag('tag1');
+      expect(App.db.getManageJournalNodeWizardConfigTag.calledWith('tag1')).to.be.true;
+      expect(controller.get('content.tag1')).to.be.equal('val1');
+    });
+  });
+
+  describe('#saveConfigTag', function() {
+
+    beforeEach(function() {
+      sinon.stub(App.db, 'setManageJournalNodeWizardConfigTag');
+    });
+
+    it('should save config tag', function() {
+      controller.saveConfigTag({name: 'tag1', value: 'val1'});
+      expect(App.db.setManageJournalNodeWizardConfigTag.calledWith({name: 'tag1', value: 'val1'})).to.be.true;
+      expect(controller.get('content.tag1')).to.be.equal('val1');
+    });
+  });
+
+  describe('#saveNameServiceId', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'setDBProperty');
+    });
+
+    it('nameServiceId should be set', function() {
+      controller.saveNameServiceId('id1');
+      expect(controller.setDBProperty.calledWith('nameServiceId', 'id1')).to.be.true;
+      expect(controller.get('content.nameServiceId')).to.be.equal('id1');
+    });
+  });
+
+  describe('#loadNameServiceId', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'getDBProperty').returns('id1');
+    });
+
+    it('nameServiceId should be set', function() {
+      controller.loadNameServiceId();
+      expect(controller.get('content.nameServiceId')).to.be.equal('id1');
+    });
+  });
+
+  describe('#clearAllSteps', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'clearInstallOptions');
+      sinon.stub(controller, 'getCluster').returns({name: 'c1'});
+    });
+
+    it('cluster should be set and clearInstallOptions should be called', function() {
+      controller.clearAllSteps();
+      expect(controller.get('content.cluster')).to.be.eql({name: 'c1'});
+      expect(controller.clearInstallOptions.calledOnce).to.be.true;
+    });
+  });
+
+  describe('#clearTasksData', function() {
+
+    beforeEach(function() {
+      sinon.stub(controller, 'saveTasksStatuses');
+      sinon.stub(controller, 'saveRequestIds');
+      sinon.stub(controller, 'saveTasksRequestIds');
+      controller.clearTasksData();
+    });
+
+    afterEach(function() {
+      controller.saveTasksRequestIds.restore();
+      controller.saveRequestIds.restore();
+      controller.saveTasksStatuses.restore();
+    });
+
+    it('saveTasksStatuses should be called', function() {
+      expect(controller.saveTasksStatuses.calledWith(undefined)).to.be.true;
+    });
+
+    it('saveRequestIds should be called', function() {
+      expect(controller.saveRequestIds.calledWith(undefined)).to.be.true;
+    });
+
+    it('saveTasksRequestIds should be called', function() {
+      expect(controller.saveTasksRequestIds.calledWith(undefined)).to.be.true;
+    });
+  });
+
+  describe('#finish', function() {
+    var mock = {
+      updateAll: Em.K
+    };
+
+    beforeEach(function() {
+      sinon.stub(App.router, 'get').returns(mock);
+      sinon.spy(mock, 'updateAll');
+      sinon.stub(controller, 'resetDbNamespace');
+      controller.finish();
+    });
+
+    afterEach(function() {
+      controller.resetDbNamespace.restore();
+      mock.updateAll.restore();
+      App.router.get.restore();
+    });
+
+    it('resetDbNamespace should be called', function() {
+      expect(controller.resetDbNamespace.calledOnce).to.be.true;
+    });
+
+    it('updateAll should be called', function() {
+      expect(mock.updateAll.calledOnce).to.be.true;
+    });
+  });
+});