Updated Branches: refs/heads/trunk 59b24e512 -> aa8de8964
AMBARI-3253. Provide UI to delete host from Ambari. (srimanth) Project: http://git-wip-us.apache.org/repos/asf/incubator-ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-ambari/commit/aa8de896 Tree: http://git-wip-us.apache.org/repos/asf/incubator-ambari/tree/aa8de896 Diff: http://git-wip-us.apache.org/repos/asf/incubator-ambari/diff/aa8de896 Branch: refs/heads/trunk Commit: aa8de8964814515780671f4966ee4855c56ef1e8 Parents: 59b24e5 Author: Srimanth Gunturi <[email protected]> Authored: Mon Sep 16 16:10:56 2013 -0700 Committer: Srimanth Gunturi <[email protected]> Committed: Mon Sep 16 16:13:30 2013 -0700 ---------------------------------------------------------------------- ambari-web/app/config.js | 3 +- ambari-web/app/controllers/main/host/details.js | 457 +++++++++++++++---- ambari-web/app/mappers/hosts_mapper.js | 18 + ambari-web/app/messages.js | 23 +- ambari-web/app/models/host_component.js | 24 + ambari-web/app/templates/main/host/details.hbs | 38 +- ambari-web/app/utils/ajax.js | 14 +- ambari-web/app/views/main/host/details.js | 5 +- ambari-web/app/views/main/host/summary.js | 65 ++- 9 files changed, 525 insertions(+), 122 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/config.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/config.js b/ambari-web/app/config.js index f8f1464..96a4cdd 100644 --- a/ambari-web/app/config.js +++ b/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) { http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/controllers/main/host/details.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/host/details.js b/ambari-web/app/controllers/main/host/details.js index 15e8c54..9cc852c 100644 --- a/ambari-web/app/controllers/main/host/details.js +++ b/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(); + } + }) } - }) \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/mappers/hosts_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/hosts_mapper.js b/ambari-web/app/mappers/hosts_mapper.js index bf1ed48..706281f 100644 --- a/ambari-web/app/mappers/hosts_mapper.js +++ b/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); } }, http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 44da0bf..600e053 100644 --- a/ambari-web/app/messages.js +++ b/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':' ! ', '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', http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/models/host_component.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js index ea33df9..7c99a3d 100644 --- a/ambari-web/app/models/host_component.js +++ b/ambari-web/app/models/host_component.js @@ -77,6 +77,30 @@ App.HostComponent = DS.Model.extend({ } }.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. */ http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/templates/main/host/details.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/host/details.hbs b/ambari-web/app/templates/main/host/details.hbs index b594486..51e1200 100644 --- a/ambari-web/app/templates/main/host/details.hbs +++ b/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> {{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> http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/utils/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js index ad90016..19dd80a 100644 --- a/ambari-web/app/utils/ajax.js +++ b/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, http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/views/main/host/details.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/details.js b/ambari-web/app/views/main/host/details.js index 677eab6..4738adc 100644 --- a/ambari-web/app/views/main/host/details.js +++ b/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() { http://git-wip-us.apache.org/repos/asf/incubator-ambari/blob/aa8de896/ambari-web/app/views/main/host/summary.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/summary.js b/ambari-web/app/views/main/host/summary.js index 8c53f6b..0260e7c 100644 --- a/ambari-web/app/views/main/host/summary.js +++ b/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,
