| /** |
| * 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('userSettingsController').dataLoading('show_bg').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 |
| * @param {Object} params |
| * @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', |
| HBASE: 'HBASE_REGIONSERVER', |
| STORM: 'SUPERVISOR' |
| }; |
| return rollingRestartComponents[serviceName] ? rollingRestartComponents[serviceName] : null; |
| }, |
| |
| /** |
| * Facade-function for restarting host components of specific service |
| * @param {String} serviceDisplayName for which service hostComponents should be restarted |
| * @param {String} serviceName for which service hostComponents should be restarted |
| * @param {bool} staleConfigsOnly restart only hostComponents with <code>staleConfig</code> true |
| * @param {Object} query |
| * @param {bool} runMmOperation |
| */ |
| restartAllServiceHostComponents: function(serviceDisplayName, serviceName, staleConfigsOnly, query, runMmOperation) { |
| const context = staleConfigsOnly ? |
| Em.I18n.t('rollingrestart.context.allWithStaleConfigsForSelectedService').format(serviceDisplayName) : |
| Em.I18n.t('rollingrestart.context.allForSelectedService').format(serviceDisplayName); |
| |
| 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'] |
| }, data => { |
| const hostComponents = this.getRestartComponentsArray(data); |
| this.restartHostComponents(hostComponents, context, 'SERVICE', query); |
| }); |
| }, |
| |
| restartCertainServiceHostComponents: function (serviceName, components, hosts, label, query, runMmOperation) { |
| const context = Em.I18n.t('rollingrestart.context.allForSelectedService').format(label); |
| if (runMmOperation) { |
| this.turnOnOffPassiveRequest('ON', Em.I18n.t('passiveState.turnOnFor').format(serviceName), serviceName); |
| } |
| this.getComponentsFromServer({ |
| services: serviceName && [serviceName], |
| hosts, |
| components, |
| passiveState: 'OFF', |
| displayParams: ['host_components/HostRoles/component_name'] |
| }, data => { |
| const hostComponents = this.getRestartComponentsArray(data); |
| this.restartHostComponents(hostComponents, context, 'SERVICE', query); |
| }); |
| }, |
| |
| getRestartComponentsArray: function (data) { |
| let hostComponents = []; |
| data.items.forEach(host => { |
| host.host_components.forEach(hostComponent => { |
| hostComponents.push(Em.Object.create({ |
| componentName: hostComponent.HostRoles.component_name, |
| hostName: host.Hosts.host_name |
| })); |
| }); |
| }); |
| return hostComponents; |
| }, |
| |
| /** |
| * construct URL from parameters for request in <code>getComponentsFromServer()</code> |
| * @param {object} options |
| * @return {{fields: string, params: string}} |
| */ |
| constructComponentsCallUrl: function (options) { |
| var multipleValueParams = { |
| 'services': 'host_components/HostRoles/service_name.in(<entity-names>)', |
| 'hosts': 'Hosts/host_name.in(<entity-names>)', |
| 'components': 'host_components/HostRoles/component_name.in(<entity-names>)' |
| }, |
| 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('<entity-names>', 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 <code>options</code> |
| * @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 <code>callback()<code> |
| * @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', |
| showLoadingPopup: true |
| }); |
| } |
| }, |
| |
| /** |
| * @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) { |
| return; |
| } |
| return App.ajax.send({ |
| name: 'common.batch.request_schedules', |
| sender: { |
| successCallback: successCallback, |
| errorCallback: errorCallback |
| }, |
| data: { |
| intervalTimeSeconds: intervalTimeSeconds, |
| tolerateSize: tolerateSize, |
| batches: this.getBatchesForRollingRestartRequest(restartHostComponents, batchSize) |
| }, |
| success: 'successCallback', |
| error: 'errorCallback', |
| showLoadingPopup: true |
| }); |
| }, |
| |
| /** |
| * 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) { |
| batches.push({ |
| "order_id" : count + 1, |
| "type" : "POST", |
| "uri" : "/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 <code>getRollingRestartComponentName</code> |
| * @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)) { |
| return this.showRollingRestartPopup(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, null, skipMaintenance); |
| } |
| return this.showWarningRollingRestartPopup(hostComponentName); |
| }, |
| |
| /** |
| * Show popup with rolling restart dialog |
| * @param {String} hostComponentName name of the host components that should be restarted |
| * @param {String} serviceName |
| * @param {boolean} isMaintenanceModeOn |
| * @param {bool} staleConfigsOnly restart only components with <code>staleConfigs</code> = true |
| * @param {App.hostComponent[]} [hostComponents] list of hostComponents that should be restarted |
| * @param {boolean} [skipMaintenance] |
| * Using this parameter will reset hostComponentName |
| */ |
| showRollingRestartPopup: function(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, hostComponents, skipMaintenance) { |
| hostComponents = hostComponents || []; |
| var componentDisplayName = App.format.role(hostComponentName, false); |
| 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(); |
| return; |
| } |
| |
| 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, false) || hostComponentName; |
| var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName); |
| var msg = Em.I18n.t('rollingrestart.notsupported.hostComponent').format(componentDisplayName); |
| |
| return App.ModalPopup.show({ |
| header : title, |
| secondary : false, |
| msg : msg, |
| bodyClass : Em.View.extend({ |
| template : Em.Handlebars.compile('<div class="alert alert-warning">{{msg}}</div>') |
| }) |
| }); |
| }, |
| |
| /** |
| * Warn user that alerts will be updated in few minutes |
| * @param {String} passiveState |
| */ |
| infoPassiveState: function(passiveState) { |
| var enabled = passiveState == 'OFF' ? 'enabled' : 'suppressed'; |
| App.ModalPopup.show({ |
| header: Em.I18n.t('common.information'), |
| secondary: null, |
| bodyClass: Em.View.extend({ |
| template: Em.Handlebars.compile('<p>{{view.message}}</p>'), |
| message: Em.I18n.t('hostPopup.warning.alertsTimeOut').format(passiveState.toLowerCase(), enabled) |
| }) |
| }); |
| }, |
| |
| /** |
| * 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 || defaultErrorCallback; |
| App.ajax.send({ |
| name : 'request_schedule.get', |
| sender : { |
| successCallbackFunction: successCallback, |
| errorCallbackFunction: errorCallback |
| }, |
| 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: successCallback, |
| errorCallbackFunction: errorCallback |
| }, |
| data : { |
| request_schedule_id : requestScheduleId |
| }, |
| success : 'successCallbackFunction', |
| error : 'errorCallbackFunction' |
| }); |
| } else { |
| successCallback({}); |
| } |
| } |
| }; |