Repository: ambari Updated Branches: refs/heads/branch-3.0-perf 94fed5503 -> d986503ca
AMBARI-21278 Integrate cluster topology updates with websocket events. (atkach) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/d986503c Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/d986503c Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/d986503c Branch: refs/heads/branch-3.0-perf Commit: d986503ca0fb4301e5b01595299162da3453f41c Parents: 94fed55 Author: Andrii Tkach <[email protected]> Authored: Mon Jun 19 15:25:44 2017 +0300 Committer: Andrii Tkach <[email protected]> Committed: Mon Jun 19 15:25:44 2017 +0300 ---------------------------------------------------------------------- ambari-web/app/app.js | 1 + ambari-web/app/assets/test/tests.js | 1 + .../controllers/global/cluster_controller.js | 1 + .../app/controllers/global/update_controller.js | 3 + ambari-web/app/controllers/main/host/details.js | 14 -- ambari-web/app/mappers.js | 3 +- .../app/mappers/socket/topology_mapper.js | 163 +++++++++++++++ ambari-web/app/models/host_component.js | 9 + ambari-web/app/utils/ajax/ajax.js | 4 +- ambari-web/app/views/main/menu.js | 2 +- .../global/cluster_controller_test.js | 6 +- .../global/update_controller_test.js | 2 + .../test/controllers/main/host/details_test.js | 45 ----- .../test/mappers/socket/topology_mapper_test.js | 201 +++++++++++++++++++ 14 files changed, 391 insertions(+), 64 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/app.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/app.js b/ambari-web/app/app.js index c667992..8d5dd58 100644 --- a/ambari-web/app/app.js +++ b/ambari-web/app/app.js @@ -180,6 +180,7 @@ module.exports = Em.Application.create({ return false; }.property('router.clusterController.isLoaded'), + clusterId: null, clusterName: null, clockDistance: null, // server clock - client clock currentStackVersion: '', http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/assets/test/tests.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/assets/test/tests.js b/ambari-web/app/assets/test/tests.js index 23d9f66..567539b 100644 --- a/ambari-web/app/assets/test/tests.js +++ b/ambari-web/app/assets/test/tests.js @@ -167,6 +167,7 @@ var files = [ 'test/mappers/configs/service_config_version_mapper_test', 'test/mappers/configs/themes_mapper_test', 'test/mappers/socket_events_mapper_test', + 'test/mappers/socket/topology_mapper_test', 'test/mixins/common/configs/enhanced_configs_test', 'test/mixins/common/configs/config_recommendations_test', 'test/mixins/common/configs/config_recommendation_parser_test', http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/controllers/global/cluster_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/global/cluster_controller.js b/ambari-web/app/controllers/global/cluster_controller.js index 29c979e..b13cc8b 100644 --- a/ambari-web/app/controllers/global/cluster_controller.js +++ b/ambari-web/app/controllers/global/cluster_controller.js @@ -134,6 +134,7 @@ App.ClusterController = Em.Controller.extend(App.ReloadPopupMixin, { this._super(); if (data.items && data.items.length > 0) { App.setProperties({ + clusterId: data.items[0].Clusters.cluster_id, clusterName: data.items[0].Clusters.cluster_name, currentStackVersion: data.items[0].Clusters.version, isKerberosEnabled: data.items[0].Clusters.security_type === 'KERBEROS' http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/controllers/global/update_controller.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/controllers/global/update_controller.js b/ambari-web/app/controllers/global/update_controller.js index e7ed53f..d9c882f 100644 --- a/ambari-web/app/controllers/global/update_controller.js +++ b/ambari-web/app/controllers/global/update_controller.js @@ -191,6 +191,7 @@ App.UpdateController = Em.Controller.extend({ //TODO limit updates by location App.StompClient.subscribe('/events/hostcomponents', socket.applyHostComponentStatusEvents.bind(socket)); App.StompClient.subscribe('/events/alerts', socket.applyAlertDefinitionSummaryEvents.bind(socket)); + App.StompClient.subscribe('/events/topologies', App.topologyMapper.map.bind(App.topologyMapper)); App.updater.run(this, 'updateServices', 'isWorking'); App.updater.run(this, 'updateHost', 'isWorking'); @@ -210,6 +211,7 @@ App.UpdateController = Em.Controller.extend({ } else { App.StompClient.unsubscribe('/events/hostcomponents'); App.StompClient.unsubscribe('/events/alerts'); + App.StompClient.unsubscribe('/events/topologies'); } }.observes('isWorking', 'App.router.mainAlertInstancesController.isUpdating'), @@ -581,6 +583,7 @@ App.UpdateController = Em.Controller.extend({ }, updateAlertDefinitionSummary: function(callback) { + //TODO move to clusterController var testUrl = '/data/alerts/alert_summary.json'; var realUrl = '/alerts?format=groupedSummary'; var url = this.getUrl(testUrl, realUrl); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/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 def75d6..aba8ed5 100644 --- a/ambari-web/app/controllers/main/host/details.js +++ b/ambari-web/app/controllers/main/host/details.js @@ -630,7 +630,6 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow */ _doDeleteHostComponentSuccessCallback: function (response, request, data) { this.set('_deletedHostComponentResult', null); - this.removeHostComponentModel(data.componentName, data.hostName); }, /** @@ -645,19 +644,6 @@ App.MainHostDetailsController = Em.Controller.extend(App.SupportClientConfigsDow }, /** - * Remove host component data from App.HostComponent model. - * - * @param {String} componentName - * @param {String} hostName - */ - removeHostComponentModel: function (componentName, hostName) { - var component = App.HostComponent.find().filterProperty('componentName', componentName).findProperty('hostName', hostName); - var serviceInCache = App.cache['services'].findProperty('ServiceInfo.service_name', component.get('service.serviceName')); - serviceInCache.host_components = serviceInCache.host_components.without(component.get('id')); - App.serviceMapper.deleteRecord(component); - }, - - /** * Send command to server to upgrade selected host component * @param {object} event * @method upgradeComponent http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/mappers.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers.js b/ambari-web/app/mappers.js index d77c834..6cba547 100644 --- a/ambari-web/app/mappers.js +++ b/ambari-web/app/mappers.js @@ -45,4 +45,5 @@ require('mappers/root_service_mapper'); require('mappers/widget_mapper'); require('mappers/widget_layout_mapper'); require('mappers/stack_upgrade_history_mapper'); -require('mappers/socket_events_mapper'); \ No newline at end of file +require('mappers/socket_events_mapper'); +require('mappers/socket/topology_mapper'); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/mappers/socket/topology_mapper.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/mappers/socket/topology_mapper.js b/ambari-web/app/mappers/socket/topology_mapper.js new file mode 100644 index 0000000..d46fa5e --- /dev/null +++ b/ambari-web/app/mappers/socket/topology_mapper.js @@ -0,0 +1,163 @@ +/** + * 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'); +var stringUtils = require('utils/string_utils'); + +App.topologyMapper = App.QuickDataMapper.create({ + + /** + * + * @param {object} event + */ + map: function(event) { + if (event.clusters[App.get('clusterId')].components) { + this.applyComponentTopologyChanges(event.clusters[App.get('clusterId')].components, event.eventType); + } + if (event.clusters[App.get('clusterId')].hosts) { + //TODO test hosts add/remove when hosts registration will be fixed + App.router.get('updateController').updateHost(Em.K, null, true); + } + }, + + /** + * + * @param {Array} components + * @param {string} eventType + */ + applyComponentTopologyChanges: function(components, eventType) { + components.forEach((component) => { + component.hostNames.forEach((hostName, index) => { + if (eventType === 'UPDATE' && component.version === 'UNKNOWN') { + this.addServiceIfNew(component.serviceName); + this.createHostComponent(component, hostName, component.publicHostNames[index]); + } else if (eventType === 'DELETE') { + this.deleteHostComponent(component, hostName); + this.deleteServiceIfHasNoComponents(component.serviceName); + } + }); + }); + }, + + /** + * + * @param {string} serviceName + */ + addServiceIfNew: function(serviceName) { + if (!App.Service.find(serviceName).get('isLoaded')) { + App.store.safeLoad(App.Service, { + id: serviceName, + service_name: serviceName, + work_status: 'INIT', + passive_state: 'OFF', + host_components: [] + }); + } + }, + + /** + * + * @param {string} serviceName + */ + deleteServiceIfHasNoComponents: function(serviceName) { + if (App.Service.find(serviceName).get('isLoaded') && + App.Service.find(serviceName).get('hostComponents.length') === 0) { + this.deleteRecord(App.Service.find(serviceName)); + } + }, + + /** + * + * @param {object} component + * @param {string} hostName + */ + deleteHostComponent: function(component, hostName) { + const id = App.HostComponent.getId(component.componentName, hostName); + if (!App.HostComponent.find(id).get('isLoaded')) { + //App.HostComponent does not contain all host-components of cluster + return; + } + this.deleteRecord(App.HostComponent.find(id)); + const host = App.Host.find(hostName); + this.updateHostComponentsOfHost(host, host.get('hostComponents').rejectProperty('id', id).mapProperty('id')); + + const service = App.Service.find(component.serviceName); + this.updateHostComponentsOfService(service, service.get('hostComponents').rejectProperty('id', id).mapProperty('id')); + }, + + /** + * + * @param {object} component + * @param {string} hostName + * @param {string} publicHostName + */ + createHostComponent: function(component, hostName, publicHostName) { + const id = App.HostComponent.getId(component.componentName, hostName); + if (!App.Host.find(hostName).get('isLoaded')) { + //App.Host does not contain all hosts of cluster + return; + } + App.store.safeLoad(App.HostComponent, { + id: id, + host_id: hostName, + display_name: component.displayName, + service_id: component.serviceName, + host_name: hostName, + passive_state: 'OFF', + work_status: 'INIT', + public_host_name: publicHostName, + component_name: component.componentName + }); + const host = App.Host.find(hostName); + this.updateHostComponentsOfHost(host, host.get('hostComponents').mapProperty('id').concat(id)); + + const service = App.Service.find(component.serviceName); + this.updateHostComponentsOfService(service, service.get('hostComponents').mapProperty('id').concat(id)); + }, + + /** + * + * @param {Em.Object} host + * @param {Array} hostComponents + */ + updateHostComponentsOfHost: function(host, hostComponents) { + const updatedHost = {}; + for (let i in App.hostsMapper.config) { + if (host.get(stringUtils.underScoreToCamelCase(i)) !== undefined) { + updatedHost[i] = host.get(stringUtils.underScoreToCamelCase(i)); + } + } + updatedHost.host_components = hostComponents; + App.store.safeLoad(App.Host, updatedHost); + }, + + /** + * + * @param {Em.Object} service + * @param {Array} hostComponents + */ + updateHostComponentsOfService: function(service, hostComponents) { + const updatedService = {}; + for (let i in App.serviceMapper.config) { + if (service.get(stringUtils.underScoreToCamelCase(i)) !== undefined) { + updatedService[i] = service.get(stringUtils.underScoreToCamelCase(i)); + } + } + updatedService.host_components = hostComponents; + App.store.safeLoad(App.Service, updatedService); + } +}); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/models/host_component.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/models/host_component.js b/ambari-web/app/models/host_component.js index cdcf991..7217dbf 100644 --- a/ambari-web/app/models/host_component.js +++ b/ambari-web/app/models/host_component.js @@ -195,6 +195,15 @@ App.HostComponent.getCount = function (componentName, type) { } }; +/** + * @param {string} componentName + * @param {string} hostName + * @returns {string} + */ +App.HostComponent.getId = function(componentName, hostName) { + return componentName + '_' + hostName; +}; + App.HostComponentStatus = { started: "STARTED", starting: "STARTING", http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/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 5d7108f..e6dbc06 100644 --- a/ambari-web/app/utils/ajax/ajax.js +++ b/ambari-web/app/utils/ajax/ajax.js @@ -1351,7 +1351,7 @@ var urls = { } }, 'cluster.load_cluster_name': { - 'real': '/clusters?fields=Clusters/security_type', + 'real': '/clusters?fields=Clusters/security_type,Clusters/cluster_id', 'mock': '/data/clusters/info.json' }, 'cluster.load_last_upgrade': { @@ -2287,7 +2287,7 @@ var urls = { mock: '/data/users/privileges_{userName}.json' }, 'router.login.clusters': { - 'real': '/clusters?fields=Clusters/provisioning_state,Clusters/security_type', + 'real': '/clusters?fields=Clusters/provisioning_state,Clusters/security_type,Clusters/cluster_id', 'mock': '/data/clusters/info.json' }, 'router.login.message': { http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/app/views/main/menu.js ---------------------------------------------------------------------- diff --git a/ambari-web/app/views/main/menu.js b/ambari-web/app/views/main/menu.js index 6e79aba..d423402 100644 --- a/ambari-web/app/views/main/menu.js +++ b/ambari-web/app/views/main/menu.js @@ -156,7 +156,7 @@ App.SideNavServiceMenuView = Em.CollectionView.extend({ return App.router.get('mainServiceController.content').filter(function (item) { return !this.get('disabledServices').contains(item.get('id')); }, this); - }.property('App.router.mainServiceController.content', 'App.router.mainServiceController.content.length'), + }.property('App.router.mainServiceController.content.length').volatile(), didInsertElement:function () { App.router.location.addObserver('lastSetURL', this, 'renderOnRoute'); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/test/controllers/global/cluster_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/global/cluster_controller_test.js b/ambari-web/test/controllers/global/cluster_controller_test.js index 170ed78..4dc8a19 100644 --- a/ambari-web/test/controllers/global/cluster_controller_test.js +++ b/ambari-web/test/controllers/global/cluster_controller_test.js @@ -122,7 +122,9 @@ describe('App.clusterController', function () { { "Clusters": { "cluster_name": "tdk", - "version": "HDP-1.3.0" + "version": "HDP-1.3.0", + "security_type": "KERBEROS", + "cluster_id": 1 } } ] @@ -130,6 +132,8 @@ describe('App.clusterController', function () { it('Check cluster', function () { controller.reloadSuccessCallback(testData); expect(App.get('clusterName')).to.equal('tdk'); + expect(App.get('clusterId')).to.equal(1); + expect(App.get('isKerberosEnabled')).to.be.true; expect(App.get('currentStackVersion')).to.equal('HDP-1.3.0'); }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/test/controllers/global/update_controller_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/controllers/global/update_controller_test.js b/ambari-web/test/controllers/global/update_controller_test.js index eafa032..0725944 100644 --- a/ambari-web/test/controllers/global/update_controller_test.js +++ b/ambari-web/test/controllers/global/update_controller_test.js @@ -71,6 +71,7 @@ describe('App.UpdateController', function () { expect(App.updater.run.called).to.equal(false); expect(App.StompClient.unsubscribe.calledWith('/events/hostcomponents')).to.be.true; expect(App.StompClient.unsubscribe.calledWith('/events/alerts')).to.be.true; + expect(App.StompClient.unsubscribe.calledWith('/events/topologies')).to.be.true; }); it('isWorking = true', function () { @@ -78,6 +79,7 @@ describe('App.UpdateController', function () { expect(App.updater.run.callCount).to.equal(12); expect(App.StompClient.subscribe.calledWith('/events/hostcomponents')).to.be.true; expect(App.StompClient.subscribe.calledWith('/events/alerts')).to.be.true; + expect(App.StompClient.subscribe.calledWith('/events/topologies')).to.be.true; }); }); http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/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 c7e1808..7827eb7 100644 --- a/ambari-web/test/controllers/main/host/details_test.js +++ b/ambari-web/test/controllers/main/host/details_test.js @@ -2539,22 +2539,13 @@ describe('App.MainHostDetailsController', function () { }; beforeEach(function () { - sinon.stub(controller, 'removeHostComponentModel', Em.K); controller.set('_deletedHostComponentResult', {}); controller._doDeleteHostComponentSuccessCallback({}, {}, data); }); - afterEach(function () { - controller.removeHostComponentModel.restore(); - }); - it('should reset `_deletedHostComponentResult`', function () { expect(controller.get('_deletedHostComponentResult')).to.be.null; }); - - it('should call `removeHostComponentModel` with correct params', function () { - expect(controller.removeHostComponentModel.calledWith('COMPONENT', 'h1')).to.be.true; - }); }); describe('#upgradeComponentSuccessCallback()', function () { @@ -3328,42 +3319,6 @@ describe('App.MainHostDetailsController', function () { }); - describe("#removeHostComponentModel()", function () { - - beforeEach(function () { - App.cache.services = [ - { - ServiceInfo: { - service_name: 'S1' - }, - host_components: ['C1_host1'] - } - ]; - sinon.stub(App.HostComponent, 'find').returns([ - Em.Object.create({ - id: 'C1_host1', - componentName: 'C1', - hostName: 'host1', - service: Em.Object.create({ - serviceName: 'S1' - }) - }) - ]); - sinon.stub(App.serviceMapper, 'deleteRecord', Em.K); - controller.removeHostComponentModel('C1', 'host1'); - }); - afterEach(function () { - App.HostComponent.find.restore(); - App.serviceMapper.deleteRecord.restore(); - }); - it("App.cache is updated", function () { - expect(App.cache.services[0].host_components).to.be.empty; - }); - it('Record is deleted', function () { - expect(App.serviceMapper.deleteRecord.calledOnce).to.be.true; - }); - }); - describe("#parseNnCheckPointTime", function () { var tests = [ { http://git-wip-us.apache.org/repos/asf/ambari/blob/d986503c/ambari-web/test/mappers/socket/topology_mapper_test.js ---------------------------------------------------------------------- diff --git a/ambari-web/test/mappers/socket/topology_mapper_test.js b/ambari-web/test/mappers/socket/topology_mapper_test.js new file mode 100644 index 0000000..7ebaf75 --- /dev/null +++ b/ambari-web/test/mappers/socket/topology_mapper_test.js @@ -0,0 +1,201 @@ +/** + * 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'); + +require('mappers/socket/topology_mapper'); + +describe('App.topologyMapper', function () { + const mapper = App.topologyMapper; + + + describe('#map', function () { + const mockCtrl = { + updateHost: sinon.spy() + }; + beforeEach(function () { + sinon.stub(mapper, 'applyComponentTopologyChanges'); + sinon.stub(App.router, 'get').returns(mockCtrl); + App.set('clusterId', 1); + }); + afterEach(function() { + mapper.applyComponentTopologyChanges.restore(); + App.router.get.restore(); + }); + it('applyComponentTopologyChanges should be called', function () { + mapper.map({clusters: {1: {components: []}}, eventType: 'UPDATE'}); + expect(mapper.applyComponentTopologyChanges.calledWith([], 'UPDATE')).to.be.true; + }); + it('updateHost should be called', function () { + mapper.map({clusters: {1: {hosts: []}}, eventType: 'UPDATE'}); + expect(mockCtrl.updateHost.calledWith(Em.K, null, true)).to.be.true; + }); + }); + + describe('#applyComponentTopologyChanges', function () { + beforeEach(function () { + sinon.stub(mapper, 'addServiceIfNew'); + sinon.stub(mapper, 'createHostComponent'); + sinon.stub(mapper, 'deleteHostComponent'); + sinon.stub(mapper, 'deleteServiceIfHasNoComponents'); + }); + afterEach(function() { + mapper.addServiceIfNew.restore(); + mapper.createHostComponent.restore(); + mapper.deleteHostComponent.restore(); + mapper.deleteServiceIfHasNoComponents.restore(); + }); + it('CREATE component event', function () { + const components = [ + { + hostNames: ['host1'], + serviceName: 'S1', + version: 'UNKNOWN', + publicHostNames: ['public1'] + } + ]; + mapper.applyComponentTopologyChanges(components, 'UPDATE'); + expect(mapper.addServiceIfNew.calledWith('S1')).to.be.true; + expect(mapper.createHostComponent.calledWith(components[0], 'host1', 'public1')).to.be.true; + }); + + it('DELETE component event', function () { + const components = [ + { + hostNames: ['host1'], + serviceName: 'S1' + } + ]; + mapper.applyComponentTopologyChanges(components, 'DELETE'); + expect(mapper.deleteHostComponent.calledWith(components[0], 'host1')).to.be.true; + expect(mapper.deleteServiceIfHasNoComponents.calledWith('S1')).to.be.true; + }); + }); + + describe('#addServiceIfNew', function () { + beforeEach(function () { + sinon.stub(App.Service, 'find').returns(Em.Object.create({isLoaded: false})); + sinon.stub(App.store, 'safeLoad'); + }); + afterEach(function() { + App.Service.find.restore(); + App.store.safeLoad.restore(); + }); + it('should load service if it does not exist yet', function () { + mapper.addServiceIfNew('S1'); + expect(App.store.safeLoad.calledOnce).to.be.true; + }); + }); + + describe('#deleteServiceIfHasNoComponents', function () { + beforeEach(function () { + sinon.stub(App.Service, 'find').returns(Em.Object.create({isLoaded: true, hostComponents: []})); + sinon.stub(mapper, 'deleteRecord'); + }); + afterEach(function() { + App.Service.find.restore(); + mapper.deleteRecord.restore(); + }); + it('should delete service record', function () { + mapper.deleteServiceIfHasNoComponents('S1'); + expect(mapper.deleteRecord.calledOnce).to.be.true; + }); + }); + + describe('#deleteHostComponent', function () { + beforeEach(function () { + sinon.stub(App.HostComponent, 'find').returns(Em.Object.create({isLoaded: true})); + sinon.stub(mapper, 'deleteRecord'); + sinon.stub(mapper, 'updateHostComponentsOfHost'); + sinon.stub(mapper, 'updateHostComponentsOfService'); + sinon.stub(App.Host, 'find').returns(Em.Object.create({hostComponents: [{id: 'C1_host1'}]})); + sinon.stub(App.Service, 'find').returns(Em.Object.create({hostComponents: [{id: 'C1_host1'}]})); + mapper.deleteHostComponent({componentName: 'C1'}, 'host1'); + }); + afterEach(function() { + App.HostComponent.find.restore(); + mapper.deleteRecord.restore(); + mapper.updateHostComponentsOfHost.restore(); + mapper.updateHostComponentsOfService.restore(); + App.Host.find.restore(); + App.Service.find.restore(); + }); + it('deleteRecord should be called', function () { + expect(mapper.deleteRecord.calledOnce).to.be.true; + }); + it('updateHostComponentsOfHost should be called', function () { + expect(mapper.updateHostComponentsOfHost.calledWith(Em.Object.create({hostComponents: [{id: 'C1_host1'}]}), [])).to.be.true; + }); + it('updateHostComponentsOfService should be called', function () { + expect(mapper.updateHostComponentsOfService.calledWith(Em.Object.create({hostComponents: [{id: 'C1_host1'}]}), [])).to.be.true; + }); + }); + + describe('#createHostComponent', function () { + beforeEach(function () { + sinon.stub(App.store, 'safeLoad'); + sinon.stub(mapper, 'updateHostComponentsOfHost'); + sinon.stub(mapper, 'updateHostComponentsOfService'); + sinon.stub(App.Host, 'find').returns(Em.Object.create({hostComponents: [], isLoaded: true})); + sinon.stub(App.Service, 'find').returns(Em.Object.create({hostComponents: []})); + mapper.createHostComponent({componentName: 'C1'}, 'host1'); + }); + afterEach(function() { + App.store.safeLoad.restore(); + mapper.updateHostComponentsOfHost.restore(); + mapper.updateHostComponentsOfService.restore(); + App.Host.find.restore(); + App.Service.find.restore(); + }); + it('deleteRecord should be called', function () { + expect(App.store.safeLoad.calledOnce).to.be.true; + }); + it('updateHostComponentsOfHost should be called', function () { + expect(mapper.updateHostComponentsOfHost.calledOnce).to.be.true; + }); + it('updateHostComponentsOfService should be called', function () { + expect(mapper.updateHostComponentsOfService.calledOnce).to.be.true; + }); + }); + + describe('#updateHostComponentsOfHost', function () { + beforeEach(function () { + sinon.stub(App.store, 'safeLoad'); + }); + afterEach(function() { + App.store.safeLoad.restore(); + }); + it('App.store.safeLoad should be called', function () { + mapper.updateHostComponentsOfHost(Em.Object.create({id: 1}), [{id: 2}]); + expect(App.store.safeLoad.calledWith(App.Host, {id: 1, hostComponents: [{id: 2}]})); + }); + }); + + describe('#updateHostComponentsOfService', function () { + beforeEach(function () { + sinon.stub(App.store, 'safeLoad'); + }); + afterEach(function() { + App.store.safeLoad.restore(); + }); + it('App.store.safeLoad should be called', function () { + mapper.updateHostComponentsOfService(Em.Object.create({id: 1}), [{id: 2}]); + expect(App.store.safeLoad.calledWith(App.Service, {id: 1, hostComponents: [{id: 2}]})); + }); + }); +});
