You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by yu...@apache.org on 2012/09/26 22:59:34 UTC

svn commit: r1390724 - in /incubator/ambari/branches/AMBARI-666: ./ ambari-web/app/ ambari-web/app/controllers/installer/ ambari-web/app/styles/ ambari-web/app/templates/installer/ ambari-web/app/views/installer/ ambari-web/test/installer/

Author: yusaku
Date: Wed Sep 26 20:59:33 2012
New Revision: 1390724

URL: http://svn.apache.org/viewvc?rev=1390724&view=rev
Log:
AMBARI-768. Implement step 5 of installer wizard (Assign Masters). (Ananya Sen via yusaku)

Added:
    incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers/installer/step5_controller.js
    incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5_view.js
    incubator/ambari/branches/AMBARI-666/ambari-web/test/installer/step5_test.js
Removed:
    incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5.js
Modified:
    incubator/ambari/branches/AMBARI-666/AMBARI-666-CHANGES.txt
    incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers.js
    incubator/ambari/branches/AMBARI-666/ambari-web/app/styles/application.less
    incubator/ambari/branches/AMBARI-666/ambari-web/app/templates/installer/step5.hbs
    incubator/ambari/branches/AMBARI-666/ambari-web/app/views.js
    incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step1_view.js

Modified: incubator/ambari/branches/AMBARI-666/AMBARI-666-CHANGES.txt
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/AMBARI-666-CHANGES.txt?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/AMBARI-666-CHANGES.txt (original)
+++ incubator/ambari/branches/AMBARI-666/AMBARI-666-CHANGES.txt Wed Sep 26 20:59:33 2012
@@ -12,6 +12,9 @@ AMBARI-666 branch (unreleased changes)
 
   NEW FEATURES
 
+  AMBARI-768. Implement step 5 of installer wizard (Assign Masters).
+  (Ananya Sen via yusaku)
+
   AMBARI-767. Add bootstrap script to ssh in parallel and setup agents on a
   list of hosts. (mahadev)
 

Modified: incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers.js?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers.js (original)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers.js Wed Sep 26 20:59:33 2012
@@ -26,6 +26,7 @@ require('controllers/installer/step1_con
 require('controllers/installer/step2_controller');
 require('controllers/installer/step3_controller');
 require('controllers/installer/step4_controller');
+require('controllers/installer/step5_controller');
 require('controllers/installer/step6_controller');
 require('controllers/installer/step7_controller');
 require('controllers/main');

Added: incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers/installer/step5_controller.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers/installer/step5_controller.js?rev=1390724&view=auto
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers/installer/step5_controller.js (added)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/controllers/installer/step5_controller.js Wed Sep 26 20:59:33 2012
@@ -0,0 +1,352 @@
+/**
+ * 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');
+
+//mock data
+
+//input
+App.selectedServices = [ 'HDFS', 'MapReduce', 'Ganglia', 'Nagios', 'HBase', 'Pig', 'Sqoop', 'Oozie', 'Hive', 'ZooKeeper'];
+
+App.hosts = [
+  {
+    host_name: 'host1',
+    cluster_name: "test",
+    total_mem: 7,
+    cpu_count: 2
+  },
+  {
+    host_name: 'host2',
+    cluster_name: "test",
+    total_mem: 4,
+    cpu_count: 2
+  },
+  {
+    host_name: 'host3',
+    cluster_name: "test",
+    total_mem: 8,
+    cpu_count: 2
+  },
+  {
+    host_name: 'host4',
+    cluster_name: "test",
+    total_mem: 8,
+    cpu_count: 2
+  },
+  {
+    host_name: 'host5',
+    cluster_name: "test",
+    total_mem: 8,
+    cpu_count: 2
+  }
+];
+
+App.masterServices = [
+  {
+    component_name: "NameNode",
+    selectedHost: 'host1',
+    availableHosts: [] // filled dynAmically
+  },
+  {
+    component_name: "ZooKeeper",
+    selectedHost: 'host3',
+    availableHosts: [] // filled dynAmically
+  },
+  {
+    component_name: "JobTracker",
+    selectedHost: 'host2',
+    availableHosts: [] // filled dynAmically
+  },
+  {
+    component_name: "HBase Master",
+    selectedHost: 'host3',
+    availableHosts: [] // filled dynAmically
+  }
+];
+
+//mapping format
+//masterHostMapping = [
+//    {
+//      host_name: 'host1',
+//      masterServices: [{component_name:"NamedNode"}, {component_name:"Jobtracker"}]
+//    },
+//    {
+//      host_name: 'host2',
+//      masterServices: [{component_name:"NamedNode"}, {component_name:"Jobtracker"}]
+//    }
+//  ];
+
+//end - mock data
+
+App.InstallerStep5Controller = Em.Controller.extend({
+  //properties
+  name: "installerStep5Controller",
+
+  hosts: [],
+  selectedServices: [],
+  selectedServicesMasters: [],
+
+  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);
+      hostInfo = " ( " + hostObj.get("total_mem") + "GB" + " " + hostObj.get("cpu_count") + "cores )";
+
+      mappingObject = Ember.Object.create({
+        host_name: item,
+        hostInfo: hostInfo,
+        masterServices: self.get("selectedServicesMasters").filterProperty("selectedHost", item)
+      });
+
+      mapping.pushObject(mappingObject);
+    }, this);
+
+    mapping.sort(this.sortHostsByName);
+
+    return mapping;
+
+  }.property("selectedServicesMasters.@each.selectedHost"),
+
+  remainingHosts: function () {
+    return (this.get("hosts.length") - this.get("masterHostMapping.length"));
+  }.property("selectedServicesMasters.@each.selectedHost"),
+
+  hasZookeeper: function () {
+    return this.selectedServices.findProperty("service_name", "ZooKeeper");
+  }.property("selectedServices"),
+
+  //methods
+  getAvailableHosts: function (componentName) {
+    var assignableHosts = [],
+      zookeeperHosts = null;
+
+    if (componentName === "ZooKeeper") {
+      zookeeperHosts = this.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").mapProperty("selectedHost").uniq();
+
+      this.get("hosts").forEach(function (item) {
+        if (!(zookeeperHosts.contains(item.get("host_name")))) {
+          assignableHosts.pushObject(item);
+        }
+      }, this);
+
+      return assignableHosts;
+
+    } else {
+      return this.get("hosts");
+    }
+  },
+
+  assignHostToMaster: function (masterService, selectedHost, zId) {
+    if (selectedHost && masterService) {
+
+      if ((masterService === "ZooKeeper") && zId) {
+        this.get('selectedServicesMasters').findProperty("zId", zId).set("selectedHost", selectedHost);
+        this.rebalanceZookeeperHosts();
+      }
+      else {
+        this.get('selectedServicesMasters').findProperty("component_name", masterService).set("selectedHost", selectedHost);
+      }
+
+    }
+  },
+
+  addZookeepers: function () {
+    /*
+     *Logic: If ZooKeeper service is selected then there can be
+     * minimum 1 ZooKeeper master in total, and
+     * maximum 1 ZooKeeper on every host
+     */
+    var maxNumZooKeepers = this.get("hosts.length"),
+      currentZooKeepers = this.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper"),
+      newZookeeper = null,
+      zookeeperHosts = null,
+      suggestedHost = null,
+      i = 0,
+      lastZoo = null;
+
+    //work only if the Zookeeper service is selected in previous step
+    if (!this.get("selectedServices").mapProperty("service_name").contains("ZooKeeper")) {
+      return false;
+    }
+
+    if (currentZooKeepers.get("length") < maxNumZooKeepers) {
+      currentZooKeepers.set("lastObject.showAddControl", false);
+      if (currentZooKeepers.get("length") > 1) {
+        currentZooKeepers.set("lastObject.showRemoveControl", true);
+      }
+
+      //create a new zookeeper based on an existing one
+      newZookeeper = Ember.Object.create({});
+      lastZoo = currentZooKeepers.get("lastObject");
+      newZookeeper.set("component_name", lastZoo.get("component_name"));
+      newZookeeper.set("selectedHost", lastZoo.get("selectedHost"));
+      newZookeeper.set("availableHosts", this.getAvailableHosts("ZooKeeper"));
+
+      if (currentZooKeepers.get("length") === (maxNumZooKeepers - 1)) {
+        newZookeeper.set("showAddControl", false);
+      } else {
+        newZookeeper.set("showAddControl", true);
+      }
+      newZookeeper.set("showRemoveControl", true);
+
+      //get recommended host for the new Zookeeper server
+      zookeeperHosts = currentZooKeepers.mapProperty("selectedHost").uniq();
+
+      for (i = 0; i < this.get("hosts.length"); i++) {
+        if (!(zookeeperHosts.contains(this.get("hosts")[i].get("host_name")))) {
+          suggestedHost = this.get("hosts")[i].get("host_name");
+          break;
+        }
+      }
+
+      newZookeeper.set("selectedHost", suggestedHost);
+      newZookeeper.set("zId", (currentZooKeepers.get("lastObject.zId") + 1));
+
+      this.get("selectedServicesMasters").pushObject(newZookeeper);
+
+      this.rebalanceZookeeperHosts();
+
+      return true;
+    }
+    return false;//if no more zookeepers can be added
+  },
+
+  removeZookeepers: function (zId) {
+    var currentZooKeepers;
+
+    //work only if the Zookeeper service is selected in previous step
+    if (!this.get("selectedServices").mapProperty("service_name").contains("ZooKeeper")) {
+      return false;
+    }
+
+    currentZooKeepers = this.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper");
+
+    if (currentZooKeepers.get("length") > 1) {
+      this.get("selectedServicesMasters").removeAt(this.get("selectedServicesMasters").indexOf(this.get("selectedServicesMasters").findProperty("zId", zId)));
+
+      currentZooKeepers = this.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper");
+      if (currentZooKeepers.get("length") < this.get("hosts.length")) {
+        currentZooKeepers.set("lastObject.showAddControl", true);
+      }
+
+      this.rebalanceZookeeperHosts();
+
+      return true;
+    }
+
+    return false;
+
+  },
+
+  rebalanceZookeeperHosts: function () {
+    //for a zookeeper update the available hosts for the other zookeepers
+
+    var currentZooKeepers = this.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper"),
+      zooHosts = currentZooKeepers.mapProperty("selectedHost"),
+      availableZooHosts = [],
+      preparedAvailableHosts = null;
+
+    //get all hosts available for zookeepers
+    this.get("hosts").forEach(function (item) {
+      if (!zooHosts.contains(item.get("host_name"))) {
+        availableZooHosts.pushObject(item);
+      }
+    }, this);
+
+    currentZooKeepers.forEach(function (item) {
+      preparedAvailableHosts = availableZooHosts.slice(0);
+      preparedAvailableHosts.pushObject(this.get("hosts").findProperty("host_name", item.get("selectedHost")))
+      preparedAvailableHosts.sort(this.sortHostsByConfig, this);
+      item.set("availableHosts", preparedAvailableHosts);
+    }, this);
+
+  },
+
+  sortHostsByConfig: function (a, b) {
+    //currently handling only total memory on the host
+    if (a.total_mem < b.total_mem) {
+      return 1;
+    }
+    else {
+      return -1;
+    }
+  },
+
+  sortHostsByName: function (a, b) {
+    if (a.host_name > b.host_name) {
+      return 1;
+    }
+    else {
+      return -1;
+    }
+  },
+
+  /*
+   * Initialize the model data
+   */
+  init: function () {
+    var zookeeperComponent = null, componentObj = null, hostObj = null;
+    this._super();
+
+    //wrap the model data into
+
+    App.hosts.forEach(function (item) {
+      hostObj = Ember.Object.create(item);
+      hostObj.set("host_info", "" + hostObj.get("host_name") + " ( " + hostObj.get("total_mem") + "GB" + " " + hostObj.get("cpu_count") + "cores )");
+      this.get("hosts").pushObject(hostObj);
+    }, this);
+
+    //sort the hosts
+    this.get("hosts").sort(this.sortHostsByConfig);
+
+    //todo: build masters from config instead
+    App.masterServices.forEach(function (item) {
+      //add the zookeeper component at the end if exists
+      if (item.component_name === "ZooKeeper") {
+        zookeeperComponent = Ember.Object.create(item);
+      } else {
+        componentObj = Ember.Object.create(item);
+        componentObj.set("availableHosts", this.get("hosts").slice(0));
+        this.get("selectedServicesMasters").pushObject(componentObj);
+      }
+    }, this);
+
+    //while initialization of the controller there will be only 1 zookeeper server
+
+    if (zookeeperComponent) {
+      zookeeperComponent.set("showAddControl", true);
+      zookeeperComponent.set("showRemoveControl", false);
+      zookeeperComponent.set("zId", 1);
+      zookeeperComponent.set("availableHosts", this.get("hosts").slice(0));
+      this.get("selectedServicesMasters").pushObject(Ember.Object.create(zookeeperComponent));
+    }
+
+    App.selectedServices.forEach(function (item) {
+      this.get("selectedServices").pushObject(Ember.Object.create({service_name: item}));
+    }, this);
+
+  }
+
+});
+
+
+

Modified: incubator/ambari/branches/AMBARI-666/ambari-web/app/styles/application.less
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/styles/application.less?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/styles/application.less (original)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/styles/application.less Wed Sep 26 20:59:33 2012
@@ -130,3 +130,91 @@ a:focus {
     outline: none;
 }
 
+/*assign masters*/
+
+.assign-masters {
+  .select-hosts{
+    width:50%;
+    float:left;
+    white-space:nowrap;
+  }
+
+  .round-corners{
+    border-radius: 8px;
+    -webkit-border-radius: 8px;
+    -moz-border-radius: 8px;
+  }
+
+  .host-assignments{
+    float:right;
+    width:45%;
+  }
+
+  .remaining-hosts{
+    padding:25px;
+    border-top:solid 1px #cccccc;
+    border-left:solid 1px #cccccc;
+    border-right:groove 5px #cccccc;
+    border-bottom:groove 5px #cccccc;
+    margin-top:20px;
+    background-color: #FCF8E3;
+    color: #C09853;
+  }
+
+  .host-assignments .mapping-box{
+    border:solid 1px #cccccc;
+    padding: 8px;
+    margin-bottom:10px;
+    background-color: #FFFAFA;
+  }
+
+  .host-assignments .assignedService{
+    padding:5px;
+    border:solid 1px #cccccc;
+    margin:2px;
+    background-color: #69BE28;
+    color:white;
+    white-space: nowrap;
+    font-size: 0.9em;
+  }
+
+  .form-horizontal .controls {
+    margin-left: 110px;
+  }
+
+  .form-horizontal .control-label {
+    width:100px;
+  }
+
+  .form-horizontal .control-group select {
+    width:75%;
+    min-width:100px;
+    max-width:250px;
+  }
+
+  .hostString{
+    margin-bottom: 5px;
+  }
+
+  .controls .badge{
+    background-color: #ADC299;
+    color: #ffffff;
+    cursor: pointer;
+    font-weight: bold;
+    text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
+  }
+
+  .assign-master  .controls .badge:hover{
+    background-color: #99B280;
+  }
+
+  .alertFlag{
+    font-size: 1.3em;
+    color: #B94A48;
+    font-weight: bold;
+    vertical-align: middle;
+  }
+}
+
+/*end assign masters*/
+

Modified: incubator/ambari/branches/AMBARI-666/ambari-web/app/templates/installer/step5.hbs
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/templates/installer/step5.hbs?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/templates/installer/step5.hbs (original)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/templates/installer/step5.hbs Wed Sep 26 20:59:33 2012
@@ -16,9 +16,65 @@
 * limitations under the License.
 -->
 
-
 <h2>{{t installer.step5.header}}</h2>
+<div class="alert alert-info">
+  {{t installer.step5.body}}
+</div>
+<div class="assign-masters">
+  <div class="select-hosts">
+    <form class="form-horizontal">
+      <!-- View for array controller -->
+      {{#each selectedServicesMasters}}
+      <div class="control-group">
+        <label class="control-label">{{component_name}}:</label>
+
+        <div class="controls">
+          {{view App.SelectHostView
+            contentBinding="availableHosts"
+            optionValuePath="content.host_name"
+            optionLabelPath="content.host_info"
+            selectedHostBinding="selectedHost"
+            serviceNameBinding="component_name"
+            zIdBinding="zId"
+          }}
+
+          {{#if showAddControl}}
+          {{view App.AddControlView
+            componentNameBinding="component_name"
+          }}
+          {{/if}}
+          {{#if showRemoveControl}}
+          {{view App.RemoveControlView
+            zIdBinding="zId"
+          }}
+          {{/if}}
+
+        </div>
+      </div>
+      {{/each}}
+
+    </form>
+  </div>
+
+  <div class="host-assignments">
+    {{#each masterHostMapping}}
+    <div class="mapping-box round-corners well">
+      <div class="hostString"><span><strong>{{host_name}}</strong></span><span>{{hostInfo}}</span></div>
+      {{#each masterServices}}
+      <span class="assignedService round-corners">{{component_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}}>Back</a>
-    <a class="btn btn-success" {{action next}}>Next</a>
+  <a class="btn" {{action back}}>Back</a>
+  <a class="btn btn-success" {{action next}}>Next</a>
 </div>
\ No newline at end of file

Modified: incubator/ambari/branches/AMBARI-666/ambari-web/app/views.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/views.js?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/views.js (original)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/views.js Wed Sep 26 20:59:33 2012
@@ -37,7 +37,7 @@ require('views/installer/step1_view');
 require('views/installer/step2_view');
 require('views/installer/step3_view');
 require('views/installer/step4_view');
-require('views/installer/step5');
+require('views/installer/step5_view');
 require('views/installer/step6_view');
 require('views/installer/step7_view');
 require('views/installer/step8');

Modified: incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step1_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step1_view.js?rev=1390724&r1=1390723&r2=1390724&view=diff
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step1_view.js (original)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step1_view.js Wed Sep 26 20:59:33 2012
@@ -25,4 +25,5 @@ App.InstallerStep1View = Em.View.extend(
   didInsertElement: function () {
     $("[rel=popover]").popover({'placement': 'right', 'trigger': 'hover'});
   }
+
 });

Added: incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5_view.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5_view.js?rev=1390724&view=auto
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5_view.js (added)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/app/views/installer/step5_view.js Wed Sep 26 20:59:33 2012
@@ -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');
+
+App.InstallerStep5View = Em.View.extend({
+
+  templateName: require('templates/installer/step5'),
+
+  submit: function (e) {
+    App.router.transitionTo('step6');
+  }
+
+});
+
+App.SelectHostView = Em.Select.extend({
+  content: [],
+  zId: null,
+  selectedHost: null,
+  serviceName: null,
+
+  change: function () {
+    App.router.get('installerStep5Controller').assignHostToMaster(this.get("serviceName"), this.get("value"), this.get("zId"));
+  },
+
+  didInsertElement: function () {
+    this.set("value", this.get("selectedHost"));
+  }
+});
+
+App.AddControlView = Em.View.extend({
+  componentName: null,
+  tagName: "span",
+  classNames: ["badge", "badge-important"],
+  template: Ember.Handlebars.compile('+'),
+
+  click: function (event) {
+    App.router.get('installerStep5Controller').addZookeepers();
+  }
+});
+
+App.RemoveControlView = Em.View.extend({
+  zId: null,
+  tagName: "span",
+  classNames: ["badge", "badge-important"],
+  template: Ember.Handlebars.compile('-'),
+
+  click: function (event) {
+    App.router.get('installerStep5Controller').removeZookeepers(this.get("zId"));
+  }
+});

Added: incubator/ambari/branches/AMBARI-666/ambari-web/test/installer/step5_test.js
URL: http://svn.apache.org/viewvc/incubator/ambari/branches/AMBARI-666/ambari-web/test/installer/step5_test.js?rev=1390724&view=auto
==============================================================================
--- incubator/ambari/branches/AMBARI-666/ambari-web/test/installer/step5_test.js (added)
+++ incubator/ambari/branches/AMBARI-666/ambari-web/test/installer/step5_test.js Wed Sep 26 20:59:33 2012
@@ -0,0 +1,152 @@
+/**
+ * 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/installer/step5_controller');
+
+describe('App.InstallerStep5Controller', function () {
+
+  var controller = App.InstallerStep5Controller.create();
+
+  describe('#getAvailableHosts()', function () {
+    it('should generate available hosts for a new zookeeper service', function () {
+      var hostsForNewZookeepers = controller.getAvailableHosts("ZooKeeper"),
+        ok = true, i = 0, masters = null;
+
+      //test that the hosts found, do not have Zookeeper master assigned to them
+      for (i = 0; i < hostsForNewZookeepers.get("length"); i++) {
+        masters = controller.get("selectedServicesMasters").filterProperty(hostsForNewZookeepers[i].get("host_name"));
+        if (masters.findProperty("component_name", "ZooKeeper")) {
+          ok = false;
+          break;
+        }
+      }
+
+      expect(ok).to.equal(true);
+    })
+
+    it('should return all hosts for services other than ZooKeeper', function () {
+      var hostsForNewZookeepers = controller.getAvailableHosts("");
+
+      expect(hostsForNewZookeepers.get("length")).to.equal(controller.get("hosts.length"));
+    })
+  })
+
+  describe('#assignHostToMaster()', function () {
+    it('should assign the selected host to the master service', function () {
+      //test non-zookeeper master
+      var SERVICE_MASTER = "NameNode",
+        HOST = "host4", ZID, status;
+
+      controller.assignHostToMaster(SERVICE_MASTER, HOST);
+      expect(controller.get("selectedServicesMasters").findProperty("component_name", "NameNode").get("selectedHost")).to.equal(HOST);
+    })
+
+    it('should assign the selected host to the ZooKeeper master service', function () {
+      //test non-zookeeper master
+      var SERVICE_MASTER = "ZooKeeper",
+        HOST = "host4", ZID = 2;
+
+      //test zookeeper master assignment with
+      if (controller.addZookeepers()) {
+        controller.assignHostToMaster(SERVICE_MASTER, HOST, ZID);
+        expect(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").findProperty("zId", ZID).get("selectedHost")).to.equal(HOST);
+      }
+    })
+  })
+
+  describe('#addZookeepers()', function () {
+    it('should add a new ZooKeeper', function () {
+      var newLength = 0;
+      if (controller.get("selectedServices").mapProperty("service_name").contains("ZooKeeper")
+        && controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length") < controller.get("hosts.length")) {
+        newLength = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length");
+        controller.addZookeepers();
+        expect(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length")).to.equal(newLength + 1);
+      }
+    })
+
+    it('should add ZooKeepers up to the number of hosts', function () {
+      var currentZooKeepers = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper"),
+        success = true;
+
+      //add ZooKeepers as long as possible
+      if (currentZooKeepers) {
+        while (success) {
+          success = controller.addZookeepers();
+        }
+
+        expect(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length")).to.equal(controller.get("hosts.length"));
+      }
+    })
+  })
+
+  describe('#removeZookeepers()', function () {
+    it('should remove a ZooKeeper', function () {
+      var newLength = 0;
+      if (controller.get("selectedServices").mapProperty("service_name").contains("ZooKeeper")) {
+        if (controller.addZookeepers()) {
+          newLength = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length");
+          controller.removeZookeepers(2);
+          expect(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length")).to.equal(newLength - 1);
+        }
+      }
+    })
+
+    it('should fail to remove a ZooKeeper if there is only 1', function () {
+      var currentZooKeepers = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper"),
+        success = true;
+
+      //remove ZooKeepers as long as possible
+      if (currentZooKeepers) {
+        while (success) {
+          success = controller.removeZookeepers(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("lastObject.zId"));
+        }
+
+        expect(controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").get("length")).to.equal(1);
+      }
+    })
+
+  })
+
+  describe('#rebalanceZookeeperHosts()', function () {
+    it('should rebalance hosts for ZooKeeper', function () {
+      //assign a host to a zookeeper and then rebalance the available hosts for the other zookeepers
+      var zookeepers = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper"),
+        aZookeeper = null, aHost = null, i = 0, ok = true;
+
+      if (zookeepers.get("length") > 1) {
+        aZookeeper = controller.get("selectedServicesMasters").filterProperty("component_name", "ZooKeeper").findProperty("zId", 1);
+        aHost = aZookeeper.get("availableHosts")[0];
+        aZookeeper.set("selectedHost", aHost.get("host_name"));
+
+        controller.rebalanceZookeeperHosts();
+
+        for (i = 0; i < zookeepers.get("length"); i++) {
+          if (zookeepers[i].get("availableHosts").mapProperty("host_name").contains(aHost)) {
+            ok = false;
+            break;
+          }
+        }
+
+        expect(ok).to.equal(true);
+      }
+    })
+  })
+
+})
\ No newline at end of file