/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ var App = require('app'); App.WizardStep3Controller = Em.Controller.extend({ name: 'wizardStep3Controller', hosts: [], content: [], bootHosts: [], registrationStartedAt: null, registrationTimeoutSecs: 120, isSubmitDisabled: true, hostStatusString: Em.I18n.t('installer.step3.hosts.summary'), hostStatusSummary: function () { var self = this; return this.get('hostStatusString').fmt(self.getCategory('value', 'Installing').get('hostsCount'), self.getCategory('value', 'Registering').get('hostsCount'), self.getCategory('value', 'Success').get('hostsCount'), self.getCategory('value', 'Failed').get('hostsCount')); }.property('categories.@each.hostsCount'), categoryObject: Em.Object.extend({ hostsCount: function () { var category = this; var hosts = this.get('controller.hosts').filterProperty('bootStatus', category.get('hostsBootStatus')); return hosts.get('length'); }.property('controller.hosts.@each.bootStatus'), // 'hosts.@each.bootStatus' label: function () { return "%@ (%@)".fmt(this.get('value'), this.get('hostsCount')); }.property('value', 'hostsCount') }), getCategory: function(field, value){ return this.get('categories').find(function(item){ return item.get(field) == value; }); }, categories: function () { var self = this; self.categoryObject.reopen({ controller: self }); return [ self.categoryObject.create({value: 'All Hosts', hostsCount: function () { return this.get('controller.hosts.length'); }.property('controller.hosts.length') }), self.categoryObject.create({value: 'Success', hostsBootStatus: 'REGISTERED' }), self.categoryObject.create({value: 'Installing', hostsBootStatus: 'RUNNING'}), self.categoryObject.create({value: 'Registering', hostsBootStatus: 'REGISTERING'}), self.categoryObject.create({value: 'Failed', hostsBootStatus: 'FAILED' }) ] }.property(), category: 'All Hosts', allChecked: false, onAllChecked: function () { var hosts = this.get('visibleHosts'); hosts.setEach('isChecked', this.get('allChecked')); }.observes('allChecked'), noHostsSelected: function () { return !(this.hosts.someProperty('isChecked', true)); }.property('hosts.@each.isChecked'), isRetryDisabled: function () { return !(this.get('bootHosts').someProperty('bootStatus', 'FAILED') && !this.get('isSubmitDisabled')); }.property('bootHosts.@each.bootStatus', 'isSubmitDisabled'), mockData: require('data/mock/step3_hosts'), mockRetryData: require('data/mock/step3_pollData'), navigateStep: function () { this.loadStep(); if (this.get('content.hosts.manualInstall') !== true) { if (App.db.getBootStatus() === false) { this.startBootstrap(); } } else { this.set('bootHosts', this.get('hosts')); if (App.testMode) { this.get('bootHosts').setEach('bootStatus', 'REGISTERED'); this.get('bootHosts').setEach('cpu', '2'); this.get('bootHosts').setEach('memory', '2000000'); this.getHostInfo(); } else { this.set('registrationStartedAt', null); this.get('bootHosts').setEach('bootStatus', 'DONE'); this.startRegistration(); } } }, clearStep: function () { this.hosts.clear(); this.bootHosts.clear(); this.set('isSubmitDisabled', true); }, loadStep: function () { console.log("TRACE: Loading step3: Confirm Hosts"); this.set('registrationStartedAt', null); this.clearStep(); var hosts = this.loadHosts(); // hosts.setEach('bootStatus', 'RUNNING'); this.renderHosts(hosts); if(this.get('hosts').length) { this.set('isSubmitDisabled', false); } }, /* Loads the hostinfo from localStorage on the insertion of view. It's being called from view */ loadHosts: function () { var hostInfo = []; hostInfo = this.get('content.hostsInfo'); var hosts = new Ember.Set(); for (var index in hostInfo) { hosts.add(hostInfo[index]); console.log("TRACE: host name is: " + hostInfo[index].name); } return hosts; }, /* Renders the set of passed hosts */ renderHosts: function (hostsInfo) { var self = this; hostsInfo.forEach(function (_hostInfo) { var hostInfo = App.HostInfo.create({ name: _hostInfo.name, bootStatus: _hostInfo.bootStatus, isChecked: false }); console.log('pushing ' + hostInfo.name); self.hosts.pushObject(hostInfo); }); }, /** * Parses and updates the content based on bootstrap API response. * Returns true if polling should continue (some hosts are in "RUNNING" state); false otherwise */ parseHostInfo: function (hostsStatusFromServer) { hostsStatusFromServer.forEach(function (_hostStatus) { var host = this.get('bootHosts').findProperty('name', _hostStatus.hostName); // check if hostname extracted from REST API data matches any hostname in content // also, make sure that bootStatus modified by isHostsRegistered call does not get overwritten // since these calls are being made in parallel if (host && !['REGISTERED', 'REGISTERING'].contains(host.get('bootStatus'))) { host.set('bootStatus', _hostStatus.status); host.set('bootLog', _hostStatus.log); } }, this); // if the data rendered by REST API has hosts in "RUNNING" state, polling will continue return this.get('bootHosts').length != 0 && this.get('bootHosts').someProperty('bootStatus', 'RUNNING'); }, /* Returns the current set of visible hosts on view (All, Succeeded, Failed) */ visibleHosts: function () { var self = this; if (this.get('category.hostsBootStatus')) { return this.hosts.filterProperty('bootStatus', self.get('category.hostsBootStatus')); } else { // if (this.get('category') === 'All Hosts') return this.hosts; } }.property('category', 'hosts.@each.bootStatus'), removeHosts: function (hosts) { var self = this; App.ModalPopup.show({ header: Em.I18n.t('installer.step3.hosts.remove.popup.header'), onPrimary: function () { App.router.send('removeHosts', hosts); self.hosts.removeObjects(hosts); if (!self.hosts.length) { self.set('isSubmitDisabled', true); } this.hide(); }, body: Em.I18n.t('installer.step3.hosts.remove.popup.body') }); }, /* Removes a single element on the trash icon click. Called from View */ removeHost: function (hostInfo) { this.removeHosts([hostInfo]); }, removeSelectedHosts: function () { if (!this.get('noHostsSelected')) { var selectedHosts = this.get('visibleHosts').filterProperty('isChecked', true); selectedHosts.forEach(function (_hostInfo) { console.log('Removing: ' + _hostInfo.name); }); this.removeHosts(selectedHosts); } }, retryHost: function (hostInfo) { this.retryHosts([hostInfo]); }, retryHosts: function (hosts) { var bootStrapData = JSON.stringify({'verbose': true, 'sshKey': this.get('content.hosts.sshKey'), hosts: hosts.mapProperty('name')}); this.numPolls = 0; if (this.get('content.hosts.manualInstall') !== true) { var requestId = App.router.get('installerController').launchBootstrap(bootStrapData); this.set('content.hosts.bootRequestId', requestId); this.set('registrationStartedAt', null); this.doBootstrap(); } else { this.set('registrationStartedAt', null); this.get('bootHosts').setEach('bootStatus', 'DONE'); this.startRegistration(); } }, retrySelectedHosts: function () { if (!this.get('isRetryDisabled')) { var selectedHosts = this.get('bootHosts').filterProperty('bootStatus', 'FAILED'); selectedHosts.forEach(function (_host) { _host.set('bootStatus', 'RUNNING'); _host.set('bootLog', 'Retrying ...'); }, this); this.retryHosts(selectedHosts); } }, numPolls: 0, startBootstrap: function () { //this.set('isSubmitDisabled', true); //TODO: uncomment after actual hookup this.numPolls = 0; this.set('registrationStartedAt', null); this.set('bootHosts', this.get('hosts')); this.get('bootHosts').setEach('bootStatus', 'PENDING'); this.doBootstrap(); }, doBootstrap: function () { this.numPolls++; var self = this; var url = App.testMode ? '/data/wizard/bootstrap/poll_' + this.numPolls + '.json' : App.apiPrefix + '/bootstrap/' + this.get('content.hosts.bootRequestId'); $.ajax({ type: 'GET', url: url, timeout: App.timeout, success: function (data) { if (data.hostsStatus !== null) { // in case of bootstrapping just one host, the server returns an object rather than an array, so // force into an array if (!(data.hostsStatus instanceof Array)) { data.hostsStatus = [ data.hostsStatus ]; } console.log("TRACE: In success function for the GET bootstrap call"); var keepPolling = self.parseHostInfo(data.hostsStatus); if (data.hostsStatus.someProperty('status', 'DONE') || data.hostsStatus.someProperty('status', 'FAILED')) { // kicking off registration polls after at least one host has succeeded self.startRegistration(); } if (keepPolling) { window.setTimeout(function () { self.doBootstrap() }, 3000); return; } } }, error: function () { console.log('Bootstrap failed'); }, statusCode: require('data/statusCodes') }); }, /* stopBootstrap: function () { console.log('stopBootstrap() called'); Ember.run.later(this, function () { this.startRegistration(); }, 1000); }, */ startRegistration: function () { if (this.get('registrationStartedAt') == null) { this.set('registrationStartedAt', new Date().getTime()); console.log('registration started at ' + this.get('registrationStartedAt')); this.isHostsRegistered(); } }, isHostsRegistered: function () { var self = this; var hosts = this.get('bootHosts'); var url = App.testMode ? '/data/wizard/bootstrap/single_host_registration.json' : App.apiPrefix + '/hosts'; $.ajax({ type: 'GET', url: url, timeout: App.timeout, success: function (data) { console.log('registration attempt...'); var jsonData = App.testMode ? data : jQuery.parseJSON(data); if (!jsonData) { console.log("Error: jsonData is null"); return; } // keep polling until all hosts have registered/failed, or registrationTimeout seconds after the last host finished bootstrapping var stopPolling = true; hosts.forEach(function (_host, index) { // Change name of first host for test mode. if (App.testMode) { if (index == 0) { _host.set('name', 'localhost.localdomain'); } } // actions to take depending on the host's current bootStatus // RUNNING - bootstrap is running; leave it alone // DONE - bootstrap is done; transition to REGISTERING // REGISTERING - bootstrap is done but has not registered; transition to REGISTERED if host found in polling API result // REGISTERED - bootstrap and registration is done; leave it alone // FAILED - either bootstrap or registration failed; leave it alone console.log(_host.name + ' bootStatus=' + _host.get('bootStatus')); switch (_host.get('bootStatus')) { case 'DONE': _host.set('bootStatus', 'REGISTERING'); _host.set('bootLog', (_host.get('bootLog') != null ? _host.get('bootLog') : '') + '\nRegistering with the server...'); // update registration timestamp so that the timeout is computed from the last host that finished bootstrapping self.get('registrationStartedAt', new Date().getTime()); stopPolling = false; break; case 'REGISTERING': if (jsonData.items.someProperty('Hosts.host_name', _host.name)) { console.log(_host.name + ' has been registered'); _host.set('bootStatus', 'REGISTERED'); _host.set('bootLog', (_host.get('bootLog') != null ? _host.get('bootLog') : '') + '\nRegistration with the server succeeded.'); } else { console.log(_host.name + ' is registering...'); stopPolling = false; } break; case 'RUNNING': stopPolling = false; break; case 'REGISTERED': case 'FAILED': default: break; } }, this); if (stopPolling) { self.getHostInfo(); } else if (new Date().getTime() - self.get('registrationStartedAt') < self.get('registrationTimeoutSecs') * 1000) { window.setTimeout(function () { self.isHostsRegistered(); }, 3000); } else { // registration timed out. mark all REGISTERING hosts to FAILED console.log('registration timed out'); hosts.filterProperty('bootStatus', 'REGISTERING').forEach(function (_host) { _host.set('bootStatus', 'FAILED'); _host.set('bootLog', (_host.get('bootLog') != null ? _host.get('bootLog') : '') + '\nRegistration with the server failed.'); }); self.getHostInfo(); } }, error: function () { console.log('Error: Getting registered host information from the server'); }, statusCode: require('data/statusCodes') }); }, registerErrPopup: function (header, message) { App.ModalPopup.show({ header: header, secondary: false, onPrimary: function () { this.hide(); }, bodyClass: Ember.View.extend({ template: Ember.Handlebars.compile(['
{{view.message}}
'].join('\n')), message: message }) }); }, /** * Get disk info and cpu count of booted hosts from server */ getHostInfo: function () { var self = this; var kbPerGb = 1024; var hosts = this.get('bootHosts'); var url = App.testMode ? '/data/wizard/bootstrap/single_host_information.json' : App.apiPrefix + '/hosts?fields=Hosts/total_mem,Hosts/cpu_count,Hosts/disk_info'; var method = 'GET'; $.ajax({ type: 'GET', url: url, contentType: 'application/json', timeout: App.timeout, success: function (data) { var jsonData = (App.testMode) ? data : jQuery.parseJSON(data); hosts.forEach(function (_host) { var host = (App.testMode) ? jsonData.items[0] : jsonData.items.findProperty('Hosts.host_name', _host.name); if (App.skipBootstrap) { _host.cpu = 2; _host.memory = ((parseInt(2000000))).toFixed(2); _host.disk_info = [{"mountpoint": "/", "type":"ext4"},{"mountpoint": "/grid/0", "type":"ext4"}, {"mountpoint": "/grid/1", "type":"ext4"}, {"mountpoint": "/grid/2", "type":"ext4"}]; } else if (host) { _host.cpu = host.Hosts.cpu_count; _host.memory = ((parseInt(host.Hosts.total_mem))).toFixed(2); _host.disk_info = host.Hosts.disk_info; console.log("The value of memory is: " + _host.memory); } }); self.set('bootHosts', hosts); console.log("The value of hosts: " + JSON.stringify(hosts)); self.stopRegistration(); }, error: function () { console.log('INFO: Getting host information(cpu_count and total_mem) from the server failed'); self.registerErrPopup(Em.I18n.t('installer.step3.hostInformation.popup.header'), Em.I18n.t('installer.step3.hostInformation.popup.body')); }, statusCode: require('data/statusCodes') }); }, stopRegistration: function () { this.set('isSubmitDisabled', false); }, submit: function () { if (!this.get('isSubmitDisabled')) { this.set('content.hostsInfo', this.get('bootHosts')); App.router.send('next'); } }, hostLogPopup: function (event, context) { var host = event.context; App.ModalPopup.show({ header: Em.I18n.t('installer.step3.hostLog.popup.header').format(host.get('name')), secondary: null, onPrimary: function () { this.hide(); }, bodyClass: Ember.View.extend({ templateName: require('templates/wizard/step3_host_log_popup'), host: host, didInsertElement: function () { var self = this; var button = $(this.get('element')).find('.textTrigger'); button.click(function () { if (self.get('isTextArea')) { $(this).text('click to highlight'); } else { $(this).text('press CTRL+C'); } self.set('isTextArea', !self.get('isTextArea')); }); $(this.get('element')).find('.content-area').mouseenter( function () { var element = $(this); element.css('border', '1px solid #dcdcdc'); button.css('visibility', 'visible'); }).mouseleave( function () { var element = $(this); element.css('border', 'none'); button.css('visibility', 'hidden'); }) }, isTextArea: false, textArea: Em.TextArea.extend({ didInsertElement: function () { var element = $(this.get('element')); element.width($(this.get('parentView').get('element')).width() - 10); element.height($(this.get('parentView').get('element')).height()); element.select(); element.css('resize', 'none'); }, readOnly: true, value: function () { return this.get('content'); }.property('content') }) }) }); }, // TODO: dummy button. Remove this after the hook up with actual REST API. mockBtn: function () { this.set('isSubmitDisabled', false); this.hosts.clear(); var hostInfo = this.mockData; this.renderHosts(hostInfo); }, pollBtn: function () { if (this.get('isSubmitDisabled')) { return; } var hosts = this.get('visibleHosts'); var selectedHosts = hosts.filterProperty('isChecked', true); selectedHosts.forEach(function (_host) { console.log('Retrying: ' + _host.name); }); var mockHosts = this.mockRetryData; mockHosts.forEach(function (_host) { console.log('Retrying: ' + _host.name); }); if (this.parseHostInfo(mockHosts, selectedHosts)) { // this.saveHostInfoToDb(); } } });