assign_master_components.js 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114
  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: function () {
  120. return this.get('content.controllerName') === 'installerController';
  121. }.property('content.controllerName'),
  122. /**
  123. * Master components which could be assigned to multiple hosts
  124. * @type {string[]}
  125. */
  126. multipleComponents: function () {
  127. return App.get('components.multipleMasters');
  128. }.property('App.components.multipleMasters'),
  129. /**
  130. * Master components which could be assigned to multiple hosts
  131. * @type {string[]}
  132. */
  133. addableComponents: function () {
  134. return App.get('components.addableMasterInstallerWizard').concat(this.get('mastersAddableInHA')).uniq();
  135. }.property('App.components.addableMasterInstallerWizard', 'mastersAddableInHA'),
  136. /**
  137. * Define state for submit button
  138. * @type {bool}
  139. */
  140. submitDisabled: false,
  141. /**
  142. * Is Submit-click processing now
  143. * @type {bool}
  144. */
  145. submitButtonClicked: false,
  146. /**
  147. * Either use or not use server validation in this controller
  148. * @type {bool}
  149. */
  150. useServerValidation: true,
  151. /**
  152. * Trigger for executing host names check for components
  153. * Should de "triggered" when host changed for some component and when new multiple component is added/removed
  154. * @type {bool}
  155. */
  156. hostNameCheckTrigger: false,
  157. /**
  158. * List of hosts
  159. * @type {Array}
  160. */
  161. hosts: [],
  162. /**
  163. * Name of multiple component which host name was changed last
  164. * @type {Object|null}
  165. */
  166. componentToRebalance: null,
  167. /**
  168. * Name of component which host was changed last
  169. * @type {string}
  170. */
  171. lastChangedComponent: null,
  172. /**
  173. * Flag for rebalance multiple components
  174. * @type {number}
  175. */
  176. rebalanceComponentHostsCounter: 0,
  177. /**
  178. * @type {Ember.Enumerable}
  179. */
  180. servicesMasters: [],
  181. /**
  182. * @type {Ember.Enumerable}
  183. */
  184. selectedServicesMasters: [],
  185. /**
  186. * Is data for current step loaded
  187. * @type {bool}
  188. */
  189. isLoaded: false,
  190. /**
  191. * Validation error messages which don't related with any master
  192. */
  193. generalErrorMessages: [],
  194. /**
  195. * Validation warning messages which don't related with any master
  196. */
  197. generalWarningMessages: [],
  198. /**
  199. * Is masters-hosts layout initial one
  200. * @type {bool}
  201. */
  202. isInitialLayout: true,
  203. /**
  204. * true if any error exists
  205. */
  206. anyError: function() {
  207. return this.get('servicesMasters').some(function(m) { return m.get('errorMessage'); }) || this.get('generalErrorMessages').some(function(m) { return m; });
  208. }.property('servicesMasters.@each.errorMessage', 'generalErrorMessages'),
  209. /**
  210. * true if any warning exists
  211. */
  212. anyWarning: function() {
  213. return this.get('servicesMasters').some(function(m) { return m.get('warnMessage'); }) || this.get('generalWarningMessages').some(function(m) { return m; });
  214. }.property('servicesMasters.@each.warnMessage', 'generalWarningMessages'),
  215. /**
  216. * Clear loaded recommendations
  217. */
  218. clearRecommendations: function() {
  219. if (this.get('content.recommendations')) {
  220. this.set('content.recommendations', null);
  221. }
  222. },
  223. /**
  224. * List of host with assigned masters
  225. * Format:
  226. * <code>
  227. * [
  228. * {
  229. * host_name: '',
  230. * hostInfo: {},
  231. * masterServices: [],
  232. * masterServicesToDisplay: [] // used only in template
  233. * },
  234. * ....
  235. * ]
  236. * </code>
  237. * @type {Ember.Enumerable}
  238. */
  239. masterHostMapping: function () {
  240. var mapping = [], mappingObject, mappedHosts, hostObj;
  241. //get the unique assigned hosts and find the master services assigned to them
  242. mappedHosts = this.get("selectedServicesMasters").mapProperty("selectedHost").uniq();
  243. mappedHosts.forEach(function (item) {
  244. hostObj = this.get("hosts").findProperty("host_name", item);
  245. // User may input invalid host name (this is handled in hostname checker). Here we just skip it
  246. if (!hostObj) return;
  247. var masterServices = this.get("selectedServicesMasters").filterProperty("selectedHost", item),
  248. masterServicesToDisplay = [];
  249. masterServices.mapProperty('display_name').uniq().forEach(function (n) {
  250. masterServicesToDisplay.pushObject(masterServices.findProperty('display_name', n));
  251. });
  252. mappingObject = Em.Object.create({
  253. host_name: item,
  254. hostInfo: hostObj.host_info,
  255. masterServices: masterServices,
  256. masterServicesToDisplay: masterServicesToDisplay
  257. });
  258. mapping.pushObject(mappingObject);
  259. }, this);
  260. return mapping.sortProperty('host_name');
  261. }.property("selectedServicesMasters.@each.selectedHost", 'selectedServicesMasters.@each.isHostNameValid'),
  262. /**
  263. * Count of hosts without masters
  264. * @type {number}
  265. */
  266. remainingHosts: function () {
  267. if (this.get('content.controllerName') === 'installerController') {
  268. return 0;
  269. } else {
  270. return (this.get("hosts.length") - this.get("masterHostMapping.length"));
  271. }
  272. }.property('masterHostMapping.length', 'selectedServicesMasters.@each.selectedHost'),
  273. /**
  274. * Update submit button status
  275. * @metohd updateIsSubmitDisabled
  276. */
  277. updateIsSubmitDisabled: function () {
  278. if (this.thereIsNoMasters()) {
  279. return false;
  280. }
  281. var isSubmitDisabled = this.get('servicesMasters').someProperty('isHostNameValid', false);
  282. if (this.get('useServerValidation')) {
  283. this.set('submitDisabled', true);
  284. if (this.get('servicesMasters').length === 0) {
  285. return;
  286. }
  287. if (!isSubmitDisabled) {
  288. if (!this.get('isInitialLayout')) {
  289. this.clearRecommendations(); // reset previous recommendations
  290. } else {
  291. this.set('isInitialLayout', false);
  292. }
  293. this.recommendAndValidate();
  294. }
  295. } else {
  296. isSubmitDisabled = isSubmitDisabled || !this.customClientSideValidation();
  297. this.set('submitDisabled', isSubmitDisabled);
  298. return isSubmitDisabled;
  299. }
  300. }.observes('servicesMasters.@each.selectedHost'),
  301. /**
  302. * Function to validate master-to-host assignments
  303. * Should be defined in controller
  304. * @returns {boolean}
  305. */
  306. customClientSideValidation: function () {
  307. return true;
  308. },
  309. /**
  310. * Send AJAX request to validate current host layout
  311. * @param blueprint - blueprint for validation (can be with/withour slave/client components)
  312. */
  313. validate: function(blueprint, callback) {
  314. var self = this;
  315. var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
  316. var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
  317. var services = installedServices.concat(selectedServices).uniq();
  318. var hostNames = self.get('hosts').mapProperty('host_name');
  319. App.ajax.send({
  320. name: 'config.validations',
  321. sender: self,
  322. data: {
  323. stackVersionUrl: App.get('stackVersionURL'),
  324. hosts: hostNames,
  325. services: services,
  326. validate: 'host_groups',
  327. recommendations: blueprint
  328. },
  329. success: 'updateValidationsSuccessCallback',
  330. error: 'updateValidationsErrorCallback'
  331. }).then(function() {
  332. if (callback) {
  333. callback();
  334. }
  335. }
  336. );
  337. },
  338. /**
  339. * Success-callback for validations request
  340. * @param {object} data
  341. * @method updateValidationsSuccessCallback
  342. */
  343. updateValidationsSuccessCallback: function (data) {
  344. var self = this;
  345. var generalErrorMessages = [];
  346. var generalWarningMessages = [];
  347. this.get('servicesMasters').setEach('warnMessage', null);
  348. this.get('servicesMasters').setEach('errorMessage', null);
  349. var anyErrors = false;
  350. var validationData = validationUtils.filterNotInstalledComponents(data);
  351. validationData.filterProperty('type', 'host-component').forEach(function(item) {
  352. var master = self.get('servicesMasters').find(function(m) {
  353. return m.component_name === item['component-name'] && m.selectedHost === item.host;
  354. });
  355. if (master) {
  356. if (item.level === 'ERROR') {
  357. anyErrors = true;
  358. master.set('errorMessage', item.message);
  359. } else if (item.level === 'WARN') {
  360. master.set('warnMessage', item.message);
  361. }
  362. }
  363. });
  364. this.set('generalErrorMessages', generalErrorMessages);
  365. this.set('generalWarningMessages', generalWarningMessages);
  366. // use this.set('submitDisabled', anyErrors); is validation results should block next button
  367. // It's because showValidationIssuesAcceptBox allow use accept validation issues and continue
  368. this.set('submitDisabled', false); //this.set('submitDisabled', anyErrors);
  369. },
  370. /**
  371. * Error-callback for validations request
  372. * @param {object} jqXHR
  373. * @param {object} ajaxOptions
  374. * @param {string} error
  375. * @param {object} opt
  376. * @method updateValidationsErrorCallback
  377. */
  378. updateValidationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
  379. App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
  380. console.log('Load validations failed');
  381. },
  382. /**
  383. * Composes selected values of comboboxes into master blueprint + merge it with currenlty installed slave blueprint
  384. */
  385. getCurrentBlueprint: function() {
  386. var self = this;
  387. var res = {
  388. blueprint: { host_groups: [] },
  389. blueprint_cluster_binding: { host_groups: [] }
  390. };
  391. var mapping = self.get('masterHostMapping');
  392. mapping.forEach(function(item, i) {
  393. var group_name = 'host-group-' + (i+1);
  394. var host_group = {
  395. name: group_name,
  396. components: item.masterServices.map(function(master) {
  397. return { name: master.component_name };
  398. })
  399. };
  400. var binding = {
  401. name: group_name,
  402. hosts: [ { fqdn: item.host_name } ]
  403. };
  404. res.blueprint.host_groups.push(host_group);
  405. res.blueprint_cluster_binding.host_groups.push(binding);
  406. });
  407. return blueprintUtils.mergeBlueprints(res, self.getCurrentSlaveBlueprint());
  408. },
  409. /**
  410. * Clear controller data (hosts, masters etc)
  411. * @method clearStep
  412. */
  413. clearStep: function () {
  414. this.setProperties({
  415. hosts: [],
  416. selectedServicesMasters: [],
  417. servicesMasters: []
  418. });
  419. App.StackServiceComponent.find().forEach(function (stackComponent) {
  420. stackComponent.set('serviceComponentId', 1);
  421. }, this);
  422. },
  423. /**
  424. * Load controller data (hosts, host components etc)
  425. * @method loadStep
  426. */
  427. loadStep: function () {
  428. console.log("WizardStep5Controller: Loading step5: Assign Masters");
  429. this.clearStep();
  430. this.renderHostInfo();
  431. this.loadComponentsRecommendationsFromServer(this.loadStepCallback);
  432. },
  433. /**
  434. * Callback after load controller data (hosts, host components etc)
  435. * @method loadStepCallback
  436. */
  437. loadStepCallback: function(components, self) {
  438. self.renderComponents(components);
  439. self.get('addableComponents').forEach(function (componentName) {
  440. self.updateComponent(componentName);
  441. }, self);
  442. if (self.thereIsNoMasters()) {
  443. console.log('no master components to add');
  444. App.router.send('next');
  445. }
  446. },
  447. /**
  448. * Returns true if there is no new master components which need assigment to host
  449. */
  450. thereIsNoMasters: function() {
  451. return !this.get("selectedServicesMasters").filterProperty('isInstalled', false).length;
  452. },
  453. /**
  454. * Used to set showAddControl flag for installer wizard
  455. * @method updateComponent
  456. */
  457. updateComponent: function (componentName) {
  458. var component = this.last(componentName);
  459. if (!component) {
  460. return;
  461. }
  462. var showControl = !App.StackServiceComponent.find().findProperty('componentName', componentName).get('stackService').get('isInstalled')
  463. || this.get('mastersAddableInHA').contains(componentName);
  464. if (showControl) {
  465. var mastersLength = this.get("selectedServicesMasters").filterProperty("component_name", componentName).length;
  466. if (mastersLength < this.getMaxNumberOfMasters(componentName)) {
  467. component.set('showAddControl', true);
  468. } else if (mastersLength == 1) {
  469. component.set('showRemoveControl', false);
  470. }
  471. }
  472. },
  473. /**
  474. * Count max number of instances for masters <code>componentName</code>, according to their cardinality and number of hosts
  475. * @param componentName
  476. * @returns {Number}
  477. */
  478. getMaxNumberOfMasters: function (componentName) {
  479. var maxByCardinality = App.StackServiceComponent.find().findProperty('componentName', componentName).get('maxToInstall');
  480. var hostsNumber = this.get("hosts.length");
  481. return Math.min(maxByCardinality, hostsNumber);
  482. },
  483. /**
  484. * Load active host list to <code>hosts</code> variable
  485. * @method renderHostInfo
  486. */
  487. renderHostInfo: function () {
  488. var hostInfo = this.get('content.hosts');
  489. var result = [];
  490. for (var index in hostInfo) {
  491. var _host = hostInfo[index];
  492. if (_host.bootStatus === 'REGISTERED') {
  493. result.push(Em.Object.create({
  494. host_name: _host.name,
  495. cpu: _host.cpu,
  496. memory: _host.memory,
  497. disk_info: _host.disk_info,
  498. host_info: Em.I18n.t('installer.step5.hostInfo').fmt(_host.name, numberUtils.bytesToSize(_host.memory, 1, 'parseFloat', 1024), _host.cpu)
  499. }));
  500. }
  501. }
  502. this.set("hosts", result);
  503. this.sortHosts(this.get('hosts'));
  504. this.set('isLoaded', true);
  505. },
  506. /**
  507. * Sort list of host-objects by properties (memory - desc, cpu - desc, hostname - asc)
  508. * @param {object[]} hosts
  509. */
  510. sortHosts: function (hosts) {
  511. hosts.sort(function (a, b) {
  512. if (a.get('memory') == b.get('memory')) {
  513. if (a.get('cpu') == b.get('cpu')) {
  514. return a.get('host_name').localeCompare(b.get('host_name')); // hostname asc
  515. }
  516. return b.get('cpu') - a.get('cpu'); // cores desc
  517. }
  518. return b.get('memory') - a.get('memory'); // ram desc
  519. });
  520. },
  521. /**
  522. * Get recommendations info from API
  523. * @return {undefined}
  524. * @param function(componentInstallationobjects, this) callback
  525. * @param bool includeMasters
  526. */
  527. loadComponentsRecommendationsFromServer: function(callback, includeMasters) {
  528. var self = this;
  529. if (this.get('content.recommendations')) {
  530. // Don't do AJAX call if recommendations has been already received
  531. // 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
  532. callback(self.createComponentInstallationObjects(), self);
  533. } else {
  534. var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
  535. var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
  536. var services = installedServices.concat(selectedServices).uniq();
  537. var hostNames = self.get('hosts').mapProperty('host_name');
  538. var data = {
  539. stackVersionUrl: App.get('stackVersionURL'),
  540. hosts: hostNames,
  541. services: services,
  542. recommend: 'host_groups'
  543. };
  544. if (includeMasters) {
  545. // Made partial recommendation request for reflect in blueprint host-layout changes which were made by user in UI
  546. data.recommendations = self.getCurrentBlueprint();
  547. } else if (!self.get('isInstallerWizard')) {
  548. data.recommendations = self.getCurrentMasterSlaveBlueprint();
  549. }
  550. return App.ajax.send({
  551. name: 'wizard.loadrecommendations',
  552. sender: self,
  553. data: data,
  554. success: 'loadRecommendationsSuccessCallback',
  555. error: 'loadRecommendationsErrorCallback'
  556. }).then(function () {
  557. callback(self.createComponentInstallationObjects(), self);
  558. });
  559. }
  560. },
  561. /**
  562. * Create components for displaying component-host comboboxes in UI assign dialog
  563. * expects content.recommendations will be filled with recommendations API call result
  564. * @return {Object[]}
  565. */
  566. createComponentInstallationObjects: function() {
  567. var self = this;
  568. var masterComponents = [];
  569. if (self.get('isInstallerWizard')) {
  570. masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnInstallerAssignMasterPage');
  571. } else {
  572. masterComponents = App.StackServiceComponent.find().filter(function(component){
  573. return component.get('isShownOnAddServiceAssignMasterPage') || self.get('mastersToShow').contains(component.get('componentName'));
  574. });
  575. }
  576. var masterHosts = self.get('content.masterComponentHosts'); //saved to local storage info
  577. var selectedNotInstalledServices = self.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName');
  578. var recommendations = this.get('content.recommendations');
  579. var resultComponents = [];
  580. var multipleComponentHasBeenAdded = {};
  581. recommendations.blueprint.host_groups.forEach(function(host_group) {
  582. var hosts = recommendations.blueprint_cluster_binding.host_groups.findProperty('name', host_group.name).hosts;
  583. hosts.forEach(function(host) {
  584. host_group.components.forEach(function(component) {
  585. var willBeAdded = true;
  586. var fullComponent = masterComponents.findProperty('componentName', component.name);
  587. // If it's master component which should be shown
  588. if (fullComponent) {
  589. // If service is already installed and not being added as a new service then render on UI only those master components
  590. // that have already installed hostComponents.
  591. // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
  592. var isNotSelectedService = !selectedNotInstalledServices.contains(fullComponent.get('serviceName'));
  593. if (isNotSelectedService) {
  594. willBeAdded = App.HostComponent.find().someProperty('componentName', component.name);
  595. }
  596. if (willBeAdded) {
  597. var savedComponents = masterHosts.filterProperty('component', component.name);
  598. if (self.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(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), saved));
  603. });
  604. }
  605. } else {
  606. var savedComponent = masterHosts.findProperty('component', component.name);
  607. resultComponents.push(self.createComponentInstallationObject(fullComponent, host.fqdn.toLowerCase(), savedComponent));
  608. }
  609. }
  610. }
  611. });
  612. });
  613. });
  614. return resultComponents;
  615. },
  616. /**
  617. * Create component for displaying component-host comboboxes in UI assign dialog
  618. * @param fullComponent - full component description
  619. * @param hostName - host fqdn where component will be installed
  620. * @param savedComponent - the same object which function returns but created before
  621. * @return {Object}
  622. */
  623. createComponentInstallationObject: function(fullComponent, hostName, savedComponent) {
  624. var componentName = fullComponent.get('componentName');
  625. var componentObj = {};
  626. componentObj.component_name = componentName;
  627. componentObj.display_name = App.format.role(fullComponent.get('componentName'));
  628. componentObj.serviceId = fullComponent.get('serviceName');
  629. componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', componentName).get('isCoHostedComponent') && !this.get('mastersToMove').contains(componentName);
  630. if (savedComponent) {
  631. componentObj.selectedHost = savedComponent.hostName;
  632. componentObj.isInstalled = savedComponent.isInstalled;
  633. } else {
  634. componentObj.selectedHost = hostName;
  635. componentObj.isInstalled = false;
  636. }
  637. return componentObj;
  638. },
  639. /**
  640. * Success-callback for recommendations request
  641. * @param {object} data
  642. * @method loadRecommendationsSuccessCallback
  643. */
  644. loadRecommendationsSuccessCallback: function (data) {
  645. this.set('content.recommendations', data.resources[0].recommendations);
  646. },
  647. /**
  648. * Error-callback for recommendations request
  649. * @param {object} jqXHR
  650. * @param {object} ajaxOptions
  651. * @param {string} error
  652. * @param {object} opt
  653. * @method loadRecommendationsErrorCallback
  654. */
  655. loadRecommendationsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
  656. App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
  657. console.log('Load recommendations failed');
  658. },
  659. /**
  660. * Put master components to <code>selectedServicesMasters</code>, which will be automatically rendered in template
  661. * @param {Ember.Enumerable} masterComponents
  662. * @method renderComponents
  663. */
  664. renderComponents: function (masterComponents) {
  665. var installedServices = App.StackService.find().filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName'); //list of shown services
  666. var result = [];
  667. var serviceComponentId, previousComponentName;
  668. this.addNewMasters(masterComponents);
  669. masterComponents.forEach(function (item) {
  670. var masterComponent = App.StackServiceComponent.find().findProperty('componentName', item.component_name);
  671. var componentObj = Em.Object.create(item);
  672. var showRemoveControl;
  673. console.log("TRACE: render master component name is: " + item.component_name);
  674. if (masterComponent.get('isMasterWithMultipleInstances')) {
  675. showRemoveControl = installedServices.contains(masterComponent.get('stackService.serviceName')) &&
  676. (masterComponents.filterProperty('component_name', item.component_name).length > 1);
  677. previousComponentName = item.component_name;
  678. componentObj.set('serviceComponentId', result.filterProperty('component_name', item.component_name).length + 1);
  679. componentObj.set("showRemoveControl", showRemoveControl);
  680. }
  681. componentObj.set('isHostNameValid', true);
  682. componentObj.set('showCurrentPrefix', this.get('showCurrentPrefix').contains(item.component_name) && item.isInstalled);
  683. componentObj.set('showAdditionalPrefix', this.get('showAdditionalPrefix').contains(item.component_name) && !item.isInstalled);
  684. if (this.get('mastersToMove').contains(item.component_name)) {
  685. componentObj.set('isInstalled', false);
  686. }
  687. result.push(componentObj);
  688. }, this);
  689. result = this.sortComponentsByServiceName(result);
  690. this.set("selectedServicesMasters", result);
  691. this.set('servicesMasters', result);
  692. },
  693. /**
  694. * Add new master components from <code>mastersToAdd</code> list
  695. * @param masterComponents
  696. * @returns {masterComponents[]}
  697. */
  698. addNewMasters: function (masterComponents) {
  699. this.get('mastersToAdd').forEach(function(masterName){
  700. var hostName = this.getHostForMaster(masterName, masterComponents);
  701. var serviceName = this.getServiceByMaster(masterName);
  702. masterComponents.push(this.createComponentInstallationObject(
  703. Em.Object.create({
  704. componentName: masterName,
  705. serviceName: serviceName
  706. }),
  707. hostName
  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. console.log('ALERT: Zookeeper service was not selected');
  861. return false;
  862. }
  863. if (currentMasters.get("length") < maxNumMasters) {
  864. currentMasters.set("lastObject.showAddControl", false);
  865. currentMasters.set("lastObject.showRemoveControl", true);
  866. //create a new master component host based on an existing one
  867. newMaster = Em.Object.create({});
  868. lastMaster = currentMasters.get("lastObject");
  869. newMaster.set("display_name", lastMaster.get("display_name"));
  870. newMaster.set("component_name", lastMaster.get("component_name"));
  871. newMaster.set("selectedHost", lastMaster.get("selectedHost"));
  872. newMaster.set("serviceId", lastMaster.get("serviceId"));
  873. newMaster.set("isInstalled", false);
  874. newMaster.set('showAdditionalPrefix', this.get('showAdditionalPrefix').contains(lastMaster.get("component_name")));
  875. if (currentMasters.get("length") === (maxNumMasters - 1)) {
  876. newMaster.set("showAddControl", false);
  877. } else {
  878. newMaster.set("showAddControl", true);
  879. }
  880. newMaster.set("showRemoveControl", true);
  881. //get recommended host for the new Zookeeper server
  882. masterHosts = currentMasters.mapProperty("selectedHost").uniq();
  883. for (i = 0; i < this.get("hosts.length"); i++) {
  884. if (!(masterHosts.contains(this.get("hosts")[i].get("host_name")))) {
  885. suggestedHost = this.get("hosts")[i].get("host_name");
  886. break;
  887. }
  888. }
  889. newMaster.set("selectedHost", suggestedHost);
  890. newMaster.set("serviceComponentId", (currentMasters.get("lastObject.serviceComponentId") + 1));
  891. this.get("selectedServicesMasters").insertAt(this.get("selectedServicesMasters").indexOf(lastMaster) + 1, newMaster);
  892. this.set('componentToRebalance', componentName);
  893. this.incrementProperty('rebalanceComponentHostsCounter');
  894. this.toggleProperty('hostNameCheckTrigger');
  895. return true;
  896. }
  897. return false;//if no more zookeepers can be added
  898. },
  899. /**
  900. * Remove component from ZooKeeper server or Hbase Master
  901. * @param {string} componentName
  902. * @param {number} serviceComponentId
  903. * @return {bool} true - removed, false - no
  904. * @method removeComponent
  905. */
  906. removeComponent: function (componentName, serviceComponentId) {
  907. var currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
  908. //work only if the multiple master service is selected in previous step
  909. if (currentMasters.length <= 1) {
  910. return false;
  911. }
  912. this.get("selectedServicesMasters").removeAt(this.get("selectedServicesMasters").indexOf(currentMasters.findProperty("serviceComponentId", serviceComponentId)));
  913. currentMasters = this.get("selectedServicesMasters").filterProperty("component_name", componentName);
  914. if (currentMasters.get("length") < this.getMaxNumberOfMasters(componentName)) {
  915. currentMasters.set("lastObject.showAddControl", true);
  916. }
  917. if (currentMasters.filterProperty('isInstalled', false).get("length") === 1) {
  918. currentMasters.set("lastObject.showRemoveControl", false);
  919. }
  920. this.set('componentToRebalance', componentName);
  921. this.incrementProperty('rebalanceComponentHostsCounter');
  922. this.toggleProperty('hostNameCheckTrigger');
  923. return true;
  924. },
  925. recommendAndValidate: function(callback) {
  926. var self = this;
  927. // load recommendations with partial request
  928. self.loadComponentsRecommendationsFromServer(function() {
  929. // For validation use latest received recommendations because it contains current master layout and recommended slave/client layout
  930. self.validate(self.get('content.recommendations'), function() {
  931. if (callback) {
  932. callback();
  933. }
  934. });
  935. }, true);
  936. },
  937. /**
  938. * Submit button click handler
  939. * @method submit
  940. */
  941. submit: function () {
  942. var self = this;
  943. if (!this.get('submitButtonClicked')) {
  944. this.set('submitButtonClicked', true);
  945. var goNextStepIfValid = function () {
  946. if (!self.get('submitDisabled')) {
  947. App.router.send('next');
  948. }
  949. self.set('submitButtonClicked', false);
  950. };
  951. if (this.get('useServerValidation')) {
  952. self.recommendAndValidate(function () {
  953. self.showValidationIssuesAcceptBox(goNextStepIfValid);
  954. });
  955. } else {
  956. self.updateIsSubmitDisabled();
  957. goNextStepIfValid();
  958. }
  959. }
  960. },
  961. /**
  962. * In case of any validation issues shows accept dialog box for user which allow cancel and fix issues or continue anyway
  963. * @method showValidationIssuesAcceptBox
  964. */
  965. showValidationIssuesAcceptBox: function(callback) {
  966. var self = this;
  967. if (self.get('anyWarning') || self.get('anyError')) {
  968. App.ModalPopup.show({
  969. primary: Em.I18n.t('common.continueAnyway'),
  970. header: Em.I18n.t('installer.step5.validationIssuesAttention.header'),
  971. body: Em.I18n.t('installer.step5.validationIssuesAttention'),
  972. onPrimary: function () {
  973. this.hide();
  974. callback();
  975. }
  976. });
  977. } else {
  978. callback();
  979. }
  980. }
  981. });