Repository: ambari Updated Branches: refs/heads/trunk 7113ce228 -> ee3bfa112
AMBARI-10693. Install clients on host: dependent clients aren't installed, dependencies info is displayed behind BG operations popup (alexantonenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/14436286 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/14436286 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/14436286 Branch: refs/heads/trunk Commit: 14436286b425c2b83ba9afa57cded39319abbef8 Parents: 7113ce2 Author: Alex Antonenko <[email protected]> Authored: Thu Apr 23 15:23:55 2015 +0300 Committer: Alex Antonenko <[email protected]> Committed: Thu Apr 23 18:04:48 2015 +0300 ---------------------------------------------------------------------- ambari-web/app/controllers/main/host/details.js | 103 +++++++---- ambari-web/app/messages.js | 1 + .../test/controllers/main/host/details_test.js | 185 ++++++++++++++----- 3 files changed, 206 insertions(+), 83 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/14436286/ambari-web/app/controllers/main/host/details.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/host/details.js b/ambari-web/app/controllers/main/host/details.js index 99fea74..d23fc79 100644 --- a/ambari-web/app/controllers/main/host/details.js +++ b/ambari-web/app/controllers/main/host/details.js @@ -432,20 +432,18 @@ App.MainHostDetailsController = Em.Controller.extend({ * add component as <code>addComponent<code> method but perform * kdc sessionstate if cluster is secure; * @param event - * @param skipConfirmation */ - addComponentWithCheck: function (event, skipConfirmation) { + addComponentWithCheck: function (event) { var componentName = event.context ? event.context.get('componentName') : ""; event.hiveMetastoreHost = (componentName == "HIVE_METASTORE" && !!this.get('content.hostName')) ? this.get('content.hostName') : null; - App.get('router.mainAdminKerberosController').getKDCSessionState(this.addComponent.bind(this, event, skipConfirmation)); + App.get('router.mainAdminKerberosController').getKDCSessionState(this.addComponent.bind(this, event)); }, /** * Send command to server to install selected host component * @param {object} event - * @param {boolean} skipConfirmation * @method addComponent */ - addComponent: function (event, skipConfirmation) { + addComponent: function (event) { var returnFunc, self = this, @@ -477,41 +475,40 @@ App.MainHostDetailsController = Em.Controller.extend({ }, Em.I18n.t('hosts.host.addComponent.' + componentName )); break; default: - returnFunc = this.addClientComponent(component, skipConfirmation); + returnFunc = this.addClientComponent(component); } return returnFunc; }, /** * Send command to server to install client on selected host * @param component - * @param skipConfirmation */ - addClientComponent: function (component, skipConfirmation) { + addClientComponent: function (component) { var self = this; var message = this.formatClientsMessage(component); - var returnFunction; - if (skipConfirmation) { - returnFunction = this.primary(component); - } else { - returnFunction = App.ModalPopup.show({ - primary: Em.I18n.t('hosts.host.addComponent.popup.confirm'), - header: Em.I18n.t('popup.confirmation.commonHeader'), + return this.showAddComponentPopup(message, function () { + self.primary(component); + }); + }, - addComponentMsg: function () { - return Em.I18n.t('hosts.host.addComponent.msg').format(message); - }.property(), + showAddComponentPopup: function (message, primary) { + return App.ModalPopup.show({ + primary: Em.I18n.t('hosts.host.addComponent.popup.confirm'), + header: Em.I18n.t('popup.confirmation.commonHeader'), - bodyClass: Em.View.extend({ - templateName: require('templates/main/host/details/addComponentPopup') - }), + addComponentMsg: function () { + return Em.I18n.t('hosts.host.addComponent.msg').format(message); + }.property(), - onPrimary: function () { - this.hide(); - self.primary(component); - } - }); - } - return returnFunction; + bodyClass: Em.View.extend({ + templateName: require('templates/main/host/details/addComponentPopup') + }), + + onPrimary: function () { + this.hide(); + primary(); + } + }); }, /** @@ -1933,7 +1930,10 @@ App.MainHostDetailsController = Em.Controller.extend({ installClients: function(event) { var clientsToInstall = [], - clientsToAdd = []; + clientsToAdd = [], + missedComponents = [], + dependentComponents = [], + self = this; event.context.forEach(function (component) { if (['INIT', 'INSTALL_FAILED'].contains(component.get('workStatus'))) { clientsToInstall.push(component); @@ -1941,14 +1941,45 @@ App.MainHostDetailsController = Em.Controller.extend({ clientsToAdd.push(component); } }); - if (clientsToInstall.length) { - this.sendComponentCommand(clientsToInstall, Em.I18n.t('host.host.details.installClients'), 'INSTALLED'); - } - clientsToAdd.forEach(function (component) { - this.addComponentWithCheck({ - context: component - }, true); + clientsToAdd.forEach(function (component, index, array) { + var dependencies = componentsUtils.checkComponentDependencies(component.get('componentName'), { + scope: 'host', + installedComponents: this.get('content.hostComponents').mapProperty('componentName') + }).reject(function (componentName) { + return array.mapProperty('componentName').contains(componentName); + }); + if (dependencies.length) { + missedComponents.pushObjects(dependencies); + dependentComponents.push(component.get('displayName')); + } }, this); + missedComponents = missedComponents.uniq(); + if (missedComponents.length) { + var popupMessage = Em.I18n.t('host.host.addComponent.popup.clients.dependedComponents.body').format(stringUtils.getFormattedStringFromArray(dependentComponents), + stringUtils.getFormattedStringFromArray(missedComponents.map(function(componentName) { + return App.StackServiceComponent.find(componentName).get('displayName'); + }))); + App.showAlertPopup(Em.I18n.t('host.host.addComponent.popup.dependedComponents.header'), popupMessage); + } else { + App.get('router.mainAdminKerberosController').getKDCSessionState(function () { + var sendInstallCommand = function () { + if (clientsToInstall.length) { + self.sendComponentCommand(clientsToInstall, Em.I18n.t('host.host.details.installClients'), 'INSTALLED'); + } + }; + if (clientsToAdd.length) { + var message = stringUtils.getFormattedStringFromArray(clientsToAdd.mapProperty('displayName')); + self.showAddComponentPopup(message, function () { + sendInstallCommand(); + clientsToAdd.forEach(function (component) { + this.primary(component); + }, self); + }); + } else { + sendInstallCommand(); + } + }); + } }, /** http://git-wip-us.apache.org/repos/asf/ambari/blob/14436286/ambari-web/app/messages.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js index 31aeb67..3e43684 100644 --- a/ambari-web/app/messages.js +++ b/ambari-web/app/messages.js @@ -2142,6 +2142,7 @@ Em.I18n.translations = { 'hosts.host.addComponent.deleteHostWithZooKeeper':'Deleting host with ZooKeeper Server may reconfigure such properties:<ul><li>ha.zookeeper.quorum</li><li>hbase.zookeeper.quorum</li><li>templeton.zookeeper.hosts</li><li>yarn.resourcemanager.zk-address</li><li>hive.zookeeper.quorum</li><li>hive.cluster.delegation.token.store.zookeeper.connectString</li></ul>', 'host.host.addComponent.popup.dependedComponents.body': '{0} requires {1} to be installed along with it on the same host. Please add them first and then try adding {0}', 'host.host.addComponent.popup.dependedComponents.header': 'Component dependencies', + 'host.host.addComponent.popup.clients.dependedComponents.body': '{0} require {1} to be installed along with them on the same host. Please add them first', 'hosts.host.zooKeeper.configs.save.note': 'This configuration is created by ambari while installing/deleting zookeeper component on a host', 'hosts.host.addComponent.securityNote':'You are running your cluster in secure mode. You must set up the keytab for {0} on {1} before you proceed. Otherwise, the component will not be able to start properly.', 'hosts.host.addComponent.popup.confirm':'Confirm Add', http://git-wip-us.apache.org/repos/asf/ambari/blob/14436286/ambari-web/test/controllers/main/host/details_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/host/details_test.js b/ambari-web/test/controllers/main/host/details_test.js index 84545d0..1a7c2ca 100644 --- a/ambari-web/test/controllers/main/host/details_test.js +++ b/ambari-web/test/controllers/main/host/details_test.js @@ -499,24 +499,44 @@ describe('App.MainHostDetailsController', function () { }); beforeEach(function () { - sinon.spy(App.ModalPopup, "show"); - sinon.stub(controller, "primary", Em.K); + sinon.stub(controller, 'showAddComponentPopup', Em.K); }); afterEach(function () { - App.ModalPopup.show.restore(); - controller.primary.restore(); + controller.showAddComponentPopup.restore(); }); it('any CLIENT component', function () { var popup = controller.addClientComponent(component); - expect(App.ModalPopup.show.calledOnce).to.be.true; - popup.onPrimary(); - expect(controller.primary.calledWith(component)).to.be.true; + expect(controller.showAddComponentPopup.calledOnce).to.be.true; + }); + + }); + + describe('#showAddComponentPopup()', function () { + + var message = 'Comp1', + component = Em.Object.create({ + componentName: ' Comp1' + }); + + beforeEach(function () { + sinon.spy(App.ModalPopup, 'show'); + sinon.stub(controller, 'primary', Em.K); + }); + + afterEach(function () { + App.ModalPopup.show.restore(); + controller.primary.restore(); }); - it('should launch primary method without confirmation', function () { - controller.addClientComponent(component, true); + it('should display add component confirmation', function () { + var popup = controller.showAddComponentPopup(message, function () { + controller.primary(component); + }); + expect(App.ModalPopup.show.calledOnce).to.be.true; + expect(popup.get('addComponentMsg')).to.eql(Em.I18n.t('hosts.host.addComponent.msg').format(message)); + popup.onPrimary(); expect(controller.primary.calledWith(component)).to.be.true; }); }); @@ -2148,50 +2168,121 @@ describe('App.MainHostDetailsController', function () { }); describe('#installClients()', function () { + + var cases = [ + { + context: [ + Em.Object.create({ + componentName: 'c0', + workStatus: 'INSTALLED' + }), + Em.Object.create({ + componentName: 'c1', + workStatus: 'INIT' + }), + Em.Object.create({ + componentName: 'c2', + workStatus: 'INSTALL_FAILED' + }) + ], + dependencies: { + c0: [], + c1: [], + c2: [] + }, + getKDCSessionStateCalled: true, + sendComponentCommandCalled: true, + showAlertPopupCalled: false, + title: 'No clients to add, some clients to install' + }, + { + context: [ + Em.Object.create({ + componentName: 'c3', + displayName: 'c3' + }) + ], + dependencies: { + c3: [] + }, + getKDCSessionStateCalled: true, + sendComponentCommandCalled: false, + showAlertPopupCalled: false, + title: 'No clients to install, some clients to add' + }, + { + context: [ + Em.Object.create({ + componentName: 'c4', + displayName: 'c4' + }) + ], + dependencies: { + c4: ['c5'] + }, + getKDCSessionStateCalled: false, + sendComponentCommandCalled: false, + showAlertPopupCalled: true, + title: 'Clients to add have unresolved dependencies' + }, + { + context: [ + Em.Object.create({ + componentName: 'c5', + displayName: 'c5' + }), + Em.Object.create({ + componentName: 'c6', + displayName: 'c6' + }) + ], + dependencies: { + c5: ['c6'], + c6: ['c5'] + }, + getKDCSessionStateCalled: true, + sendComponentCommandCalled: false, + showAlertPopupCalled: false, + title: 'Clients to add have mutual dependencies' + } + ], + componentsUtils = require('utils/components'); + beforeEach(function () { sinon.stub(controller, 'sendComponentCommand', Em.K); - sinon.stub(controller, 'addComponentWithCheck', Em.K); + sinon.stub(controller, 'showAddComponentPopup', Em.K); + sinon.stub(App.get('router.mainAdminKerberosController'), 'getKDCSessionState', function (arg) { + return arg(); + }); + sinon.stub(App, 'showAlertPopup', Em.K); + sinon.stub(App.StackServiceComponent, 'find', function (componentName) { + return Em.Object.create({ + displayName: componentName + }); + }); + controller.set('content.hostComponents', []); }); afterEach(function () { controller.sendComponentCommand.restore(); - controller.addComponentWithCheck.restore(); - }); - it('No clients to install, some clients to add', function () { - var event = { - context: [ - Em.Object.create() - ] - }; - controller.installClients(event); - expect(controller.sendComponentCommand.called).to.be.false; - expect(controller.addComponentWithCheck.calledWith({ - context: Em.Object.create() - }, true)).to.be.true; + controller.showAddComponentPopup.restore(); + App.get('router.mainAdminKerberosController').getKDCSessionState.restore(); + App.showAlertPopup.restore(); + App.StackServiceComponent.find.restore(); + componentsUtils.checkComponentDependencies.restore(); }); - it('Some clients to install, no clients to add', function () { - var event = { - context: [ - Em.Object.create({ - workStatus: 'INSTALLED' - }), - Em.Object.create({ - workStatus: 'INIT' - }), - Em.Object.create({ - workStatus: 'INSTALL_FAILED' - }) - ] - }; - controller.installClients(event); - expect(controller.sendComponentCommand.calledWith([ - Em.Object.create({ - workStatus: 'INIT' - }), - Em.Object.create({ - workStatus: 'INSTALL_FAILED' - }) - ], Em.I18n.t('host.host.details.installClients'), 'INSTALLED')).to.be.true; - expect(controller.addComponentWithCheck.called).to.be.false; + + cases.forEach(function (item) { + it(item.title, function () { + sinon.stub(componentsUtils, 'checkComponentDependencies', function (componentName, params) { + return item.dependencies[componentName]; + }); + controller.installClients({ + context: item.context + }); + expect(App.get('router.mainAdminKerberosController').getKDCSessionState.calledOnce).to.equal(item.getKDCSessionStateCalled); + expect(controller.sendComponentCommand.calledOnce).to.equal(item.sendComponentCommandCalled); + expect(App.showAlertPopup.calledOnce).to.equal(item.showAlertPopupCalled); + }); }); });
