assign_master_components.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117
  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 blueprintUtils = require('utils/blueprint');
  20. var numberUtils = require('utils/number_utils');
  21. var validationUtils = require('utils/validator');
  22. /**
  23. * Mixin for assign master-to-host step in wizards
  24. * Implements basic logic of assign masters page
  25. * Should be used with controller linked with App.AssignMasterComponentsView
  26. * @type {Ember.Mixin}
  27. */
  28. App.AssignMasterComponents = Em.Mixin.create({
  29. /**
  30. * Array of master component names to show on the page
  31. * By default is empty, this means that masters of all selected services should be shown
  32. * @type {Array}
  33. */
  34. mastersToShow: [],
  35. /**
  36. * Array of master component names to add for install
  37. * @type {Array}
  38. */
  39. mastersToAdd: [],
  40. /**
  41. * Array of master component names, that are already installed, but should have ability to change host
  42. * @type {Array}
  43. */
  44. mastersToMove: [],
  45. /**
  46. * Array of master component names, that should be addable
  47. * Are used in HA wizards to add components, that are not addable for other wizards
  48. * @type {Array}
  49. */
  50. mastersAddableInHA: [],
  51. /**
  52. * Array of master component names to show 'Current' prefix in label before component name
  53. * Prefix will be shown only for installed instances
  54. * @type {Array}
  55. */
  56. showCurrentPrefix: [],
  57. /**
  58. * Array of master component names to show 'Additional' prefix in label before component name
  59. * Prefix will be shown only for not installed instances
  60. * @type {Array}
  61. */
  62. showAdditionalPrefix: [],
  63. /**
  64. * Array of objects with label and host keys to show specific hosts on the page
  65. * @type {Array}
  66. * format:
  67. * [
  68. * {
  69. * label: 'Current',
  70. * host: 'c6401.ambari.apache.org'
  71. * },
  72. * {
  73. * label: 'Additional',
  74. * host: function () {
  75. * return 'c6402.ambari.apache.org';
  76. * }.property()
  77. * }
  78. * ]
  79. */
  80. additionalHostsList: [],
  81. /**
  82. * Define whether show already installed masters first
  83. * @type {Boolean}
  84. */
  85. showInstalledMastersFirst: false,
  86. /**
  87. * Array of <code>servicesMasters</code> objects, that will be shown on the page
  88. * Are filtered using <code>mastersToShow</code>
  89. * @type {Array}
  90. */
  91. servicesMastersToShow: function () {
  92. var mastersToShow = this.get('mastersToShow');
  93. var servicesMasters = this.get('servicesMasters');
  94. var result = [];
  95. if (!mastersToShow.length) {
  96. result = servicesMasters;
  97. } else {
  98. mastersToShow.forEach(function (master) {
  99. result = result.concat(servicesMasters.filterProperty('component_name', master));
  100. });
  101. }
  102. if (this.get('showInstalledMastersFirst')) {
  103. result = this.sortMasterComponents(result);
  104. }
  105. return result;
  106. }.property('servicesMasters.length', 'mastersToShow.length', 'showInstalledMastersFirst'),
  107. /**
  108. * Sort masters, installed masters will be first.
  109. * @param masters
  110. * @returns {Array}
  111. */
  112. sortMasterComponents: function (masters) {
  113. return [].concat(masters.filterProperty('isInstalled'), masters.filterProperty('isInstalled', false));
  114. },
  115. /**
  116. * Check if <code>installerWizard</code> used
  117. * @type {bool}
  118. */
  119. isInstallerWizard: Em.computed.equal('content.controllerName', 'installerController'),
  120. /**
  121. * Master components which could be assigned to multiple hosts
  122. * @type {string[]}
  123. */
  124. multipleComponents: Em.computed.alias('App.components.multipleMasters'),
  125. /**
  126. * Master components which could be assigned to multiple hosts
  127. * @type {string[]}
  128. */
  129. addableComponents: function () {
  130. return App.get('components.addableMasterInstallerWizard').concat(this.get('mastersAddableInHA')).uniq();
  131. }.property('App.components.addableMasterInstallerWizard', 'mastersAddableInHA'),
  132. /**
  133. * Define state for submit button
  134. * @type {bool}
  135. */
  136. submitDisabled: false,
  137. /**
  138. * Is Submit-click processing now
  139. * @type {bool}
  140. */
  141. submitButtonClicked: false,
  142. /**
  143. * Either use or not use server validation in this controller
  144. * @type {bool}
  145. */
  146. useServerValidation: true,
  147. /**
  148. * Trigger for executing host names check for components
  149. * Should de "triggered" when host changed for some component and when new multiple component is added/removed
  150. * @type {bool}
  151. */
  152. hostNameCheckTrigger: false,
  153. /**
  154. * List of hosts
  155. * @type {Array}
  156. */
  157. hosts: [],
  158. /**
  159. * Name of multiple component which host name was changed last
  160. * @type {Object|null}
  161. */
  162. componentToRebalance: null,
  163. /**
  164. * Name of component which host was changed last
  165. * @type {string}
  166. */
  167. lastChangedComponent: null,
  168. /**
  169. * Flag for rebalance multiple components
  170. * @type {number}
  171. */
  172. rebalanceComponentHostsCounter: 0,
  173. /**
  174. * @type {Ember.Enumerable}
  175. */
  176. servicesMasters: [],
  177. /**
  178. * @type {Ember.Enumerable}
  179. */
  180. selectedServicesMasters: [],
  181. /**
  182. * Is data for current step loaded
  183. * @type {bool}
  184. */
  185. isLoaded: false,
  186. /**
  187. * Validation error messages which don't related with any master
  188. */
  189. generalErrorMessages: [],
  190. /**
  191. * Validation warning messages which don't related with any master
  192. */
  193. generalWarningMessages: [],
  194. /**
  195. * Is masters-hosts layout initial one
  196. * @type {bool}
  197. */
  198. isInitialLayout: true,
  199. /**
  200. * true if any error exists
  201. */
  202. anyError: function() {
  203. return this.get('servicesMasters').some(function(m) { return m.get('errorMessage'); }) || this.get('generalErrorMessages').some(function(m) { return m; });
  204. }.property('servicesMasters.@each.errorMessage', 'generalErrorMessages'),
  205. /**
  206. * true if any warning exists
  207. */
  208. anyWarning: function() {
  209. return this.get('servicesMasters').some(function(m) { return m.get('warnMessage'); }) || this.get('generalWarningMessages').some(function(m) { return m; });
  210. }.property('servicesMasters.@each.warnMessage', 'generalWarningMessages'),
  211. /**
  212. * Clear loaded recommendations
  213. */
  214. clearRecommendations: function() {
  215. if (this.get('content.recommendations')) {
  216. this.set('content.recommendations', null);
  217. }
  218. },
  219. /**
  220. * List of host with assigned masters
  221. * Format:
  222. * <code>
  223. * [
  224. * {
  225. * host_name: '',
  226. * hostInfo: {},
  227. * masterServices: [],
  228. * masterServicesToDisplay: [] // used only in template
  229. * },
  230. * ....
  231. * ]
  232. * </code>
  233. * @type {Ember.Enumerable}
  234. */
  235. masterHostMapping: function () {
  236. var mapping = [], mappingObject, mappedHosts, hostObj;
  237. //get the unique assigned hosts and find the master services assigned to them
  238. mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq();
  239. mappedHosts.forEach(function (item) {
  240. hostObj = this.get("hosts").findProperty("host_name", item);
  241. // User may input invalid host name (this is handled in hostname checker). Here we just skip it
  242. if (!hostObj) return;
  243. var masterServices = this.get("selectedServicesMasters").filterProperty("selectedHost", item),
  244. masterServicesToDisplay = [];
  245. masterServices.mapProperty('display_name').uniq().forEach(function (n) {
  246. masterServicesToDisplay.pushObject(masterServices.findProperty('display_name', n));
  247. });
  248. mappingObject = Em.Object.create({
  249. host_name: item,
  250. hostInfo: hostObj.host_info,
  251. masterServices: masterServices,
  252. masterServicesToDisplay: masterServicesToDisplay
  253. });
  254. mapping.pushObject(mappingObject);
  255. }, this);
  256. return mapping.sortProperty('host_name');
  257. }.property("selectedServicesMasters.@each.selectedHost", 'selectedServicesMasters.@each.isHostNameValid'),
  258. /**
  259. * Count of hosts without masters
  260. * @type {number}
  261. */
  262. remainingHosts: function () {
  263. if (this.get('content.controllerName') === 'installerController') {
  264. return 0;
  265. } else {
  266. return (this.get("hosts.length") - this.get("masterHostMapping.length"));
  267. }
  268. }.property('masterHostMapping.length', 'selectedServicesMasters.@each.selectedHost'),
  269. /**
  270. * Update submit button status
  271. * @metohd updateIsSubmitDisabled
  272. */
  273. updateIsSubmitDisabled: function () {
  274. if (this.thereIsNoMasters()) {
  275. return false;
  276. }
  277. var isSubmitDisabled = this.get('servicesMasters').someProperty('isHostNameValid', false);
  278. if (this.get('useServerValidation')) {
  279. this.set('submitDisabled', true);
  280. if (this.get('servicesMasters').length === 0) {
  281. return;
  282. }
  283. if (!isSubmitDisabled) {
  284. if (!this.get('isInitialLayout')) {
  285. this.clearRecommendations(); // reset previous recommendations
  286. } else {
  287. this.set('isInitialLayout', false);
  288. }
  289. this.recommendAndValidate();
  290. }
  291. } else {
  292. isSubmitDisabled = isSubmitDisabled || !this.customClientSideValidation();
  293. this.set('submitDisabled', isSubmitDisabled);
  294. return isSubmitDisabled;
  295. }
  296. }.observes('servicesMasters.@each.selectedHost'),
  297. /**
  298. * Function to validate master-to-host assignments
  299. * Should be defined in controller
  300. * @returns {boolean}
  301. */
  302. customClientSideValidation: function () {
  303. return true;
  304. },
  305. /**
  306. * Send AJAX request to validate current host layout
  307. * @param blueprint - blueprint for validation (can be with/withour slave/client components)
  308. */
  309. validate: function(blueprint, callback) {
  310. var self = this;
  311. var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
  312. var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
  313. var services = installedServices.concat(selectedServices).uniq();
  314. var hostNames = self.get('hosts').mapProperty('host_name');
  315. App.ajax.send({
  316. name: 'config.validations',
  317. sender: self,
  318. data: {
  319. stackVersionUrl: App.get('stackVersionURL'),
  320. hosts: hostNames,
  321. services: services,
  322. validate: 'host_groups',
  323. recommendations: blueprint
  324. },
  325. success: 'updateValidationsSuccessCallback',
  326. error: 'updateValidationsErrorCallback'
  327. }).then(function() {
  328. if (callback) {
  329. callback();
  330. }
  331. }
  332. );
  333. },
  334. /**
  335. * Success-callback for validations request
  336. * @param {object} data
  337. * @method updateValidationsSuccessCallback
  338. */
  339. updateValidationsSuccessCallback: function (data) {
  340. var self = this;
  341. var generalErrorMessages = [];
  342. var generalWarningMessages = [];
  343. this.get('servicesMasters').setEach('warnMessage', null);
  344. this.get('servicesMasters').setEach('errorMessage', null);
  345. var anyErrors = false;
  346. var validationData = validationUtils.filterNotInstalledComponents(data);
  347. validationData.filterProperty('type', 'host-component').forEach(function(item) {
  348. var master = self.get('servicesMasters').find(function(m) {
  349. return m.component_name === item['component-name'] && m.selectedHost === item.host;
  350. });
  351. if (master) {
  352. if (item.level === 'ERROR') {
  353. anyErrors = true;
  354. master.set('errorMessage', item.message);
  355. } else if (item.level === 'WARN') {
  356. master.set('warnMessage', item.message);
  357. }
  358. }
  359. });
  360. this.set('generalErrorMessages', generalErrorMessages);
  361. this.set('generalWarningMessages', generalWarningMessages);
  362. // use this.set('submitDisabled', anyErrors); is validation results should block next button
  363. // It's because showValidationIssuesAcceptBox allow use accept validation issues and continue
  364. this.set('submitDisabled', false); //this.set('submitDisabled', anyErrors);
  365. },
  366. /**
  367. * Error-callback for validations request
  368. * @param {object} jqXHR
  369. * @param {object} ajaxOptions
  370. * @param {string} error
  371. * @param {object} opt
  372. * @method updateValidationsErrorCallback
  373. */
  374. updateValidationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
  375. },
  376. /**
  377. * Composes selected values of comboboxes into master blueprint + merge it with currenlty installed slave blueprint
  378. */
  379. getCurrentBlueprint: function() {
  380. var self = this;
  381. var res = {
  382. blueprint: { host_groups: [] },
  383. blueprint_cluster_binding: { host_groups: [] }
  384. };
  385. var mapping = self.get('masterHostMapping');
  386. mapping.forEach(function(item, i) {
  387. var group_name = 'host-group-' + (i+1);
  388. var host_group = {
  389. name: group_name,
  390. components: item.masterServices.map(function(master) {
  391. return { name: master.component_name };
  392. })
  393. };
  394. var binding = {
  395. name: group_name,
  396. hosts: [ { fqdn: item.host_name } ]
  397. };
  398. res.blueprint.host_groups.push(host_group);
  399. res.blueprint_cluster_binding.host_groups.push(binding);
  400. });
  401. return blueprintUtils.mergeBlueprints(res, self.getCurrentSlaveBlueprint());
  402. },
  403. /**
  404. * Clear controller data (hosts, masters etc)
  405. * @method clearStep
  406. */
  407. clearStep: function () {
  408. this.setProperties({
  409. hosts: [],
  410. selectedServicesMasters: [],
  411. servicesMasters: []
  412. });
  413. App.StackServiceComponent.find().forEach(function (stackComponent) {
  414. stackComponent.set('serviceComponentId', 1);
  415. }, this);
  416. },
  417. /**
  418. * Load controller data (hosts, host components etc)
  419. * @method loadStep
  420. */
  421. loadStep: function () {
  422. this.clearStep();
  423. this.renderHostInfo();
  424. this.loadComponentsRecommendationsFromServer(this.loadStepCallback);
  425. },
  426. /**
  427. * Callback after load controller data (hosts, host components etc)
  428. * @method loadStepCallback
  429. */
  430. loadStepCallback: function(components, self) {
  431. self.renderComponents(components);
  432. self.get('addableComponents').forEach(function (componentName) {
  433. self.updateComponent(componentName);
  434. }, self);
  435. if (self.thereIsNoMasters()) {
  436. App.router.send('next');
  437. }
  438. },
  439. /**
  440. * Returns true if there is no new master components which need assigment to host
  441. */
  442. thereIsNoMasters: function() {
  443. return !this.get("selectedServicesMasters").filterProperty('isInstalled', false).length;
  444. },
  445. /**
  446. * Used to set showAddControl flag for installer wizard
  447. * @method updateComponent
  448. */
  449. updateComponent: function (componentName) {
  450. var component = this.last(componentName);
  451. if (!component) {
  452. return;
  453. }
  454. var showControl = !App.StackServiceComponent.find().findProperty('componentName', componentName).get('stackService').get('isInstalled')
  455. || this.get('mastersAddableInHA').contains(componentName);
  456. if (showControl) {
  457. var mastersLength = this.get("selectedServicesMasters").filterProperty("component_name", componentName).length;
  458. if (mastersLength < this.getMaxNumberOfMasters(componentName)) {
  459. component.set('showAddControl', true);
  460. } else {
  461. component.set('showRemoveControl', mastersLength != 1);
  462. }
  463. }
  464. },
  465. /**
  466. * Count max number of instances for masters <code>componentName</code>, according to their cardinality and number of hosts
  467. * @param componentName
  468. * @returns {Number}
  469. */
  470. getMaxNumberOfMasters: function (componentName) {
  471. var maxByCardinality = App.StackServiceComponent.find().findProperty('componentName', componentName).get('maxToInstall');
  472. var hostsNumber = this.get("hosts.length");
  473. return Math.min(maxByCardinality, hostsNumber);
  474. },
  475. /**
  476. * Load active host list to <code>hosts</code> variable
  477. * @method renderHostInfo
  478. */
  479. renderHostInfo: function () {
  480. var hostInfo = this.get('content.hosts');
  481. var result = [];
  482. for (var index in hostInfo) {
  483. var _host = hostInfo[index];
  484. if (_host.bootStatus === 'REGISTERED') {
  485. result.push(Em.Object.create({
  486. host_name: _host.name,
  487. cpu: _host.cpu,
  488. memory: _host.memory,
  489. disk_info: _host.disk_info,
  490. host_info: Em.I18n.t('installer.step5.hostInfo').fmt(_host.name, numberUtils.bytesToSize(_host.memory, 1, 'parseFloat', 1024), _host.cpu)
  491. }));
  492. }
  493. }
  494. this.set("hosts", result);
  495. this.sortHosts(this.get('hosts'));
  496. this.set('isLoaded', true);
  497. },
  498. /**
  499. * Sort list of host-objects by properties (memory - desc, cpu - desc, hostname - asc)
  500. * @param {object[]} hosts
  501. */
  502. sortHosts: function (hosts) {
  503. hosts.sort(function (a, b) {
  504. if (a.get('memory') == b.get('memory')) {
  505. if (a.get('cpu') == b.get('cpu')) {
  506. return a.get('host_name').localeCompare(b.get('host_name')); // hostname asc
  507. }
  508. return b.get('cpu') - a.get('cpu'); // cores desc
  509. }
  510. return b.get('memory') - a.get('memory'); // ram desc
  511. });
  512. },
  513. /**
  514. * Get recommendations info from API
  515. * @param {function}callback
  516. * @param {boolean} includeMasters
  517. * @method loadComponentsRecommendationsFromServer
  518. */
  519. loadComponentsRecommendationsFromServer: function(callback, includeMasters) {
  520. var self = this;
  521. if (this.get('content.recommendations')) {
  522. // Don't do AJAX call if recommendations has been already received
  523. // But if user returns to previous step (selecting services), stored recommendations will be cleared in routers' next handler and AJAX call will be made again
  524. callback(self.createComponentInstallationObjects(), self);
  525. }
  526. else {
  527. var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
  528. var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
  529. var services = installedServices.concat(selectedServices).uniq();
  530. var hostNames = self.get('hosts').mapProperty('host_name');
  531. var data = {
  532. stackVersionUrl: App.get('stackVersionURL'),
  533. hosts: hostNames,
  534. services: services,
  535. recommend: 'host_groups'
  536. };
  537. if (includeMasters) {
  538. // Made partial recommendation request for reflect in blueprint host-layout changes which were made by user in UI
  539. data.recommendations = self.getCurrentBlueprint();
  540. }
  541. else
  542. if (!self.get('isInstallerWizard')) {
  543. data.recommendations = self.getCurrentMasterSlaveBlueprint();
  544. }
  545. return App.ajax.send({
  546. name: 'wizard.loadrecommendations',
  547. sender: self,
  548. data: data,
  549. success: 'loadRecommendationsSuccessCallback',
  550. error: 'loadRecommendationsErrorCallback'
  551. }).then(function () {
  552. callback(self.createComponentInstallationObjects(), self);
  553. });
  554. }
  555. },
  556. /**
  557. * Create components for displaying component-host comboboxes in UI assign dialog
  558. * expects content.recommendations will be filled with recommendations API call result
  559. * @return {Object[]}
  560. */
  561. createComponentInstallationObjects: function() {
  562. var stackMasterComponentsMap = {},
  563. masterHosts = this.get('content.masterComponentHosts'), //saved to local storage info
  564. servicesToAdd = this.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'),
  565. recommendations = this.get('content.recommendations'),
  566. resultComponents = [],
  567. multipleComponentHasBeenAdded = {},
  568. hostGroupsMap = {};
  569. App.StackServiceComponent.find().forEach(function(component) {
  570. if (this.get('isInstallerWizard')) {
  571. if (component.get('isShownOnInstallerAssignMasterPage')) {
  572. stackMasterComponentsMap[component.get('componentName')] = component;
  573. }
  574. } else {
  575. if (component.get('isShownOnAddServiceAssignMasterPage') || this.get('mastersToShow').contains(component.get('componentName'))) {
  576. stackMasterComponentsMap[component.get('componentName')] = component;
  577. }
  578. }
  579. }, this);
  580. recommendations.blueprint_cluster_binding.host_groups.forEach(function(group) {
  581. hostGroupsMap[group.name] = group;
  582. });
  583. recommendations.blueprint.host_groups.forEach(function(host_group) {
  584. var hosts = hostGroupsMap[host_group.name] ? hostGroupsMap[host_group.name].hosts : [];
  585. hosts.forEach(function(host) {
  586. host_group.components.forEach(function(component) {
  587. var willBeDisplayed = true;
  588. var stackMasterComponent = stackMasterComponentsMap[component.name];
  589. if (stackMasterComponent) {
  590. // If service is already installed and not being added as a new service then render on UI only those master components
  591. // that have already installed hostComponents.
  592. // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
  593. if (!servicesToAdd.contains(stackMasterComponent.get('serviceName'))) {
  594. willBeDisplayed = masterHosts.someProperty('component', component.name);
  595. }
  596. if (willBeDisplayed) {
  597. var savedComponents = masterHosts.filterProperty('component', component.name);
  598. if (this.get('multipleComponents').contains(component.name) && savedComponents.length > 0) {
  599. if (!multipleComponentHasBeenAdded[component.name]) {
  600. multipleComponentHasBeenAdded[component.name] = true;
  601. savedComponents.forEach(function(saved) {
  602. resultComponents.push(this.createComponentInstallationObject(stackMasterComponent, host.fqdn.toLowerCase(), saved));
  603. }, this);
  604. }
  605. }
  606. else {
  607. var savedComponent = masterHosts.findProperty('component', component.name);
  608. resultComponents.push(this.createComponentInstallationObject(stackMasterComponent, host.fqdn.toLowerCase(), savedComponent));
  609. }
  610. }
  611. }
  612. }, this);
  613. }, this);
  614. }, this);
  615. return resultComponents;
  616. },
  617. /**
  618. * Create component for displaying component-host comboboxes in UI assign dialog
  619. * @param fullComponent - full component description
  620. * @param hostName - host fqdn where component will be installed
  621. * @param savedComponent - the same object which function returns but created before
  622. * @return {Object}
  623. */
  624. createComponentInstallationObject: function(fullComponent, hostName, savedComponent) {
  625. var componentName = fullComponent.get('componentName');
  626. var componentObj = {};
  627. componentObj.component_name = componentName;
  628. componentObj.display_name = App.format.role(fullComponent.get('componentName'));
  629. componentObj.serviceId = fullComponent.get('serviceName');
  630. componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', componentName).get('isCoHostedComponent') && !this.get('mastersToMove').contains(componentName);
  631. componentObj.selectedHost = savedComponent ? savedComponent.hostName : hostName;
  632. componentObj.isInstalled = savedComponent ? savedComponent.isInstalled : false;
  633. return componentObj;
  634. },
  635. /**
  636. * Success-callback for recommendations request
  637. * @param {object} data
  638. * @method loadRecommendationsSuccessCallback
  639. */
  640. loadRecommendationsSuccessCallback: function (data) {
  641. this.set('content.recommendations', data.resources[0].recommendations);
  642. },
  643. /**
  644. * Error-callback for recommendations request
  645. * @param {object} jqXHR
  646. * @param {object} ajaxOptions
  647. * @param {string} error
  648. * @param {object} opt
  649. * @method loadRecommendationsErrorCallback
  650. */
  651. loadRecommendationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
  652. App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
  653. },
  654. /**
  655. * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
  656. * @param {Ember.Enumerable} masterComponents
  657. * @method renderComponents
  658. */
  659. renderComponents: function (masterComponents) {
  660. var installedServices = App.StackService.find().filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'); //list of shown services
  661. var result = [];
  662. var serviceComponentId, previousComponentName;
  663. this.addNewMasters(masterComponents);
  664. masterComponents.forEach(function (item) {
  665. var masterComponent = App.StackServiceComponent.find().findProperty('componentName', item.component_name);
  666. var componentObj = Em.Object.create(item);
  667. var showRemoveControl;
  668. if (masterComponent.get('isMasterWithMultipleInstances')) {
  669. showRemoveControl = installedServices.contains(masterComponent.get('stackService.serviceName')) &&
  670. (masterComponents.filterProperty('component_name', item.component_name).length > 1);
  671. previousComponentName = item.component_name;
  672. componentObj.set('serviceComponentId', result.filterProperty('component_name', item.component_name).length + 1);
  673. componentObj.set("showRemoveControl", showRemoveControl);
  674. }
  675. componentObj.set('isHostNameValid', true);
  676. componentObj.set('showCurrentPrefix', this.get('showCurrentPrefix').contains(item.component_name) && item.isInstalled);
  677. componentObj.set('showAdditionalPrefix', this.get('showAdditionalPrefix').contains(item.component_name) && !item.isInstalled);
  678. if (this.get('mastersToMove').contains(item.component_name)) {
  679. componentObj.set('isInstalled', false);
  680. }
  681. result.push(componentObj);
  682. }, this);
  683. result = this.sortComponentsByServiceName(result);
  684. this.set("selectedServicesMasters", result);
  685. this.set('servicesMasters', result);
  686. },
  687. /**
  688. * Add new master components from <code>mastersToAdd</code> list
  689. * @param masterComponents
  690. * @returns {masterComponents[]}
  691. */
  692. addNewMasters: function (masterComponents) {
  693. this.get('mastersToAdd').forEach(function (masterName, index, mastersToAdd) {
  694. var toBeAddedNumber = mastersToAdd.filter(function (name) {
  695. return name === masterName;
  696. }).length,
  697. alreadyAddedNumber = masterComponents.filterProperty('component_name', masterName).rejectProperty('isInstalled').length;
  698. if (toBeAddedNumber > alreadyAddedNumber) {
  699. var hostName = this.getHostForMaster(masterName, masterComponents),
  700. serviceName = this.getServiceByMaster(masterName);
  701. masterComponents.push(this.createComponentInstallationObject(
  702. Em.Object.create({
  703. componentName: masterName,
  704. serviceName: serviceName
  705. }),
  706. hostName
  707. ));
  708. }
  709. }, this);
  710. return masterComponents;
  711. },
  712. /**
  713. * Find available host for master and return it
  714. * If there is no available hosts returns false
  715. * @param master
  716. * @param allMasters
  717. * @returns {*}
  718. */
  719. getHostForMaster: function (master, allMasters) {
  720. var usedHosts = allMasters.filterProperty('component_name', master).mapProperty('selectedHost');
  721. var allHosts = this.get('hosts');
  722. for (var i = 0; i < allHosts.length; i++) {
  723. if (!usedHosts.contains(allHosts[i].get('host_name'))) {
  724. return allHosts[i].get('host_name');
  725. }
  726. }
  727. return false;
  728. },
  729. /**
  730. * Find serviceName for master by it's componentName
  731. * @param master
  732. * @returns {*}
  733. */
  734. getServiceByMaster: function (master) {
  735. return App.StackServiceComponent.find().findProperty('componentName', master).get('serviceName');
  736. },
  737. /**
  738. * Sort components by their service (using <code>App.StackService.displayOrder</code>)
  739. * Services not in App.StackService.displayOrder are moved to the end of the list
  740. *
  741. * @param components
  742. * @returns {*}
  743. */
  744. sortComponentsByServiceName: function(components) {
  745. var displayOrder = App.StackService.displayOrder;
  746. var indexForUnordered = Math.max(displayOrder.length, components.length);
  747. return components.sort(function (a, b) {
  748. var aValue = displayOrder.indexOf(a.serviceId) != -1 ? displayOrder.indexOf(a.serviceId) : indexForUnordered;
  749. var bValue = displayOrder.indexOf(b.serviceId) != -1 ? displayOrder.indexOf(b.serviceId) : indexForUnordered;
  750. return aValue - bValue;
  751. });
  752. },
  753. /**
  754. * Update dependent co-hosted components according to the change in the component host
  755. * @method updateCoHosts
  756. */
  757. updateCoHosts: function () {
  758. var components = App.StackServiceComponent.find().filterProperty('isOtherComponentCoHosted');
  759. var selectedServicesMasters = this.get('selectedServicesMasters');
  760. components.forEach(function (component) {
  761. var componentName = component.get('componentName');
  762. var hostComponent = selectedServicesMasters.findProperty('component_name', componentName);
  763. var dependentCoHosts = component.get('coHostedComponents');
  764. dependentCoHosts.forEach(function (coHostedComponent) {
  765. var dependentHostComponent = selectedServicesMasters.findProperty('component_name', coHostedComponent);
  766. if (!this.get('mastersToMove').contains(coHostedComponent) && hostComponent && dependentHostComponent) dependentHostComponent.set('selectedHost', hostComponent.get('selectedHost'));
  767. }, this);
  768. }, this);
  769. }.observes('selectedServicesMasters.@each.selectedHost'),
  770. /**
  771. * On change callback for inputs
  772. * @param {string} componentName
  773. * @param {string} selectedHost
  774. * @param {number} serviceComponentId
  775. * @method assignHostToMaster
  776. */
  777. assignHostToMaster: function (componentName, selectedHost, serviceComponentId) {
  778. var flag = this.isHostNameValid(componentName, selectedHost);
  779. var component;
  780. this.updateIsHostNameValidFlag(componentName, serviceComponentId, flag);
  781. if (serviceComponentId) {
  782. component = this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId);
  783. if (component) component.set("selectedHost", selectedHost);
  784. }
  785. else {
  786. this.get('selectedServicesMasters').findProperty("component_name", componentName).set("selectedHost", selectedHost);
  787. }
  788. },
  789. /**
  790. * Determines if hostName is valid for component:
  791. * <ul>
  792. * <li>host name shouldn't be empty</li>
  793. * <li>host should exist</li>
  794. * <li>host should have only one component with <code>componentName</code></li>
  795. * </ul>
  796. * @param {string} componentName
  797. * @param {string} selectedHost
  798. * @returns {boolean} true - valid, false - invalid
  799. * @method isHostNameValid
  800. */
  801. isHostNameValid: function (componentName, selectedHost) {
  802. return (selectedHost.trim() !== '') &&
  803. this.get('hosts').mapProperty('host_name').contains(selectedHost) &&
  804. (this.get('selectedServicesMasters').
  805. filterProperty('component_name', componentName).
  806. mapProperty('selectedHost').
  807. filter(function (h) {
  808. return h === selectedHost;
  809. }).length <= 1);
  810. },
  811. /**
  812. * Update <code>isHostNameValid</code> property with <code>flag</code> value
  813. * for component with name <code>componentName</code> and
  814. * <code>serviceComponentId</code>-property equal to <code>serviceComponentId</code>-parameter value
  815. * @param {string} componentName
  816. * @param {number} serviceComponentId
  817. * @param {bool} flag
  818. * @method updateIsHostNameValidFlag
  819. */
  820. updateIsHostNameValidFlag: function (componentName, serviceComponentId, flag) {
  821. var component;
  822. if (componentName) {
  823. if (serviceComponentId) {
  824. component = this.get('selectedServicesMasters').filterProperty('component_name', componentName).findProperty("serviceComponentId", serviceComponentId);
  825. if (component) component.set("isHostNameValid", flag);
  826. } else {
  827. this.get('selectedServicesMasters').findProperty("component_name", componentName).set("isHostNameValid", flag);
  828. }
  829. }
  830. },
  831. /**
  832. * Returns last component of selected type
  833. * @param {string} componentName
  834. * @return {Em.Object|null}
  835. * @method last
  836. */
  837. last: function (componentName) {
  838. return this.get("selectedServicesMasters").filterProperty("component_name", componentName).get("lastObject");
  839. },
  840. /**
  841. * Add new component to ZooKeeper Server and Hbase master
  842. * @param {string} componentName
  843. * @return {bool} true - added, false - not added
  844. * @method addComponent
  845. */
  846. addComponent: function (componentName) {
  847. /*
  848. * Logic: If ZooKeeper or Hbase service is selected then there can be
  849. * minimum 1 ZooKeeper or Hbase master in total, and
  850. * maximum 1 ZooKeeper or Hbase on every host
  851. */
  852. var maxNumMasters = this.getMaxNumberOfMasters(componentName),
  853. currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName),
  854. newMaster = null,
  855. masterHosts = null,
  856. suggestedHost = null,
  857. i = 0,
  858. lastMaster = null;
  859. if (!currentMasters.length) {
  860. return false;
  861. }
  862. if (currentMasters.get("length") < maxNumMasters) {
  863. currentMasters.set("lastObject.showAddControl", false);
  864. currentMasters.set("lastObject.showRemoveControl", true);
  865. //create a new master component host based on an existing one
  866. newMaster = Em.Object.create({});
  867. lastMaster = currentMasters.get("lastObject");
  868. newMaster.set("display_name", lastMaster.get("display_name"));
  869. newMaster.set("component_name", lastMaster.get("component_name"));
  870. newMaster.set("selectedHost", lastMaster.get("selectedHost"));
  871. newMaster.set("serviceId", lastMaster.get("serviceId"));
  872. newMaster.set("isInstalled", false);
  873. newMaster.set('showAdditionalPrefix', this.get('showAdditionalPrefix').contains(lastMaster.get("component_name")));
  874. if (currentMasters.get("length") === (maxNumMasters - 1)) {
  875. newMaster.set("showAddControl", false);
  876. } else {
  877. newMaster.set("showAddControl", true);
  878. }
  879. newMaster.set("showRemoveControl", true);
  880. //get recommended host for the new Zookeeper server
  881. masterHosts = currentMasters.mapProperty("selectedHost").uniq();
  882. for (i = 0; i < this.get("hosts.length"); i++) {
  883. if (!(masterHosts.contains(this.get("hosts")[i].get("host_name")))) {
  884. suggestedHost = this.get("hosts")[i].get("host_name");
  885. break;
  886. }
  887. }
  888. newMaster.set("selectedHost", suggestedHost);
  889. newMaster.set("serviceComponentId", (currentMasters.get("lastObject.serviceComponentId") + 1));
  890. this.get("selectedServicesMasters").insertAt(this.get("selectedServicesMasters").indexOf(lastMaster) + 1, newMaster);
  891. this.setProperties({
  892. componentToRebalance: componentName,
  893. lastChangedComponent: componentName
  894. });
  895. this.incrementProperty('rebalanceComponentHostsCounter');
  896. this.toggleProperty('hostNameCheckTrigger');
  897. return true;
  898. }
  899. return false;//if no more zookeepers can be added
  900. },
  901. /**
  902. * Remove component from ZooKeeper server or Hbase Master
  903. * @param {string} componentName
  904. * @param {number} serviceComponentId
  905. * @return {bool} true - removed, false - no
  906. * @method removeComponent
  907. */
  908. removeComponent: function (componentName, serviceComponentId) {
  909. var currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
  910. //work only if the multiple master service is selected in previous step
  911. if (currentMasters.length <= 1) {
  912. return false;
  913. }
  914. this.get("selectedServicesMasters").removeAt(this.get("selectedServicesMasters").indexOf(currentMasters.findProperty("serviceComponentId", serviceComponentId)));
  915. currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
  916. if (currentMasters.get("length") < this.getMaxNumberOfMasters(componentName)) {
  917. currentMasters.set("lastObject.showAddControl", true);
  918. }
  919. if (currentMasters.filterProperty('isInstalled', false).get("length") === 1) {
  920. currentMasters.set("lastObject.showRemoveControl", false);
  921. }
  922. this.setProperties({
  923. componentToRebalance: componentName,
  924. lastChangedComponent: componentName
  925. });
  926. this.incrementProperty('rebalanceComponentHostsCounter');
  927. this.toggleProperty('hostNameCheckTrigger');
  928. return true;
  929. },
  930. recommendAndValidate: function(callback) {
  931. var self = this;
  932. // load recommendations with partial request
  933. self.loadComponentsRecommendationsFromServer(function() {
  934. // For validation use latest received recommendations because it contains current master layout and recommended slave/client layout
  935. self.validate(self.get('content.recommendations'), function() {
  936. if (callback) {
  937. callback();
  938. }
  939. });
  940. }, true);
  941. },
  942. /**
  943. * Submit button click handler
  944. * @method submit
  945. */
  946. submit: function () {
  947. var self = this;
  948. if (!this.get('submitButtonClicked')) {
  949. this.set('submitButtonClicked', true);
  950. var goNextStepIfValid = function () {
  951. if (!self.get('submitDisabled')) {
  952. App.router.send('next');
  953. }
  954. self.set('submitButtonClicked', false);
  955. };
  956. if (this.get('useServerValidation')) {
  957. self.recommendAndValidate(function () {
  958. self.showValidationIssuesAcceptBox(goNextStepIfValid);
  959. });
  960. } else {
  961. self.updateIsSubmitDisabled();
  962. goNextStepIfValid();
  963. }
  964. }
  965. },
  966. /**
  967. * In case of any validation issues shows accept dialog box for user which allow cancel and fix issues or continue anyway
  968. * @method showValidationIssuesAcceptBox
  969. */
  970. showValidationIssuesAcceptBox: function(callback) {
  971. var self = this;
  972. if (self.get('anyWarning') || self.get('anyError')) {
  973. App.ModalPopup.show({
  974. primary: Em.I18n.t('common.continueAnyway'),
  975. header: Em.I18n.t('installer.step5.validationIssuesAttention.header'),
  976. body: Em.I18n.t('installer.step5.validationIssuesAttention'),
  977. onPrimary: function () {
  978. this.hide();
  979. callback();
  980. }
  981. });
  982. } else {
  983. callback();
  984. }
  985. }
  986. });