Updated Branches: refs/heads/trunk 67b7c76fd -> e5d440b4f
AMBARI-4390. Add ability to abort Rolling Restart. (srimanth) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e5d440b4 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e5d440b4 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e5d440b4 Branch: refs/heads/trunk Commit: e5d440b4f1eb342e0bcd92b792fdc4240a44395f Parents: 67b7c76 Author: Srimanth Gunturi <[email protected]> Authored: Wed Jan 22 15:26:51 2014 -0800 Committer: Srimanth Gunturi <[email protected]> Committed: Thu Jan 23 11:51:03 2014 -0800 ---------------------------------------------------------------------- .../global/background_operations_controller.js | 75 +++++++++++++++++--- ambari-web/app/controllers/main/service.js | 8 ++- ambari-web/app/controllers/main/service/item.js | 4 +- ambari-web/app/messages.js | 3 + ambari-web/app/styles/application.less | 7 ++ .../templates/common/host_progress_popup.hbs | 18 +++++ ambari-web/app/utils/ajax.js | 13 ++++ .../app/utils/batch_scheduled_requests.js | 75 +++++++++++++++++++- ambari-web/app/utils/host_progress_popup.js | 69 +++++++++++++++++- 9 files changed, 255 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/controllers/global/background_operations_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/global/background_operations_controller.js b/ambari-web/app/controllers/global/background_operations_controller.js index 04a5339..9905cef 100644 --- a/ambari-web/app/controllers/global/background_operations_controller.js +++ b/ambari-web/app/controllers/global/background_operations_controller.js @@ -178,7 +178,9 @@ App.BackgroundOperationsController = Em.Controller.extend({ hostsMap: {}, tasks: [], dependentService: requestParams.dependentService, - previousTaskStatusMap: {} + sourceRequestScheduleId: request.Requests.source_schedule_id, + previousTaskStatusMap: {}, + contextCommand: requestParams.contextCommand }); self.get("services").unshift(rq); } @@ -202,15 +204,24 @@ App.BackgroundOperationsController = Em.Controller.extend({ parseRequestContext: function (requestContext) { var parsedRequestContext; var service; - var command; + var contextCommand; if (requestContext) { - if (requestContext.indexOf("_PARSE_") !== -1) { - command = requestContext.split('.')[1]; - service = requestContext.split('.')[2]; - if (service === 'ALL_SERVICES') { - parsedRequestContext = Em.I18n.t("requestInfo." + command.toLowerCase()).format(Em.I18n.t('common.allServices')); - } else { - parsedRequestContext = Em.I18n.t("requestInfo." + command.toLowerCase()).format(App.Service.DisplayNames[service]); + if (requestContext.indexOf(App.BackgroundOperationsController.CommandContexts.PREFIX) !== -1) { + var contextSplits = requestContext.split('.'); + contextCommand = contextSplits[1]; + service = contextSplits[2]; + switch(contextCommand){ + case "STOP": + case "START": + if (service === 'ALL_SERVICES') { + parsedRequestContext = Em.I18n.t("requestInfo." + contextCommand.toLowerCase()).format(Em.I18n.t('common.allServices')); + } else { + parsedRequestContext = Em.I18n.t("requestInfo." + contextCommand.toLowerCase()).format(App.Service.DisplayNames[service]); + } + break; + case "ROLLING-RESTART": + parsedRequestContext = Em.I18n.t("rollingrestart.rest.context").format(App.format.role(service), contextSplits[3], contextSplits[4]); + break; } } else { parsedRequestContext = requestContext; @@ -220,7 +231,8 @@ App.BackgroundOperationsController = Em.Controller.extend({ } return { requestContext: parsedRequestContext, - dependentService: service + dependentService: service, + contextCommand: contextCommand } }, @@ -246,3 +258,46 @@ App.BackgroundOperationsController = Em.Controller.extend({ } }); + +/** + * Each background operation has a context in which it operates. + * Generally these contexts are fixed messages. However, we might + * want to associate semantics to this context - like showing, disabling + * buttons when certain operations are in progress. + * + * To make this possible we have command contexts where the context + * is not a human readable string, but a pattern indicating the command + * it is running. When UI shows these, they are translated into human + * readable strings. + * + * General pattern of context names is "_PARSE_.{COMMAND}.{ID}[.{Additional-Data}...]" + */ +App.BackgroundOperationsController.CommandContexts = { + PREFIX : "_PARSE_", + /** + * Stops all services + */ + STOP_ALL_SERVICES : "_PARSE_.STOP.ALL_SERVICES", + /** + * Starts all services + */ + START_ALL_SERVICES : "_PARSE_.START.ALL_SERVICES", + /** + * Starts service indicated by serviceID. + * @param {String} serviceID Parameter {0}. Example: HDFS + */ + START_SERVICE : "_PARSE_.START.{0}", + /** + * Stops service indicated by serviceID. + * @param {String} serviceID Parameter {0}. Example: HDFS + */ + STOP_SERVICE : "_PARSE_.STOP.{0}", + /** + * Performs rolling restart of componentID in batches. + * This context is the batchNumber batch out of totalBatchCount batches. + * @param {String} componentID Parameter {0}. Example "DATANODE" + * @param {Number} batchNumber Parameter {1}. Batch number of this batch. Example 3. + * @param {Number} totalBatchCount Parameter {2}. Total number of batches. Example 10. + */ + ROLLING_RESTART : "_PARSE_.ROLLING-RESTART.{0}.{1}.{2}" +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/controllers/main/service.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service.js b/ambari-web/app/controllers/main/service.js index 766fee3..27c26e2 100644 --- a/ambari-web/app/controllers/main/service.js +++ b/ambari-web/app/controllers/main/service.js @@ -121,9 +121,13 @@ App.MainServiceController = Em.ArrayController.extend({ allServicesCall: function(state) { var data; if (state == 'stopAllService') { - data = '{"RequestInfo": {"context" :"_PARSE_.STOP.ALL_SERVICES"}, "Body": {"ServiceInfo": {"state": "INSTALLED"}}}'; + data = '{"RequestInfo": {"context" :"' + + App.BackgroundOperationsController.CommandContexts.STOP_ALL_SERVICES + + '"}, "Body": {"ServiceInfo": {"state": "INSTALLED"}}}'; } else { - data = '{"RequestInfo": {"context" :"_PARSE_.START.ALL_SERVICES"}, "Body": {"ServiceInfo": {"state": "STARTED"}}}'; + data = '{"RequestInfo": {"context" :"' + + App.BackgroundOperationsController.CommandContexts.START_ALL_SERVICES + + '"}, "Body": {"ServiceInfo": {"state": "STARTED"}}}'; } App.ajax.send({ http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/controllers/main/service/item.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/item.js b/ambari-web/app/controllers/main/service/item.js index 91e353c..b1c7b27 100644 --- a/ambari-web/app/controllers/main/service/item.js +++ b/ambari-web/app/controllers/main/service/item.js @@ -103,9 +103,9 @@ App.MainServiceItemController = Em.Controller.extend({ startStopPopupPrimary: function (serviceHealth) { var requestInfo = ""; if (serviceHealth == "STARTED") { - requestInfo = '_PARSE_.START.' + this.get('content.serviceName'); + requestInfo = App.BackgroundOperationsController.CommandContexts.START_SERVICE.format(this.get('content.serviceName')); } else { - requestInfo = '_PARSE_.STOP.' + this.get('content.serviceName'); + requestInfo = App.BackgroundOperationsController.CommandContexts.STOP_SERVICE.format(this.get('content.serviceName')); } App.ajax.send({ http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index e41b8df..1bf7a52 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -202,6 +202,9 @@ Em.I18n.translations = { 'hostPopup.status.category.aborted':'Aborted ({0})', 'hostPopup.status.category.timedout':'Timedout ({0})', 'hostPopup.header.postFix':' Background Operations Running', + 'hostPopup.bgop.sourceRequestSchedule.running': 'Future operations of this batch request can be aborted', + 'hostPopup.bgop.sourceRequestSchedule.aborted': 'Future operations of this batch request have been aborted', + 'hostPopup.bgop.abort.rollingRestart': 'Abort Rolling Restart', 'question.sure':'Are you sure?', http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/styles/application.less ---------------------------------------------------------------------- diff --git a/ambari-web/app/styles/application.less b/ambari-web/app/styles/application.less index 30aabb6..50015c7 100644 --- a/ambari-web/app/styles/application.less +++ b/ambari-web/app/styles/application.less @@ -1443,6 +1443,13 @@ width:100%; cursor: pointer; } + .request-schedule-abort { + margin-top: 7px; + .btn { + margin-top: -5px; + } + } + .task-top-wrap { width: 100%; http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/templates/common/host_progress_popup.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/common/host_progress_popup.hbs b/ambari-web/app/templates/common/host_progress_popup.hbs index 7e43c9c..3827820 100644 --- a/ambari-web/app/templates/common/host_progress_popup.hbs +++ b/ambari-web/app/templates/common/host_progress_popup.hbs @@ -82,6 +82,24 @@ }} </div> </div> + {{#if view.isRequestSchedule}} + {{#if view.sourceRequestScheduleRunning}} + <div class="alert alert-info request-schedule-abort"> + {{t hostPopup.bgop.sourceRequestSchedule.running}} + <button type="button" class="btn btn-warning pull-right" + {{action doAbortRequestSchedule view.sourceRequestScheduleId target="view"}}> + {{view.requestScheduleAbortLabel}} + </button> + </div> + {{/if}} + {{#if view.sourceRequestScheduleAborted}} + <div class="alert alert-info request-schedule-abort"> + {{t hostPopup.bgop.sourceRequestSchedule.aborted}} + </div> + {{/if}} + {{/if}} + </div> + </div> <div id="host-info"> {{#if view.isHostEmptyList}} <div class="log-list-wrap">{{t hostPopup.noHostsToShow}}</div> http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/utils/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js index fb1ae7d..c7d73a1 100644 --- a/ambari-web/app/utils/ajax.js +++ b/ambari-web/app/utils/ajax.js @@ -1294,6 +1294,19 @@ var urls = { } } }, + 'request_schedule.delete': { + 'real': '/clusters/{clusterName}/request_schedules/{request_schedule_id}', + 'mock': '', + 'format' : function(data) { + return { + type : 'DELETE', + } + } + }, + 'request_schedule.get': { + 'real': '/clusters/{clusterName}/request_schedules/{request_schedule_id}', + 'mock': '', + }, 'restart.service.hostComponents' : { 'real' : '/clusters/{clusterName}/requests', 'mock' : '', http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/utils/batch_scheduled_requests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/batch_scheduled_requests.js b/ambari-web/app/utils/batch_scheduled_requests.js index bf9164f..33dad38 100644 --- a/ambari-web/app/utils/batch_scheduled_requests.js +++ b/ambari-web/app/utils/batch_scheduled_requests.js @@ -163,7 +163,6 @@ module.exports = { batchCount = Math.ceil(restartHostComponents.length / batchSize), sampleHostComponent = restartHostComponents.objectAt(0), componentName = sampleHostComponent.get('componentName'), - componentDisplayName = App.format.role(componentName), serviceName = sampleHostComponent.get('service.serviceName'); for ( var count = 0; count < batchCount; count++) { @@ -178,7 +177,7 @@ module.exports = { "uri" : App.apiPrefix + "/clusters/" + App.get('clusterName') + "/requests", "RequestBodyInfo" : { "RequestInfo" : { - "context" : Em.I18n.t('rollingrestart.rest.context').format(componentDisplayName, (count + 1), batchCount), + "context" : "_PARSE_.ROLLING-RESTART." + componentName + "." + (count + 1) + "." + batchCount, "command" : "RESTART", "service_name" : serviceName, "component_name" : componentName, @@ -258,5 +257,77 @@ module.exports = { }) }); } + }, + + /** + * Retrieves the latest information about a specific request schedule + * identified by 'requestScheduleId' + * + * @param {Number} + * requestScheduleId ID of the request schedule to get + * @param {Function} + * successCallback Called with request_schedule data from server. An + * empty object returned for invalid ID. + * @param {Function} + * errorCallback Optional error callback. Default behavior is to + * popup default error dialog. + */ + getRequestSchedule: function(requestScheduleId, successCallback, errorCallback) { + if (requestScheduleId != null && !isNaN(requestScheduleId) && requestScheduleId > -1) { + errorCallback = errorCallback ? errorCallback : defaultErrorCallback; + App.ajax.send({ + name : 'request_schedule.get', + sender : { + successCallbackFunction : function(data) { + successCallback(data); + }, + errorCallbackFunction : function(xhr, textStatus, error, opt) { + errorCallback(xhr, textStatus, error, opt); + } + }, + data : { + request_schedule_id : requestScheduleId, + }, + success : 'successCallbackFunction', + error : 'errorCallbackFunction' + }); + } else { + successCallback({}); + } + }, + + /** + * Attempts to abort a specific request schedule identified by 'requestScheduleId' + * + * @param {Number} + * requestScheduleId ID of the request schedule to get + * @param {Function} + * successCallback Called when request schedule successfully aborted + * @param {Function} + * errorCallback Optional error callback. Default behavior is to + * popup default error dialog. + */ + doAbortRequestSchedule: function(requestScheduleId, successCallback, errorCallback) { + if (requestScheduleId != null && !isNaN(requestScheduleId) && requestScheduleId > -1) { + errorCallback = errorCallback ? errorCallback : defaultErrorCallback; + App.ajax.send({ + name : 'request_schedule.delete', + sender : { + successCallbackFunction : function(data) { + successCallback(data); + }, + errorCallbackFunction : function(xhr, textStatus, error, opt) { + errorCallback(xhr, textStatus, error, opt); + } + }, + data : { + request_schedule_id : requestScheduleId, + }, + success : 'successCallbackFunction', + error : 'errorCallbackFunction' + }); + } else { + successCallback({}); + } } }; \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ambari/blob/e5d440b4/ambari-web/app/utils/host_progress_popup.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/host_progress_popup.js b/ambari-web/app/utils/host_progress_popup.js index e20b7da..fe921eb 100644 --- a/ambari-web/app/utils/host_progress_popup.js +++ b/ambari-web/app/utils/host_progress_popup.js @@ -17,6 +17,7 @@ */ var App = require('app'); +var batchUtils = require('utils/batch_scheduled_requests'); /** * App.HostPopup is for the popup that shows up upon clicking already-performed or currently-in-progress operations @@ -244,7 +245,9 @@ App.HostPopup = Em.Object.create({ icon: status[1], barColor: status[2], isInProgress: status[3], - barWidth: "width:" + service.progress + "%;" + barWidth: "width:" + service.progress + "%;", + sourceRequestScheduleId: service.get('sourceRequestScheduleId'), + contextCommand: service.get('contextCommand') }); allNewServices.push(newService); }); @@ -467,6 +470,10 @@ App.HostPopup = Em.Object.create({ isHostEmptyList: true, isTasksEmptyList: true, controller: this, + sourceRequestScheduleId: -1, + sourceRequestScheduleRunning: false, + sourceRequestScheduleAborted: false, + sourceRequestScheduleCommand: null, hosts: self.get('hosts'), services: self.get('servicesInfo'), @@ -722,8 +729,68 @@ App.HostPopup = Em.Object.create({ this.set('hosts', this.get('hosts').concat(servicesInfo.slice(50, servicesInfo.length))); }); } + // Determine if source request schedule is present + this.set('sourceRequestScheduleId', event.context.get("sourceRequestScheduleId")); + this.set('sourceRequestScheduleCommand', event.context.get('contextCommand')); + this.refreshRequestScheduleInfo(); }, + isRequestSchedule : function() { + var id = this.get('sourceRequestScheduleId'); + return id != null && !isNaN(id) && id > -1; + }.property('sourceRequestScheduleId'), + + refreshRequestScheduleInfo : function() { + var self = this; + var id = this.get('sourceRequestScheduleId'); + batchUtils.getRequestSchedule(id, function(data) { + if (data != null && data.RequestSchedule.status != null) { + switch (data.RequestSchedule.status) { + case 'DISABLED': + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', true); + break; + case 'COMPLETED': + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + break; + case 'SCHEDULED': + self.set('sourceRequestScheduleRunning', true); + self.set('sourceRequestScheduleAborted', false); + break; + } + } else { + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + } + }, function(xhr, textStatus, error, opt) { + console.log("Error getting request schedule information: ", textStatus, error, opt); + self.set('sourceRequestScheduleRunning', false); + self.set('sourceRequestScheduleAborted', false); + }); + }.observes('sourceRequestScheduleId'), + + /** + * Attempts to abort the current request schedule + */ + doAbortRequestSchedule: function(event){ + var self = this; + var id = event.context; + console.log("Aborting request schedule: ", id); + batchUtils.doAbortRequestSchedule(id, function(){ + self.refreshRequestScheduleInfo(); + }); + }, + + requestScheduleAbortLabel : function() { + var label = Em.I18n.t("common.abort"); + var command = this.get('sourceRequestScheduleCommand'); + if (command != null && "ROLLING-RESTART" == command) { + label = Em.I18n.t("hostPopup.bgop.abort.rollingRestart"); + } + return label; + }.property('sourceRequestScheduleCommand'), + /** * Onclick handler for selected Host * @param event
