configs_saver.js 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160
  1. /**
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. */
  18. var App = require('app');
  19. var dataManipulationUtils = require('utils/data_manipulation');
  20. var lazyLoading = require('utils/lazy_loading');
  21. /**
  22. * this mixin has method to save configs (used in MainServiceInfoConfigsController)
  23. * all methods are divided into couple groups :
  24. * 0. HELPERS - some helper methods
  25. * 1. PRE SAVE CHECKS - warning popups and validations checks
  26. * 2. PREPARE CONFIGS TO SAVE - filtering and formatting changed configs
  27. * 2.1 PREPARE DATABASE CONFIGS - modify database properties
  28. * 2.2 ADD DYNAMIC CONFIGS - !!!NEED INVESTIGATION
  29. * 3. GENERATING JSON TO SAVE - generating json data
  30. * 4. AJAX REQUESTS - ajax request
  31. * 5. AFTER SAVE INFO - after save methods like to show popup with result
  32. * 6. ADDITIONAL
  33. */
  34. App.ConfigsSaverMixin = Em.Mixin.create({
  35. /**
  36. * @type {boolean}
  37. */
  38. saveConfigsFlag: true,
  39. /**
  40. * file names of changed configs
  41. * @type {string[]}
  42. */
  43. modifiedFileNames: [],
  44. /**
  45. * List of heapsize properties not to be parsed
  46. * @type {string[]}
  47. */
  48. heapsizeException: ['hadoop_heapsize', 'yarn_heapsize', 'nodemanager_heapsize', 'resourcemanager_heapsize', 'apptimelineserver_heapsize', 'jobhistory_heapsize', 'nfsgateway_heapsize', 'accumulo_master_heapsize', 'accumulo_tserver_heapsize', 'accumulo_monitor_heapsize', 'accumulo_gc_heapsize', 'accumulo_other_heapsize'],
  49. /**
  50. * Regular expression for heapsize properties detection
  51. * @type {regexp}
  52. */
  53. heapsizeRegExp: /_heapsize|_newsize|_maxnewsize|_permsize|_maxpermsize$/,
  54. /**
  55. * clear info to default
  56. * @method clearSaveInfo
  57. */
  58. clearSaveInfo: function() {
  59. this.set('modifiedFileNames', []);
  60. },
  61. /**
  62. * method to run saving configs
  63. * @method saveStepConfigs
  64. */
  65. saveStepConfigs: function() {
  66. if (!this.get("isSubmitDisabled")) {
  67. this.startSave();
  68. this.showWarningPopupsBeforeSave();
  69. }
  70. },
  71. /**
  72. * get config group object for current service
  73. * @param serviceName
  74. * @returns {App.ConfigGroup}
  75. */
  76. getGroupFromModel: function(serviceName) {
  77. if (this.get('selectedService.serviceName') === serviceName) {
  78. return this.get('selectedConfigGroup');
  79. } else {
  80. var groups = App.ServiceConfigGroup.find().filterProperty('serviceName', serviceName);
  81. if (this.get('selectedConfigGroup.isDefault')) {
  82. return groups.length ? groups.findProperty('isDefault', true) : null;
  83. } else {
  84. return groups.length ? groups.findProperty('name', this.get('selectedConfigGroup.dependentConfigGroups')[serviceName]) : null;
  85. }
  86. }
  87. },
  88. /**
  89. * Save changed configs and config groups
  90. * @method saveConfigs
  91. */
  92. saveConfigs: function () {
  93. var selectedConfigGroup = this.get('selectedConfigGroup');
  94. if (selectedConfigGroup.get('isDefault')) {
  95. var data = [];
  96. this.get('stepConfigs').forEach(function(stepConfig) {
  97. var serviceConfig = this.getServiceConfigToSave(stepConfig.get('serviceName'), stepConfig.get('configs'));
  98. if (serviceConfig) {
  99. data.push(serviceConfig);
  100. }
  101. }, this);
  102. if (Em.isArray(data) && data.length) {
  103. this.putChangedConfigurations(data, true);
  104. } else {
  105. this.onDoPUTClusterConfigurations();
  106. }
  107. } else {
  108. this.get('stepConfigs').forEach(function(stepConfig) {
  109. var serviceName = stepConfig.get('serviceName');
  110. var configs = stepConfig.get('configs');
  111. var configGroup = this.getGroupFromModel(serviceName);
  112. if (configGroup) {
  113. if (configGroup.get('isDefault')) {
  114. var configsToSave = this.getServiceConfigToSave(serviceName, configs);
  115. if (configsToSave) {
  116. this.putChangedConfigurations([configsToSave], false);
  117. }
  118. } else {
  119. var overridenConfigs = this.getConfigsForGroup(configs, configGroup.get('name'));
  120. if (Em.isArray(overridenConfigs)) {
  121. this.saveGroup(overridenConfigs, configGroup, this.get('content.serviceName') === serviceName);
  122. }
  123. }
  124. }
  125. }, this);
  126. }
  127. },
  128. /*********************************** 0. HELPERS ********************************************/
  129. /**
  130. * tells controller in saving configs was started
  131. * for now just changes flag <code>saveInProgress<code> to true
  132. * @private
  133. * @method startSave
  134. */
  135. startSave: function() {
  136. this.set("saveInProgress", true);
  137. },
  138. /**
  139. * tells controller that save has been finished
  140. * for now just changes flag <code>saveInProgress<code> to true
  141. * @private
  142. * @method completeSave
  143. */
  144. completeSave: function() {
  145. this.set("saveInProgress", false);
  146. },
  147. /**
  148. * Are some unsaved changes available
  149. * @returns {boolean}
  150. * @method hasUnsavedChanges
  151. */
  152. hasUnsavedChanges: function () {
  153. return !Em.isNone(this.get('hash')) && this.get('hash') != this.getHash();
  154. },
  155. /*********************************** 1. PRE SAVE CHECKS ************************************/
  156. /**
  157. * show some warning popups before user save configs
  158. * @private
  159. * @method showWarningPopupsBeforeSave
  160. */
  161. showWarningPopupsBeforeSave: function() {
  162. var self = this;
  163. if (this.isDirChanged()) {
  164. App.showConfirmationPopup(function() {
  165. self.showChangedDependentConfigs(null, function() {
  166. self.restartServicePopup();
  167. });
  168. },
  169. Em.I18n.t('services.service.config.confirmDirectoryChange').format(self.get('content.displayName')),
  170. this.completeSave.bind(this)
  171. );
  172. } else {
  173. self.showChangedDependentConfigs(null, function() {
  174. self.restartServicePopup();
  175. }, this.completeSave.bind(this));
  176. }
  177. },
  178. /**
  179. * Runs config validation before save
  180. * @private
  181. * @method restartServicePopup
  182. */
  183. restartServicePopup: function () {
  184. this.serverSideValidation()
  185. .done(this.saveConfigs.bind(this))
  186. .fail(this.completeSave.bind(this));
  187. },
  188. /**
  189. * Define if user has changed some dir properties
  190. * @return {Boolean}
  191. * @private
  192. * @method isDirChanged
  193. */
  194. isDirChanged: function () {
  195. var dirChanged = false;
  196. var serviceName = this.get('content.serviceName');
  197. if (serviceName === 'HDFS') {
  198. var hdfsConfigs = this.get('stepConfigs').findProperty('serviceName', 'HDFS').get('configs');
  199. if ((hdfsConfigs.findProperty('name', 'dfs.namenode.name.dir') && hdfsConfigs.findProperty('name', 'dfs.namenode.name.dir').get('isNotDefaultValue')) ||
  200. (hdfsConfigs.findProperty('name', 'dfs.namenode.checkpoint.dir') && hdfsConfigs.findProperty('name', 'dfs.namenode.checkpoint.dir').get('isNotDefaultValue')) ||
  201. (hdfsConfigs.findProperty('name', 'dfs.datanode.data.dir') && hdfsConfigs.findProperty('name', 'dfs.datanode.data.dir').get('isNotDefaultValue'))) {
  202. dirChanged = true;
  203. }
  204. }
  205. return dirChanged;
  206. },
  207. /*********************************** 2. GENERATING DATA TO SAVE ****************************/
  208. /**
  209. * get config properties for that fileNames that was changed
  210. * @param stepConfigs
  211. * @private
  212. * @returns {Array}
  213. */
  214. getModifiedConfigs: function(stepConfigs) {
  215. var modifiedConfigs = stepConfigs
  216. // get only modified and created configs
  217. .filter(function (config) {
  218. return config.get('isNotDefaultValue') || config.get('isNotSaved');
  219. })
  220. // get file names and add file names that was modified, for example after property removing
  221. .mapProperty('filename').concat(this.get('modifiedFileNames')).uniq()
  222. // get configs by filename
  223. .map(function (fileName) {
  224. return stepConfigs.filterProperty('filename', fileName);
  225. });
  226. if (!!modifiedConfigs.length) {
  227. // concatenate results
  228. modifiedConfigs = modifiedConfigs.reduce(function (current, prev) {
  229. return current.concat(prev);
  230. });
  231. }
  232. return modifiedConfigs;
  233. },
  234. /**
  235. * get configs that belongs to config group
  236. * @param stepConfigs
  237. * @private
  238. * @param configGroupName
  239. */
  240. getConfigsForGroup: function(stepConfigs, configGroupName) {
  241. var overridenConfigs = [];
  242. stepConfigs.filterProperty('overrides').forEach(function (config) {
  243. overridenConfigs = overridenConfigs.concat(config.get('overrides'));
  244. });
  245. // find custom original properties that assigned to selected config group
  246. return overridenConfigs.concat(stepConfigs.filterProperty('group')
  247. .filter(function (config) {
  248. return config.get('group.name') == configGroupName;
  249. }));
  250. },
  251. /**
  252. *
  253. * @param serviceName
  254. * @param configs
  255. * @private
  256. * @returns {*}
  257. */
  258. getServiceConfigToSave: function(serviceName, configs) {
  259. if (serviceName === 'YARN') {
  260. configs = App.config.textareaIntoFileConfigs(configs, 'capacity-scheduler.xml');
  261. }
  262. /**
  263. * generates list of properties that was changed
  264. * @type {Array}
  265. */
  266. var modifiedConfigs = this.getModifiedConfigs(configs);
  267. // save modified original configs that have no group
  268. modifiedConfigs = this.saveSiteConfigs(modifiedConfigs.filter(function (config) {
  269. return !config.get('group');
  270. }));
  271. if (!Em.isArray(modifiedConfigs) || modifiedConfigs.length == 0) return null;
  272. var fileNamesToSave = modifiedConfigs.mapProperty('filename').concat(this.get('modifiedFileNames')).uniq();
  273. var configsToSave = this.generateDesiredConfigsJSON(modifiedConfigs, fileNamesToSave, this.get('serviceConfigVersionNote'));
  274. if (configsToSave.length > 0) {
  275. return JSON.stringify({
  276. Clusters: {
  277. desired_config: configsToSave
  278. }
  279. });
  280. } else {
  281. return null;
  282. }
  283. },
  284. /**
  285. * save site configs
  286. * @param configs
  287. * @private
  288. * @method saveSiteConfigs
  289. */
  290. saveSiteConfigs: function (configs) {
  291. configs = this.setHiveHostName(configs);
  292. configs = this.setOozieHostName(configs);
  293. this.formatConfigValues(configs);
  294. return configs;
  295. },
  296. /**
  297. * Represent boolean value as string (true => 'true', false => 'false') and trim other values
  298. * @param serviceConfigProperties
  299. * @private
  300. * @method formatConfigValues
  301. */
  302. formatConfigValues: function (serviceConfigProperties) {
  303. serviceConfigProperties.forEach(function (_config) {
  304. if (typeof _config.get('value') === "boolean") _config.set('value', _config.value.toString());
  305. _config.set('value', App.config.trimProperty(_config, true));
  306. });
  307. },
  308. /*********************************** 2.1 PREPARE DATABASE CONFIGS ****************************/
  309. /**
  310. * set hive hostnames in configs
  311. * @param configs
  312. * @private
  313. * @method setHiveHostName
  314. */
  315. setHiveHostName: function (configs) {
  316. var dbHostPropertyName = null, configsToRemove = [];
  317. if (configs.someProperty('name', 'hive_database')) {
  318. var hiveDb = configs.findProperty('name', 'hive_database');
  319. switch(hiveDb.value) {
  320. case 'New MySQL Database':
  321. case 'New PostgreSQL Database':
  322. dbHostPropertyName = configs.someProperty('name', 'hive_ambari_host') ? 'hive_ambari_host' : dbHostPropertyName;
  323. configsToRemove = ['hive_existing_mysql_host', 'hive_existing_mysql_database', 'hive_existing_oracle_host', 'hive_existing_oracle_database', 'hive_existing_postgresql_host', 'hive_existing_postgresql_database', 'hive_existing_mssql_server_database', 'hive_existing_mssql_server_host', 'hive_existing_mssql_server_2_database', 'hive_existing_mssql_server_2_host'];
  324. break;
  325. case 'Existing MySQL Database':
  326. dbHostPropertyName = configs.someProperty('name', 'hive_existing_mysql_host') ? 'hive_existing_mysql_host' : dbHostPropertyName;
  327. configsToRemove = ['hive_ambari_database', 'hive_existing_oracle_host', 'hive_existing_oracle_database', 'hive_existing_postgresql_host', 'hive_existing_postgresql_database', 'hive_existing_mssql_server_database', 'hive_existing_mssql_server_host', 'hive_existing_mssql_server_2_database', 'hive_existing_mssql_server_2_host'];
  328. break;
  329. case 'Existing PostgreSQL Database':
  330. dbHostPropertyName = configs.someProperty('name', 'hive_existing_postgresql_host') ? 'hive_existing_postgresql_host' : dbHostPropertyName;
  331. configsToRemove = ['hive_ambari_database', 'hive_existing_mysql_host', 'hive_existing_mysql_database', 'hive_existing_oracle_host', 'hive_existing_oracle_database', 'hive_existing_mssql_server_database', 'hive_existing_mssql_server_host', 'hive_existing_mssql_server_2_database', 'hive_existing_mssql_server_2_host'];
  332. break;
  333. case 'Existing Oracle Database':
  334. dbHostPropertyName = configs.someProperty('name', 'hive_existing_oracle_host') ? 'hive_existing_oracle_host' : dbHostPropertyName;
  335. configsToRemove = ['hive_ambari_database', 'hive_existing_mysql_host', 'hive_existing_mysql_database', 'hive_existing_postgresql_host', 'hive_existing_postgresql_database', 'hive_existing_mssql_server_database', 'hive_existing_mssql_server_host', 'hive_existing_mssql_server_2_database', 'hive_existing_mssql_server_2_host'];
  336. break;
  337. case 'Existing MSSQL Server database with SQL authentication':
  338. dbHostPropertyName = configs.someProperty('name', 'hive_existing_mssql_server_host') ? 'hive_existing_mssql_server_host' : dbHostPropertyName;
  339. configsToRemove = ['hive_ambari_database', 'hive_existing_mysql_host', 'hive_existing_mysql_database', 'hive_existing_postgresql_host', 'hive_existing_postgresql_database', 'hive_existing_oracle_host', 'hive_existing_oracle_database', 'hive_existing_mssql_server_2_database', 'hive_existing_mssql_server_2_host'];
  340. break;
  341. case 'Existing MSSQL Server database with integrated authentication':
  342. dbHostPropertyName = configs.someProperty('name', 'hive_existing_mssql_server_2_host') ? 'hive_existing_mssql_server_2_host' : dbHostPropertyName;
  343. configsToRemove = ['hive_ambari_database', 'hive_existing_mysql_host', 'hive_existing_mysql_database', 'hive_existing_postgresql_host', 'hive_existing_postgresql_database', 'hive_existing_oracle_host', 'hive_existing_oracle_database', 'hive_existing_mssql_server_database', 'hive_existing_mssql_server_host'];
  344. break;
  345. }
  346. configs = dataManipulationUtils.rejectPropertyValues(configs, 'name', configsToRemove);
  347. }
  348. if (dbHostPropertyName) {
  349. var hiveHostNameProperty = App.ServiceConfigProperty.create(App.config.get('preDefinedSiteProperties').findProperty('name', 'hive_hostname'));
  350. hiveHostNameProperty.set('value', configs.findProperty('name', dbHostPropertyName).get('value'));
  351. configs.pushObject(hiveHostNameProperty);
  352. }
  353. return configs;
  354. },
  355. /**
  356. * set oozie hostnames in configs
  357. * @param configs
  358. * @private
  359. * @method setOozieHostName
  360. */
  361. setOozieHostName: function (configs) {
  362. var dbHostPropertyName = null, configsToRemove = [];
  363. if (configs.someProperty('name', 'oozie_database')) {
  364. var oozieDb = configs.findProperty('name', 'oozie_database');
  365. switch (oozieDb.value) {
  366. case 'New Derby Database':
  367. configsToRemove = ['oozie_ambari_database', 'oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_existing_postgresql_host', 'oozie_existing_postgresql_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  368. break;
  369. case 'New MySQL Database':
  370. var ambariHost = configs.findProperty('name', 'oozie_ambari_host');
  371. if (ambariHost) {
  372. ambariHost.name = 'oozie_hostname';
  373. }
  374. configsToRemove = ['oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_derby_database', 'oozie_existing_postgresql_host', 'oozie_existing_postgresql_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  375. break;
  376. case 'Existing MySQL Database':
  377. dbHostPropertyName = configs.someProperty('name', 'oozie_existing_mysql_host') ? 'oozie_existing_mysql_host' : dbHostPropertyName;
  378. configsToRemove = ['oozie_ambari_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_derby_database', 'oozie_existing_postgresql_host', 'oozie_existing_postgresql_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  379. break;
  380. case 'Existing PostgreSQL Database':
  381. dbHostPropertyName = configs.someProperty('name', 'oozie_existing_postgresql_host') ? 'oozie_existing_postgresql_host' : dbHostPropertyName;
  382. configsToRemove = ['oozie_ambari_database', 'oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  383. break;
  384. case 'Existing Oracle Database':
  385. dbHostPropertyName = configs.someProperty('name', 'oozie_existing_oracle_host') ? 'oozie_existing_oracle_host' : dbHostPropertyName;
  386. configsToRemove = ['oozie_ambari_database', 'oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_derby_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  387. break;
  388. case 'Existing MSSQL Server database with SQL authentication':
  389. dbHostPropertyName = configs.someProperty('name', 'oozie_existing_mssql_server_host') ? 'oozie_existing_mssql_server_host' : dbHostPropertyName;
  390. configsToRemove = ['oozie_ambari_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_derby_database', 'oozie_existing_postgresql_host', 'oozie_existing_postgresql_database', 'oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_existing_mssql_server_2_database', 'oozie_existing_mssql_server_2_host'];
  391. break;
  392. case 'Existing MSSQL Server database with integrated authentication':
  393. dbHostPropertyName = configs.someProperty('name', 'oozie_existing_mssql_server_2_host') ? 'oozie_existing_mssql_server_2_host' : dbHostPropertyName;
  394. configsToRemove = ['oozie_ambari_database', 'oozie_existing_oracle_host', 'oozie_existing_oracle_database', 'oozie_derby_database', 'oozie_existing_postgresql_host', 'oozie_existing_postgresql_database', 'oozie_existing_mysql_host', 'oozie_existing_mysql_database', 'oozie_existing_mssql_server_database', 'oozie_existing_mssql_server_host'];
  395. break;
  396. }
  397. configs = dataManipulationUtils.rejectPropertyValues(configs, 'name', configsToRemove);
  398. }
  399. if (dbHostPropertyName) {
  400. var oozieHostNameProperty = App.ServiceConfigProperty.create(App.config.get('preDefinedSiteProperties').findProperty('name', 'oozie_hostname'));
  401. oozieHostNameProperty.set('value', configs.findProperty('name', dbHostPropertyName).get('value'));
  402. configs.pushObject(oozieHostNameProperty);
  403. }
  404. return configs;
  405. },
  406. /*********************************** 3. GENERATING JSON TO SAVE *****************************/
  407. /**
  408. * generating common JSON object for desired configs
  409. * @param configsToSave
  410. * @param fileNamesToSave
  411. * @param serviceConfigNote
  412. * @param {boolean} [isNotDefaultGroup=false]
  413. * @returns {Array}
  414. */
  415. generateDesiredConfigsJSON: function(configsToSave, fileNamesToSave, serviceConfigNote, isNotDefaultGroup) {
  416. var desired_config = [];
  417. if (Em.isArray(configsToSave) && Em.isArray(fileNamesToSave) && fileNamesToSave.length && configsToSave.length) {
  418. serviceConfigNote = serviceConfigNote || "";
  419. var tagVersion = "version" + (new Date).getTime();
  420. fileNamesToSave.forEach(function(fName) {
  421. if (this.allowSaveSite(fName)) {
  422. var properties = configsToSave.filterProperty('filename', fName);
  423. var type = App.config.getConfigTagFromFileName(fName);
  424. desired_config.push(this.createDesiredConfig(type, tagVersion, properties, serviceConfigNote, isNotDefaultGroup));
  425. }
  426. }, this);
  427. }
  428. return desired_config;
  429. },
  430. /**
  431. * for some file names we have a restriction
  432. * and can't save them, in this this method will return false
  433. * @param fName
  434. * @returns {boolean}
  435. */
  436. allowSaveSite: function(fName) {
  437. switch (fName) {
  438. case 'mapred-queue-acls.xml':
  439. return false;
  440. case 'core-site.xml':
  441. return ['HDFS', 'GLUSTERFS'].contains(this.get('content.serviceName'));
  442. default :
  443. return true;
  444. }
  445. },
  446. /**
  447. * generating common JSON object for desired config
  448. * @param {string} type - file name without '.xml'
  449. * @param {string} tagVersion - version + timestamp
  450. * @param {App.ConfigProperty[]} properties - array of properties from model
  451. * @param {string} serviceConfigNote
  452. * @param {boolean} [isNotDefaultGroup=false]
  453. * @returns {{type: string, tag: string, properties: {}, properties_attributes: {}|undefined, service_config_version_note: string|undefined}}
  454. */
  455. createDesiredConfig: function(type, tagVersion, properties, serviceConfigNote, isNotDefaultGroup) {
  456. Em.assert('type and tagVersion should be defined', type && tagVersion);
  457. var desired_config = {
  458. "type": type,
  459. "tag": tagVersion,
  460. "properties": {}
  461. };
  462. if (!isNotDefaultGroup) {
  463. desired_config.service_config_version_note = serviceConfigNote || "";
  464. }
  465. var attributes = { final: {} };
  466. if (Em.isArray(properties)) {
  467. properties.forEach(function(property) {
  468. if (property.get('isRequiredByAgent')) {
  469. desired_config.properties[property.get('name')] = this.formatValueBeforeSave(property);
  470. /**
  471. * add is final value
  472. */
  473. if (property.get('isFinal')) {
  474. attributes.final[property.get('name')] = "true";
  475. }
  476. }
  477. }, this);
  478. }
  479. if (Object.keys(attributes.final).length) {
  480. desired_config.properties_attributes = attributes;
  481. }
  482. return desired_config;
  483. },
  484. /**
  485. * format value before save performs some changing of values
  486. * according to the rules that includes heapsizeException trimming and some custom rules
  487. * @param {App.ConfigProperty} property
  488. * @returns {string}
  489. */
  490. formatValueBeforeSave: function(property) {
  491. var name = property.get('name');
  492. var value = property.get('value');
  493. //TODO check for core-site
  494. if (this.get('heapsizeRegExp').test(name) && !this.get('heapsizeException').contains(name) && !(value).endsWith("m")) {
  495. return value += "m";
  496. }
  497. if (typeof property.get('value') === "boolean") {
  498. return property.get('value').toString();
  499. }
  500. switch (name) {
  501. case 'storm.zookeeper.servers':
  502. case 'nimbus.seeds':
  503. if (Em.isArray(value)) {
  504. return JSON.stringify(value).replace(/"/g, "'");
  505. } else {
  506. return value;
  507. }
  508. break;
  509. default:
  510. return App.config.trimProperty(property, true);
  511. }
  512. },
  513. /*********************************** 4. AJAX REQUESTS **************************************/
  514. /**
  515. * save config group
  516. * @param overridenConfigs
  517. * @param selectedConfigGroup
  518. * @param showPopup
  519. */
  520. saveGroup: function(overridenConfigs, selectedConfigGroup, showPopup) {
  521. var groupHosts = [];
  522. var fileNamesToSave = overridenConfigs.mapProperty('filename').uniq();
  523. selectedConfigGroup.get('hosts').forEach(function (hostName) {
  524. groupHosts.push({"host_name": hostName});
  525. });
  526. this.putConfigGroupChanges({
  527. ConfigGroup: {
  528. "id": selectedConfigGroup.get('configGroupId'),
  529. "cluster_name": App.get('clusterName'),
  530. "group_name": selectedConfigGroup.get('name'),
  531. "tag": selectedConfigGroup.get('service.id'),
  532. "description": selectedConfigGroup.get('description'),
  533. "hosts": groupHosts,
  534. "service_config_version_note": this.get('serviceConfigVersionNote'),
  535. "desired_configs": this.generateDesiredConfigsJSON(overridenConfigs, fileNamesToSave, null, true)
  536. }
  537. }, showPopup);
  538. },
  539. /**
  540. * persist properties of config groups to server
  541. * show result popup if <code>showPopup</code> is true
  542. * @param data {Object}
  543. * @param showPopup {Boolean}
  544. * @method putConfigGroupChanges
  545. */
  546. putConfigGroupChanges: function (data, showPopup) {
  547. var ajaxOptions = {
  548. name: 'config_groups.update_config_group',
  549. sender: this,
  550. data: {
  551. id: data.ConfigGroup.id,
  552. configGroup: data
  553. }
  554. };
  555. if (showPopup) {
  556. ajaxOptions.success = "putConfigGroupChangesSuccess";
  557. }
  558. return App.ajax.send(ajaxOptions);
  559. },
  560. /**
  561. * Saves configuration of set of sites. The provided data
  562. * contains the site name and tag to be used.
  563. * @param {Object[]} services
  564. * @param {boolean} showPopup
  565. * @return {$.ajax}
  566. * @method putChangedConfigurations
  567. */
  568. putChangedConfigurations: function (services, showPopup) {
  569. var ajaxData = {
  570. name: 'common.across.services.configurations',
  571. sender: this,
  572. data: {
  573. data: '[' + services.toString() + ']'
  574. },
  575. error: 'doPUTClusterConfigurationSiteErrorCallback'
  576. };
  577. if (showPopup) {
  578. ajaxData.success = 'doPUTClusterConfigurationSiteSuccessCallback'
  579. }
  580. return App.ajax.send(ajaxData);
  581. },
  582. /*********************************** 5. AFTER SAVE INFO ************************************/
  583. /**
  584. * @private
  585. * @method putConfigGroupChangesSuccess
  586. */
  587. putConfigGroupChangesSuccess: function () {
  588. this.set('saveConfigsFlag', true);
  589. this.onDoPUTClusterConfigurations();
  590. },
  591. /**
  592. * @private
  593. * @method doPUTClusterConfigurationSiteSuccessCallback
  594. */
  595. doPUTClusterConfigurationSiteSuccessCallback: function () {
  596. this.onDoPUTClusterConfigurations();
  597. },
  598. /**
  599. * @private
  600. * @method doPUTClusterConfigurationSiteErrorCallback
  601. */
  602. doPUTClusterConfigurationSiteErrorCallback: function () {
  603. this.set('saveConfigsFlag', false);
  604. this.doPUTClusterConfigurationSiteSuccessCallback();
  605. },
  606. /**
  607. * On save configs handler. Open save configs popup with appropriate message
  608. * and clear config dependencies list.
  609. * @private
  610. * @method onDoPUTClusterConfigurations
  611. */
  612. onDoPUTClusterConfigurations: function () {
  613. var header, message, messageClass, value, status = 'unknown', urlParams = '',
  614. result = {
  615. flag: this.get('saveConfigsFlag'),
  616. message: null,
  617. value: null
  618. },
  619. extendedModel = App.Service.extendedModel[this.get('content.serviceName')],
  620. currentService = extendedModel ? App[extendedModel].find(this.get('content.serviceName')) : App.Service.find(this.get('content.serviceName'));
  621. if (!result.flag) {
  622. result.message = Em.I18n.t('services.service.config.failSaveConfig');
  623. }
  624. App.router.get('clusterController').updateClusterData();
  625. App.router.get('updateController').updateComponentConfig(function () {
  626. });
  627. var flag = result.flag;
  628. if (result.flag === true) {
  629. header = Em.I18n.t('services.service.config.saved');
  630. message = Em.I18n.t('services.service.config.saved.message');
  631. messageClass = 'alert alert-success';
  632. // warn the user if any of the components are in UNKNOWN state
  633. urlParams += ',ServiceComponentInfo/installed_count,ServiceComponentInfo/total_count';
  634. if (this.get('content.serviceName') === 'HDFS') {
  635. urlParams += '&ServiceComponentInfo/service_name.in(HDFS)'
  636. }
  637. } else {
  638. header = Em.I18n.t('common.failure');
  639. message = result.message;
  640. messageClass = 'alert alert-error';
  641. value = result.value;
  642. }
  643. if(currentService){
  644. App.QuickViewLinks.proto().set('content', currentService);
  645. App.QuickViewLinks.proto().loadTags();
  646. }
  647. this.showSaveConfigsPopup(header, flag, message, messageClass, value, status, urlParams);
  648. this.clearDependentConfigs();
  649. },
  650. /**
  651. * Show save configs popup
  652. * @return {App.ModalPopup}
  653. * @private
  654. * @method showSaveConfigsPopup
  655. */
  656. showSaveConfigsPopup: function (header, flag, message, messageClass, value, status, urlParams) {
  657. var self = this;
  658. if (flag) {
  659. this.set('forceTransition', flag);
  660. self.loadStep();
  661. }
  662. return App.ModalPopup.show({
  663. header: header,
  664. primary: Em.I18n.t('ok'),
  665. secondary: null,
  666. onPrimary: function () {
  667. this.hide();
  668. if (!flag) {
  669. self.completeSave();
  670. }
  671. },
  672. onClose: function () {
  673. this.hide();
  674. self.completeSave();
  675. },
  676. disablePrimary: true,
  677. bodyClass: Ember.View.extend({
  678. flag: flag,
  679. message: function () {
  680. return this.get('isLoaded') ? message : Em.I18n.t('services.service.config.saving.message');
  681. }.property('isLoaded'),
  682. messageClass: function () {
  683. return this.get('isLoaded') ? messageClass : 'alert alert-info';
  684. }.property('isLoaded'),
  685. setDisablePrimary: function () {
  686. this.get('parentView').set('disablePrimary', !this.get('isLoaded'));
  687. }.observes('isLoaded'),
  688. runningHosts: [],
  689. runningComponentCount: 0,
  690. unknownHosts: [],
  691. unknownComponentCount: 0,
  692. siteProperties: value,
  693. isLoaded: false,
  694. componentsFilterSuccessCallback: function (response) {
  695. var count = 0,
  696. view = this,
  697. lazyLoadHosts = function (dest) {
  698. lazyLoading.run({
  699. initSize: 20,
  700. chunkSize: 50,
  701. delay: 50,
  702. destination: dest,
  703. source: hosts,
  704. context: view
  705. });
  706. },
  707. /**
  708. * Map components for their hosts
  709. * Return format:
  710. * <code>
  711. * {
  712. * host1: [component1, component2, ...],
  713. * host2: [component3, component4, ...]
  714. * }
  715. * </code>
  716. * @return {object}
  717. */
  718. setComponents = function (item, components) {
  719. item.host_components.forEach(function (c) {
  720. var name = c.HostRoles.host_name;
  721. if (!components[name]) {
  722. components[name] = [];
  723. }
  724. components[name].push(App.format.role(item.ServiceComponentInfo.component_name));
  725. });
  726. return components;
  727. },
  728. /**
  729. * Map result of <code>setComponents</code> to array
  730. * @return {{name: string, components: string}[]}
  731. */
  732. setHosts = function (components) {
  733. var hosts = [];
  734. Em.keys(components).forEach(function (key) {
  735. hosts.push({
  736. name: key,
  737. components: components[key].join(', ')
  738. });
  739. });
  740. return hosts;
  741. },
  742. components = {},
  743. hosts = [];
  744. switch (status) {
  745. case 'unknown':
  746. response.items.filter(function (item) {
  747. return (item.ServiceComponentInfo.total_count > item.ServiceComponentInfo.started_count + item.ServiceComponentInfo.installed_count);
  748. }).forEach(function (item) {
  749. var total = item.ServiceComponentInfo.total_count,
  750. started = item.ServiceComponentInfo.started_count,
  751. installed = item.ServiceComponentInfo.installed_count,
  752. unknown = total - started + installed;
  753. components = setComponents(item, components);
  754. count += unknown;
  755. });
  756. hosts = setHosts(components);
  757. this.set('unknownComponentCount', count);
  758. lazyLoadHosts(this.get('unknownHosts'));
  759. break;
  760. case 'started':
  761. response.items.filterProperty('ServiceComponentInfo.started_count').forEach(function (item) {
  762. var started = item.ServiceComponentInfo.started_count;
  763. components = setComponents(item, components);
  764. count += started;
  765. hosts = setHosts(components);
  766. });
  767. this.set('runningComponentCount', count);
  768. lazyLoadHosts(this.get('runningHosts'));
  769. break;
  770. }
  771. },
  772. componentsFilterErrorCallback: function () {
  773. this.set('isLoaded', true);
  774. },
  775. didInsertElement: function () {
  776. return App.ajax.send({
  777. name: 'components.filter_by_status',
  778. sender: this,
  779. data: {
  780. clusterName: App.get('clusterName'),
  781. urlParams: urlParams
  782. },
  783. success: 'componentsFilterSuccessCallback',
  784. error: 'componentsFilterErrorCallback'
  785. });
  786. },
  787. getDisplayMessage: function () {
  788. var displayMsg = [];
  789. var siteProperties = this.get('siteProperties');
  790. if (siteProperties) {
  791. siteProperties.forEach(function (_siteProperty) {
  792. var displayProperty = _siteProperty.siteProperty;
  793. var displayNames = _siteProperty.displayNames;
  794. if (displayNames && displayNames.length) {
  795. if (displayNames.length === 1) {
  796. displayMsg.push(displayProperty + Em.I18n.t('as') + displayNames[0]);
  797. } else {
  798. var name;
  799. displayNames.forEach(function (_name, index) {
  800. if (index === 0) {
  801. name = _name;
  802. } else if (index === siteProperties.length - 1) {
  803. name = name + Em.I18n.t('and') + _name;
  804. } else {
  805. name = name + ', ' + _name;
  806. }
  807. }, this);
  808. displayMsg.push(displayProperty + Em.I18n.t('as') + name);
  809. }
  810. } else {
  811. displayMsg.push(displayProperty);
  812. }
  813. }, this);
  814. }
  815. return displayMsg;
  816. }.property('siteProperties'),
  817. runningHostsMessage: function () {
  818. return Em.I18n.t('services.service.config.stopService.runningHostComponents').format(this.get('runningComponentCount'), this.get('runningHosts.length'));
  819. }.property('runningComponentCount', 'runningHosts.length'),
  820. unknownHostsMessage: function () {
  821. return Em.I18n.t('services.service.config.stopService.unknownHostComponents').format(this.get('unknownComponentCount'), this.get('unknownHosts.length'));
  822. }.property('unknownComponentCount', 'unknownHosts.length'),
  823. templateName: require('templates/main/service/info/configs_save_popup')
  824. })
  825. })
  826. },
  827. /*********************************** 6. ADDITIONAL *******************************************/
  828. /**
  829. * If some configs are changed and user navigates away or select another config-group, show this popup with propose to save changes
  830. * @param {String} path
  831. * @param {object} callback - callback with action to change configs view(change group or version)
  832. * @return {App.ModalPopup}
  833. * @method showSavePopup
  834. */
  835. showSavePopup: function (path, callback) {
  836. var self = this;
  837. return App.ModalPopup.show({
  838. header: Em.I18n.t('common.warning'),
  839. bodyClass: Em.View.extend({
  840. templateName: require('templates/common/configs/save_configuration'),
  841. showSaveWarning: true,
  842. notesArea: Em.TextArea.extend({
  843. classNames: ['full-width'],
  844. placeholder: Em.I18n.t('dashboard.configHistory.info-bar.save.popup.placeholder'),
  845. onChangeValue: function() {
  846. this.get('parentView.parentView').set('serviceConfigNote', this.get('value'));
  847. }.observes('value')
  848. })
  849. }),
  850. footerClass: Ember.View.extend({
  851. templateName: require('templates/main/service/info/save_popup_footer')
  852. }),
  853. primary: Em.I18n.t('common.save'),
  854. secondary: Em.I18n.t('common.cancel'),
  855. onSave: function () {
  856. self.set('serviceConfigVersionNote', this.get('serviceConfigNote'));
  857. self.saveStepConfigs();
  858. this.hide();
  859. },
  860. onDiscard: function () {
  861. self.set('preSelectedConfigVersion', null);
  862. if (path) {
  863. self.set('forceTransition', true);
  864. App.router.route(path);
  865. } else if (callback) {
  866. // Prevent multiple popups
  867. self.set('hash', self.getHash());
  868. callback();
  869. }
  870. this.hide();
  871. },
  872. onCancel: function () {
  873. this.hide();
  874. }
  875. });
  876. },
  877. /**
  878. * TODO the methods below are not used as the logic was changed
  879. * check and delete if this methods is not required
  880. */
  881. /**
  882. * filter out unchanged configurations
  883. * @param {Array} configsToSave
  884. * @private
  885. * @method filterChangedConfiguration
  886. */
  887. filterChangedConfiguration: function (configsToSave) {
  888. var changedConfigs = [];
  889. configsToSave.forEach(function (configSite) {
  890. var oldConfig = App.router.get('configurationController').getConfigsByTags([
  891. {siteName: configSite.type, tagName: this.loadedClusterSiteToTagMap[configSite.type]}
  892. ]);
  893. oldConfig = oldConfig[0] || {};
  894. var oldProperties = oldConfig.properties || {};
  895. var oldAttributes = oldConfig["properties_attributes"] || {};
  896. var newProperties = configSite.properties || {};
  897. var newAttributes = configSite["properties_attributes"] || {};
  898. if (this.isAttributesChanged(oldAttributes, newAttributes) || this.isConfigChanged(oldProperties, newProperties) || this.get('modifiedFileNames').contains(App.config.getOriginalFileName(configSite.type))) {
  899. changedConfigs.push(configSite);
  900. }
  901. }, this);
  902. return changedConfigs;
  903. },
  904. /**
  905. * Compares the loaded config values with the saving config values.
  906. * @param {Object} loadedConfig -
  907. * loadedConfig: {
  908. * configName1: "configValue1",
  909. * configName2: "configValue2"
  910. * }
  911. * @param {Object} savingConfig
  912. * savingConfig: {
  913. * configName1: "configValue1",
  914. * configName2: "configValue2"
  915. * }
  916. * @returns {boolean}
  917. * @private
  918. * @method isConfigChanged
  919. */
  920. isConfigChanged: function (loadedConfig, savingConfig) {
  921. if (loadedConfig != null && savingConfig != null) {
  922. var seenLoadKeys = [];
  923. for (var loadKey in loadedConfig) {
  924. if (!loadedConfig.hasOwnProperty(loadKey)) continue;
  925. seenLoadKeys.push(loadKey);
  926. var loadValue = loadedConfig[loadKey];
  927. var saveValue = savingConfig[loadKey];
  928. if ("boolean" == typeof(saveValue)) {
  929. saveValue = saveValue.toString();
  930. }
  931. if (saveValue == null) {
  932. saveValue = "null";
  933. }
  934. if (loadValue !== saveValue) {
  935. return true;
  936. }
  937. }
  938. for (var saveKey in savingConfig) {
  939. if (seenLoadKeys.indexOf(saveKey) < 0) {
  940. return true;
  941. }
  942. }
  943. }
  944. return false;
  945. },
  946. /**
  947. * Compares the loaded config properties attributes with the saving config properties attributes.
  948. * @param {Object} oldAttributes -
  949. * oldAttributes: {
  950. * supports: {
  951. * final: {
  952. * "configValue1" : "true",
  953. * "configValue2" : "true"
  954. * }
  955. * }
  956. * }
  957. * @param {Object} newAttributes
  958. * newAttributes: {
  959. * supports: {
  960. * final: {
  961. * "configValue1" : "true",
  962. * "configValue2" : "true"
  963. * }
  964. * }
  965. * }
  966. * @returns {boolean}
  967. * @private
  968. * @method isAttributesChanged
  969. */
  970. isAttributesChanged: function (oldAttributes, newAttributes) {
  971. oldAttributes = oldAttributes.final || {};
  972. newAttributes = newAttributes.final || {};
  973. var key;
  974. for (key in oldAttributes) {
  975. if (oldAttributes.hasOwnProperty(key)
  976. && (!newAttributes.hasOwnProperty(key) || newAttributes[key] !== oldAttributes[key])) {
  977. return true;
  978. }
  979. }
  980. for (key in newAttributes) {
  981. if (newAttributes.hasOwnProperty(key)
  982. && (!oldAttributes.hasOwnProperty(key) || newAttributes[key] !== oldAttributes[key])) {
  983. return true;
  984. }
  985. }
  986. return false;
  987. },
  988. /**
  989. * Save "final" attribute for properties
  990. * @param {Array} properties - array of properties
  991. * @returns {Object|null}
  992. * @method getConfigAttributes
  993. */
  994. getConfigAttributes: function(properties) {
  995. var attributes = {
  996. final: {}
  997. };
  998. var finalAttributes = attributes.final;
  999. var hasAttributes = false;
  1000. properties.forEach(function (property) {
  1001. if (property.isRequiredByAgent !== false && property.isFinal) {
  1002. hasAttributes = true;
  1003. finalAttributes[property.name] = "true";
  1004. }
  1005. });
  1006. if (hasAttributes) {
  1007. return attributes;
  1008. }
  1009. return null;
  1010. },
  1011. /**
  1012. * create site object
  1013. * @param {string} siteName
  1014. * @param {string} tagName
  1015. * @param {object[]} siteObj
  1016. * @return {Object}
  1017. * @method createSiteObj
  1018. */
  1019. createSiteObj: function (siteName, tagName, siteObj) {
  1020. var heapsizeException = this.get('heapsizeException');
  1021. var heapsizeRegExp = this.get('heapsizeRegExp');
  1022. var siteProperties = {};
  1023. siteObj.forEach(function (_siteObj) {
  1024. var value = _siteObj.value;
  1025. if (_siteObj.isRequiredByAgent == false) return;
  1026. // site object name follow the format *permsize/*heapsize and the value NOT ends with "m"
  1027. if (heapsizeRegExp.test(_siteObj.name) && !heapsizeException.contains(_siteObj.name) && !(_siteObj.value).endsWith("m")) {
  1028. value += "m";
  1029. }
  1030. siteProperties[_siteObj.name] = value;
  1031. switch (siteName) {
  1032. case 'falcon-startup.properties':
  1033. case 'falcon-runtime.properties':
  1034. case 'pig-properties':
  1035. siteProperties[_siteObj.name] = value;
  1036. break;
  1037. default:
  1038. siteProperties[_siteObj.name] = this.setServerConfigValue(_siteObj.name, value);
  1039. }
  1040. }, this);
  1041. var result = {"type": siteName, "tag": tagName, "properties": siteProperties};
  1042. var attributes = this.getConfigAttributes(siteObj);
  1043. if (attributes) {
  1044. result['properties_attributes'] = attributes;
  1045. }
  1046. return result;
  1047. },
  1048. /**
  1049. * This method will be moved to config's decorators class.
  1050. *
  1051. * For now, provide handling for special properties that need
  1052. * be specified in special format required for server.
  1053. *
  1054. * @param configName {String} - name of config property
  1055. * @param value {*} - value of config property
  1056. *
  1057. * @return {String} - formatted value
  1058. * @method setServerConfigValue
  1059. */
  1060. setServerConfigValue: function (configName, value) {
  1061. switch (configName) {
  1062. case 'storm.zookeeper.servers':
  1063. case 'nimbus.seeds':
  1064. if(Em.isArray(value)) {
  1065. return JSON.stringify(value).replace(/"/g, "'");
  1066. } else {
  1067. return value;
  1068. }
  1069. break;
  1070. default:
  1071. return value;
  1072. }
  1073. }
  1074. });