Browse Source

AMBARI-3253. Provide UI to delete host from Ambari. (srimanth)

Srimanth Gunturi 12 years ago
parent
commit
aa8de89648

+ 2 - 1
ambari-web/app/config.js

@@ -58,7 +58,8 @@ App.supports = {
   hue: false,
   ldapGroupMapping: false,
   localRepositories: false,
-  highAvailability: true
+  highAvailability: true,
+  deleteHost: false
 };
 
 if (App.enableExperimental) {

+ 365 - 92
ambari-web/app/controllers/main/host/details.js

@@ -78,8 +78,8 @@ App.MainHostDetailsController = Em.Controller.extend({
 
       error: function (request, ajaxOptions, error) {
         //do something
-        callback(null);
-        console.log('error on change component host status')
+        console.log('error on change component host status');
+        App.ajax.defaultErrorHandler(request, url, method);
       },
 
       statusCode: require('data/statusCodes')
@@ -94,38 +94,130 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     App.showConfirmationPopup(function() {
       var component = event.context;
-
-      self.sendCommandToServer('/hosts/' + self.get('content.hostName') + '/host_components/' + component.get('componentName').toUpperCase(),{
-        RequestInfo : {
-          "context" : Em.I18n.t('requestInfo.startHostComponent') + " " + component.get('displayName')
-        },
-        Body:{
-          HostRoles:{
-            state: 'STARTED'
-          }
+      var context = Em.I18n.t('requestInfo.startHostComponent') + " " + component.get('displayName');
+      self.sendStartComponentCommand(component, context);
+    });
+  },
+  
+  /**
+   * PUTs a command to server to start a component. If no 
+   * specific component is provided, all components are started.
+   * @param component  When <code>null</code> all startable components are started. 
+   * @param context  Context under which this command is beign sent. 
+   */
+  sendStartComponentCommand: function(component, context) {
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    var dataToSend = {
+      RequestInfo : {
+        "context" : context
+      },
+      Body:{
+        HostRoles:{
+          state: 'STARTED'
         }
-      }, 'PUT',
-        function(requestId){
-
-        if(!requestId){
-          return;
+      }
+    };
+    if (component === null) {
+      var allComponents = this.get('content.hostComponents');
+      var startable = [];
+      allComponents.forEach(function (c) {
+        if (c.get('isMaster') || c.get('isSlave')) {
+          startable.push(c.get('componentName'));
         }
+      });
+      dataToSend.RequestInfo.query = "HostRoles/component_name.in(" + startable.join(',') + ")";
+    }
+    this.sendCommandToServer(url, dataToSend, 'PUT',
+      function(requestId){
+
+      if(!requestId){
+        return;
+      }
 
-        console.log('Send request for STARTING successfully');
+      console.log('Send request for STARTING successfully');
 
-        if (App.testMode) {
+      if (App.testMode) {
+        if(component === null){
+          var allComponents = this.get('content.hostComponents');
+          allComponents.forEach(function(component){
+            component.set('workStatus', App.HostComponentStatus.stopping);
+            setTimeout(function(){
+              component.set('workStatus', App.HostComponentStatus.stopped);
+            },App.testModeDelayForActions);
+          });
+        } else {
           component.set('workStatus', App.HostComponentStatus.starting);
           setTimeout(function(){
             component.set('workStatus', App.HostComponentStatus.started);
           },App.testModeDelayForActions);
-        } else {
-          App.router.get('clusterController').loadUpdatedStatusDelayed(500);
         }
+      } else {
+        App.router.get('clusterController').loadUpdatedStatusDelayed(500);
+      }
+      App.router.get('backgroundOperationsController').showPopup();
+    });
+  },
 
-        App.router.get('backgroundOperationsController').showPopup();
-
-      });
+  /**
+   * Deletes the given host component, or all host components.
+   * 
+   * @param component  When <code>null</code> all host components are deleted.
+   * @return  <code>null</code> when components get deleted.
+   *          <code>{xhr: XhrObj, url: "http://", method: "DELETE"}</code> 
+   *          when components failed to get deleted. 
+   */
+  _doDeleteHostComponent: function(component) {
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    url = App.apiPrefix + '/clusters/' + App.router.getClusterName() + url;
+    var deleted = null;
+    $.ajax({
+      type: 'DELETE',
+      url: url,
+      timeout: App.timeout,
+      async: false,
+      success: function (data) {
+        deleted = null;
+        // If ZooKeeper Server component was removed, 
+        // restart ZooKeeper service.
+        /*
+         * Commenting it out as user can restart service
+         * whenever they want. We mention in message.
+        if (component.get('componentName') === 'ZOOKEEPER_SERVER') {
+          App.ajax.send({
+            'name': 'service.item.start_stop',
+            'sender': this,
+            'data': {
+              'requestInfo': 'Stop ZooKeeper',
+              'serviceName': 'ZOOKEEPER',
+              'state': 'INSTALLED'
+            },
+            'callback': function() {
+              App.ajax.send({
+                'name': 'service.item.start_stop',
+                'sender': this,
+                'data': {
+                  'requestInfo': 'Start ZooKeeper',
+                  'serviceName': 'ZOOKEEPER',
+                  'state': 'STARTED'
+                }
+              });
+            }
+          });
+        }*/
+      },
+      error: function (xhr, textStatus, errorThrown) {
+        console.log('Error deleting host component');
+        console.log(textStatus);
+        console.log(errorThrown);
+        deleted = {xhr: xhr, url: url, method: 'DELETE'};
+      },
+      statusCode: require('data/statusCodes')
     });
+    return deleted;
   },
 
   /**
@@ -176,36 +268,68 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     App.showConfirmationPopup(function() {
       var component = event.context;
-      self.sendCommandToServer('/hosts/' + self.get('content.hostName') + '/host_components/' + component.get('componentName').toUpperCase(),{
-        RequestInfo : {
-          "context" : Em.I18n.t('requestInfo.stopHostComponent')+ " " + component.get('displayName')
-        },
-        Body:{
-          HostRoles:{
-            state: 'INSTALLED'
-          }
+      var context = Em.I18n.t('requestInfo.stopHostComponent')+ " " + component.get('displayName');
+      self.sendStopComponentCommand(component, context);
+    });
+  },
+  
+  /**
+   * PUTs a command to server to stop a component. If no 
+   * specific component is provided, all components are stopped.
+   * @param component  When <code>null</code> all components are stopped. 
+   * @param context  Context under which this command is beign sent. 
+   */
+  sendStopComponentCommand: function(component, context){
+    var url = component !== null ? 
+        '/hosts/' + this.get('content.hostName') + '/host_components/' + component.get('componentName').toUpperCase() : 
+        '/hosts/' + this.get('content.hostName') + '/host_components';
+    var dataToSend = {
+      RequestInfo : {
+        "context" : context
+      },
+      Body:{
+        HostRoles:{
+          state: 'INSTALLED'
         }
-      }, 'PUT',
-        function(requestId){
-        if(!requestId){
-          return;
+      }
+    };
+    if (component === null) {
+      var allComponents = this.get('content.hostComponents');
+      var startable = [];
+      allComponents.forEach(function (c) {
+        if (c.get('isMaster') || c.get('isSlave')) {
+          startable.push(c.get('componentName'));
         }
+      });
+      dataToSend.RequestInfo.query = "HostRoles/component_name.in(" + startable.join(',') + ")";
+    }
+    this.sendCommandToServer( url, dataToSend, 'PUT',
+      function(requestId){
+      if(!requestId){
+        return;
+      }
 
-        console.log('Send request for STOPPING successfully');
+      console.log('Send request for STOPPING successfully');
 
-        if (App.testMode) {
+      if (App.testMode) {
+        if(component === null){
+          var allComponents = this.get('content.hostComponents');
+          allComponents.forEach(function(component){
+            component.set('workStatus', App.HostComponentStatus.stopping);
+            setTimeout(function(){
+              component.set('workStatus', App.HostComponentStatus.stopped);
+            },App.testModeDelayForActions);
+          });
+        } else {
           component.set('workStatus', App.HostComponentStatus.stopping);
           setTimeout(function(){
             component.set('workStatus', App.HostComponentStatus.stopped);
           },App.testModeDelayForActions);
-        } else {
-          App.router.get('clusterController').loadUpdatedStatusDelayed(500);
         }
-
-        App.router.get('backgroundOperationsController').showPopup();
-
-      });
-
+      } else {
+        App.router.get('clusterController').loadUpdatedStatusDelayed(500);
+      }
+      App.router.get('backgroundOperationsController').showPopup();
     });
   },
 
@@ -217,6 +341,7 @@ App.MainHostDetailsController = Em.Controller.extend({
     var self = this;
     var component = event.context;
     var componentName = component.get('componentName').toUpperCase().toString();
+    var subComponentNames = component.get('subComponentNames');
     var displayName = component.get('displayName');
 
     var securityEnabled = App.router.get('mainAdminSecurityController').getUpdatedSecurityStatus();
@@ -227,19 +352,40 @@ App.MainHostDetailsController = Em.Controller.extend({
       }, Em.I18n.t('hosts.host.addComponent.securityNote').format(componentName,self.get('content.hostName')));
     }
     else {
+      var dn = displayName;
+      if (subComponentNames !== null && subComponentNames.length > 0) {
+        var dns = [];
+        subComponentNames.forEach(function(scn){
+          dns.push(App.format.role(scn));
+        });
+        dn += " ("+dns.join(", ")+")";
+      }
+      var dialogContent = 
+        [Em.I18n.t('hosts.host.addComponent.msg').format(dn) + "<br><br>",
+        '{{t hosts.host.addComponent.note}}'];
       App.ModalPopup.show({
         primary: Em.I18n.t('yes'),
         secondary: Em.I18n.t('no'),
         header: Em.I18n.t('popup.confirmation.commonHeader'),
         bodyClass: Ember.View.extend({
-          template: Ember.Handlebars.compile([
-            '{{t hosts.delete.popup.body}}<br><br>',
-            '{{t hosts.host.addComponent.note}}'
-          ].join(''))
+          template: Ember.Handlebars.compile(dialogContent.join(''))
         }),
         onPrimary: function () {
           this.hide();
-          self.primary(component);
+          if (component.get('componentName') === 'CLIENTS') {
+            // Clients component has many sub-components which 
+            // need to be installed.
+            var scs = component.get('subComponentNames');
+            scs.forEach(function (sc) {
+              var c = Em.Object.create({
+                displayName: App.format.role(sc),
+                componentName: sc
+              });
+              self.primary(c);
+            });
+          } else {
+            self.primary(component);
+          }
         }
       });
     }
@@ -483,43 +629,116 @@ App.MainHostDetailsController = Em.Controller.extend({
       App.router.get('backgroundOperationsController').showPopup();
     });
   },
+  
+  doAction: function(option) {
+    switch (option.context.action) {
+      case "deleteHost":
+        this.validateAndDeleteHost();
+        break;
+      case "startAllComponents":
+        this.doStartAllComponents();
+        break;
+      case "stopAllComponents":
+        this.doStopAllComponents();
+        break;
+      default:
+        break;
+    }
+  },
+  
+  doStartAllComponents: function() {
+    var self = this;
+    var components = this.get('content.hostComponents');
+    var componentsLength = components == null ? 0 : components.get('length');
+    if (componentsLength > 0) {
+      App.showConfirmationPopup(function() {
+        self.sendStartComponentCommand(null, 
+            Em.I18n.t('hosts.host.maintainance.startAllComponents.context'));
+      });
+    }
+  },
+  
+  doStopAllComponents: function() {
+    var self = this;
+    var components = this.get('content.hostComponents');
+    var componentsLength = components == null ? 0 : components.get('length');
+    if (componentsLength > 0) {
+      App.showConfirmationPopup(function() {
+        self.sendStopComponentCommand(null, 
+            Em.I18n.t('hosts.host.maintainance.stopAllComponents.context'));
+      });
+    }
+  },
 
   /**
    * Deletion of hosts not supported for this version
-   *
-   * validateDeletion: function () { var slaveComponents = [ 'DataNode',
-   * 'TaskTracker', 'RegionServer' ]; var masterComponents = []; var
-   * workingComponents = [];
-   *
-   * var components = this.get('content.components');
-   * components.forEach(function (cInstance) { var cName =
-   * cInstance.get('componentName'); if (slaveComponents.contains(cName)) { if
-   * (cInstance.get('workStatus') === App.HostComponentStatus.stopped &&
-   * !cInstance.get('decommissioned')) { workingComponents.push(cName); } } else {
-   * masterComponents.push(cName); } }); // debugger; if
-   * (workingComponents.length || masterComponents.length) {
-   * this.raiseWarning(workingComponents, masterComponents); } else {
-   * this.deleteButtonPopup(); } },
    */
-
-  raiseWarning: function (workingComponents, masterComponents) {
+   validateAndDeleteHost: function () {
+     if (!App.supports.deleteHost) {
+       return;
+     }
+     var stoppedStates = [App.HostComponentStatus.stopped, 
+                          App.HostComponentStatus.install_failed, 
+                          App.HostComponentStatus.upgrade_failed];
+     var masterComponents = [];
+     var runningComponents = [];
+     var unknownComponents = [];
+     var nonDeletableComponents = [];
+     var components = this.get('content.hostComponents');
+     if (components!=null && components.get('length')>0){
+       components.forEach(function (cInstance) { 
+         var workStatus = cInstance.get('workStatus');
+         if (cInstance.get('isMaster') && !cInstance.get('isDeletable')) {
+           masterComponents.push(cInstance.get('displayName'));
+         }
+         if (stoppedStates.indexOf(workStatus) < 0) {
+           runningComponents.push(cInstance.get('displayName'));
+         }
+         if (!cInstance.get('isDeletable')) {
+           nonDeletableComponents.push(cInstance.get('displayName'));
+         }
+         if (workStatus === App.HostComponentStatus.unknown) {
+           unknownComponents.push(cInstance.get('displayName'));
+         }
+       });
+     }
+     if (masterComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += Em.I18n.t('hosts.cant.do.popup.masterList.body').format(masterComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += masterComponents.join(", ");
+       bodyHtml += "</i>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     } else if (nonDeletableComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += Em.I18n.t('hosts.cant.do.popup.nonDeletableList.body').format(nonDeletableComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += nonDeletableComponents.join(", ");
+       bodyHtml += "</i>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     } else if(runningComponents.length > 0) {
+       var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+       bodyHtml += Em.I18n.t('hosts.cant.do.popup.runningList.body').format(runningComponents.length);
+       bodyHtml += "</p><i>";
+       bodyHtml += runningComponents.join(", ");
+       bodyHtml += "</i><br><br><p>";
+       bodyHtml += Em.I18n.t('hosts.cant.do.popup.runningList.body.end');
+       bodyHtml += "</p>";
+       this.raiseDeleteComponentsError(bodyHtml);
+       return;
+     }
+     this._doDeleteHost(unknownComponents);
+  },
+  
+  raiseDeleteComponentsError: function (bodyHtml) {
     var self = this;
-    var masterString = '';
-    var workingString = '';
-    if(masterComponents && masterComponents.length) {
-      var masterList = masterComponents.join(', ');
-      var ml_text = Em.I18n.t('hosts.cant.do.popup.masterList.body');
-      masterString = ml_text.format(masterList);
-    }
-    if(workingComponents && workingComponents.length) {
-      var workingList = workingComponents.join(', ');
-      var wl_text = Em.I18n.t('hosts.cant.do.popup.workingList.body');
-      workingString = wl_text.format(workingList);
-    }
     App.ModalPopup.show({
-      header: Em.I18n.t('hosts.cant.do.popup.header'),
+      header: Em.I18n.t('hosts.cant.do.popup.title'),
       html: true,
-      body: masterString + workingString,
+      encodeBody: false,
+      body: bodyHtml,
       primary: Em.I18n.t('ok'),
       secondary: null,
       onPrimary: function() {
@@ -531,19 +750,73 @@ App.MainHostDetailsController = Em.Controller.extend({
   /**
    * show confirmation popup to delete host
    */
-  deleteButtonPopup: function() {
+  _doDeleteHost: function(unknownComponents) {
     var self = this;
-    App.showConfirmationPopup(function(){
-      self.removeHost();
-    });
-  },
-
-  /**
-   * remove host and open hosts page
-   */
-  removeHost: function () {
-    App.router.get('mainHostController').checkRemoved(this.get('content.id'));
-    App.router.transitionTo('hosts');
+    var bodyHtml = "<p><i class=\"icon-warning-sign\"></i> ";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body').format("<i>"+this.get('content.publicHostName')+"</i>");
+    bodyHtml += "</p>";
+    if (unknownComponents!=null && unknownComponents.length > 0) {
+      bodyHtml += "<div class=\"alert\">";
+      bodyHtml += Em.I18n.t('hosts.delete.popup.unknownComponents') + "<br>";
+      bodyHtml += "<i>"
+      bodyHtml += unknownComponents.join(", ");
+      bodyHtml += "</i></div>";
+    }
+    bodyHtml += "<p>";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg1');
+    bodyHtml += "</p><p>";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg2');
+    bodyHtml += "</p><p>";
+    bodyHtml += "<span class=\"label label-important\">"+Em.I18n.t('common.important')+"</span>  ";
+    bodyHtml += Em.I18n.t('hosts.delete.popup.body.msg3');
+    bodyHtml += "</p>";
+    App.ModalPopup.show({
+      header: Em.I18n.t('hosts.delete.popup.title'),
+      html: true,
+      encodeBody: false,
+      body: bodyHtml,
+      primary: Em.I18n.t('ok'),
+      secondary: Em.I18n.t('common.cancel'),
+      onPrimary: function() {
+        var dialogSelf = this;
+        var allComponents = self.get('content.hostComponents');
+        var deleteError = null;
+        allComponents.forEach(function(component){
+          if (!deleteError) {
+            deleteError = self._doDeleteHostComponent(component);
+          }
+        });
+        if (!deleteError) {
+          var url = App.apiPrefix + '/clusters/' + App.router.getClusterName() + '/hosts/' + self.get('content.hostName');
+          $.ajax({
+            type: 'DELETE',
+            url: url,
+            timeout: App.timeout,
+            async: false,
+            success: function (data) {
+              dialogSelf.hide();
+              App.router.get('updateController').updateAll();
+              App.router.transitionTo('hosts.index');
+            },
+            error: function (xhr, textStatus, errorThrown) {
+              console.log('Error deleting host component');
+              console.log(textStatus);
+              console.log(errorThrown);
+              dialogSelf.hide();
+              xhr.responseText = "{\"message\": \"" + xhr.statusText + "\"}";
+              App.ajax.defaultErrorHandler(xhr, url, 'DELETE', xhr.status);
+            },
+            statusCode: require('data/statusCodes')
+          });
+        } else {
+          dialogSelf.hide();
+          deleteError.xhr.responseText = "{\"message\": \"" + deleteError.xhr.statusText + "\"}";
+          App.ajax.defaultErrorHandler(deleteError.xhr, deleteError.url, deleteError.method, deleteError.xhr.status);
+        }
+      },
+      onSecondary: function() {
+        this.hide();
+      }
+    })
   }
-
 })

+ 18 - 0
ambari-web/app/mappers/hosts_mapper.js

@@ -51,6 +51,24 @@ App.hostsMapper = App.QuickDataMapper.create({
   map: function (json) {
     if (json.items) {
       var result = this.parse(json.items);
+      var clientHosts = App.Host.find();
+      if (clientHosts != null && clientHosts.get('length') !== result.length) {
+        var serverHostIds = {};
+        result.forEach(function (host) {
+          serverHostIds[host.id] = host.id;
+        });
+        var hostsToDelete = [];
+        clientHosts.forEach(function (host) {
+          if (host !== null && !serverHostIds[host.get('hostName')]) {
+            // Delete old ones as new ones will be
+            // loaded by loadMany().
+            hostsToDelete.push(host);
+          }
+        });
+        hostsToDelete.forEach(function (host) {
+          host.deleteRecord();
+        });
+      }
       App.store.loadMany(this.get('model'), result);
     }
   },

+ 18 - 5
ambari-web/app/messages.js

@@ -147,6 +147,7 @@ Em.I18n.translations = {
   'common.persist.error' : 'Error in persisting web client state at ambari server:',
   'common.update.error' : 'Error in retrieving web client state from ambari server',
   'common.tags': 'Tags',
+  'common.important': 'Important',
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
   'requestInfo.startServices':'Start Services',
@@ -515,7 +516,7 @@ Em.I18n.translations = {
 
   'installer.step10.header':'Summary',
   'installer.step10.body':'Here is the summary of the install process.',
-  'installer.step10.nagiosRestartRequired':'<b>Important!</b> Restarting Nagios service is required for the alerts and notifications to work properly.  After clicking on the Complete button to dismiss this wizard, go to Services -> Nagios to restart the Nagios service.',
+  'installer.step10.nagiosRestartRequired':'<b>Important!</b> Restarting Nagios service is required for alerts and notifications to work properly.  After clicking on the Complete button to dismiss this wizard, go to Services -> Nagios to restart the Nagios service.',
   'installer.step10.hostsSummary':'The cluster consists of {0} hosts',
   'installer.step10.servicesSummary':'Installed and started services successfully on {0} new ',
   'installer.step10.warnings':' warnings',
@@ -1172,10 +1173,13 @@ Em.I18n.translations = {
   'hosts.host.summary.hostMetrics':'Host Metrics',
 
   'hosts.host.details.deleteHost':'Delete Host',
+  'hosts.host.details.startAllComponents':'Start All Components',
+  'hosts.host.details.stopAllComponents':'Stop All Components',
 
   'host.host.componentFilter.master':'Master Components',
   'host.host.componentFilter.slave':'Slave Components',
   'host.host.componentFilter.client':'Client Components',
+  'hosts.host.addComponent.msg':'Are you sure you want to add {0}?',
   'hosts.host.addComponent.note':'Note: After this component is installed, go to Services -> Nagios to restart the Nagios service.  This is required for the alerts and notifications to work properly.',
   'hosts.host.addComponent.securityNote':'You are running your cluster in secure mode. You must set up the keytab for {0} on {1} before you proceed. Otherwise, the component will not be able to start properly.',
   'hosts.host.datanode.decommission':'Decommission DataNode',
@@ -1193,14 +1197,23 @@ Em.I18n.translations = {
   'hosts.host.healthStatusCategory.orange': "Slave Down",
   'hosts.host.healthStatusCategory.yellow': "No Heartbeat",
   'hosts.host.alerts.label': 'Alerts',
+  'hosts.host.maintainance.stopAllComponents.context': 'Stop All Host Components',
+  'hosts.host.maintainance.startAllComponents.context': 'Start All Host Components',
   'hosts.host.alerts.st':'&nbsp;!&nbsp;',
   'hosts.decommission.popup.body':'Are you sure?',
   'hosts.decommission.popup.header':'Confirmation',
-  'hosts.delete.popup.body':'Are you sure?',
+  'hosts.delete.popup.body':'Are you sure you want to delete host {0}?',
+  'hosts.delete.popup.body.msg1':'This will remove the host from Ambari\'s management. Ambari will ignore any communications from this host.',
+  'hosts.delete.popup.body.msg2':'Installed bits of service components will not be removed from the system. Individual service components should not be restarted later to join the cluster. This will introduce inconsistencies in monitoring data.',
+  'hosts.delete.popup.body.msg3':'Nagios service should be restarted for alerts and notifications to work properly. ZooKeeper service should be restarted if any ZooKeeper components are removed. Go to the <i>Services</i> page to restart services.',
   'hosts.delete.popup.header':'Confirmation',
-  'hosts.cant.do.popup.header':'Operation not allowed',
-  'hosts.cant.do.popup.masterList.body':'You cannot delete this host because it is hosting following master services: {0}.',
-  'hosts.cant.do.popup.workingList.body':'You cannot delete this host because following slave services are not fully stopped or decommissioned: {0}.',
+  'hosts.delete.popup.title':'Delete Host',
+  'hosts.delete.popup.unknownComponents':'Components with unknown status:',
+  'hosts.cant.do.popup.title':'Delete Host Error',
+  'hosts.cant.do.popup.masterList.body':'Host with {0} master components cannot be deleted',
+  'hosts.cant.do.popup.nonDeletableList.body':'Deletion of the following {0} components is not supported. ',
+  'hosts.cant.do.popup.runningList.body':'Host cannot be deleted with the following {0} components running. ',
+  'hosts.cant.do.popup.runningList.body.end':'Stop the components before reattempting to delete host. Some components might need special actions performed before deletion from cluster. For example, DataNode has to be decommissioned before being deleted.',
   'hosts.add.header':'Add Host Wizard',
   'hosts.assignRack':'Assign Rack',
 

+ 24 - 0
ambari-web/app/models/host_component.js

@@ -76,6 +76,30 @@ App.HostComponent = DS.Model.extend({
         return false;
     }
   }.property('componentName'),
+  /**
+   * Only certain components can be deleted.
+   * They include some from master components, 
+   * some from slave components, and rest from 
+   * client components.
+   */
+  isDeletable: function() {
+    var canDelete = false;
+    switch (this.get('componentName')) {
+      case 'DATANODE':
+      case 'TASKTRACKER':
+      case 'ZOOKEEPER_SERVER':
+      case 'HBASE_REGIONSERVER':
+      case 'GANGLIA_MONITOR':
+      case 'NODEMANAGER':
+        canDelete = true;
+        break;
+      default:
+    }
+    if (!canDelete) {
+      canDelete = this.get('isClient');
+    }
+    return canDelete;
+  }.property('componentName', 'isClient'),
   /**
    * A host-component is decommissioning when it is in HDFS service's list of
    * decomNodes.

+ 20 - 18
ambari-web/app/templates/main/host/details.hbs

@@ -24,26 +24,28 @@
     <span class="label label-success alerts-count" {{action "showAlertsPopup" content target="App.router.mainHostController"}}>{{t hosts.host.alert.noAlerts}}</span>
   {{/if}}
   <div><a href="javascript:void(null)" data-toggle="modal" {{action back}}><i class="icon-arrow-left"></i>&nbsp;{{t common.back}}</a></div>
-<!--   {{#if App.isAdmin}} -->
-<!--   <div class="host-maintenance"> -->
-<!--     <div class="host-maintenance-btn btn-group display-inline-block"> -->
-<!--       <a class="btn dropdown-toggle" data-toggle="dropdown" href="#"> -->
-<!--         {{t services.service.actions.maintenance}} -->
-<!--         <span class="caret"></span> -->
-<!--       </a> -->
-<!--       <ul class="dropdown-menu"> -->
-<!--       dropdown menu links -->
-<!--         {{#each option in view.maintenance}} -->
-<!--         <li> -->
-<!--         <a {{action validateDeletion target="controller"}} href="#">{{option.label}}</a> -->
-<!--         </li> -->
-<!--         {{/each}} -->
-<!--       </ul> -->
-<!--     </div> -->
-<!--   </div> -->
-<!--   {{/if}} -->
   <div class="content">
     {{view App.MainHostMenuView}}
+    {{#if App.isAdmin}}
+      {{#if App.supports.deleteHost}}
+				<div class="service-button">
+				    <div class="btn-group display-inline-block">
+				      <a class="btn dropdown-toggle" data-toggle="dropdown" href="#">
+				        {{t services.service.actions.maintenance}}
+				        <span class="caret"></span>
+				      </a>
+				      <ul class="dropdown-menu">
+				        <!-- dropdown menu links -->
+				        {{#each option in view.maintenance}}
+				        <li {{bindAttr class="controller.isStopDisabled:disabled"}}>
+				          <a {{action "doAction" option target="controller" href=true}}>{{option.label}}</a>
+				        </li>
+				        {{/each}}
+				      </ul>
+				    </div>
+				</div>
+			{{/if}}
+	  {{/if}}
     {{outlet}}
   </div>
 </div>

+ 11 - 3
ambari-web/app/utils/ajax.js

@@ -812,6 +812,11 @@ var urls = {
       };
     }
   },
+  'admin.delete_host': {
+    'real': '/clusters/{clusterName}/hosts/{hostName}',
+    'mock': '',
+    'type': 'DELETE'
+  },
   'admin.security.all_configurations': {
     'real': '/clusters/{clusterName}/configurations?{urlParams}',
     'mock':'',
@@ -1220,8 +1225,9 @@ App.ajax = {
    * @jqXHR {jqXHR Object}
    * @url {string}
    * @method {String} Http method
+   * @showStatus {number} HTTP response code which should be shown. Default is 500.
    */
-  defaultErrorHandler: function(jqXHR,url,method) {
+  defaultErrorHandler: function(jqXHR,url,method,showStatus) {
     method = method || 'GET';
     var self = this;
     var api = " received on " + method + " method for API: " + url;
@@ -1231,12 +1237,14 @@ App.ajax = {
       var message = json.message;
     } catch (err) {
     }
-
+    if (showStatus === null) {
+      showStatus = 500;
+    }
     if (message === undefined) {
       showMessage = false;
     }
     var statusCode = jqXHR.status + " status code";
-    if (jqXHR.status === 500 && !this.modalPopup) {
+    if (jqXHR.status === showStatus && !this.modalPopup) {
       this.modalPopup = App.ModalPopup.show({
         header: jqXHR.statusText,
         secondary: false,

+ 4 - 1
ambari-web/app/views/main/host/details.js

@@ -27,7 +27,10 @@ App.MainHostDetailsView = Em.View.extend({
   }.property('App.router.mainHostDetailsController.content'),
 
   maintenance: function(){
-    var options = [{action: 'deleteHost', 'label': this.t('hosts.host.details.deleteHost')}];
+    var options = [
+         {action: 'startAllComponents', 'label': this.t('hosts.host.details.startAllComponents')},
+         {action: 'stopAllComponents', 'label': this.t('hosts.host.details.stopAllComponents')},
+         {action: 'deleteHost', 'label': this.t('hosts.host.details.deleteHost')}];
     return options;
   }.property('controller.content'),
   didInsertElement: function() {

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

@@ -123,13 +123,69 @@ App.MainHostSummaryView = Em.View.extend({
 
   addableComponentObject: Em.Object.extend({
     componentName: '',
+    subComponentNames: null,
     displayName: function () {
+      if (this.get('componentName') === 'CLIENTS') {
+        return this.t('common.clients');
+      }
       return App.format.role(this.get('componentName'));
     }.property('componentName')
   }),
   isAddComponent: function () {
     return this.get('content.healthClass') !== 'health-status-DEAD-YELLOW';
   }.property('content.healthClass'),
+  
+  installableClientComponents: function() {
+    var installableClients = [];
+    if (!App.supports.deleteHost) {
+      return installableClients;
+    }
+    App.Service.find().forEach(function(svc){
+      switch(svc.get('serviceName')){
+        case 'PIG':
+          installableClients.push('PIG');
+          break;
+        case 'SQOOP':
+          installableClients.push('SQOOP');
+          break;
+        case 'HCAT':
+          installableClients.push('HCAT');
+          break;
+        case 'HDFS':
+          installableClients.push('HDFS_CLIENT');
+          break;
+        case 'OOZIE':
+          installableClients.push('OOZIE_CLIENT');
+          break;
+        case 'ZOOKEEPER':
+          installableClients.push('ZOOKEEPER_CLIENT');
+          break;
+        case 'HIVE':
+          installableClients.push('HIVE_CLIENT');
+          break;
+        case 'HBASE':
+          installableClients.push('HBASE_CLIENT');
+          break;
+        case 'YARN':
+          installableClients.push('YARN_CLIENT');
+          break;
+        case 'MAPREDUCE':
+          installableClients.push('MAPREDUCE_CLIENT');
+          break;
+        case 'MAPREDUCE2':
+          installableClients.push('MAPREDUCE2_CLIENT');
+          break;
+      }
+    });
+    this.get('content.hostComponents').forEach(function (component) {
+      var index = installableClients.indexOf(component.get('componentName'));
+      if (index > -1) {
+        installableClients.splice(index, 1);
+      }
+    }, this);
+    return installableClients;
+  }.property('content', 'content.hostComponents.length', 'App.Service', 'App.supports.deleteHost'),
+  
   addableComponents: function () {
     var components = [];
     var services = App.Service.find();
@@ -138,7 +194,9 @@ App.MainHostSummaryView = Em.View.extend({
     var regionServerExists = false;
     var zookeeperServerExists = false;
     var nodeManagerExists = false;
-
+    
+    var installableClients = this.get('installableClientComponents');
+    
     this.get('content.hostComponents').forEach(function (component) {
       switch (component.get('componentName')) {
         case 'DATANODE':
@@ -174,8 +232,11 @@ App.MainHostSummaryView = Em.View.extend({
     if (!nodeManagerExists && services.findProperty('serviceName', 'YARN')) {
       components.pushObject(this.addableComponentObject.create({ 'componentName': 'NODEMANAGER' }));
     }
+    if (installableClients.length > 0) {
+      components.pushObject(this.addableComponentObject.create({ 'componentName': 'CLIENTS', subComponentNames: installableClients }));
+    }
     return components;
-  }.property('content', 'content.hostComponents.length'),
+  }.property('content', 'content.hostComponents.length', 'installableClientComponents'),
 
   ComponentView: Em.View.extend({
     content: null,