Repository: ambari Updated Branches: refs/heads/branch-2.5 01b0d00db -> 96b9a3a0a
AMBARI-18939. Add Service wizard: Allow adding slave components from different service conditionally (akovalenko) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/ea6c3768 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/ea6c3768 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/ea6c3768 Branch: refs/heads/branch-2.5 Commit: ea6c37681c2a51033463c39a793d13ec4e964a93 Parents: 01b0d00 Author: Aleksandr Kovalenko <[email protected]> Authored: Sat Nov 19 00:56:47 2016 +0200 Committer: Aleksandr Kovalenko <[email protected]> Committed: Tue Jan 10 16:46:12 2017 +0200 ---------------------------------------------------------------------- .../controllers/main/service/add_controller.js | 17 +++- ambari-web/app/controllers/wizard.js | 45 ++++++++++- .../app/controllers/wizard/step6_controller.js | 41 ++++++++++ ambari-web/app/mappers/stack_service_mapper.js | 2 +- ambari-web/app/utils/ajax/ajax.js | 2 +- .../main/service/add_controller_test.js | 81 +++++++++++++++++++ .../test/controllers/wizard/step6_test.js | 85 ++++++++++++++++++++ 7 files changed, 269 insertions(+), 4 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/app/controllers/main/service/add_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/main/service/add_controller.js b/ambari-web/app/controllers/main/service/add_controller.js index 04427a2..4310f8d 100644 --- a/ambari-web/app/controllers/main/service/add_controller.js +++ b/ambari-web/app/controllers/main/service/add_controller.js @@ -467,7 +467,8 @@ App.AddServiceController = App.WizardController.extend(App.AddSecurityConfigs, { installSelectedServices: function (callback) { var name = 'common.services.update'; var selectedServices = this.get('content.services').filterProperty('isInstalled', false).filterProperty('isSelected', true).mapProperty('serviceName'); - var data = this.generateDataForInstallServices(selectedServices); + var dependentServices = this.getDependentServices(); + var data = this.generateDataForInstallServices(selectedServices.concat(dependentServices)); this.installServicesRequest(name, data, callback.bind(this)); }, @@ -483,6 +484,20 @@ App.AddServiceController = App.WizardController.extend(App.AddSecurityConfigs, { }, /** + * return list of services by dependent slave components + * @returns {Array} + */ + getDependentServices: function () { + var result = []; + this.get('content.slaveComponentHosts').forEach(function (slaveComponent) { + if (slaveComponent.hosts.someProperty('isInstalled', false)) { + result.push(App.StackServiceComponent.find().findProperty('componentName', slaveComponent.componentName).get('serviceName')); + } + }); + return result.uniq(); + }, + + /** * installs clients before install new services * on host where some components require this * @method installAdditionalClients http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/app/controllers/wizard.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard.js b/ambari-web/app/controllers/wizard.js index a759235..bc73e8f 100644 --- a/ambari-web/app/controllers/wizard.js +++ b/ambari-web/app/controllers/wizard.js @@ -1307,13 +1307,56 @@ App.WizardController = Em.Controller.extend(App.LocalStorage, App.ThemesMappingM var hasServicesWithSlave = services.someProperty('hasSlave'); var hasServicesWithClient = services.someProperty('hasClient'); var hasServicesWithCustomAssignedNonMasters = services.someProperty('hasNonMastersWithCustomAssignment'); - this.set('content.skipSlavesStep', !hasServicesWithSlave && !hasServicesWithClient || !hasServicesWithCustomAssignedNonMasters); + var hasDependentSlaveComponent = this.hasDependentSlaveComponent(services); + this.set('content.skipSlavesStep', (!hasServicesWithSlave && !hasServicesWithClient || !hasServicesWithCustomAssignedNonMasters) && !hasDependentSlaveComponent); if (this.get('content.skipSlavesStep')) { this.get('isStepDisabled').findProperty('step', step).set('value', this.get('content.skipSlavesStep')); } }, /** + * Determine if there is some service with some component, that has dependent slave component already installed in cluster, but not on all hosts + * @param services + * @returns {boolean} + */ + hasDependentSlaveComponent: function (services) { + var result = false; + var dependentSlaves = []; + var hosts = this.get('content.hosts'); + + if (hosts) { + services.forEach(function (service) { + service.get('serviceComponents').forEach(function (component) { + component.get('dependencies').forEach(function (dependency) { + var dependentService = App.StackService.find().findProperty('serviceName', dependency.serviceName); + var dependentComponent = dependentService.get('serviceComponents').findProperty('componentName', dependency.componentName); + if (dependentComponent.get('isSlave') && dependentService.get('isInstalled')) { + dependentSlaves.push({component: dependentComponent.get('componentName'), count: 0}); + } + }); + }); + }); + + var hostNames = Em.keys(hosts); + for (var i = 0; i < dependentSlaves.length; i++) { + var maxToInstall = App.StackServiceComponent.find().findProperty('componentName', dependentSlaves[i].component).get('maxToInstall'); + maxToInstall = maxToInstall === Infinity ? hostNames.length : maxToInstall; + hostNames.forEach(function (hostName) { + var hostComponents = hosts[hostName].hostComponents.mapProperty('HostRoles.component_name'); + dependentSlaves[i].count += hostComponents.contains(dependentSlaves[i].component); + }); + + if (dependentSlaves[i].count < maxToInstall) { + result = true; + break; + } + } + } + + return result; + }, + + /** * Load config themes for enhanced config layout. * * @method loadConfigThemes http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/app/controllers/wizard/step6_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/wizard/step6_controller.js b/ambari-web/app/controllers/wizard/step6_controller.js index 1697028..3968af7 100644 --- a/ambari-web/app/controllers/wizard/step6_controller.js +++ b/ambari-web/app/controllers/wizard/step6_controller.js @@ -432,6 +432,7 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { } else { this.restoreComponentsSelection(hostsObj, slaveComponents); } + this.enableCheckboxesForDependentComponents(hostsObj); this.selectClientHost(hostsObj); return hostsObj; }, @@ -459,6 +460,46 @@ App.WizardStep6Controller = Em.Controller.extend(App.BlueprintMixin, { }, /** + * Enable checkboxes for dependent components of already installed services, that can be added + * @param hostsObj + */ + enableCheckboxesForDependentComponents: function (hostsObj) { + var dependentSlaves = {}; + App.StackService.find().filterProperty('isSelected').forEach(function (service) { + service.get('serviceComponents').forEach(function (component) { + component.get('dependencies').forEach(function (dependency) { + var dependentService = App.StackService.find().findProperty('serviceName', dependency.serviceName); + var dependentComponent = dependentService.get('serviceComponents').findProperty('componentName', dependency.componentName); + if (dependentComponent.get('isSlave') && dependentService.get('isInstalled')) { + dependentSlaves[dependentComponent.get('componentName')] = []; + } + }); + }); + }); + + if (!Em.keys(dependentSlaves)) return false; + + hostsObj.forEach(function (hostObj) { + hostObj.checkboxes.forEach(function (checkbox) { + if (dependentSlaves[checkbox.component] && !checkbox.isInstalled) { + dependentSlaves[checkbox.component].push(checkbox); + } + }); + }); + + for (var component in dependentSlaves) { + if (dependentSlaves.hasOwnProperty(component)) { + var maxToInstall = App.StackServiceComponent.find().findProperty('componentName', component).get('maxToInstall'); + maxToInstall = maxToInstall === Infinity ? hostsObj.length : maxToInstall; + if (maxToInstall > hostsObj.length - dependentSlaves[component].length) { + dependentSlaves[component].setEach('isDisabled', false); + } + } + } + return true; + }, + + /** * restore previous component selection * @param {Array} hostsObj * @param {Array} slaveComponents http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/app/mappers/stack_service_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/stack_service_mapper.js b/ambari-web/app/mappers/stack_service_mapper.js index 0ed49c8..21c4db9 100644 --- a/ambari-web/app/mappers/stack_service_mapper.js +++ b/ambari-web/app/mappers/stack_service_mapper.js @@ -98,7 +98,7 @@ App.stackServiceMapper = App.QuickDataMapper.create({ var serviceComponents = []; item.components.forEach(function (serviceComponent) { var dependencies = serviceComponent.dependencies.map(function (dependecy) { - return { Dependencies: App.keysUnderscoreToCamelCase(App.permit(dependecy.Dependencies, ['component_name', 'scope'])) }; + return { Dependencies: App.keysUnderscoreToCamelCase(App.permit(dependecy.Dependencies, ['component_name', 'scope', 'service_name'])) }; }); serviceComponent.StackServiceComponents.id = serviceComponent.StackServiceComponents.component_name; serviceComponent.StackServiceComponents.dependencies = dependencies; http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/app/utils/ajax/ajax.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/utils/ajax/ajax.js b/ambari-web/app/utils/ajax/ajax.js index 2cdc395..64b6366 100644 --- a/ambari-web/app/utils/ajax/ajax.js +++ b/ambari-web/app/utils/ajax/ajax.js @@ -1966,7 +1966,7 @@ var urls = { 'type': 'DELETE' }, 'wizard.service_components': { - 'real': '{stackUrl}/services?fields=StackServices/*,components/*,components/dependencies/Dependencies/scope,artifacts/Artifacts/artifact_name', + 'real': '{stackUrl}/services?fields=StackServices/*,components/*,components/dependencies/Dependencies/scope,components/dependencies/Dependencies/service_name,artifacts/Artifacts/artifact_name', 'mock': '/data/stacks/HDP-2.1/service_components.json' }, 'wizard.step9.installer.get_host_status': { http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/test/controllers/main/service/add_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/main/service/add_controller_test.js b/ambari-web/test/controllers/main/service/add_controller_test.js index 51c8dbf..7470f89 100644 --- a/ambari-web/test/controllers/main/service/add_controller_test.js +++ b/ambari-web/test/controllers/main/service/add_controller_test.js @@ -316,6 +316,7 @@ describe('App.AddServiceController', function() { sinon.stub(this.controller, 'setDBProperty', function(key, value) { mock.db = value; }); + sinon.stub(this.controller, 'hasDependentSlaveComponent'); sinon.stub(App.store, 'commit', Em.K); this.mockStackService = sinon.stub(App.StackService, 'find'); this.mockService = sinon.stub(App.Service, 'find'); @@ -324,6 +325,7 @@ describe('App.AddServiceController', function() { afterEach(function() { this.mockGetDBProperty.restore(); this.controller.setDBProperty.restore(); + this.controller.hasDependentSlaveComponent.restore(); this.mockStackService.restore(); this.mockService.restore(); App.store.commit.restore(); @@ -564,4 +566,83 @@ describe('App.AddServiceController', function() { }); + describe('#getDependentServices', function () { + + beforeEach(function () { + sinon.stub(App.StackServiceComponent, 'find').returns([ + Em.Object.create({ + componentName: 'c1', + serviceName: 's1' + }), + Em.Object.create({ + componentName: 'c2', + serviceName: 's2' + }), + Em.Object.create({ + componentName: 'c3', + serviceName: 's3' + }), + Em.Object.create({ + componentName: 'c4', + serviceName: 's1' + }) + ]); + }); + + [ + { + title: 'should return empty array', + sch: [], + expect: [] + }, + { + title: 'should return services for not installed slaves', + sch: [ + { + componentName: 'c1', + hosts: [ + { + isInstalled: false + }, + { + isInstalled: true + } + ] + }, + { + componentName: 'c2', + hosts: [ + { + isInstalled: false + }, + { + isInstalled: true + } + ] + }, + { + componentName: 'c4', + hosts: [ + { + isInstalled: false + }, + { + isInstalled: true + } + ] + } + ], + expect: ['s1', 's2'] + } + ].forEach(function (test) { + describe(test.title, function () { + it(function () { + addServiceController.set('content.slaveComponentHosts', test.sch); + expect(addServiceController.getDependentServices()).to.eql(test.expect); + }); + }) + }); + + }); + }); http://git-wip-us.apache.org/repos/asf/ambari/blob/ea6c3768/ambari-web/test/controllers/wizard/step6_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/wizard/step6_test.js b/ambari-web/test/controllers/wizard/step6_test.js index ed8f16a..356b371 100644 --- a/ambari-web/test/controllers/wizard/step6_test.js +++ b/ambari-web/test/controllers/wizard/step6_test.js @@ -193,6 +193,7 @@ describe('App.WizardStep6Controller', function () { sinon.stub(controller, 'setInstalledComponents'); sinon.stub(controller, 'restoreComponentsSelection'); sinon.stub(controller, 'selectClientHost'); + sinon.stub(controller, 'enableCheckboxesForDependentComponents'); }); afterEach(function() { @@ -200,6 +201,7 @@ describe('App.WizardStep6Controller', function () { controller.setInstalledComponents.restore(); controller.restoreComponentsSelection.restore(); controller.selectClientHost.restore(); + controller.enableCheckboxesForDependentComponents.restore(); }); describe("slaveComponents is null", function() { @@ -1899,4 +1901,87 @@ describe('App.WizardStep6Controller', function () { }); }); + describe('#enableCheckboxesForDependentComponents', function () { + + beforeEach(function () { + sinon.stub(App.StackService, 'find').returns([ + Em.Object.create({ + serviceName: 's1', + isInstalled: false, + isSelected: true, + serviceComponents: [ + Em.Object.create({ + componentName: 'c1', + isSlave: true, + dependencies: [ + { + serviceName: 's2', + componentName: 'c2' + } + ] + }) + ] + }), + Em.Object.create({ + serviceName: 's2', + isInstalled: true, + isSelected: false, + serviceComponents: [ + Em.Object.create({ + componentName: 'c2', + isSlave: true, + dependencies: [] + }) + ] + }) + ]); + sinon.stub(App.StackServiceComponent, 'find').returns([ + Em.Object.create({ + componentName: 'c2', + maxToInstall: 2 + }) + ]); + }); + + afterEach(function () { + App.StackService.find.restore(); + App.StackServiceComponent.find.restore(); + }); + + it('it should enable appropriate checkboxes', function() { + var hostObj = [ + { + checkboxes: [ + { + component: 'c1', + isInstalled: false, + isDisabled: false + }, + { + component: 'c2', + isInstalled: false, + isDisabled: true + } + ] + }, + { + checkboxes: [ + { + component: 'c1', + isInstalled: false, + isDisabled: false + }, + { + component: 'c2', + isInstalled: false, + isDisabled: true + } + ] + } + ]; + expect(controller.enableCheckboxesForDependentComponents(hostObj)).to.be.true; + expect(hostObj[1].checkboxes[1].isDisabled).to.be.false; + }) + }); + });
