batch_scheduled_requests.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one or more
  3. * contributor license agreements. See the NOTICE file distributed with this
  4. * work for additional information regarding copyright ownership. The ASF
  5. * licenses this file to you under the Apache License, Version 2.0 (the
  6. * "License"); you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  13. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  14. * License for the specific language governing permissions and limitations under
  15. * the License.
  16. */
  17. var App = require('app');
  18. /**
  19. * Default success callback for ajax-requests in this module
  20. * @type {Function}
  21. */
  22. var defaultSuccessCallback = function(data, ajaxOptions, params) {
  23. App.router.get('applicationController').dataLoading().done(function(initValue) {
  24. params.query && params.query.set('status', 'SUCCESS');
  25. if (initValue) {
  26. App.router.get('backgroundOperationsController').showPopup();
  27. }
  28. });
  29. };
  30. /**
  31. * Default error callback for ajax-requests in this module
  32. * @param {Object} xhr
  33. * @param {String} textStatus
  34. * @param {String} error
  35. * @param {Object} opt
  36. * @type {Function}
  37. */
  38. var defaultErrorCallback = function(xhr, textStatus, error, opt, params) {
  39. params.query && params.query.set('status', 'FAIL');
  40. App.ajax.defaultErrorHandler(xhr, opt.url, 'POST', xhr.status);
  41. };
  42. /**
  43. * Contains helpful utilities for handling batch and scheduled requests.
  44. */
  45. module.exports = {
  46. /**
  47. * Some services have components which have a need for rolling restarts. This
  48. * method returns the name of the host-component which supports rolling
  49. * restarts for a service.
  50. * @param {String} serviceName
  51. */
  52. getRollingRestartComponentName: function(serviceName) {
  53. var rollingRestartComponents = {
  54. HDFS: 'DATANODE',
  55. YARN: 'NODEMANAGER',
  56. MAPREDUCE: 'TASKTRACKER',
  57. HBASE: 'HBASE_REGIONSERVER',
  58. STORM: 'SUPERVISOR'
  59. };
  60. return rollingRestartComponents[serviceName] ? rollingRestartComponents[serviceName] : null;
  61. },
  62. /**
  63. * Facade-function for restarting host components of specific service
  64. * @param {String} serviceName for which service hostComponents should be restarted
  65. * @param {bool} staleConfigsOnly restart only hostComponents with <code>staleConfig</code> true
  66. * @param {Object} query
  67. * @param {bool} runMmOperation
  68. */
  69. restartAllServiceHostComponents: function(serviceName, staleConfigsOnly, query, runMmOperation) {
  70. var self = this;
  71. var context = staleConfigsOnly ? Em.I18n.t('rollingrestart.context.allWithStaleConfigsForSelectedService').format(serviceName) : Em.I18n.t('rollingrestart.context.allForSelectedService').format(serviceName);
  72. if (runMmOperation) {
  73. this.turnOnOffPassiveRequest('ON', Em.I18n.t('passiveState.turnOnFor').format(serviceName), serviceName);
  74. }
  75. this.getComponentsFromServer({
  76. services: [serviceName],
  77. staleConfigs: staleConfigsOnly ? staleConfigsOnly : null,
  78. passiveState: 'OFF',
  79. displayParams: ['host_components/HostRoles/component_name']
  80. }, function (data) {
  81. var hostComponents = [];
  82. data.items.forEach(function (host) {
  83. host.host_components.forEach(function (hostComponent) {
  84. hostComponents.push(Em.Object.create({
  85. componentName: hostComponent.HostRoles.component_name,
  86. hostName: host.Hosts.host_name
  87. }))
  88. });
  89. });
  90. self.restartHostComponents(hostComponents, context, "SERVICE", query);
  91. });
  92. },
  93. /**
  94. * construct URL from parameters for request in <code>getComponentsFromServer()</code>
  95. * @param options
  96. * @return {{fields: string, params: string}}
  97. */
  98. constructComponentsCallUrl: function (options) {
  99. var multipleValueParams = {
  100. 'services': 'host_components/HostRoles/service_name.in(<entity-names>)',
  101. 'hosts': 'Hosts/host_name.in(<entity-names>)',
  102. 'components': 'host_components/HostRoles/component_name.in(<entity-names>)'
  103. },
  104. singleValueParams = {
  105. staleConfigs: 'host_components/HostRoles/stale_configs=',
  106. passiveState: 'Hosts/maintenance_state=',
  107. workStatus: 'host_components/HostRoles/state='
  108. },
  109. displayParams = options.displayParams || [],
  110. urlParams = '',
  111. addAmpersand = false;
  112. for (var i in multipleValueParams) {
  113. var arrayParams = options[i];
  114. if (Array.isArray(arrayParams) && arrayParams.length > 0) {
  115. if (addAmpersand) {
  116. urlParams += '&';
  117. addAmpersand = false;
  118. }
  119. urlParams += multipleValueParams[i].replace('<entity-names>', arrayParams.join(','));
  120. addAmpersand = true;
  121. }
  122. }
  123. for (var j in singleValueParams) {
  124. var param = options[j];
  125. if (!Em.isNone(param)) {
  126. urlParams += (addAmpersand) ? '&' : '';
  127. urlParams += singleValueParams[j] + param.toString();
  128. addAmpersand = true;
  129. }
  130. }
  131. var params = urlParams,
  132. fields = '';
  133. displayParams.forEach(function (displayParam, index, array) {
  134. if (index === 0) {
  135. fields += (addAmpersand) ? '&' : '';
  136. fields += 'fields=';
  137. }
  138. fields += displayParam;
  139. fields += (array.length === (index + 1)) ? '' : ",";
  140. });
  141. fields += '&minimal_response=true';
  142. return {fields: fields.substring(1, fields.length), params: params};
  143. },
  144. /**
  145. * make GET call to server in order to obtain host-components
  146. * which correspond to filter params from <code>options</code>
  147. * @param options
  148. * @param callback
  149. */
  150. getComponentsFromServer: function (options, callback) {
  151. var request_parameters = this.constructComponentsCallUrl(options);
  152. return App.ajax.send({
  153. name: 'host.host_components.filtered',
  154. sender: this,
  155. data: {
  156. parameters: request_parameters.params,
  157. fields: request_parameters.fields,
  158. callback: callback
  159. },
  160. success: 'getComponentsFromServerSuccessCallback'
  161. });
  162. },
  163. /**
  164. * pass request outcome to <code>callback()<code>
  165. * @param data
  166. * @param opt
  167. * @param params
  168. */
  169. getComponentsFromServerSuccessCallback: function (data, opt, params) {
  170. params.callback(data);
  171. },
  172. /**
  173. * Restart list of host components
  174. * @param {Ember.Enumerable} hostComponentsList list of host components should be restarted
  175. * @param {String} context message to show in BG popup
  176. * @param {String} level - operation level, can be ("CLUSTER", "SERVICE", "HOST", "HOSTCOMPONENT")
  177. * @param {String} query
  178. */
  179. restartHostComponents: function (hostComponentsList, context, level, query) {
  180. context = context || Em.I18n.t('rollingrestart.context.default');
  181. /**
  182. * Format: {
  183. * 'DATANODE': ['host1', 'host2'],
  184. * 'NAMENODE': ['host1', 'host3']
  185. * ...
  186. * }
  187. */
  188. var componentToHostsMap = {};
  189. var hosts = [];
  190. hostComponentsList.forEach(function(hc) {
  191. var hostName = hc.get('hostName');
  192. var componentName = hc.get('componentName');
  193. if (!componentToHostsMap[componentName]) {
  194. componentToHostsMap[componentName] = [];
  195. }
  196. componentToHostsMap[componentName].push(hostName);
  197. hosts.push(hostName);
  198. });
  199. var resource_filters = [];
  200. for (var componentName in componentToHostsMap) {
  201. if (componentToHostsMap.hasOwnProperty(componentName)) {
  202. resource_filters.push({
  203. service_name: App.StackServiceComponent.find(componentName).get('serviceName'),
  204. component_name: componentName,
  205. hosts: componentToHostsMap[componentName].join(",")
  206. });
  207. }
  208. }
  209. if (hostComponentsList.length > 0) {
  210. var serviceComponentName = hostComponentsList[0].get("componentName");
  211. var serviceName = App.StackServiceComponent.find(serviceComponentName).get('serviceName');
  212. var operation_level = this.getOperationLevelobject(level, serviceName,
  213. serviceComponentName);
  214. }
  215. if (resource_filters.length) {
  216. App.ajax.send({
  217. name: 'restart.hostComponents',
  218. sender: {
  219. successCallback: defaultSuccessCallback,
  220. errorCallback: defaultErrorCallback
  221. },
  222. data: {
  223. context: context,
  224. resource_filters: resource_filters,
  225. query: query,
  226. operation_level: operation_level
  227. },
  228. success: 'successCallback',
  229. error: 'errorCallback'
  230. });
  231. }
  232. },
  233. /**
  234. * @param {String} level - operation level name, can be ("CLUSTER", "SERVICE", "HOST", "HOSTCOMPONENT")
  235. * @param {String} serviceName
  236. * @param {String} componentName
  237. * @returns {Object} {{level: *, cluster_name: *}} - operation level object
  238. * @method getOperationLevelobject - create operation level object to be included into ajax query
  239. */
  240. getOperationLevelobject: function(level, serviceName, componentName) {
  241. var operationLevel = {
  242. "level": level,
  243. "cluster_name": App.get("clusterName")
  244. };
  245. if (level === "SERVICE") {
  246. operationLevel["service_name"] = serviceName;
  247. } else if(level !== "HOST") {
  248. operationLevel["service_name"] = serviceName;
  249. operationLevel["hostcomponent_name"] = componentName;
  250. }
  251. return operationLevel;
  252. },
  253. turnOnOffPassiveRequest: function(state, message, serviceName, callback) {
  254. return App.ajax.send({
  255. 'name': 'common.service.passive',
  256. 'sender': {
  257. 'successCallback': callback || defaultSuccessCallback,
  258. 'errorCallback': defaultErrorCallback
  259. },
  260. 'data': {
  261. 'requestInfo': message,
  262. 'serviceName': serviceName,
  263. 'passive_state': state
  264. },
  265. 'success': 'successCallback'
  266. });
  267. },
  268. /**
  269. * Makes a REST call to the server requesting the rolling restart of the
  270. * provided host components.
  271. * @param {Array} restartHostComponents list of host components should be restarted
  272. * @param {Number} batchSize size of each batch
  273. * @param {Number} intervalTimeSeconds delay between two batches
  274. * @param {Number} tolerateSize task failure tolerance
  275. * @param {callback} successCallback
  276. * @param {callback} errorCallback
  277. */
  278. _doPostBatchRollingRestartRequest: function(restartHostComponents, batchSize, intervalTimeSeconds, tolerateSize, successCallback, errorCallback) {
  279. successCallback = successCallback || defaultSuccessCallback;
  280. errorCallback = errorCallback || defaultErrorCallback;
  281. if (!restartHostComponents.length) {
  282. console.log('No batch rolling restart if no restartHostComponents provided!');
  283. return;
  284. }
  285. App.ajax.send({
  286. name: 'rolling_restart.post',
  287. sender: {
  288. successCallback: successCallback,
  289. errorCallback: errorCallback
  290. },
  291. data: {
  292. intervalTimeSeconds: intervalTimeSeconds,
  293. tolerateSize: tolerateSize,
  294. batches: this.getBatchesForRollingRestartRequest(restartHostComponents, batchSize)
  295. },
  296. success: 'successCallback',
  297. error: 'errorCallback'
  298. });
  299. },
  300. /**
  301. * Create list of batches for rolling restart request
  302. * @param {Array} restartHostComponents list host components should be restarted
  303. * @param {Number} batchSize size of each batch
  304. * @returns {Array} list of batches
  305. */
  306. getBatchesForRollingRestartRequest: function(restartHostComponents, batchSize) {
  307. var hostIndex = 0,
  308. batches = [],
  309. batchCount = Math.ceil(restartHostComponents.length / batchSize),
  310. sampleHostComponent = restartHostComponents.objectAt(0),
  311. componentName = sampleHostComponent.get('componentName'),
  312. serviceName = sampleHostComponent.get('serviceName');
  313. for ( var count = 0; count < batchCount; count++) {
  314. var hostNames = [];
  315. for ( var hc = 0; hc < batchSize && hostIndex < restartHostComponents.length; hc++) {
  316. hostNames.push(restartHostComponents.objectAt(hostIndex++).get('hostName'));
  317. }
  318. if (hostNames.length > 0) {
  319. batches.push({
  320. "order_id" : count + 1,
  321. "type" : "POST",
  322. "uri" : App.apiPrefix + "/clusters/" + App.get('clusterName') + "/requests",
  323. "RequestBodyInfo" : {
  324. "RequestInfo" : {
  325. "context" : "_PARSE_.ROLLING-RESTART." + componentName + "." + (count + 1) + "." + batchCount,
  326. "command" : "RESTART"
  327. },
  328. "Requests/resource_filters": [{
  329. "service_name" : serviceName,
  330. "component_name" : componentName,
  331. "hosts" : hostNames.join(",")
  332. }]
  333. }
  334. });
  335. }
  336. }
  337. return batches;
  338. },
  339. /**
  340. * Launches dialog to handle rolling restarts of host components.
  341. *
  342. * Rolling restart is supported only for components listed in <code>getRollingRestartComponentName</code>
  343. * @see getRollingRestartComponentName
  344. * @param {String} hostComponentName
  345. * Type of host-component to restart across cluster
  346. * (ex: DATANODE)
  347. * @param {bool} staleConfigsOnly
  348. * Pre-select host-components which have stale
  349. * configurations
  350. */
  351. launchHostComponentRollingRestart: function(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, skipMaintenance) {
  352. if (App.get('components.rollinRestartAllowed').contains(hostComponentName)) {
  353. this.showRollingRestartPopup(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, null, skipMaintenance);
  354. }
  355. else {
  356. this.showWarningRollingRestartPopup(hostComponentName);
  357. }
  358. },
  359. /**
  360. * Show popup with rolling restart dialog
  361. * @param {String} hostComponentName name of the host components that should be restarted
  362. * @param {bool} staleConfigsOnly restart only components with <code>staleConfigs</code> = true
  363. * @param {App.hostComponent[]} hostComponents list of hostComponents that should be restarted (optional).
  364. * Using this parameter will reset hostComponentName
  365. */
  366. showRollingRestartPopup: function(hostComponentName, serviceName, isMaintenanceModeOn, staleConfigsOnly, hostComponents, skipMaintenance) {
  367. hostComponents = hostComponents || [];
  368. var componentDisplayName = App.format.role(hostComponentName);
  369. var self = this;
  370. if (!componentDisplayName) {
  371. componentDisplayName = hostComponentName;
  372. }
  373. var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName);
  374. var viewExtend = {
  375. turnOnMmMsg: Em.I18n.t('passiveState.turnOnFor').format(serviceName),
  376. turnOnMm: false,
  377. staleConfigsOnly : staleConfigsOnly,
  378. hostComponentName : hostComponentName,
  379. skipMaintenance: skipMaintenance,
  380. serviceName: serviceName,
  381. isServiceInMM: isMaintenanceModeOn,
  382. didInsertElement: function () {
  383. var view = this;
  384. this.set('parentView.innerView', this);
  385. if (hostComponents.length) {
  386. view.initialize();
  387. } else {
  388. self.getComponentsFromServer({
  389. components: [hostComponentName],
  390. displayParams: ['host_components/HostRoles/stale_configs', 'Hosts/maintenance_state', 'host_components/HostRoles/maintenance_state'],
  391. staleConfigs: staleConfigsOnly ? staleConfigsOnly : null
  392. }, function (data) {
  393. var wrappedHostComponents = [];
  394. data.items.forEach(function (host) {
  395. host.host_components.forEach(function(hostComponent){
  396. wrappedHostComponents.push(Em.Object.create({
  397. componentName: hostComponent.HostRoles.component_name,
  398. serviceName: App.StackServiceComponent.find(hostComponent.HostRoles.component_name).get('serviceName'),
  399. hostName: host.Hosts.host_name,
  400. staleConfigs: hostComponent.HostRoles.stale_configs,
  401. hostPassiveState: host.Hosts.maintenance_state,
  402. passiveState: hostComponent.HostRoles.maintenance_state
  403. }));
  404. });
  405. });
  406. view.set('allHostComponents', wrappedHostComponents);
  407. view.initialize();
  408. });
  409. }
  410. }
  411. };
  412. if (hostComponents.length) {
  413. viewExtend.allHostComponents = hostComponents;
  414. }
  415. App.ModalPopup.show({
  416. header : title,
  417. hostComponentName : hostComponentName,
  418. serviceName: serviceName,
  419. isServiceInMM: isMaintenanceModeOn,
  420. staleConfigsOnly : staleConfigsOnly,
  421. skipMaintenance: skipMaintenance,
  422. innerView : null,
  423. bodyClass : App.RollingRestartView.extend(viewExtend),
  424. classNames : [ 'rolling-restart-popup' ],
  425. primary : Em.I18n.t('rollingrestart.dialog.primary'),
  426. onPrimary : function() {
  427. var dialog = this;
  428. var restartComponents = this.get('innerView.restartHostComponents');
  429. var batchSize = this.get('innerView.batchSize');
  430. var waitTime = this.get('innerView.interBatchWaitTimeSeconds');
  431. var tolerateSize = this.get('innerView.tolerateSize');
  432. if (this.get('innerView.turnOnMm')) {
  433. self.turnOnOffPassiveRequest('ON', Em.I18n.t('passiveState.turnOnFor').format(serviceName), serviceName.toUpperCase());
  434. }
  435. self._doPostBatchRollingRestartRequest(restartComponents, batchSize, waitTime, tolerateSize, function(data, ajaxOptions, params) {
  436. dialog.hide();
  437. defaultSuccessCallback(data, ajaxOptions, params);
  438. });
  439. },
  440. updateButtons : function() {
  441. var errors = this.get('innerView.errors');
  442. this.set('disablePrimary', (errors != null && errors.length > 0))
  443. }.observes('innerView.errors')
  444. });
  445. },
  446. /**
  447. * Show warning popup about not supported host components
  448. * @param {String} hostComponentName
  449. */
  450. showWarningRollingRestartPopup: function(hostComponentName) {
  451. var componentDisplayName = App.format.role(hostComponentName);
  452. if (!componentDisplayName) {
  453. componentDisplayName = hostComponentName;
  454. }
  455. var title = Em.I18n.t('rollingrestart.dialog.title').format(componentDisplayName);
  456. var msg = Em.I18n.t('rollingrestart.notsupported.hostComponent').format(componentDisplayName);
  457. console.log(msg);
  458. App.ModalPopup.show({
  459. header : title,
  460. secondary : false,
  461. msg : msg,
  462. bodyClass : Em.View.extend({
  463. template : Em.Handlebars.compile('<div class="alert alert-warning">{{msg}}</div>')
  464. })
  465. });
  466. },
  467. /**
  468. * Warn user that alerts will be updated in few minutes
  469. * @param {String} hostComponentName
  470. */
  471. infoPassiveState: function(passiveState) {
  472. var enabled = passiveState == 'OFF' ? 'enabled' : 'suppressed';
  473. App.ModalPopup.show({
  474. header: Em.I18n.t('common.information'),
  475. secondary: null,
  476. bodyClass: Ember.View.extend({
  477. template: Ember.Handlebars.compile('<p>{{view.message}}</p>'),
  478. message: function() {
  479. return Em.I18n.t('hostPopup.warning.alertsTimeOut').format(passiveState.toLowerCase(), enabled);
  480. }.property()
  481. })
  482. });
  483. },
  484. /**
  485. * Retrieves the latest information about a specific request schedule
  486. * identified by 'requestScheduleId'
  487. *
  488. * @param {Number} requestScheduleId ID of the request schedule to get
  489. * @param {Function} successCallback Called with request_schedule data from server. An
  490. * empty object returned for invalid ID.
  491. * @param {Function} errorCallback Optional error callback. Default behavior is to
  492. * popup default error dialog.
  493. */
  494. getRequestSchedule: function(requestScheduleId, successCallback, errorCallback) {
  495. if (requestScheduleId != null && !isNaN(requestScheduleId) && requestScheduleId > -1) {
  496. errorCallback = errorCallback ? errorCallback : defaultErrorCallback;
  497. App.ajax.send({
  498. name : 'request_schedule.get',
  499. sender : {
  500. successCallbackFunction : function(data) {
  501. successCallback(data);
  502. },
  503. errorCallbackFunction : function(xhr, textStatus, error, opt) {
  504. errorCallback(xhr, textStatus, error, opt);
  505. }
  506. },
  507. data : {
  508. request_schedule_id : requestScheduleId
  509. },
  510. success : 'successCallbackFunction',
  511. error : 'errorCallbackFunction'
  512. });
  513. } else {
  514. successCallback({});
  515. }
  516. },
  517. /**
  518. * Attempts to abort a specific request schedule identified by 'requestScheduleId'
  519. *
  520. * @param {Number} requestScheduleId ID of the request schedule to get
  521. * @param {Function} successCallback Called when request schedule successfully aborted
  522. * @param {Function} errorCallback Optional error callback. Default behavior is to
  523. * popup default error dialog.
  524. */
  525. doAbortRequestSchedule: function(requestScheduleId, successCallback, errorCallback) {
  526. if (requestScheduleId != null && !isNaN(requestScheduleId) && requestScheduleId > -1) {
  527. errorCallback = errorCallback || defaultErrorCallback;
  528. App.ajax.send({
  529. name : 'common.delete.request_schedule',
  530. sender : {
  531. successCallbackFunction : function(data) {
  532. successCallback(data);
  533. },
  534. errorCallbackFunction : function(xhr, textStatus, error, opt) {
  535. errorCallback(xhr, textStatus, error, opt);
  536. }
  537. },
  538. data : {
  539. request_schedule_id : requestScheduleId
  540. },
  541. success : 'successCallbackFunction',
  542. error : 'errorCallbackFunction'
  543. });
  544. } else {
  545. successCallback({});
  546. }
  547. }
  548. };