/** * 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'); /** * Default success callback for ajax-requests in this module * @type {Function} */ var defaultSuccessCallback = function(data, ajaxOptions, params) { App.router.get('applicationController').dataLoading().done(function(initValue) { params.query && params.query.set('status', 'SUCCESS'); if (initValue) { App.router.get('backgroundOperationsController').showPopup(); } }); }; /** * Default error callback for ajax-requests in this module * @param {Object} xhr * @param {String} textStatus * @param {String} error * @param {Object} opt * @type {Function} */ var defaultErrorCallback = function(xhr, textStatus, error, opt, params) { params.query && params.query.set('status', 'FAIL'); App.ajax.defaultErrorHandler(xhr, opt.url, 'POST', xhr.status); }; /** * Contains helpful utilities for handling batch and scheduled requests. */ module.exports = { /** * Some services have components which have a need for rolling restarts. This * method returns the name of the host-component which supports rolling * restarts for a service. * @param {String} serviceName */ getRollingRestartComponentName: function(serviceName) { var rollingRestartComponents = { HDFS: 'DATANODE', YARN: 'NODEMANAGER', MAPREDUCE: 'TASKTRACKER', HBASE: 'HBASE_REGIONSERVER', STORM: 'SUPERVISOR' }; return rollingRestartComponents[serviceName] ? rollingRestartComponents[serviceName] : null; }, /** * Facade-function for restarting host components of specific service * @param {String} serviceName for which service hostComponents should be restarted * @param {bool} staleConfigsOnly restart only hostComponents with staleConfig true * @param {Object} query * @param {bool} runMmOperation */ restartAllServiceHostComponents: function(serviceName, staleConfigsOnly, query, runMmOperation) { var self = this; var context = staleConfigsOnly ? Em.I18n.t('rollingrestart.context.allWithStaleConfigsForSelectedService').format(serviceName) : Em.I18n.t('rollingrestart.context.allForSelectedService').format(serviceName); if (runMmOperation) { this.turnOnOffPassiveRequest('ON', Em.I18n.t('passiveState.turnOnFor').format(serviceName), serviceName); } this.getComponentsFromServer({ services: [serviceName], staleConfigs: staleConfigsOnly ? staleConfigsOnly : null, passiveState: 'OFF', displayParams: ['host_components/HostRoles/component_name'] }, function (data) { var hostComponents = []; data.items.forEach(function (host) { host.host_components.forEach(function (hostComponent) { hostComponents.push(Em.Object.create({ componentName: hostComponent.HostRoles.component_name, hostName: host.Hosts.host_name })) }); }); self.restartHostComponents(hostComponents, context, "SERVICE", query); }); }, /** * construct URL from parameters for request in getComponentsFromServer() * @param options * @return {{fields: string, params: string}} */ constructComponentsCallUrl: function (options) { var multipleValueParams = { 'services': 'host_components/HostRoles/service_name.in()', 'hosts': 'Hosts/host_name.in()', 'components': 'host_components/HostRoles/component_name.in()' }, singleValueParams = { staleConfigs: 'host_components/HostRoles/stale_configs=', passiveState: 'Hosts/maintenance_state=', workStatus: 'host_components/HostRoles/state=' }, displayParams = options.displayParams || [], urlParams = '', addAmpersand = false; for (var i in multipleValueParams) { var arrayParams = options[i]; if (Array.isArray(arrayParams) && arrayParams.length > 0) { if (addAmpersand) { urlParams += '&'; addAmpersand = false; } urlParams += multipleValueParams[i].replace('', arrayParams.join(',')); addAmpersand = true; } } for (var j in singleValueParams) { var param = options[j]; if (!Em.isNone(param)) { urlParams += (addAmpersand) ? '&' : ''; urlParams += singleValueParams[j] + param.toString(); addAmpersand = true; } } var params = urlParams, fields = ''; displayParams.forEach(function (displayParam, index, array) { if (index === 0) { fields += (addAmpersand) ? '&' : ''; fields += 'fields='; } fields += displayParam; fields += (array.length === (index + 1)) ? '' : ","; }); fields += '&minimal_response=true'; return {fields: fields.substring(1, fields.length), params: params}; }, /** * make GET call to server in order to obtain host-components * which correspond to filter params from options * @param options * @param callback */ getComponentsFromServer: function (options, callback) { var request_parameters = this.constructComponentsCallUrl(options); return App.ajax.send({ name: 'host.host_components.filtered', sender: this, data: { parameters: request_parameters.params, fields: request_parameters.fields, callback: callback }, success: 'getComponentsFromServerSuccessCallback' }); }, /** * pass request outcome to callback() * @param data * @param opt * @param params */ getComponentsFromServerSuccessCallback: function (data, opt, params) { params.callback(data); }, /** * Restart list of host components * @param {Ember.Enumerable} hostComponentsList list of host components should be restarted * @param {String} context message to show in BG popup * @param {String} level - operation level, can be ("CLUSTER", "SERVICE", "HOST", "HOSTCOMPONENT") * @param {String} query */ restartHostComponents: function (hostComponentsList, context, level, query) { context = context || Em.I18n.t('rollingrestart.context.default'); /** * Format: { * 'DATANODE': ['host1', 'host2'], * 'NAMENODE': ['host1', 'host3'] * ... * } */ var componentToHostsMap = {}; var hosts = []; hostComponentsList.forEach(function(hc) { var hostName = hc.get('hostName'); var componentName = hc.get('componentName'); if (!componentToHostsMap[componentName]) { componentToHostsMap[componentName] = []; } componentToHostsMap[componentName].push(hostName); hosts.push(hostName); }); var resource_filters = []; for (var componentName in componentToHostsMap) { if (componentToHostsMap.hasOwnProperty(componentName)) { resource_filters.push({ service_name: App.StackServiceComponent.find(componentName).get('serviceName'), component_name: componentName, hosts: componentToHostsMap[componentName].join(",") }); } } if (hostComponentsList.length > 0) { var serviceComponentName = hostComponentsList[0].get("componentName"); var serviceName = App.StackServiceComponent.find(serviceComponentName).get('serviceName'); var operation_level = this.getOperationLevelobject(level, serviceName, serviceComponentName); } if (resource_filters.length) { App.ajax.send({ name: 'restart.hostComponents', sender: { successCallback: defaultSuccessCallback, errorCallback: defaultErrorCallback }, data: { context: context, resource_filters: resource_filters, query: query, operation_level: operation_level }, success: 'successCallback', error: 'errorCallback' }); } }, /** * @param {String} level - operation level name, can be ("CLUSTER", "SERVICE", "HOST", "HOSTCOMPONENT") * @param {String} serviceName * @param {String} componentName * @returns {Object} {{level: *, cluster_name: *}} - operation level object * @method getOperationLevelobject - create operation level object to be included into ajax query */ getOperationLevelobject: function(level, serviceName, componentName) { var operationLevel = { "level": level, "cluster_name": App.get("clusterName") }; if (level === "SERVICE") { operationLevel["service_name"] = serviceName; } else if(level !== "HOST") { operationLevel["service_name"] = serviceName; operationLevel["hostcomponent_name"] = componentName; } return operationLevel; }, turnOnOffPassiveRequest: function(state, message, serviceName, callback) { return App.ajax.send({ 'name': 'common.service.passive', 'sender': { 'successCallback': callback || defaultSuccessCallback, 'errorCallback': defaultErrorCallback }, 'data': { 'requestInfo': message, 'serviceName': serviceName, 'passive_state': state }, 'success': 'successCallback' }); }, /** * Makes a REST call to the server requesting the rolling restart of the * provided host components. * @param {Array} restartHostComponents list of host components should be restarted * @param {Number} batchSize size of each batch * @param {Number} intervalTimeSeconds delay between two batches * @param {Number} tolerateSize task failure tolerance * @param {callback} successCallback * @param {callback} errorCallback */ _doPostBatchRollingRestartRequest: function(restartHostComponents, batchSize, intervalTimeSeconds, tolerateSize, successCallback, errorCallback) { successCallback = successCallback || defaultSuccessCallback; errorCallback = errorCallback || defaultErrorCallback; if (!restartHostComponents.length) { console.log('No batch rolling restart if no restartHostComponents provided!'); return; } App.ajax.send({ name: 'rolling_restart.post', sender: { successCallback: successCallback, errorCallback: errorCallback }, data: { intervalTimeSeconds: intervalTimeSeconds, tolerateSize: tolerateSize, batches: this.getBatchesForRollingRestartRequest(restartHostComponents, batchSize) }, success: 'successCallback', error: 'errorCallback' }); }, /** * Create list of batches for rolling restart request * @param {Array} restartHostComponents list host components should be restarted * @param {Number} batchSize size of each batch * @returns {Array} list of batches */ getBatchesForRollingRestartRequest: function(restartHostComponents, batchSize) { var hostIndex = 0, batches = [], batchCount = Math.ceil(restartHostComponents.length / batchSize), sampleHostComponent = restartHostComponents.objectAt(0), componentName = sampleHostComponent.get('componentName'), serviceName = sampleHostComponent.get('serviceName'); for ( var count = 0; count < batchCount; count++) { var hostNames = []; for ( var hc = 0; hc < batchSize && hostIndex < restartHostComponents.length; hc++) { hostNames.push(restartHostComponents.objectAt(hostIndex++).get('hostName')); } if (hostNames.length > 0) { batches.push({ "order_id" : count + 1, "type" : "POST", "uri" : App.apiPrefix + "/clusters/" + App.get('clusterName') + "/requests", "RequestBodyInfo" : { "RequestInfo" : { "context" : "_PARSE_.ROLLING-RESTART." + componentName + "." + (count + 1) + "." + batchCount, "command" : "RESTART" }, "Requests/resource_filters": [{ "service_name" : serviceName, "component_name" : componentName, "hosts" : hostNames.join(",") }] } }); } } return batches; }, /** * Launches dialog to handle rolling restarts of host components. * * Rolling restart is supported only for components listed in getRollingRestartComponentName * @see getRollingRestartComponentName * @param {String} hostComponentName * Type of host-component to restart across cluster * (ex: DATANODE) * @param {bool} staleConfigsOnly * Pre-select host-components which have stale * configurations */ launchHostComponentRollingRestart: function(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, skipMaintenance) { if (App.get('components.rollinRestartAllowed').contains(hostComponentName)) { this.showRollingRestartPopup(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, null, skipMaintenance); } else { this.showWarningRollingRestartPopup(hostComponentName); } }, /** * Show popup with rolling restart dialog * @param {String} hostComponentName name of the host components that should be restarted * @param {bool} staleConfigsOnly restart only components with staleConfigs = true * @param {App.hostComponent[]} hostComponents list of hostComponents that should be restarted (optional). * Using this parameter will reset hostComponentName */ showRollingRestartPopup: function(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, hostComponents, skipMaintenance) { hostComponents = hostComponents || []; var componentDisplayName = App.format.role(hostComponentName); var self = this; if (!componentDisplayName) { componentDisplayName = hostComponentName; } var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName); var viewExtend = { turnOnMmMsg: Em.I18n.t('passiveState.turnOnFor').format(serviceName), turnOnMm: false, staleConfigsOnly : staleConfigsOnly, hostComponentName : hostComponentName, skipMaintenance: skipMaintenance, serviceName: serviceName, isServiceInMM: isMaintenanceModeOn, didInsertElement: function () { var view = this; this.set('parentView.innerView', this); if (hostComponents.length) { view.initialize(); } else { self.getComponentsFromServer({ components: [hostComponentName], displayParams: ['host_components/HostRoles/stale_configs', 'Hosts/maintenance_state', 'host_components/HostRoles/maintenance_state'], staleConfigs: staleConfigsOnly ? staleConfigsOnly : null }, function (data) { var wrappedHostComponents = []; data.items.forEach(function (host) { host.host_components.forEach(function(hostComponent){ wrappedHostComponents.push(Em.Object.create({ componentName: hostComponent.HostRoles.component_name, serviceName: App.StackServiceComponent.find(hostComponent.HostRoles.component_name).get('serviceName'), hostName: host.Hosts.host_name, staleConfigs: hostComponent.HostRoles.stale_configs, hostPassiveState: host.Hosts.maintenance_state, passiveState: hostComponent.HostRoles.maintenance_state })); }); }); view.set('allHostComponents', wrappedHostComponents); view.initialize(); }); } } }; if (hostComponents.length) { viewExtend.allHostComponents = hostComponents; } App.ModalPopup.show({ header : title, hostComponentName : hostComponentName, serviceName: serviceName, isServiceInMM: isMaintenanceModeOn, staleConfigsOnly : staleConfigsOnly, skipMaintenance: skipMaintenance, innerView : null, bodyClass : App.RollingRestartView.extend(viewExtend), classNames : [ 'rolling-restart-popup' ], primary : Em.I18n.t('rollingrestart.dialog.primary'), onPrimary : function() { var dialog = this; var restartComponents = this.get('innerView.restartHostComponents'); var batchSize = this.get('innerView.batchSize'); var waitTime = this.get('innerView.interBatchWaitTimeSeconds'); var tolerateSize = this.get('innerView.tolerateSize'); if (this.get('innerView.turnOnMm')) { self.turnOnOffPassiveRequest('ON', Em.I18n.t('passiveState.turnOnFor').format(serviceName), serviceName.toUpperCase()); } self._doPostBatchRollingRestartRequest(restartComponents, batchSize, waitTime, tolerateSize, function(data, ajaxOptions, params) { dialog.hide(); defaultSuccessCallback(data, ajaxOptions, params); }); }, updateButtons : function() { var errors = this.get('innerView.errors'); this.set('disablePrimary', (errors != null && errors.length > 0)) }.observes('innerView.errors') }); }, /** * Show warning popup about not supported host components * @param {String} hostComponentName */ showWarningRollingRestartPopup: function(hostComponentName) { var componentDisplayName = App.format.role(hostComponentName); if (!componentDisplayName) { componentDisplayName = hostComponentName; } var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName); var msg = Em.I18n.t('rollingrestart.notsupported.hostComponent').format(componentDisplayName); console.log(msg); App.ModalPopup.show({ header : title, secondary : false, msg : msg, bodyClass : Em.View.extend({ template : Em.Handlebars.compile('
{{msg}}
') }) }); }, /** * Warn user that alerts will be updated in few minutes * @param {String} hostComponentName */ infoPassiveState: function(passiveState) { var enabled = passiveState == 'OFF' ? 'enabled' : 'suppressed'; App.ModalPopup.show({ header: Em.I18n.t('common.information'), secondary: null, bodyClass: Ember.View.extend({ template: Ember.Handlebars.compile('

{{view.message}}

'), message: function() { return Em.I18n.t('hostPopup.warning.alertsTimeOut').format(passiveState.toLowerCase(), enabled); }.property() }) }); }, /** * 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 || defaultErrorCallback; App.ajax.send({ name : 'common.delete.request_schedule', 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({}); } } };