/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var App = require('app'); App.BackgroundOperationsController = Em.Controller.extend({ name: 'backgroundOperationsController', /** * Whether we need to refresh background operations or not */ isWorking : false, allOperationsCount : 0, /** * For host component popup */ services:[], serviceTimestamp: null, /** * Possible levels: * REQUESTS_LIST * HOSTS_LIST * TASKS_LIST * TASK_DETAILS */ levelInfo: Em.Object.create({ name: 'REQUESTS_LIST', requestId: null, taskId: null }), /** * Start polling, when isWorking become true */ startPolling: function(){ if(this.get('isWorking')){ this.requestMostRecent(); App.updater.run(this, 'requestMostRecent', 'isWorking', App.bgOperationsUpdateInterval); } }.observes('isWorking'), /** * Get requests data from server * @param callback */ requestMostRecent: function (callback) { var queryParams = this.getQueryParams(); App.ajax.send({ 'name': queryParams.name, 'sender': this, 'success': queryParams.successCallback, 'callback': callback, 'data': queryParams.data }); return !this.isInitLoading(); }, /** * indicate whether data for current level has already been loaded or not * @return {Boolean} */ isInitLoading: function () { var levelInfo = this.get('levelInfo'); var request = this.get('services').findProperty('id', levelInfo.get('requestId')); if (levelInfo.get('name') === 'HOSTS_LIST') { return !!(request && App.isEmptyObject(request.get('hostsMap'))); } return false; }, /** * construct params of ajax query regarding displayed level */ getQueryParams: function () { var levelInfo = this.get('levelInfo'); var count = App.db.getBGOOperationsCount(); var result = { name: 'background_operations.get_most_recent', successCallback: 'callBackForMostRecent', data: { 'operationsCount': count } }; if (levelInfo.get('name') === 'TASK_DETAILS' && !App.get('testMode')) { result.name = 'background_operations.get_by_task'; result.successCallback = 'callBackFilteredByTask'; result.data = { 'taskId': levelInfo.get('taskId'), 'requestId': levelInfo.get('requestId') }; } else if (levelInfo.get('name') === 'TASKS_LIST' || levelInfo.get('name') === 'HOSTS_LIST') { result.name = 'background_operations.get_by_request'; result.successCallback = 'callBackFilteredByRequest'; result.data = { 'requestId': levelInfo.get('requestId') }; } return result; }, /** * Push hosts and their tasks to request * @param data * @param ajaxQuery * @param params */ callBackFilteredByRequest: function (data, ajaxQuery, params) { var requestId = data.Requests.id; var requestInputs = data.Requests.inputs; var request = this.get('services').findProperty('id', requestId); var hostsMap = {}; var previousTaskStatusMap = request.get('previousTaskStatusMap'); var currentTaskStatusMap = {}; data.tasks.forEach(function (task) { var host = hostsMap[task.Tasks.host_name]; task.Tasks.request_id = requestId; task.Tasks.request_inputs = requestInputs; if (host) { host.logTasks.push(task); host.isModified = (host.isModified) ? true : previousTaskStatusMap[task.Tasks.id] !== task.Tasks.status; } else { hostsMap[task.Tasks.host_name] = { name: task.Tasks.host_name, publicName: task.Tasks.host_name, logTasks: [task], isModified: previousTaskStatusMap[task.Tasks.id] !== task.Tasks.status }; } currentTaskStatusMap[task.Tasks.id] = task.Tasks.status; }, this); /** * sync up request progress with up to date progress of hosts on Host's list, * to avoid discrepancies while waiting for response with latest progress of request * after switching to operation's list */ if (request.get('isRunning')) { request.set('progress', App.HostPopup.getProgress(data.tasks)); request.set('status', App.HostPopup.getStatus(data.tasks)[0]); request.set('isRunning', (request.get('progress') !== 100)); } request.set('previousTaskStatusMap', currentTaskStatusMap); request.set('hostsMap', hostsMap); this.set('serviceTimestamp', App.dateTime()); }, /** * Update task, with uploading two additional properties: stdout and stderr * @param data * @param ajaxQuery * @param params */ callBackFilteredByTask: function (data, ajaxQuery, params) { var request = this.get('services').findProperty('id', data.Tasks.request_id); var host = request.get('hostsMap')[data.Tasks.host_name]; var task = host.logTasks.findProperty('Tasks.id', data.Tasks.id); task.Tasks.status = data.Tasks.status; task.Tasks.stdout = data.Tasks.stdout; task.Tasks.stderr = data.Tasks.stderr; // Put some command information to task object task.Tasks.command = data.Tasks.command; task.Tasks.custom_command_name = data.Tasks.custom_command_name; task.Tasks.structured_out = data.Tasks.structured_out; task.Tasks.output_log = data.Tasks.output_log; task.Tasks.error_log = data.Tasks.error_log; this.set('serviceTimestamp', App.dateTime()); }, /** * Prepare, received from server, requests for host component popup * @param data */ callBackForMostRecent: function (data) { var runningServices = 0; var currentRequestIds = []; var countIssued = App.db.getBGOOperationsCount(); var countGot = data.itemTotal; data.items.forEach(function (request) { var rq = this.get("services").findProperty('id', request.Requests.id); var isRunning = this.isRequestRunning(request); var requestParams = this.parseRequestContext(request.Requests.request_context); this.assignScheduleId(request, requestParams); currentRequestIds.push(request.Requests.id); if (rq) { rq.set('progress', Math.floor(request.Requests.progress_percent)); rq.set('status', request.Requests.request_status); rq.set('isRunning', isRunning); rq.set('startTime', request.Requests.start_time); rq.set('endTime', request.Requests.end_time); } else { rq = Em.Object.create({ id: request.Requests.id, name: requestParams.requestContext, displayName: requestParams.requestContext, progress: Math.floor(request.Requests.progress_percent), status: request.Requests.request_status, isRunning: isRunning, hostsMap: {}, tasks: [], startTime: request.Requests.start_time, endTime: request.Requests.end_time, dependentService: requestParams.dependentService, sourceRequestScheduleId: request.Requests.request_schedule && request.Requests.request_schedule.schedule_id, previousTaskStatusMap: {}, contextCommand: requestParams.contextCommand }); this.get("services").unshift(rq); //To sort DESC by request id this.set("services", this.get("services").sort( function(a,b) { return b.get('id') - a.get('id'); })) ; } runningServices += ~~isRunning; }, this); this.removeOldRequests(currentRequestIds); this.set("allOperationsCount", runningServices); this.set('isShowMoreAvailable', countGot >= countIssued); this.set('serviceTimestamp', App.dateTime()); }, isShowMoreAvailable: null, /** * remove old requests * as API returns 10, or 20 , or 30 ...etc latest request, the requests that absent in response should be removed * @param currentRequestIds */ removeOldRequests: function (currentRequestIds) { this.get('services').forEach(function (service, index, services) { if (!currentRequestIds.contains(service.id)) { services.splice(index, 1); } }); }, /** * identify whether request is running by task counters * @param request * @return {Boolean} */ isRequestRunning: function (request) { return (request.Requests.task_count - (request.Requests.aborted_task_count + request.Requests.completed_task_count + request.Requests.failed_task_count + request.Requests.timed_out_task_count - request.Requests.queued_task_count)) > 0; }, /** * identify whether there is only one host in request * @param inputs * @return {Boolean} */ isOneHost: function (inputs) { if (!inputs) { return false; } inputs = JSON.parse(inputs); if (inputs && inputs.included_hosts) { return inputs.included_hosts.split(',').length < 2; } return false }, /** * assign schedule_id of request to null if it's Recommision operation * @param request * @param requestParams */ assignScheduleId: function (request, requestParams) { var oneHost = this.isOneHost(request.Requests.inputs); if (request.Requests.request_schedule && oneHost && /Recommission/.test(requestParams.requestContext)) { request.Requests.request_schedule.schedule_id = null; } }, /** * parse request context and if keyword "_PARSE_" is present then format it * @param requestContext * @return {Object} */ parseRequestContext: function (requestContext) { var parsedRequestContext; var service; var contextCommand; if (requestContext) { 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.format.role(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; } } else { parsedRequestContext = Em.I18n.t('requestInfo.unspecified'); } return { requestContext: parsedRequestContext, dependentService: service, contextCommand: contextCommand } }, popupView: null, /** * Onclick handler for background operations number located right to logo */ showPopup: function(){ // load the checkbox on footer first, then show popup. var self = this; App.router.get('applicationController').dataLoading().done(function (initValue) { App.updater.immediateRun('requestMostRecent'); if(self.get('popupView') && App.HostPopup.get('isBackgroundOperations')){ self.set ('popupView.isNotShowBgChecked', !initValue); self.set('popupView.isOpen', true); $(self.get('popupView.element')).appendTo('#wrapper'); } else { self.set('popupView', App.HostPopup.initPopup("", self, true)); self.set ('popupView.isNotShowBgChecked', !initValue); } }); } }); /** * 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}" };