assign_master_components.js 42 KB

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