You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ak...@apache.org on 2015/01/29 18:41:08 UTC

ambari git commit: AMBARI-9402. Redundant page in add services wizard for adding AMS. (akovalenko)

Repository: ambari
Updated Branches:
  refs/heads/trunk d2af57f63 -> 3d744e879


AMBARI-9402. Redundant page in add services wizard for adding AMS. (akovalenko)


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/3d744e87
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/3d744e87
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/3d744e87

Branch: refs/heads/trunk
Commit: 3d744e879f194a72aaf300db8038b40535a662ce
Parents: d2af57f
Author: Aleksandr Kovalenko <ak...@hortonworks.com>
Authored: Thu Jan 29 18:13:24 2015 +0200
Committer: Aleksandr Kovalenko <ak...@hortonworks.com>
Committed: Thu Jan 29 19:40:42 2015 +0200

----------------------------------------------------------------------
 .../controllers/main/service/add_controller.js  | 153 ++++++++++++----
 ambari-web/app/models/stack_service.js          |   5 +
 ambari-web/app/routes/add_service_routes.js     |  74 ++++----
 ambari-web/app/templates/main/service/add.hbs   |   6 +-
 ambari-web/app/views/main/service/add_view.js   |  47 +----
 .../main/service/add_controller_test.js         | 176 +++++++++++++++++++
 ambari-web/test/models/stack_service_test.js    |  18 ++
 7 files changed, 362 insertions(+), 117 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/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 9ecc85b..793823e 100644
--- a/ambari-web/app/controllers/main/service/add_controller.js
+++ b/ambari-web/app/controllers/main/service/add_controller.js
@@ -63,6 +63,76 @@ App.AddServiceController = App.WizardController.extend({
     group: "hadoop"
   }),
 
+  loadMap: {
+    '1': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.loadServices();
+        }
+      }
+    ],
+    '2': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.loadMasterComponentHosts();
+          this.load('hosts');
+        }
+      }
+    ],
+    '2': [
+      {
+        type: 'async',
+        callback: function () {
+          var dfd = $.Deferred();
+          var self = this;
+          this.loadHosts().done(function () {
+            self.loadMasterComponentHosts();
+            self.load('hosts');
+            dfd.resolve();
+          });
+          return dfd.promise();
+        }
+      }
+    ],
+    '3': [
+      {
+        type: 'async',
+        callback: function () {
+          var dfd = $.Deferred();
+          var self = this;
+          this.loadHosts().done(function () {
+            self.loadServices();
+            self.loadClients();
+            self.loadSlaveComponentHosts();//depends on loadServices
+            dfd.resolve();
+          });
+          return dfd.promise();
+        }
+      }
+    ],
+    '4': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.loadServiceConfigGroups();
+          this.loadServiceConfigProperties();
+        }
+      }
+    ],
+    '5': [
+      {
+        type: 'sync',
+        callback: function () {
+          this.checkSecurityStatus();
+          this.load('cluster');
+          this.set('content.additionalClients', []);
+        }
+      }
+    ]
+  },
+
   setCurrentStep: function (currentStep, completed) {
     this._super(currentStep, completed);
     App.clusterStatus.setClusterStatus({
@@ -71,6 +141,50 @@ App.AddServiceController = App.WizardController.extend({
     });
   },
 
+  loadHosts: function () {
+    var dfd;
+    if (this.getDBProperty('hosts')) {
+      dfd = $.Deferred();
+      dfd.resolve();
+    } else {
+      dfd = App.ajax.send({
+        name: 'hosts.confirmed',
+        sender: this,
+        data: {},
+        success: 'loadHostsSuccessCallback',
+        error: 'loadHostsErrorCallback'
+      });
+    }
+    return dfd.promise();
+  },
+
+  loadHostsSuccessCallback: function (response) {
+    var installedHosts = {};
+
+    response.items.forEach(function (item, indx) {
+      installedHosts[item.Hosts.host_name] = {
+        name: item.Hosts.host_name,
+        cpu: item.Hosts.cpu_count,
+        memory: item.Hosts.total_mem,
+        disk_info: item.Hosts.disk_info,
+        osType: item.Hosts.os_type,
+        osArch: item.Hosts.os_arch,
+        ip: item.Hosts.ip,
+        bootStatus: "REGISTERED",
+        isInstalled: true,
+        hostComponents: item.host_components,
+        id: indx++
+      };
+    });
+    this.setDBProperty('hosts', installedHosts);
+    this.set('content.hosts', installedHosts);
+  },
+
+  loadHostsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
+    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.method, jqXHR.status);
+    console.log('Loading hosts failed');
+  },
+
   /**
    * Load services data. Will be used at <code>Select services(step4)</code> step
    */
@@ -101,9 +215,10 @@ App.AddServiceController = App.WizardController.extend({
         item.set('isSelected', isSelected || (this.get("currentStep") == "1" ? isInstalled : isSelected));
         item.set('isInstalled', isInstalled);
       }, this);
-      var isServiceWithSlave = App.StackService.find().filterProperty('isSelected').filterProperty('hasSlave').filterProperty('isInstalled', false).mapProperty('serviceName').length;
-      var isServiceWithClient = App.StackService.find().filterProperty('isSelected').filterProperty('hasClient').filterProperty('isInstalled', false).mapProperty('serviceName').length;
-      this.set('content.skipSlavesStep', !isServiceWithSlave && !isServiceWithClient);
+      var isServiceWithSlave = App.StackService.find().filterProperty('isSelected').filterProperty('hasSlave').filterProperty('isInstalled', false).length;
+      var isServiceWithClient = App.StackService.find().filterProperty('isSelected').filterProperty('hasClient').filterProperty('isInstalled', false).length;
+      var isServiceWithCustomAssignedNonMasters = App.StackService.find().filterProperty('isSelected').filterProperty('hasNonMastersWithCustomAssignment').filterProperty('isInstalled', false).length;
+      this.set('content.skipSlavesStep', !isServiceWithSlave && !isServiceWithClient || !isServiceWithCustomAssignedNonMasters);
       if (this.get('content.skipSlavesStep')) {
         this.get('isStepDisabled').findProperty('step', 3).set('value', this.get('content.skipSlavesStep'));
       }
@@ -258,7 +373,7 @@ App.AddServiceController = App.WizardController.extend({
     var selectedServices = this.get('content.services').filterProperty('isSelected', true).mapProperty('serviceName');
     var installedComponentsMap = {};
     var uninstalledComponents = [];
-    var hosts = this.get('content.hosts');
+    var hosts = this.getDBProperty('hosts') || this.get('content.hosts');
     var masterComponents = App.get('components.masters');
     var nonMasterComponentHosts = [];
 
@@ -305,7 +420,7 @@ App.AddServiceController = App.WizardController.extend({
     }
 
     if (!nonMasterComponentHosts.length) {
-      nonMasterComponentHosts.push(Object.keys(this.get('content.hosts'))[0]);
+      nonMasterComponentHosts.push(Object.keys(hosts)[0]);
     }
     var uninstalledComponentHosts =  nonMasterComponentHosts.map(function(_hostName){
       return {
@@ -350,34 +465,6 @@ App.AddServiceController = App.WizardController.extend({
   },
 
   /**
-   * Load data for all steps until <code>current step</code>
-   */
-  loadAllPriorSteps: function () {
-    var step = this.get('currentStep');
-    switch (step) {
-      case '8':
-      case '7':
-      case '6':
-      case '5':
-        this.checkSecurityStatus();
-        this.load('cluster');
-        this.set('content.additionalClients', []);
-      case '4':
-        this.loadServiceConfigGroups();
-        this.loadServiceConfigProperties();
-      case '3':
-        this.loadServices();
-        this.loadClients();
-        this.loadSlaveComponentHosts();//depends on loadServices
-      case '2':
-        this.loadMasterComponentHosts();
-        this.load('hosts');
-      case '1':
-        this.loadServices();
-    }
-  },
-
-  /**
    * Remove all loaded data.
    * Created as copy for App.router.clearAllSteps
    */

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/ambari-web/app/models/stack_service.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/stack_service.js b/ambari-web/app/models/stack_service.js
index 67f1e8a..92dd900 100644
--- a/ambari-web/app/models/stack_service.js
+++ b/ambari-web/app/models/stack_service.js
@@ -141,6 +141,11 @@ App.StackService = DS.Model.extend({
     return serviceComponents.someProperty('isSlave');
   }.property('serviceName'),
 
+  hasNonMastersWithCustomAssignment: function () {
+    var serviceComponents = this.get('serviceComponents');
+    return serviceComponents.rejectProperty('isMaster').rejectProperty('cardinality', 'ALL').length > 0;
+  }.property('serviceName'),
+
   isClientOnlyService: function () {
     var serviceComponents = this.get('serviceComponents');
     return serviceComponents.everyProperty('isClient');

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/ambari-web/app/routes/add_service_routes.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/routes/add_service_routes.js b/ambari-web/app/routes/add_service_routes.js
index 02723f9..b1e67d5 100644
--- a/ambari-web/app/routes/add_service_routes.js
+++ b/ambari-web/app/routes/add_service_routes.js
@@ -103,10 +103,11 @@ module.exports = App.WizardRoute.extend({
       controller.setCurrentStep('1');
       controller.set('hideBackButton', true);
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        var wizardStep4Controller = router.get('wizardStep4Controller');
-        wizardStep4Controller.set('wizardController', controller);
-        controller.connectOutlet('wizardStep4', controller.get('content.services').filterProperty('isInstallable', true));
+        controller.loadAllPriorSteps().done(function () {
+          var wizardStep4Controller = router.get('wizardStep4Controller');
+          wizardStep4Controller.set('wizardController', controller);
+          controller.connectOutlet('wizardStep4', controller.get('content.services').filterProperty('isInstallable', true));
+        });
       });
     },
     next: function (router) {
@@ -130,8 +131,9 @@ module.exports = App.WizardRoute.extend({
       controller.setCurrentStep('2');
       controller.set('hideBackButton', false);
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        controller.connectOutlet('wizardStep5', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          controller.connectOutlet('wizardStep5', controller.get('content'));
+        });
       });
 
     },
@@ -154,10 +156,11 @@ module.exports = App.WizardRoute.extend({
       var controller = router.get('addServiceController');
       controller.setCurrentStep('3');
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        var wizardStep6Controller = router.get('wizardStep6Controller');
-        wizardStep6Controller.set('wizardController', controller);
-        controller.connectOutlet('wizardStep6', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          var wizardStep6Controller = router.get('wizardStep6Controller');
+          wizardStep6Controller.set('wizardController', controller);
+          controller.connectOutlet('wizardStep6', controller.get('content'));
+        });
       });
     },
     back: function(router){
@@ -192,12 +195,13 @@ module.exports = App.WizardRoute.extend({
       controller.setCurrentStep('4');
       controller.dataLoading().done(function () {
         var wizardStep7Controller = router.get('wizardStep7Controller');
-        controller.loadAllPriorSteps();
-        controller.loadAdvancedConfigs(wizardStep7Controller);
-        wizardStep7Controller.getConfigTags();
-        wizardStep7Controller.set('wizardController', controller);
-        controller.usersLoading().done(function () {
-          controller.connectOutlet('wizardStep7', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          controller.loadAdvancedConfigs(wizardStep7Controller);
+          wizardStep7Controller.getConfigTags();
+          wizardStep7Controller.set('wizardController', controller);
+          controller.usersLoading().done(function () {
+            controller.connectOutlet('wizardStep7', controller.get('content'));
+          });
         });
       });
     },
@@ -233,9 +237,10 @@ module.exports = App.WizardRoute.extend({
       controller.setCurrentStep('5');
       controller.dataLoading().done(function () {
         var kerberosStep4Controller = router.get('kerberosWizardStep4Controller');
-        controller.loadAllPriorSteps();
-        kerberosStep4Controller.set('wizardController', controller);
-        controller.connectOutlet('kerberosWizardStep4', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          kerberosStep4Controller.set('wizardController', controller);
+          controller.connectOutlet('kerberosWizardStep4', controller.get('content'));
+        });
       });
     },
     back: function(router){
@@ -269,10 +274,11 @@ module.exports = App.WizardRoute.extend({
       var controller = router.get('addServiceController');
       controller.setCurrentStep(App.supports.automatedKerberos ? '6' : '5');
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        var wizardStep8Controller = router.get('wizardStep8Controller');
-        wizardStep8Controller.set('wizardController', controller);
-        controller.connectOutlet('wizardStep8', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          var wizardStep8Controller = router.get('wizardStep8Controller');
+          wizardStep8Controller.set('wizardController', controller);
+          controller.connectOutlet('wizardStep8', controller.get('content'));
+        });
       });
     },
     back: function(router){
@@ -318,13 +324,14 @@ module.exports = App.WizardRoute.extend({
       var controller = router.get('addServiceController');
       controller.setCurrentStep(App.supports.automatedKerberos ? '7' : '6');
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        var wizardStep9Controller = router.get('wizardStep9Controller');
-        wizardStep9Controller.set('wizardController', controller);
-        if (!App.get('testMode')) {              //if test mode is ON don't disable prior steps link.
-          controller.setLowerStepsDisable(App.supports.automatedKerberos ? 7 : 6);
-        }
-        controller.connectOutlet('wizardStep9', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          var wizardStep9Controller = router.get('wizardStep9Controller');
+          wizardStep9Controller.set('wizardController', controller);
+          if (!App.get('testMode')) {              //if test mode is ON don't disable prior steps link.
+            controller.setLowerStepsDisable(App.supports.automatedKerberos ? 7 : 6);
+          }
+          controller.connectOutlet('wizardStep9', controller.get('content'));
+        });
       });
     },
     back: Em.Router.transitionTo('step6'),
@@ -366,9 +373,10 @@ module.exports = App.WizardRoute.extend({
       var controller = router.get('addServiceController');
       controller.setCurrentStep(App.supports.automatedKerberos ? '8' : '7');
       controller.dataLoading().done(function () {
-        controller.loadAllPriorSteps();
-        controller.setLowerStepsDisable(App.supports.automatedKerberos ? 8 : 7);
-        controller.connectOutlet('wizardStep10', controller.get('content'));
+        controller.loadAllPriorSteps().done(function () {
+          controller.setLowerStepsDisable(App.supports.automatedKerberos ? 8 : 7);
+          controller.connectOutlet('wizardStep10', controller.get('content'));
+        });
       });
     },
     back: Em.Router.transitionTo('step7'),

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/ambari-web/app/templates/main/service/add.hbs
----------------------------------------------------------------------
diff --git a/ambari-web/app/templates/main/service/add.hbs b/ambari-web/app/templates/main/service/add.hbs
index a42d1fe..0e94b00 100644
--- a/ambari-web/app/templates/main/service/add.hbs
+++ b/ambari-web/app/templates/main/service/add.hbs
@@ -47,11 +47,7 @@
           </div>
         </div>
           <div class="wizard-content well span9">
-            {{#if view.isLoaded}}
-              {{outlet}}
-            {{else}}
-                <div class="spinner"></div>
-            {{/if}}
+            {{outlet}}
           </div>
       </div>
     </div>

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/ambari-web/app/views/main/service/add_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/main/service/add_view.js b/ambari-web/app/views/main/service/add_view.js
index 78b6978..d3a8c08 100644
--- a/ambari-web/app/views/main/service/add_view.js
+++ b/ambari-web/app/views/main/service/add_view.js
@@ -21,51 +21,6 @@ var App = require('app');
 
 App.AddServiceView = Em.View.extend(App.WizardMenuMixin, {
 
-  templateName: require('templates/main/service/add'),
+  templateName: require('templates/main/service/add')
 
-  isLoaded: false,
-
-  willInsertElement: function () {
-    this.loadHosts();
-  },
-
-  /**
-   * send request to fetch all hosts information
-   */
-  loadHosts: function () {
-    App.ajax.send({
-      name: 'hosts.confirmed',
-      sender: this,
-      data: {},
-      success: 'loadHostsSuccessCallback',
-      error: 'loadHostsErrorCallback'
-    });
-  },
-
-  loadHostsSuccessCallback: function (response) {
-    var installedHosts = {};
-
-    response.items.forEach(function (item, indx) {
-      installedHosts[item.Hosts.host_name] = {
-        name: item.Hosts.host_name,
-        cpu: item.Hosts.cpu_count,
-        memory: item.Hosts.total_mem,
-        disk_info: item.Hosts.disk_info,
-        osType: item.Hosts.os_type,
-        osArch: item.Hosts.os_arch,
-        ip: item.Hosts.ip,
-        bootStatus: "REGISTERED",
-        isInstalled: true,
-        hostComponents: item.host_components,
-        id: indx++
-      };
-    });
-    this.get('controller').setDBProperty('hosts', installedHosts);
-    this.set('controller.content.hosts', installedHosts);
-    this.set('isLoaded', true);
-  },
-
-  loadHostsErrorCallback: function(){
-    this.set('isLoaded', true);
-  }
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/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 01f992c..a688ad0 100644
--- a/ambari-web/test/controllers/main/service/add_controller_test.js
+++ b/ambari-web/test/controllers/main/service/add_controller_test.js
@@ -162,4 +162,180 @@ describe('App.AddServiceController', function() {
       });
     });
   });
+
+  describe('#loadHosts', function () {
+
+    var cases = [
+      {
+        hosts: {},
+        isAjaxRequestSent: false,
+        title: 'hosts are already loaded'
+      },
+      {
+        areHostsLoaded: false,
+        isAjaxRequestSent: true,
+        title: 'hosts aren\'t yet loaded'
+      }
+    ];
+
+    beforeEach(function () {
+      sinon.stub(App.ajax, 'send', function () {
+        return {
+          promise: Em.K
+        };
+      });
+    });
+
+    afterEach(function () {
+      addServiceController.getDBProperty.restore();
+      App.ajax.send.restore();
+    });
+
+    cases.forEach(function (item) {
+      it(item.title, function () {
+        sinon.stub(addServiceController, 'getDBProperty').withArgs('hosts').returns(item.hosts);
+        addServiceController.loadHosts();
+        expect(App.ajax.send.calledOnce).to.equal(item.isAjaxRequestSent);
+      });
+    });
+
+  });
+
+  describe('#loadHostsSuccessCallback', function () {
+
+    it('should load hosts to local db and model', function () {
+      var diskInfo = [
+          {
+            available: '600000',
+            used: '400000',
+            percent: '40%',
+            size: '10000000',
+            type: 'ext4',
+            mountpoint: '/'
+          },
+          {
+            available: '500000',
+            used: '300000',
+            percent: '50%',
+            size: '6000000',
+            type: 'ext4',
+            mountpoint: '/'
+          }
+        ],
+        hostComponents = [
+          [
+            {
+              HostRoles: {
+                component_name: 'c0',
+                state: 'STARTED'
+              }
+            },
+            {
+              HostRoles: {
+                component_name: 'c1',
+                state: 'INSTALLED'
+              }
+            }
+          ],
+          [
+            {
+              HostRoles: {
+                component_name: 'c2',
+                state: 'STARTED'
+              }
+            },
+            {
+              HostRoles: {
+                component_name: 'c3',
+                state: 'INSTALLED'
+              }
+            }
+          ]
+        ],
+        response = {
+          items: [
+            {
+              Hosts: {
+                cpu_count: 1,
+                disk_info: [
+                  diskInfo[0]
+                ],
+                host_name: 'h0',
+                ip: '10.1.1.0',
+                os_arch: 'x86_64',
+                os_type: 'centos6',
+                total_mem: 4194304
+              },
+              host_components: hostComponents[0]
+            },
+            {
+              Hosts: {
+                cpu_count: 2,
+                disk_info: [
+                  diskInfo[1]
+                ],
+                host_name: 'h1',
+                ip: '10.1.1.1',
+                os_arch: 'x86',
+                os_type: 'centos5',
+                total_mem: 3145728
+              },
+              host_components: hostComponents[1]
+            }
+          ]
+        },
+        expected = {
+          h0: {
+            name: 'h0',
+            cpu: 1,
+            memory: 4194304,
+            disk_info: [diskInfo[0]],
+            osType: 'centos6',
+            osArch: 'x86_64',
+            ip: '10.1.1.0',
+            bootStatus: 'REGISTERED',
+            isInstalled: true,
+            hostComponents: hostComponents[0],
+            id: 0
+          },
+          h1: {
+            name: 'h1',
+            cpu: 2,
+            memory: 3145728,
+            disk_info: [diskInfo[1]],
+            osType: 'centos5',
+            osArch: 'x86',
+            ip: '10.1.1.1',
+            bootStatus: 'REGISTERED',
+            isInstalled: true,
+            hostComponents: hostComponents[1],
+            id: 1
+          }
+        };
+      addServiceController.loadHostsSuccessCallback(response);
+      var hostsInDb = addServiceController.getDBProperty('hosts');
+      var hostsInModel = addServiceController.get('content.hosts');
+      expect(hostsInDb).to.eql(expected);
+      expect(hostsInModel).to.eql(expected);
+    });
+
+  });
+
+  describe('#loadHostsErrorCallback', function () {
+
+    beforeEach(function () {
+      sinon.stub(App.ajax, 'defaultErrorHandler', Em.K);
+    });
+
+    afterEach(function () {
+      App.ajax.defaultErrorHandler.restore();
+    });
+
+    it('should execute default error handler', function () {
+      addServiceController.loadHostsErrorCallback({status: '500'}, 'textStatus', 'errorThrown', {url: 'url', method: 'GET'});
+      expect(App.ajax.defaultErrorHandler.calledOnce).to.be.true;
+      expect(App.ajax.defaultErrorHandler.calledWith({status: '500'}, 'url', 'GET', '500')).to.be.true;
+    });
+
+  });
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/3d744e87/ambari-web/test/models/stack_service_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/stack_service_test.js b/ambari-web/test/models/stack_service_test.js
index 65cd41c..2e11ad4 100644
--- a/ambari-web/test/models/stack_service_test.js
+++ b/ambari-web/test/models/stack_service_test.js
@@ -198,6 +198,24 @@ describe('App.StackService', function () {
     });
   });
 
+  describe('#hasNonMastersWithCustomAssignment', function () {
+    it('No serviceComponents', function () {
+      ss.set('serviceComponents', []);
+      ss.propertyDidChange('hasNonMastersWithCustomAssignment');
+      expect(ss.get('hasNonMastersWithCustomAssignment')).to.be.false;
+    });
+    it('All non-master serviceComponents are required on all hosts', function () {
+      ss.set('serviceComponents', [Em.Object.create({isMaster: true}), Em.Object.create({isSlave: true, cardinality: 'ALL'}), Em.Object.create({isClient: true, cardinality: 'ALL'})]);
+      ss.propertyDidChange('hasNonMastersWithCustomAssignment');
+      expect(ss.get('hasNonMastersWithCustomAssignment')).to.be.false;
+    });
+    it('Has non-master serviceComponents not required on all hosts', function () {
+      ss.set('serviceComponents', [Em.Object.create({isSlave: true}), Em.Object.create({isClient: true})]);
+      ss.propertyDidChange('hasNonMastersWithCustomAssignment');
+      expect(ss.get('hasNonMastersWithCustomAssignment')).to.be.true;
+    });
+  });
+
   describe('#isClientOnlyService', function () {
     it('Has not only client serviceComponents', function () {
       ss.set('serviceComponents', [Em.Object.create({isSlave: true}), Em.Object.create({isClient: true})]);