Browse Source

AMBARI-5143 Should not allow decommission/recommission of slave component if master component is not running. (ababiichuk)

aBabiichuk 11 years ago
parent
commit
15013b7199

+ 4 - 6
ambari-web/app/controllers/main/host/details.js

@@ -671,12 +671,11 @@ App.MainHostDetailsController = Em.Controller.extend({
 
   /**
    * send command to server to run decommission on DATANODE, TASKTRACKER, NODEMANAGER, REGIONSERVER
-   * @param event
+   * @param component
    */
-  decommission: function(event){
+  decommission: function(component){
     var self = this;
     App.showConfirmationPopup(function(){
-      var component = event.context;
       var svcName = component.get('service.serviceName');
       var hostName = self.get('content.hostName');
       // HDFS service, decommission DataNode
@@ -707,12 +706,11 @@ App.MainHostDetailsController = Em.Controller.extend({
   
   /**
    * send command to server to run recommission on DATANODE, TASKTRACKER, NODEMANAGER
-   * @param event
+   * @param component
    */
-  recommission: function(event){
+  recommission: function(component){
     var self = this;
     App.showConfirmationPopup(function(){
-      var component = event.context;
       var svcName = component.get('service.serviceName');
       var hostName = self.get('content.hostName');
       // HDFS service, Recommission datanode

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

@@ -1542,6 +1542,7 @@ Em.I18n.translations = {
   'hosts.host.alerts.st':' ! ',
   'hosts.decommission.popup.body':'Are you sure?',
   'hosts.decommission.popup.header':'Confirmation',
+  'hosts.decommission.tooltip.warning':'Cannot {0} since {1} is not running',
   'hosts.delete.popup.body':'Are you sure you want to delete host <i>{0}</i>?',
   'hosts.delete.popup.body.msg1':'By removing this host, Ambari will ignore future communications from this host. Software packages will not be removed from the host. The components on the host should not be restarted. If you wish to readd this host to the cluster, be sure to clean the host.',
   'hosts.delete.popup.body.msg2':'After deleting this host, Nagios should be restarted to remove this host from Nagios monitoring. Go to the <i>Services</i> page to restart Nagios.',

+ 43 - 1
ambari-web/app/mixins/main/host/details/host_components/decommissionable.js

@@ -62,6 +62,24 @@ App.Decommissionable = Em.Mixin.create({
    */
   isComponentRecommissionAvailable: null,
 
+  /**
+   * Component with stopped masters can't be docommissioned
+   * @type {bool}
+   */
+  isComponentDecommissionDisable: function() {
+    return this.get('content.service.workStatus') != App.HostComponentStatus.started;
+  }.property('content.service.workStatus'),
+
+  /**
+   * Tooltip message shows if decommission/recommission is disabled
+   * when masters for current component is down
+   */
+  decommissionTooltipMessage: function() {
+    if (this.get('isComponentDecommissionDisable') && (this.get('isComponentRecommissionAvailable') || this.get('isComponentDecommissionAvailable'))) {
+      var decom = this.get('isComponentRecommissionAvailable') ? Em.I18n.t('common.recommission') : Em.I18n.t('common.decommission');
+      return Em.I18n.t('hosts.decommission.tooltip.warning').format(decom, App.format.role(this.get('componentForCheckDecommission')));
+    }
+  }.property('isComponentDecommissionDisable', 'isComponentRecommissionAvailable', 'isComponentDecommissionAvailable', 'componentForCheckDecommission'),
   /**
    * Recalculated component status based on decommission
    * @type {string}
@@ -243,6 +261,30 @@ App.Decommissionable = Em.Mixin.create({
    */
   updateDecommissionStatus: function() {
     Em.run.once(this, 'loadComponentDecommissionStatus');
-  }.observes('content.workStatus', 'content.passiveState')
+  }.observes('content.workStatus', 'content.passiveState'),
+
 
+  decommissionView: Em.View.extend({
+
+    templateName: require('templates/main/host/decommission'),
+
+    text: function() {
+      return this.get('parentView.isComponentDecommissionAvailable') ? Em.I18n.t('common.decommission') : Em.I18n.t('common.recommission');
+    }.property('parentView.isComponentDecommissionAvailable'),
+
+    didInsertElement: function() {
+      this._super();
+      App.tooltip($("[rel='decommissionTooltip']"));
+    },
+
+    click: function() {
+      if (!this.get('parentView.isComponentDecommissionDisable')) {
+        if (this.get('parentView.isComponentDecommissionAvailable')) {
+          this.get('controller').decommission(this.get('parentView.content'));
+        } else {
+          this.get('controller').recommission(this.get('parentView.content'));
+        }
+      }
+    }
+  })
 });

+ 99 - 39
ambari-web/app/templates/main/host/bulk_operation_menu.hbs

@@ -26,56 +26,116 @@
     <li class="dropdown-submenu">
       <a {{bindAttr class="view.parentView.selectedCategory.hasHosts::disabled"}} tabindex="-1" href="javascript:void(null);">{{view.menuItems.s.label}}
         ({{view.parentView.selectedCategory.hostsCount}})</a>
-      <ul {{bindAttr class="view.parentView.selectedCategory.hasHosts::hidden :dropdown-menu"}}>
-        {{#each subMenuItem in view.menuItems.s.submenu}}
-          <li class="dropdown-submenu">
-            <a href="javascript:void(null);">{{subMenuItem.label}}</a>
-            <ul class="dropdown-menu">
-              {{#each menuL3Item in subMenuItem.submenu}}
-                <li>
-                  <a {{action "bulkOperationConfirm" menuL3Item.operationData target="view.parentView"}} href="#">{{menuL3Item.label}}</a>
-                </li>
-              {{/each}}
-            </ul>
-          </li>
-        {{/each}}
-      </ul>
+        <ul {{bindAttr class="view.parentView.selectedCategory.hasHosts::hidden :dropdown-menu"}}>
+          {{#view view.hostItemView}}
+              <a href="javascript:void(null);">{{view.label}}</a>
+              <ul class="dropdown-menu">
+                {{#each operation in view.operationsInfo}}
+                  {{#if operation.label.length}}
+                      <li>
+                        {{#view view.operationView contentBinding="operation.operationData" selection="s"}}
+                            <a href="javascript:void(null);">{{operation.label}}</a>
+                        {{/view}}
+                      </li>
+                  {{/if}}
+                {{/each}}
+              </ul>
+          {{/view}}
+          {{#each component in view.components}}
+            {{#view view.slaveItemView contentBinding="component"}}
+                <a href="javascript:void(null);">{{component.componentNameFormatted}}</a>
+                <ul class="dropdown-menu">
+                  {{#each operation in view.operationsInfo}}
+                    {{#if operation.decommission}}
+                      {{#view view.advancedOperationView contentBinding="operation.operationData" selection="s"}}
+                          <a href="javascript:void(null);">{{operation.label}}</a>
+                      {{/view}}
+                    {{else}}
+                      {{#view view.commonOperationView contentBinding="operation.operationData" selection="s"}}
+                          <a href="javascript:void(null);">{{operation.label}}</a>
+                      {{/view}}
+                    {{/if}}
+                  {{/each}}
+                </ul>
+            {{/view}}
+          {{/each}}
+        </ul>
     </li>
     <li class="dropdown-submenu">
       <a {{bindAttr class="view.parentView.hasFilteredItems::disabled"}} tabindex="-1" href="javascript:void(null);">{{view.menuItems.f.label}}
         ({{view.parentView.filteredContent.length}})</a>
-      <ul {{bindAttr class="view.parentView.hasFilteredItems::hidden :dropdown-menu"}}>
-        {{#each subMenuItem in view.menuItems.f.submenu}}
-          <li class="dropdown-submenu">
-            <a href="javascript:void(null);">{{subMenuItem.label}}</a>
+        <ul {{bindAttr class="view.parentView.hasFilteredItems::hidden :dropdown-menu"}}>
+          {{#view view.hostItemView}}
+            <a href="javascript:void(null);">{{view.label}}</a>
             <ul class="dropdown-menu">
-              {{#each menuL3Item in subMenuItem.submenu}}
-                <li>
-                  <a {{action "bulkOperationConfirm" menuL3Item.operationData target="view.parentView"}} href="#">{{menuL3Item.label}}</a>
-                </li>
+              {{#each operation in view.operationsInfo}}
+                {{#if operation.label.length}}
+                  <li>
+                    {{#view view.operationView contentBinding="operation.operationData" selection="f"}}
+                      <a href="javascript:void(null);">{{operation.label}}</a>
+                    {{/view}}
+                  </li>
+                {{/if}}
               {{/each}}
             </ul>
-          </li>
-        {{/each}}
-      </ul>
+          {{/view}}
+          {{#each component in view.components}}
+              {{#view view.slaveItemView contentBinding="component"}}
+                <a href="javascript:void(null);">{{component.componentNameFormatted}}</a>
+                  <ul class="dropdown-menu">
+                    {{#each operation in view.operationsInfo}}
+                      {{#if operation.decommission}}
+                        {{#view view.advancedOperationView contentBinding="operation.operationData" selection="f"}}
+                        <a href="javascript:void(null);">{{operation.label}}</a>
+                        {{/view}}
+                      {{else}}
+                        {{#view view.commonOperationView contentBinding="operation.operationData" selection="f"}}
+                        <a href="javascript:void(null);">{{operation.label}}</a>
+                        {{/view}}
+                      {{/if}}
+                    {{/each}}
+                  </ul>
+              {{/view}}
+          {{/each}}
+        </ul>
     </li>
     <li class="dropdown-submenu">
       <a tabindex="-1" href="javascript:void(null);">{{view.menuItems.a.label}}
         ({{view.parentView.content.length}})</a>
-      <ul class="dropdown-menu">
-        {{#each subMenuItem in view.menuItems.a.submenu}}
-          <li class="dropdown-submenu">
-            <a href="javascript:void(null);">{{subMenuItem.label}}</a>
-            <ul class="dropdown-menu">
-              {{#each menuL3Item in subMenuItem.submenu}}
-                <li>
-                  <a {{action "bulkOperationConfirm" menuL3Item.operationData target="view.parentView"}} href="#">{{menuL3Item.label}}</a>
-                </li>
-              {{/each}}
-            </ul>
-          </li>
-        {{/each}}
-      </ul>
+        <ul class="dropdown-menu">
+          {{#view view.hostItemView}}
+              <a href="javascript:void(null);">{{view.label}}</a>
+              <ul class="dropdown-menu">
+                {{#each operation in view.operationsInfo}}
+                  {{#if operation.label.length}}
+                      <li>
+                        {{#view view.operationView contentBinding="operation.operationData" selection="a"}}
+                            <a href="javascript:void(null);">{{operation.label}}</a>
+                        {{/view}}
+                      </li>
+                  {{/if}}
+                {{/each}}
+              </ul>
+          {{/view}}
+          {{#each component in view.components}}
+            {{#view view.slaveItemView contentBinding="component"}}
+                <a href="javascript:void(null);">{{component.componentNameFormatted}}</a>
+                <ul class="dropdown-menu">
+                  {{#each operation in view.operationsInfo}}
+                    {{#if operation.decommission}}
+                      {{#view view.advancedOperationView contentBinding="operation.operationData" selection="a"}}
+                          <a href="javascript:void(null);">{{operation.label}}</a>
+                      {{/view}}
+                    {{else}}
+                      {{#view view.commonOperationView contentBinding="operation.operationData" selection="a"}}
+                          <a href="javascript:void(null);">{{operation.label}}</a>
+                      {{/view}}
+                    {{/if}}
+                  {{/each}}
+                </ul>
+            {{/view}}
+          {{/each}}
+        </ul>
     </li>
   </ul>
 </div>

+ 25 - 0
ambari-web/app/templates/main/host/decommission.hbs

@@ -0,0 +1,25 @@
+{{!
+* 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.
+}}
+
+
+<li rel='decommissionTooltip' {{bindAttr data-original-title="view.parentView.decommissionTooltipMessage" class="view.parentView.noActionAvailable"}}>
+    <a href="javascript:void(null)" data-toggle="modal" {{bindAttr class="view.parentView.isComponentDecommissionDisable:disabled"}}>
+      {{view.text}}
+    </a>
+</li>
+

+ 2 - 10
ambari-web/app/templates/main/host/details/host_component.hbs

@@ -50,18 +50,10 @@
       </a>
       <ul class="dropdown-menu">
         {{#if view.isComponentDecommissionAvailable}}
-          <li {{bindAttr class="view.noActionAvailable"}}>
-            <a href="javascript:void(null)" data-toggle="modal" {{action "decommission" view.content target="controller"}}>
-              {{t common.decommission}}
-            </a>
-          </li>
+          {{view view.decommissionView}}
         {{/if}}
         {{#if view.isComponentRecommissionAvailable}}
-          <li {{bindAttr class="view.noActionAvailable"}}>
-            <a href="javascript:void(null)" data-toggle="modal" {{action "recommission" view.content target="controller"}}>
-              {{t common.recommission}}
-            </a>
-          </li>
+          {{view view.decommissionView}}
         {{/if}}
         {{#if view.isRestartableComponent}}
           <li {{bindAttr class="view.isRestartComponentDisabled:hidden"}}>

+ 2 - 3
ambari-web/app/views/main/host.js

@@ -110,11 +110,10 @@ App.MainHostView = App.TableView.extend({
   /**
    * Confirmation Popup for bulk Operations
    */
-  bulkOperationConfirm: function(event) {
-    var operationData = event.context;
+  bulkOperationConfirm: function(operationData, selection) {
     var hosts = [];
     var self = this;
-    switch(operationData.selection) {
+    switch(selection) {
       case 's':
         hosts = this.get('content').filterProperty('selected');
         break;

+ 1 - 0
ambari-web/app/views/main/host/details/host_component_views/regionserver_view.js

@@ -20,6 +20,7 @@ var App = require('app');
 
 App.RegionServerComponentView = App.HostComponentView.extend(App.Decommissionable, {
 
+  componentForCheckDecommission: 'HBASE_MASTER',
   /**
    * load Recommission/Decommission status of RegionServer
    */

+ 272 - 182
ambari-web/app/views/main/host/hosts_table_menu_view.js

@@ -22,195 +22,285 @@ App.HostTableMenuView = Em.View.extend({
 
   templateName: require('templates/main/host/bulk_operation_menu'),
 
+  menuItems: function () {
+    return {
+      s: {label: Em.I18n.t('hosts.table.menu.l1.selectedHosts')},
+      f: {label: Em.I18n.t('hosts.table.menu.l1.filteredHosts')},
+      a: {label: Em.I18n.t('hosts.table.menu.l1.allHosts')}
+    };
+  }.property('App.router.clusterController.isLoaded'),
+
+  components: function(){
+    var menuItems = [
+    Em.Object.create({
+      serviceName: 'HDFS',
+      componentName: 'DATANODE',
+      masterComponentName: 'NAMENODE',
+      componentNameFormatted: Em.I18n.t('dashboard.services.hdfs.datanodes')
+    }),
+    Em.Object.create({
+      serviceName: 'YARN',
+      componentName: 'NODEMANAGER',
+      masterComponentName: 'RESOURCEMANAGER',
+      componentNameFormatted: Em.I18n.t('dashboard.services.yarn.nodeManagers')
+    }),
+    Em.Object.create({
+      serviceName: 'HBASE',
+      componentName: 'HBASE_REGIONSERVER',
+      masterComponentName: 'HBASE_MASTER',
+      componentNameFormatted: Em.I18n.t('dashboard.services.hbase.regionServers')
+    }),
+    Em.Object.create({
+      serviceName: 'MAPREDUCE',
+      componentName: 'TASKTRACKER',
+      masterComponentName: 'JOBTRACKER',
+      componentNameFormatted: Em.I18n.t('dashboard.services.mapreduce.taskTrackers')
+    }),
+    Em.Object.create({
+      serviceName: 'STORM',
+      componentName: 'SUPERVISOR',
+      masterComponentName: 'SUPERVISOR',
+      componentNameFormatted: Em.I18n.t('dashboard.services.storm.supervisors')
+    })];
+
+    return menuItems.filter(function(item){
+      return App.Service.find().findProperty('serviceName',item.serviceName);
+    });
+  }.property(),
+
+
   /**
-   * Get third-level menu items for slave components
-   * @param {String} componentNameForDecommission for decommission and recommission used another component name
-   * @param {String} componentNameForOtherActions host component name that should be processed
-   * operationData format:
-   * <code>
-   *  {
-   *    action: 'STARTED|INSTALLED|RESTART|DECOMMISSION|DECOMMISSION_OFF', // action for selected host components
-   *    message: 'some text', // just text to BG popup
-   *    componentName: 'DATANODE|NODEMANAGER...', //component name that should be processed
-   *    realComponentName: 'DATANODE|NODEMANAGER...', // used only for decommission(_off) actions
-   *    serviceName: 'HDFS|YARN|HBASE...', // service name of the processed component
-   *    componentNameFormatted: 'DataNodes|NodeManagers...' // "user-friendly" string with component name (used in BG popup)
-   *  }
-   *  </code>
-   *
-   * @returns {Array}
+   * slaveItemView build second-level menu
+   * for slave component
    */
-  getSlaveItemsTemplate: function(componentNameForDecommission, componentNameForOtherActions) {
-    var menuItems = Em.A([
-      Em.Object.create({
-        label: Em.I18n.t('common.start'),
-        operationData: Em.Object.create({
-          action: App.HostComponentStatus.started,
-          message: Em.I18n.t('common.start'),
-          componentName: componentNameForOtherActions
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('common.stop'),
-        operationData: Em.Object.create({
-          action: App.HostComponentStatus.stopped,
-          message: Em.I18n.t('common.stop'),
-          componentName: componentNameForOtherActions
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('common.restart'),
-        operationData: Em.Object.create({
-          action: 'RESTART',
-          message: Em.I18n.t('common.restart'),
-          componentName: componentNameForOtherActions
-        })
-      })
-    ]);
-    if(App.get('components.decommissionAllowed').contains(componentNameForOtherActions)) {
-      menuItems.pushObject(Em.Object.create({
-        label: Em.I18n.t('common.decommission'),
-        operationData: Em.Object.create({
-          action: 'DECOMMISSION',
-          message: Em.I18n.t('common.decommission'),
-          componentName: componentNameForDecommission,
-          realComponentName: componentNameForOtherActions
-        })
-      }));
-      menuItems.pushObject(Em.Object.create({
-        label: Em.I18n.t('common.recommission'),
-        operationData: Em.Object.create({
-          action: 'DECOMMISSION_OFF',
-          message: Em.I18n.t('common.recommission'),
-          componentName: componentNameForDecommission,
-          realComponentName: componentNameForOtherActions
+
+  slaveItemView: Em.View.extend({
+
+    tagName: 'li',
+
+    classNames: ['dropdown-submenu'],
+
+    /**
+     * Get third-level menu items ingo for slave components
+     * operationData format:
+     * <code>
+     *  {
+     *    action: 'STARTED|INSTALLED|RESTART|DECOMMISSION|DECOMMISSION_OFF', // action for selected host components
+     *    message: 'some text', // just text to BG popup
+     *    componentName: 'DATANODE|NODEMANAGER...', //component name that should be processed
+     *    realComponentName: 'DATANODE|NODEMANAGER...', // used only for decommission(_off) actions
+     *    serviceName: 'HDFS|YARN|HBASE...', // service name of the processed component
+     *    componentNameFormatted: 'DataNodes|NodeManagers...' // "user-friendly" string with component name (used in BG popup)
+     *  }
+     *  </code>
+     *
+     * @returns {Array}
+     */
+
+    operationsInfo: function () {
+      var content = this.get('content');
+      var menuItems = Em.A([
+        Em.Object.create({
+          label: Em.I18n.t('common.start'),
+          operationData: Em.Object.create({
+            action: App.HostComponentStatus.started,
+            message: Em.I18n.t('common.start'),
+            componentName: content.componentName,
+            serviceName: content.serviceName,
+            componentNameFormatted: content.componentNameFormatted
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('common.stop'),
+          operationData: Em.Object.create({
+            action: App.HostComponentStatus.stopped,
+            message: Em.I18n.t('common.stop'),
+            componentName: content.componentName,
+            serviceName: content.serviceName,
+            componentNameFormatted: content.componentNameFormatted
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('common.restart'),
+          operationData: Em.Object.create({
+            action: 'RESTART',
+            message: Em.I18n.t('common.restart'),
+            componentName: content.componentName,
+            serviceName: content.serviceName,
+            componentNameFormatted: content.componentNameFormatted
+          })
         })
-      }));
-    }
-    return menuItems;
-  },
+      ]);
+      if (App.get('components.decommissionAllowed').contains(content.componentName)) {
+        menuItems.pushObject(Em.Object.create({
+          label: Em.I18n.t('common.decommission'),
+          decommission: true,
+          operationData: Em.Object.create({
+            action: 'DECOMMISSION',
+            message: Em.I18n.t('common.decommission'),
+            componentName: content.masterComponentName,
+            realComponentName: content.componentName,
+            serviceName: content.serviceName,
+            componentNameFormatted: content.componentNameFormatted
+          })
+        }));
+        menuItems.pushObject(Em.Object.create({
+          label: Em.I18n.t('common.recommission'),
+          decommission: true,
+          operationData: Em.Object.create({
+            action: 'DECOMMISSION_OFF',
+            message: Em.I18n.t('common.recommission'),
+            componentName: content.masterComponentName,
+            realComponentName: content.componentName,
+            serviceName: content.serviceName,
+            componentNameFormatted: content.componentNameFormatted
+          })
+        }));
+      }
+      return menuItems;
+    }.property("content"),
+
+    /**
+     * commonOperationView is used for third-level menu items
+     * for simple operations ('START','STOP','RESTART')
+     */
+    commonOperationView: Em.View.extend({
+      tagName: 'li',
+
+      /**
+       * click function use
+       * App.MainHostView as a thirl level parent
+       * and runs it's function
+       */
+      click: function () {
+        this.get('parentView.parentView.parentView').bulkOperationConfirm(this.get('content'), this.get('selection'));
+      }
+    }),
+
+    /**
+     * advancedOperationView is used for third level menu item
+     * for advanced operations ('RECOMMISSION','DECOMMISSION')
+     */
+    advancedOperationView: Em.View.extend({
+      tagName: 'li',
+      rel: 'menuTooltip',
+      classNameBindings: ['disabledElement'],
+      attributeBindings: ['tooltipMsg:data-original-title'],
+
+      service: function () {
+        return App.router.get('mainServiceController.content').findProperty('serviceName', this.get('content.serviceName'))
+      }.property('App.router.mainServiceController.content.@each', 'content'),
+
+      tooltipMsg: function () {
+        return (this.get('disabledElement') == 'disabled') ?
+           Em.I18n.t('hosts.decommission.tooltip.warning').format(this.get('content.message'),  App.format.role(this.get('content.componentName'))) : '';
+      }.property('disabledElement','content.componentName'),
+
+      disabledElement: function () {
+        return (this.get('service.workStatus') != 'STARTED') ? 'disabled' : '';
+      }.property('service.workStatus'),
+
+      /**
+       * click function use
+       * App.MainHostView as a thirl level parent
+       * and runs it's function
+       */
+      click: function () {
+        if (this.get('disabledElement') == 'disabled') {
+          return;
+        }
+        this.get('parentView.parentView.parentView').bulkOperationConfirm(this.get('content'), this.get('selection'));
+      },
+
+      didInsertElement: function () {
+        App.tooltip($(this.get('element')));
+      }
+    })
+  }),
 
   /**
-   * Get third-level menu items for Hosts
-   * operationData format:
-   * <code>
-   *  {
-   *    action: 'STARTED|INSTALLED|RESTART..', // action for selected hosts (will be applied for each host component in selected hosts)
-   *    actionToCheck: 'INSTALLED|STARTED..' // state to filter host components should be processed
-   *    message: 'some text', // just text to BG popup
-   *  }
-   *  </code>
-   * @returns {Array}
+   * hostItemView build second-level menu
+   * for host
    */
-  getHostItemsTemplate: function() {
-    return Em.A([
-      Em.Object.create({
-        label: Em.I18n.t('hosts.host.details.startAllComponents'),
-        operationData: Em.Object.create({
-          action: 'STARTED',
-          actionToCheck: 'INSTALLED',
-          message: Em.I18n.t('hosts.host.details.startAllComponents')
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('hosts.host.details.stopAllComponents'),
-        operationData: Em.Object.create({
-          action: 'INSTALLED',
-          actionToCheck: 'STARTED',
-          message: Em.I18n.t('hosts.host.details.stopAllComponents')
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('hosts.table.menu.l2.restartAllComponents'),
-        operationData: Em.Object.create({
-          action: 'RESTART',
-          message: Em.I18n.t('hosts.table.menu.l2.restartAllComponents')
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('passiveState.turnOn'),
-        operationData: Em.Object.create({
-          state: 'ON',
-          action: 'PASSIVE_STATE',
-          message: Em.I18n.t('passiveState.turnOnFor').format('hosts')
-        })
-      }),
-      Em.Object.create({
-        label: Em.I18n.t('passiveState.turnOff'),
-        operationData: Em.Object.create({
-          state: 'OFF',
-          action: 'PASSIVE_STATE',
-          message: Em.I18n.t('passiveState.turnOffFor').format('hosts')
+
+  hostItemView: Em.View.extend({
+
+    tagName: 'li',
+
+    classNames: ['dropdown-submenu'],
+
+    label: Em.I18n.t('common.hosts'),
+
+    /** Get third-level menu items for Hosts
+     * operationData format:
+     * <code>
+     *  {
+     *    action: 'STARTED|INSTALLED|RESTART..', // action for selected hosts (will be applied for each host component in selected hosts)
+     *    actionToCheck: 'INSTALLED|STARTED..' // state to filter host components should be processed
+     *    message: 'some text', // just text to BG popup
+     *  }
+     *  </code>
+     * @returns {Array}
+     */
+    operationsInfo: function () {
+      return Em.A([
+        Em.Object.create({
+          label: Em.I18n.t('hosts.host.details.startAllComponents'),
+          operationData: Em.Object.create({
+            action: 'STARTED',
+            actionToCheck: 'INSTALLED',
+            message: Em.I18n.t('hosts.host.details.startAllComponents')
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('hosts.host.details.stopAllComponents'),
+          operationData: Em.Object.create({
+            action: 'INSTALLED',
+            actionToCheck: 'STARTED',
+            message: Em.I18n.t('hosts.host.details.stopAllComponents')
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('hosts.table.menu.l2.restartAllComponents'),
+          operationData: Em.Object.create({
+            action: 'RESTART',
+            message: Em.I18n.t('hosts.table.menu.l2.restartAllComponents')
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('passiveState.turnOn'),
+          operationData: Em.Object.create({
+            state: 'ON',
+            action: 'PASSIVE_STATE',
+            message: Em.I18n.t('passiveState.turnOnFor').format('hosts')
+          })
+        }),
+        Em.Object.create({
+          label: Em.I18n.t('passiveState.turnOff'),
+          operationData: Em.Object.create({
+            state: 'OFF',
+            action: 'PASSIVE_STATE',
+            message: Em.I18n.t('passiveState.turnOffFor').format('hosts')
+          })
         })
-      })
-    ]);
-  },
+      ]);
+    }.property(),
 
-  /**
-   * Get second-level menu
-   * @param {String} selection
-   * <code>
-   *   "s" - selected hosts
-   *   "a" - all hosts
-   *   "f" - filtered hosts
-   * </code>
-   * @returns {Array}
-   */
-  getSubMenuItemsTemplate: function(selection) {
-    var submenu = Em.A([{label: Em.I18n.t('common.hosts'), submenu: this.getHostItemsTemplate()}]);
-
-    if (!!App.HDFSService.find().content.length) {
-      var slaveItemsForHdfs = this.getSlaveItemsTemplate('NAMENODE', 'DATANODE');
-      slaveItemsForHdfs.setEach('operationData.serviceName', 'HDFS');
-      slaveItemsForHdfs.setEach('operationData.componentNameFormatted', Em.I18n.t('dashboard.services.hdfs.datanodes'));
-      submenu.push({label: Em.I18n.t('dashboard.services.hdfs.datanodes'), submenu: slaveItemsForHdfs});
-    }
-
-    if (!!App.YARNService.find().content.length) {
-      var slaveItemsForYarn = this.getSlaveItemsTemplate('RESOURCEMANAGER', 'NODEMANAGER');
-      slaveItemsForYarn.setEach('operationData.serviceName', 'YARN');
-      slaveItemsForYarn.setEach('operationData.componentNameFormatted', Em.I18n.t('dashboard.services.yarn.nodeManagers'));
-      submenu.push({label: Em.I18n.t('dashboard.services.yarn.nodeManagers'), submenu: slaveItemsForYarn});
-    }
-
-    if (!!App.HBaseService.find().content.length) {
-      var slaveItemsForHBase = this.getSlaveItemsTemplate('HBASE_MASTER', 'HBASE_REGIONSERVER');
-      slaveItemsForHBase.setEach('operationData.serviceName', 'HBASE');
-      slaveItemsForHBase.setEach('operationData.componentNameFormatted', Em.I18n.t('dashboard.services.hbase.regionServers'));
-      submenu.push({label: Em.I18n.t('dashboard.services.hbase.regionServers'), submenu: slaveItemsForHBase});
-    }
-
-    if (!!App.MapReduceService.find().content.length) {
-      var slaveItemsForMapReduce = this.getSlaveItemsTemplate('JOBTRACKER', 'TASKTRACKER');
-      slaveItemsForMapReduce.setEach('operationData.serviceName', 'MAPREDUCE');
-      slaveItemsForMapReduce.setEach('operationData.componentNameFormatted', Em.I18n.t('dashboard.services.mapreduce.taskTrackers'));
-      submenu.push({label: Em.I18n.t('dashboard.services.mapreduce.taskTrackers'), submenu: slaveItemsForMapReduce});
-    }
-
-    if (!!App.Service.find().filterProperty('serviceName', 'STORM').length) {
-      var slaveItemsForStorm = this.getSlaveItemsTemplate('SUPERVISOR', 'SUPERVISOR');
-      slaveItemsForStorm.setEach('operationData.serviceName', 'STORM');
-      slaveItemsForStorm.setEach('operationData.componentNameFormatted', Em.I18n.t('dashboard.services.storm.supervisors'));
-      submenu.push({label: Em.I18n.t('dashboard.services.storm.supervisors'), submenu: slaveItemsForStorm});
-    }
-
-    submenu.forEach(function(item) {
-      item.submenu.forEach(function(subitem) {
-        subitem.operationData.selection = selection;
-      });
-    });
-    return submenu;
-  },
+    /**
+     * commonOperationView is used for third-level menu items
+     * for all operations for host
+     */
+    operationView: Em.View.extend({
+      tagName: 'li',
 
-  /**
-   * Menu-items for Hosts table
-   * @type {Object}
-   */
-  menuItems: function() {
-    return {
-      s: {label: Em.I18n.t('hosts.table.menu.l1.selectedHosts'), submenu: this.getSubMenuItemsTemplate('s')},
-      f: {label: Em.I18n.t('hosts.table.menu.l1.filteredHosts'), submenu: this.getSubMenuItemsTemplate('f')},
-      a: {label: Em.I18n.t('hosts.table.menu.l1.allHosts'), submenu: this.getSubMenuItemsTemplate('a')}
-    };
-  }.property('App.router.clusterController.isLoaded')
+      /**
+       * click function use
+       * App.MainHostView as a thirl level parent
+       * and runs it's function
+       */
+      click: function () {
+        this.get('parentView.parentView.parentView').bulkOperationConfirm(this.get('content'), this.get('selection'));
+      }
+    })
+  })
 });