Updated Branches: refs/heads/trunk a2d7c9e57 -> 26fbaef39
AMBARI-4380. Hosts API-calls. (onechiporenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/26fbaef3 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/26fbaef3 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/26fbaef3 Branch: refs/heads/trunk Commit: 26fbaef393288000fd666886fd89bcdabf52df77 Parents: a2d7c9e Author: Oleg Nechiporenko <onechipore...@apache.org> Authored: Wed Jan 22 13:17:18 2014 +0200 Committer: Oleg Nechiporenko <onechipore...@apache.org> Committed: Wed Jan 22 13:17:18 2014 +0200 ---------------------------------------------------------------------- ambari-web/app/controllers/main/host.js | 64 +++++ ambari-web/app/messages.js | 1 + .../templates/main/host/bulk_operation_menu.hbs | 4 +- ambari-web/app/utils/ajax.js | 21 ++ ambari-web/app/views/common/table_view.js | 8 + ambari-web/app/views/main/host.js | 7 + .../views/main/host/hosts_table_menu_view.js | 42 +++- ambari-web/test/controllers/main/host_test.js | 249 +++++++++++++++---- 8 files changed, 339 insertions(+), 57 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/controllers/main/host.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/host.js b/ambari-web/app/controllers/main/host.js index 5536742..97c9358 100644 --- a/ambari-web/app/controllers/main/host.js +++ b/ambari-web/app/controllers/main/host.js @@ -156,6 +156,70 @@ App.MainHostController = Em.ArrayController.extend({ } } } + else { + if (operationData.action === 'RESTART') { + this.bulkOperationForHostsRestart(operationData, hosts); + } + else { + this.bulkOperationForHosts(operationData, hosts); + } + } + }, + + /** + * Do bulk operation for selected hosts + * @param {Object} operationData - data about bulk operation (action, hostComponents etc) + * @param {Array} hosts - list of affected hosts + */ + bulkOperationForHosts: function(operationData, hosts) { + var query = []; + hosts.forEach(function(host) { + var subQuery = '(HostRoles/component_name.in(%@)&HostRoles/host_name=' + host.get('hostName') + ')'; + var components = []; + host.get('hostComponents').forEach(function(hostComponent) { + if (hostComponent.get('isMaster') || hostComponent.get('isSlave')) { + if (hostComponent.get('workStatus') === operationData.actionToCheck) { + components.push(hostComponent.get('componentName')); + } + } + }); + if (components.length) { + query.push(subQuery.fmt(components.join(','))); + } + }); + if (query.length) { + query = query.join('|'); + App.ajax.send({ + name: 'bulk_request.hosts.all_components', + sender: this, + data: { + query: query, + state: operationData.action, + requestInfo: operationData.message + }, + success: 'bulkOperationForHostComponentsSuccessCallback' + }); + } + else { + App.ModalPopup.show({ + header: Em.I18n.t('rolling.nothingToDo.header'), + body: Em.I18n.t('rolling.nothingToDo.body').format(Em.I18n.t('hosts.host.maintainance.allComponents.context')), + secondary: false + }); + } + }, + + /** + * Bulk restart for selected hosts + * @param {Object} operationData - data about bulk operation (action, hostComponents etc) + * @param {Array} hosts - list of affected hosts + */ + bulkOperationForHostsRestart: function(operationData, hosts) { + var hostComponents = []; + hosts.forEach(function(host) { + hostComponents.pushObjects(host.get('hostComponents').toArray()); + }); + batchUtils.restartHostComponents(hostComponents); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 77eb4b4..a2bc6fc 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -1412,6 +1412,7 @@ Em.I18n.translations = { 'hosts.host.healthStatusCategory.orange': "Slave Down", 'hosts.host.healthStatusCategory.yellow': "Lost Heartbeat", 'hosts.host.alerts.label': 'Alerts', + 'hosts.host.maintainance.allComponents.context': 'All Host Components', 'hosts.host.maintainance.stopAllComponents.context': 'Stop All Host Components', 'hosts.host.maintainance.startAllComponents.context': 'Start All Host Components', 'hosts.host.alerts.st':' ! ', http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/templates/main/host/bulk_operation_menu.hbs ---------------------------------------------------------------------- diff --git a/ambari-web/app/templates/main/host/bulk_operation_menu.hbs b/ambari-web/app/templates/main/host/bulk_operation_menu.hbs index d54d64f..e9d96d8 100644 --- a/ambari-web/app/templates/main/host/bulk_operation_menu.hbs +++ b/ambari-web/app/templates/main/host/bulk_operation_menu.hbs @@ -22,7 +22,7 @@ <li class="dropdown-submenu"> <a tabindex="-1" href="javascript:void(null);">{{view.menuItems.s.label}} ({{view.parentView.parentView.selectedCategory.hostsCount}})</a> - <ul class="dropdown-menu"> + <ul {{bindAttr class="view.parentView.parentView.selectedCategory.hasHosts::hidden :dropdown-menu"}}> {{#each subMenuItem in view.menuItems.s.submenu}} <li class="dropdown-submenu"> <a href="javascript:void(null);">{{subMenuItem.label}}</a> @@ -40,7 +40,7 @@ <li class="dropdown-submenu"> <a tabindex="-1" href="javascript:void(null);">{{view.menuItems.f.label}} ({{view.parentView.parentView.filteredContent.length}})</a> - <ul class="dropdown-menu"> + <ul {{bindAttr class="view.parentView.parentView.hasFilteredItems::hidden :dropdown-menu"}}> {{#each subMenuItem in view.menuItems.f.submenu}} <li class="dropdown-submenu"> <a href="javascript:void(null);">{{subMenuItem.label}}</a> http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/utils/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax.js b/ambari-web/app/utils/ajax.js index dc8aa5d..ed2b77d 100644 --- a/ambari-web/app/utils/ajax.js +++ b/ambari-web/app/utils/ajax.js @@ -1356,6 +1356,27 @@ var urls = { } }, + 'bulk_request.hosts.all_components': { + 'real': '/clusters/{clusterName}/host_components', + 'mock': '', + 'format': function(data) { + return { + type: 'PUT', + data: JSON.stringify({ + RequestInfo: { + context: data.requestInfo, + query: data.query + }, + Body: { + HostRoles: { + state: data.state + } + } + }) + } + } + }, + 'bulk_request.decommission': { 'real' : '/clusters/{clusterName}/requests', 'mock' : '', http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/views/common/table_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/common/table_view.js b/ambari-web/app/views/common/table_view.js index e5751dd..1eabd75 100644 --- a/ambari-web/app/views/common/table_view.js +++ b/ambari-web/app/views/common/table_view.js @@ -342,6 +342,14 @@ App.TableView = Em.View.extend({ filteredContent: [], /** + * Determine if <code>filteredContent</code> is empty or not + * @type {Boolean} + */ + hasFilteredItems: function() { + return !!this.get('filteredContent.length'); + }.property('filteredContent.length'), + + /** * Contains content to show on the current page of data page view * @type {Array} */ http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/views/main/host.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host.js b/ambari-web/app/views/main/host.js index 340e71a..8057a4f 100644 --- a/ambari-web/app/views/main/host.js +++ b/ambari-web/app/views/main/host.js @@ -333,6 +333,12 @@ App.MainHostView = App.TableView.extend({ hostsCount: 0, /** + * Determine if category has hosts + * @type {Boolean} + */ + hasHosts: false, + + /** * Add "active" class for category span-wrapper if current category is selected * @type {String} */ @@ -362,6 +368,7 @@ App.MainHostView = App.TableView.extend({ else { this.set('hostsCount', this.get('view.content').filterProperty(this.get('hostProperty')).get('length')); } + this.set('hasHosts', !!this.get('hostsCount')); }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/app/views/main/host/hosts_table_menu_view.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/host/hosts_table_menu_view.js b/ambari-web/app/views/main/host/hosts_table_menu_view.js index b6bd742..9fbbdef 100644 --- a/ambari-web/app/views/main/host/hosts_table_menu_view.js +++ b/ambari-web/app/views/main/host/hosts_table_menu_view.js @@ -24,6 +24,20 @@ App.HostTableMenuView = Em.View.extend({ /** * Get third-level menu items for slave components + * @param {String} componentNameForDecommission for decommission and recommission used another component name + * @param {String} componentNameForOtherActions host component name that should be processed + * operationData format: + * <code> + * { + * action: 'STARTED|INSTALLED|RESTART|DECOMMISSION|DECOMMISSION_OFF', // action for selected host components + * message: 'some text', // just text to BG popup + * componentName: 'DATANODE|NODEMANAGER...', //component name that should be processed + * realComponentName: 'DATANODE|NODEMANAGER...', // used only for decommission(_off) actions + * serviceName: 'HDFS|YARN|HBASE...', // service name of the processed component + * componentNameFormatted: 'DataNodes|NodeManagers...' // "user-friendly" string with component name (used in BG popup) + * } + * </code> + * * @returns {Array} */ getSlaveItemsTemplate: function(componentNameForDecommission, componentNameForOtherActions) { @@ -75,6 +89,14 @@ App.HostTableMenuView = Em.View.extend({ /** * Get third-level menu items for Hosts + * operationData format: + * <code> + * { + * action: 'STARTED|INSTALLED|RESTART', // action for selected hosts (will be applied for each host component in selected hosts) + * actionToCheck: 'INSTALLED|STARTED' // state to filter host components should be processed + * message: 'some text', // just text to BG popup + * } + * </code> * @returns {Array} */ getHostItemsTemplate: function() { @@ -82,25 +104,28 @@ App.HostTableMenuView = Em.View.extend({ Em.Object.create({ label: Em.I18n.t('hosts.host.details.startAllComponents'), operationData: Em.Object.create({ - action: 'start_all', + action: 'STARTED', + actionToCheck: 'INSTALLED', message: Em.I18n.t('hosts.host.details.startAllComponents') }) }), Em.Object.create({ label: Em.I18n.t('hosts.host.details.stopAllComponents'), operationData: Em.Object.create({ - action: 'stop_all', + action: 'INSTALLED', + actionToCheck: 'STARTED', message: Em.I18n.t('hosts.host.details.stopAllComponents') }) }), Em.Object.create({ label: Em.I18n.t('hosts.table.menu.l2.restartAllComponents'), operationData: Em.Object.create({ - action: 'restart_all', + action: 'RESTART', message: Em.I18n.t('hosts.table.menu.l2.restartAllComponents') }) - }), - Em.Object.create({ + }) + //@todo uncomment when API will be ready + /*Em.Object.create({ label: Em.I18n.t('maintenance.turnOn'), operationData: Em.Object.create({ action: 'turn_on_maintenance', @@ -113,13 +138,18 @@ App.HostTableMenuView = Em.View.extend({ action: 'turn_off_maintenance', message: Em.I18n.t('maintenance.turnOff') }) - }) + })*/ ]); }, /** * Get second-level menu * @param {String} selection + * <code> + * "s" - selected hosts + * "a" - all hosts + * "f" - filtered hosts + * </code> * @returns {Array} */ getSubMenuItemsTemplate: function(selection) { http://git-wip-us.apache.org/repos/asf/ambari/blob/26fbaef3/ambari-web/test/controllers/main/host_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/host_test.js b/ambari-web/test/controllers/main/host_test.js index 1260935..f18e92a 100644 --- a/ambari-web/test/controllers/main/host_test.js +++ b/ambari-web/test/controllers/main/host_test.js @@ -16,70 +16,221 @@ * limitations under the License. */ -/* var App = require('app'); -require('models/cluster'); -require('models/service'); -require('models/pagination'); +var validator = require('utils/validator'); +require('utils/component'); +require('utils/batch_scheduled_requests'); require('controllers/main/host'); describe('MainHostController', function () { - describe('#sortByName()', function () { - it('should change isSort value to true', function () { - var mainHostController = App.MainHostController.create(); - mainHostController.set('isSort', false); - mainHostController.sortByName(); - expect(mainHostController.get('isSort')).to.equal(true); - }); + var hostController; - it('should inverse sortingAsc ', function () { - var mainHostController = App.MainHostController.create(); - mainHostController.set('sortingAsc', false); - mainHostController.sortByName(); - expect(mainHostController.get('sortingAsc')).to.equal(true); - mainHostController.sortByName(); - expect(mainHostController.get('sortingAsc')).to.equal(false); - }) + describe('#bulkOperation', function() { + + beforeEach(function() { + hostController = App.MainHostController.create({ + bulkOperationForHostsRestart: function(){}, + bulkOperationForHosts: function(){}, + bulkOperationForHostComponentsRestart: function(){}, + bulkOperationForHostComponentsDecommission: function(){}, + bulkOperationForHostComponents: function(){} + }); + sinon.spy(hostController, 'bulkOperationForHostsRestart'); + sinon.spy(hostController, 'bulkOperationForHosts'); + sinon.spy(hostController, 'bulkOperationForHostComponentsRestart'); + sinon.spy(hostController, 'bulkOperationForHostComponentsDecommission'); + sinon.spy(hostController, 'bulkOperationForHostComponents'); + }); + + afterEach(function() { + hostController.bulkOperationForHosts.restore(); + hostController.bulkOperationForHostsRestart.restore(); + hostController.bulkOperationForHostComponentsRestart.restore(); + hostController.bulkOperationForHostComponentsDecommission.restore(); + hostController.bulkOperationForHostComponents.restore(); + }); + + it('RESTART for hosts', function() { + var operationData = { + action: 'RESTART' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostsRestart.calledOnce).to.be.true; + }); + + it('START for hosts', function() { + var operationData = { + action: 'STARTED' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHosts.calledOnce).to.be.true; + }); + + it('STOP for hosts', function() { + var operationData = { + action: 'INSTALLED' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHosts.calledOnce).to.be.true; + }); + + it('RESTART for hostComponents', function() { + var operationData = { + action: 'RESTART', + componentNameFormatted: 'DataNodes' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostComponentsRestart.calledOnce).to.be.true; + }); + + it('START for hostComponents', function() { + var operationData = { + action: 'STARTED', + componentNameFormatted: 'DataNodes' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostComponents.calledOnce).to.be.true; + }); + + it('STOP for hostComponents', function() { + var operationData = { + action: 'INSTALLED', + componentNameFormatted: 'DataNodes' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostComponents.calledOnce).to.be.true; + }); + + it('DECOMMISSION for hostComponents', function() { + var operationData = { + action: 'DECOMMISSION', + componentNameFormatted: 'DataNodes' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostComponentsDecommission.calledOnce).to.be.true; + }); + + it('RECOMMISSION for hostComponents', function() { + var operationData = { + action: 'DECOMMISSION_OFF', + componentNameFormatted: 'DataNodes' + }; + hostController.bulkOperation(operationData, []); + expect(hostController.bulkOperationForHostComponentsDecommission.calledOnce).to.be.true; + }); + + }); + + describe('#bulkOperationForHosts', function() { + + beforeEach(function(){ + hostController = App.MainHostController.create({}); + sinon.spy($, 'ajax'); + }); + + afterEach(function() { + $.ajax.restore(); }); + var tests = [ + { + operationData: {}, + hosts: [], + m: 'no hosts', + e: false + }, + { + operationData: { + actionToCheck: 'STARTED' + }, + hosts: [ + Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: true, isSlave: false, host: {hostName:'host1'}, workStatus: 'STARTED', componentName: 'NAMENODE'}), + Em.Object.create({isMaster: false, isSlave: true, host: {hostName:'host1'}, workStatus: 'STARTED', componentName: 'DATANODE'}) + ]) + }) + ], + m: '1 host. components are in proper state', + e: true + }, + { + operationData: { + actionToCheck: 'INSTALLED' + }, + hosts: [ + Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: true, isSlave: false, host: {hostName:'host1'}, workStatus: 'STARTED', componentName: 'NAMENODE'}), + Em.Object.create({isMaster: false, isSlave: true, host: {hostName:'host1'}, workStatus: 'STARTED', componentName: 'DATANODE'}) + ]) + }) + ], + m: '1 host. components are not in proper state', + e: false + }, + { + operationData: { + actionToCheck: 'INSTALLED' + }, + hosts: [ + Em.Object.create({ + hostComponents: Em.A([ + Em.Object.create({isMaster: true, isSlave: false, host: {hostName:'host1'}, workStatus: 'INSTALLED', componentName: 'NAMENODE'}), + Em.Object.create({isMaster: false, isSlave: true, host: {hostName:'host1'}, workStatus: 'STARTED', componentName: 'DATANODE'}) + ]) + }) + ], + m: '1 host. some components are in proper state', + e: true + } + ]; - describe('#showNextPage, #showPreviousPage()', function () { - it('should change rangeStart according to page', function () { - var mainHostController = App.MainHostController.create(); - mainHostController.set('pageSize', 3); - mainHostController.showNextPage(); - expect(mainHostController.get('rangeStart')).to.equal(3); - mainHostController.showPreviousPage(); - expect(mainHostController.get('rangeStart')).to.equal(0); - }) + tests.forEach(function(test) { + it(test.m, function() { + hostController.bulkOperationForHosts(test.operationData, test.hosts); + expect($.ajax.called).to.equal(test.e); + }); }); + }); + + describe('#bulkOperationForHostsRestart', function() { + + beforeEach(function(){ + hostController = App.MainHostController.create({}); + sinon.spy($, 'ajax'); + }); - describe('#sortClass()', function () { - it('should return \'icon-arrow-down\' if sortingAsc is true', function () { - var mainHostController = App.MainHostController.create({}); - mainHostController.set('sortingAsc', true); - expect(mainHostController.get('sortClass')).to.equal('icon-arrow-down'); - }); - it('should return \'icon-arrow-up\' if sortingAsc is false', function () { - var mainHostController = App.MainHostController.create({}); - mainHostController.set('sortingAsc', false); - expect(mainHostController.get('sortClass')).to.equal('icon-arrow-up'); - }) + afterEach(function() { + $.ajax.restore(); }); + var tests = [ + { + hosts: Em.A([]), + m: 'No hosts', + e: false + }, + { + hosts: Em.A([ + Em.Object.create({ + hostComponents: Em.A([Em.Object.create({}), Em.Object.create({})]) + }) + ]), + m: 'One host', + e: true + } + ]; - describe('#allChecked', function () { - it('should fill selectedhostsids array', function () { - var mainHostController = App.MainHostController.create(); - mainHostController.set('allChecked', false); - expect(mainHostController.get('selectedHostsIds').length).to.equal(0); - mainHostController.set('allChecked', true); - expect(!!(mainHostController.get('selectedHostsIds').length)).to.equal(true); - }) + tests.forEach(function(test) { + it(test.m, function() { + hostController.bulkOperationForHostsRestart({}, test.hosts); + expect($.ajax.calledOnce).to.equal(test.e) + }); }); + }); -}); -*/ +}); \ No newline at end of file