service_config_property.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540
  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 validator = require('utils/validator');
  20. App.ServiceConfigProperty = Em.Object.extend({
  21. id: '', //either 'puppet var' or 'site property'
  22. name: '',
  23. displayName: '',
  24. /**
  25. * value that is shown on IU
  26. * and is changing by user
  27. * @type {String|null}
  28. */
  29. value: '',
  30. /**
  31. * value that is saved on cluster configs
  32. * and stored in /api/v1/clusters/{name}/configurations
  33. * @type {String|null}
  34. */
  35. savedValue: null,
  36. /**
  37. * value that is returned from server as recommended
  38. * or stored on stack
  39. * @type {String|null}
  40. */
  41. recommendedValue: null,
  42. /**
  43. * initial value of config. if value is saved it will be initial
  44. * otherwise first recommendedValue will be initial
  45. * @type {String|null}
  46. */
  47. initialValue: null,
  48. /**
  49. * value that is shown on IU
  50. * and is changing by user
  51. * @type {boolean}
  52. */
  53. isFinal: false,
  54. /**
  55. * value that is saved on cluster configs api
  56. * @type {boolean}
  57. */
  58. savedIsFinal: null,
  59. /**
  60. * value that is returned from server as recommended
  61. * or stored on stack
  62. * @type {boolean}
  63. */
  64. recommendedIsFinal: null,
  65. /**
  66. * @type {boolean}
  67. */
  68. supportsFinal: false,
  69. retypedPassword: '',
  70. defaultDirectory: '',
  71. description: '',
  72. displayType: 'string', // string, digits, number, directories, custom
  73. unit: '',
  74. category: 'General',
  75. isRequired: true, // by default a config property is required
  76. isReconfigurable: true, // by default a config property is reconfigurable
  77. isEditable: true, // by default a config property is editable
  78. isNotEditable: Ember.computed.not('isEditable'),
  79. hideFinalIcon: function () {
  80. return (!this.get('isFinal'))&& this.get('isNotEditable');
  81. }.property('isFinal', 'isNotEditable'),
  82. isVisible: true,
  83. isMock: false, // mock config created created only to displaying
  84. isRequiredByAgent: true, // Setting it to true implies property will be stored in configuration
  85. isSecureConfig: false,
  86. errorMessage: '',
  87. warnMessage: '',
  88. serviceConfig: null, // points to the parent App.ServiceConfig object
  89. filename: '',
  90. isOriginalSCP : true, // if true, then this is original SCP instance and its value is not overridden value.
  91. parentSCP: null, // This is the main SCP which is overridden by this. Set only when isOriginalSCP is false.
  92. selectedHostOptions : null, // contain array of hosts configured with overridden value
  93. overrides : null,
  94. overrideValues: [],
  95. group: null, // Contain group related to this property. Set only when isOriginalSCP is false.
  96. isUserProperty: null, // This property was added by user. Hence they get removal actions etc.
  97. isOverridable: true,
  98. compareConfigs: [],
  99. isComparison: false,
  100. hasCompareDiffs: false,
  101. showLabel: true,
  102. error: false,
  103. warn: false,
  104. /**
  105. * true if property has warning or error
  106. * @type {boolean}
  107. */
  108. hasIssues: function () {
  109. var originalSCPIssued = (this.get('errorMessage') + this.get('warnMessage')) !== "";
  110. var overridesIssue = false;
  111. (this.get('overrides') || []).forEach(function(override) {
  112. if (override.get('errorMessage') + override.get('warnMessage') !== "") {
  113. overridesIssue = true;
  114. return;
  115. }
  116. });
  117. return originalSCPIssued || overridesIssue;
  118. }.property('errorMessage', 'warnMessage', 'overrideErrorTrigger'),
  119. overrideErrorTrigger: 0, //Trigger for overridable property error
  120. isRestartRequired: false,
  121. restartRequiredMessage: 'Restart required',
  122. index: null, //sequence number in category
  123. editDone: false, //Text field: on focusOut: true, on focusIn: false
  124. isNotSaved: false, // user property was added but not saved
  125. hasInitialValue: false, //if true then property value is defined and saved to server
  126. isHiddenByFilter: false, //if true then hide this property (filtered out)
  127. rowStyleClass: null, // CSS-Class to be applied on the row showing this config
  128. showAsTextBox: false,
  129. /**
  130. * @type {boolean}
  131. */
  132. recommendedValueExists: function () {
  133. return !Em.isNone(this.get('recommendedValue')) && (this.get('recommendedValue') != "")
  134. && this.get('isRequiredByAgent') && !this.get('cantBeUndone');
  135. }.property('recommendedValue'),
  136. /**
  137. * Usage example see on <code>App.ServiceConfigRadioButtons.handleDBConnectionProperty()</code>
  138. *
  139. * @property {Ember.View} additionalView - custom view related to property
  140. **/
  141. additionalView: null,
  142. /**
  143. * On Overridable property error message, change overrideErrorTrigger value to recount number of errors service have
  144. */
  145. observeErrors: function () {
  146. this.set("overrideErrorTrigger", this.get("overrideErrorTrigger") + 1);
  147. }.observes("overrides.@each.errorMessage"),
  148. /**
  149. * No override capabilities for fields which are not edtiable
  150. * and fields which represent master hosts.
  151. */
  152. isPropertyOverridable: function () {
  153. var overrideable = this.get('isOverridable');
  154. var editable = this.get('isEditable');
  155. var overrides = this.get('overrides');
  156. var dt = this.get('displayType');
  157. return overrideable && (editable || !overrides || !overrides.length) && ("masterHost" != dt);
  158. }.property('isEditable', 'displayType', 'isOverridable', 'overrides.length'),
  159. isOverridden: function() {
  160. var overrides = this.get('overrides');
  161. return (overrides != null && overrides.get('length')>0) || !this.get('isOriginalSCP');
  162. }.property('overrides', 'overrides.length', 'isOriginalSCP'),
  163. isOverrideChanged: function () {
  164. if (Em.isNone(this.get('overrides')) && this.get('overrideValues.length') === 0) return false;
  165. return JSON.stringify(this.get('overrides').mapProperty('isFinal')) !== JSON.stringify(this.get('overrideIsFinalValues'))
  166. || JSON.stringify(this.get('overrides').mapProperty('value')) !== JSON.stringify(this.get('overrideValues'));
  167. }.property('isOverridden', 'overrides.@each.isNotDefaultValue'),
  168. isRemovable: function() {
  169. var isOriginalSCP = this.get('isOriginalSCP');
  170. var isUserProperty = this.get('isUserProperty');
  171. var isEditable = this.get('isEditable');
  172. var hasOverrides = this.get('overrides.length') > 0;
  173. // Removable when this is a user property, or it is not an original property and it is editable
  174. return isEditable && !hasOverrides && (isUserProperty || !isOriginalSCP);
  175. }.property('isUserProperty', 'isOriginalSCP', 'overrides.length'),
  176. init: function () {
  177. if ((this.get('id') === 'puppet var') && this.get('value') == '') {
  178. if (this.get('savedValue')) {
  179. this.set('value', this.get('savedValue'));
  180. } else if (this.get('recommendedValue')) {
  181. this.set('value', this.get('recommendedValue'));
  182. }
  183. }
  184. if(this.get("displayType") === "password"){
  185. this.set('retypedPassword', this.get('value'));
  186. this.set('recommendedValue', '');
  187. }
  188. this.set('initialValue', this.get('value'));
  189. },
  190. /**
  191. * Indicates when value is not the default value.
  192. * Returns false when there is no default value.
  193. */
  194. isNotDefaultValue: function () {
  195. var value = this.get('value');
  196. var savedValue = this.get('savedValue');
  197. var supportsFinal = this.get('supportsFinal');
  198. var isFinal = this.get('isFinal');
  199. var savedIsFinal = this.get('savedIsFinal');
  200. // ignore precision difference for configs with type of `float` which value may ends with 0
  201. // e.g. between 0.4 and 0.40
  202. if (this.get('stackConfigProperty') && this.get('stackConfigProperty.valueAttributes.type') == 'float') {
  203. savedValue = !Em.isNone(savedValue) ? '' + parseFloat(savedValue) : null;
  204. value = '' + parseFloat(value);
  205. }
  206. return (savedValue != null && value !== savedValue) || (supportsFinal && !Em.isNone(savedIsFinal) && isFinal !== savedIsFinal);
  207. }.property('value', 'savedValue', 'isEditable', 'isFinal', 'savedIsFinal'),
  208. /**
  209. * Don't show "Undo" for hosts on Installer Step7
  210. */
  211. cantBeUndone: function() {
  212. return ["masterHost", "slaveHosts", "masterHosts", "slaveHost", "radio button"].contains(this.get('displayType'));
  213. }.property('displayType'),
  214. /**
  215. * Used in <code>templates/common/configs/service_config_category.hbs</code>
  216. * @type {boolean}
  217. */
  218. undoAvailable: function () {
  219. return !this.get('cantBeUndone') && this.get('isNotDefaultValue');
  220. }.property('cantBeUndone', 'isNotDefaultValue'),
  221. /**
  222. * Used in <code>templates/common/configs/service_config_category.hbs</code>
  223. * @type {boolean}
  224. */
  225. removeAvailable: function () {
  226. return this.get('isRemovable') && !this.get('isComparison');
  227. }.property('isComparison', 'isRemovable'),
  228. /**
  229. * Used in <code>templates/common/configs/service_config_category.hbs</code>
  230. * @type {boolean}
  231. */
  232. switchGroupAvailable: function () {
  233. return !this.get('isEditable') && this.get('group');
  234. }.property('isEditable', 'group'),
  235. /**
  236. * Used in <code>templates/common/configs/service_config_category.hbs</code>
  237. * @type {boolean}
  238. */
  239. setRecommendedAvailable: function () {
  240. return this.get('isEditable') && this.get('recommendedValueExists');
  241. }.property('isEditable', 'recommendedValueExists'),
  242. /**
  243. * Used in <code>templates/common/configs/service_config_category.hbs</code>
  244. * @type {boolean}
  245. */
  246. overrideAvailable: function () {
  247. return !this.get('isComparison') && this.get('isPropertyOverridable') && (this.get('displayType') !== 'password');
  248. }.property('isPropertyOverridable', 'isComparison'),
  249. isValid: function () {
  250. return this.get('errorMessage') === '';
  251. }.property('errorMessage'),
  252. viewClass: function () {
  253. switch (this.get('displayType')) {
  254. case 'checkbox':
  255. if (this.get('dependentConfigPattern')) {
  256. return App.ServiceConfigCheckboxWithDependencies;
  257. } else {
  258. return App.ServiceConfigCheckbox;
  259. }
  260. case 'password':
  261. return App.ServiceConfigPasswordField;
  262. case 'combobox':
  263. return App.ServiceConfigComboBox;
  264. case 'radio button':
  265. return App.ServiceConfigRadioButtons;
  266. break;
  267. case 'directories':
  268. case 'datanodedirs':
  269. return App.ServiceConfigTextArea;
  270. break;
  271. case 'content':
  272. return App.ServiceConfigTextAreaContent;
  273. break;
  274. case 'multiLine':
  275. return App.ServiceConfigTextArea;
  276. break;
  277. case 'custom':
  278. return App.ServiceConfigBigTextArea;
  279. case 'masterHost':
  280. return App.ServiceConfigMasterHostView;
  281. case 'label':
  282. return App.ServiceConfigLabelView;
  283. case 'masterHosts':
  284. return App.ServiceConfigMasterHostsView;
  285. case 'slaveHosts':
  286. return App.ServiceConfigSlaveHostsView;
  287. case 'supportTextConnection':
  288. return App.checkConnectionView;
  289. default:
  290. if (this.get('unit')) {
  291. return App.ServiceConfigTextFieldWithUnit;
  292. } else {
  293. return App.ServiceConfigTextField;
  294. }
  295. }
  296. }.property('displayType'),
  297. validate: function () {
  298. var value = this.get('value');
  299. var supportsFinal = this.get('supportsFinal');
  300. var isFinal = this.get('isFinal');
  301. var valueRange = this.get('valueRange');
  302. var values = [];//value split by "," to check UNIX users, groups list
  303. var isError = false;
  304. var isWarn = false;
  305. if (typeof value === 'string' && value.length === 0) {
  306. if (this.get('isRequired')) {
  307. this.set('errorMessage', 'This is required');
  308. isError = true;
  309. } else {
  310. return;
  311. }
  312. }
  313. if (!isError) {
  314. switch (this.get('displayType')) {
  315. case 'int':
  316. if (!validator.isValidInt(value)) {
  317. this.set('errorMessage', 'Must contain digits only');
  318. isError = true;
  319. } else {
  320. if(valueRange){
  321. if(value < valueRange[0] || value > valueRange[1]){
  322. this.set('errorMessage', 'Must match the range');
  323. isError = true;
  324. }
  325. }
  326. }
  327. break;
  328. case 'float':
  329. if (!validator.isValidFloat(value)) {
  330. this.set('errorMessage', 'Must be a valid number');
  331. isError = true;
  332. }
  333. break;
  334. case 'UNIXList':
  335. if(value != '*'){
  336. values = value.split(',');
  337. for(var i = 0, l = values.length; i < l; i++){
  338. if(!validator.isValidUNIXUser(values[i])){
  339. if(this.get('type') == 'USERS'){
  340. this.set('errorMessage', 'Must be a valid list of user names');
  341. } else {
  342. this.set('errorMessage', 'Must be a valid list of group names');
  343. }
  344. isError = true;
  345. }
  346. }
  347. }
  348. break;
  349. case 'checkbox':
  350. break;
  351. case 'datanodedirs':
  352. if (!validator.isValidDataNodeDir(value)) {
  353. this.set('errorMessage', 'dir format is wrong, can be "[{storage type}]/{dir name}"');
  354. isError = true;
  355. }
  356. else {
  357. if (!validator.isAllowedDir(value)) {
  358. this.set('errorMessage', 'Cannot start with "home(s)"');
  359. isError = true;
  360. }
  361. }
  362. break;
  363. case 'directories':
  364. case 'directory':
  365. if (!validator.isValidDir(value)) {
  366. this.set('errorMessage', 'Must be a slash or drive at the start');
  367. isError = true;
  368. }
  369. else {
  370. if (!validator.isAllowedDir(value)) {
  371. this.set('errorMessage', 'Can\'t start with "home(s)"');
  372. isError = true;
  373. }
  374. }
  375. break;
  376. case 'custom':
  377. break;
  378. case 'email':
  379. if (!validator.isValidEmail(value)) {
  380. this.set('errorMessage', 'Must be a valid email address');
  381. isError = true;
  382. }
  383. break;
  384. case 'host':
  385. var hiveOozieHostNames = ['hive_hostname','hive_existing_mysql_host','hive_existing_oracle_host','hive_ambari_host',
  386. 'oozie_hostname','oozie_existing_mysql_host','oozie_existing_oracle_host','oozie_ambari_host'];
  387. if(hiveOozieHostNames.contains(this.get('name'))) {
  388. if (validator.hasSpaces(value)) {
  389. this.set('errorMessage', Em.I18n.t('host.spacesValidation'));
  390. isError = true;
  391. }
  392. } else {
  393. if (validator.isNotTrimmed(value)) {
  394. this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
  395. isError = true;
  396. }
  397. }
  398. break;
  399. case 'advanced':
  400. if(this.get('name')=='javax.jdo.option.ConnectionURL' || this.get('name')=='oozie.service.JPAService.jdbc.url') {
  401. if (validator.isNotTrimmed(value)) {
  402. this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
  403. isError = true;
  404. }
  405. }
  406. break;
  407. case 'password':
  408. // retypedPassword is set by the retypePasswordView child view of App.ServiceConfigPasswordField
  409. if (value !== this.get('retypedPassword')) {
  410. this.set('errorMessage', 'Passwords do not match');
  411. isError = true;
  412. }
  413. }
  414. }
  415. if (!isError) {
  416. isError = this._validateOverrides();
  417. }
  418. if (!isWarn || isError) { // Errors get priority
  419. this.set('warnMessage', '');
  420. this.set('warn', false);
  421. } else {
  422. this.set('warn', true);
  423. }
  424. if (!isError) {
  425. this.set('errorMessage', '');
  426. this.set('error', false);
  427. } else {
  428. this.set('error', true);
  429. }
  430. }.observes('value', 'isFinal', 'retypedPassword'),
  431. /**
  432. * Check config overrides and parent config overrides (if exist)
  433. * @returns {boolean}
  434. * @private
  435. * @method _validateOverrides
  436. */
  437. _validateOverrides: function () {
  438. var self = this;
  439. var isError = false;
  440. var value = this._getValueForCheck(this.get('value'));
  441. var isOriginalSCP = this.get('isOriginalSCP');
  442. var supportsFinal = this.get('supportsFinal');
  443. var isFinal = this.get('isFinal');
  444. var parentSCP = this.get('parentSCP');
  445. if (!isOriginalSCP) {
  446. if (!Em.isNone(parentSCP)) {
  447. if (value === this._getValueForCheck(parentSCP.get('value'))) {
  448. if (supportsFinal) {
  449. if (isFinal === parentSCP.get('isFinal')) {
  450. this.set('errorMessage', Em.I18n.t('config.override.valueEqualToParentConfig'));
  451. isError = true;
  452. }
  453. }
  454. else {
  455. this.set('errorMessage', Em.I18n.t('config.override.valueEqualToParentConfig'));
  456. isError = true;
  457. }
  458. }
  459. else {
  460. var overrides = parentSCP.get('overrides');
  461. if (overrides) {
  462. overrides.forEach(function (override) {
  463. if (self == override) return;
  464. if (value === self._getValueForCheck(override.get('value'))) {
  465. if (supportsFinal) {
  466. if (isFinal === parentSCP.get('isFinal')) {
  467. self.set('errorMessage', Em.I18n.t('config.override.valueEqualToAnotherOverrideConfig'));
  468. isError = true;
  469. }
  470. }
  471. else {
  472. self.set('errorMessage', Em.I18n.t('config.override.valueEqualToAnotherOverrideConfig'));
  473. isError = true;
  474. }
  475. }
  476. });
  477. }
  478. }
  479. }
  480. }
  481. return isError;
  482. },
  483. /**
  484. * Some values should be little bit changed before checking for overrides values
  485. * `directories`-values should be "trimmed" for multiple mew-line symbols
  486. * @param {string} value
  487. * @returns {string}
  488. * @private
  489. */
  490. _getValueForCheck: function (value) {
  491. value = '' + value;
  492. switch(this.get('displayType')) {
  493. case 'directories':
  494. return value.replace(/(\n\r?)+/g, '\n');
  495. break;
  496. default:
  497. return value;
  498. }
  499. }
  500. });