فهرست منبع

AMBARI-6624. Make validation for minimum required number of service components from cardinality field of stacks API (Buzhor Denys via alexantonenko)

Alex Antonenko 10 سال پیش
والد
کامیت
a6873d64cf

+ 36 - 10
ambari-web/app/controllers/wizard/step6_controller.js

@@ -18,6 +18,7 @@
 
 
 var App = require('app');
 var App = require('app');
 var db = require('utils/db');
 var db = require('utils/db');
+var stringUtils = require('utils/string_utils');
 
 
 /**
 /**
  * By Step 6, we have the following information stored in App.db and set on this
  * By Step 6, we have the following information stored in App.db and set on this
@@ -80,6 +81,10 @@ App.WizardStep6Controller = Em.Controller.extend({
     return this.get('content.controllerName') === 'addServiceController';
     return this.get('content.controllerName') === 'addServiceController';
   }.property('content.controllerName'),
   }.property('content.controllerName'),
 
 
+  installedServiceNames: function() {
+    return this.get('content.services').filterProperty('isInstalled').mapProperty('serviceName');
+  }.property('content.services').cacheable(),
+
   /**
   /**
    * Verify condition that at least one checkbox of each component was checked
    * Verify condition that at least one checkbox of each component was checked
    * @method clearError
    * @method clearError
@@ -241,6 +246,7 @@ App.WizardStep6Controller = Em.Controller.extend({
             name: serviceComponent.get('componentName'),
             name: serviceComponent.get('componentName'),
             label: serviceComponent.get('displayName'),
             label: serviceComponent.get('displayName'),
             allChecked: false,
             allChecked: false,
+            isRequired: serviceComponent.get('isRequired'),
             noChecked: true
             noChecked: true
           }));
           }));
         }
         }
@@ -468,7 +474,8 @@ App.WizardStep6Controller = Em.Controller.extend({
   },
   },
 
 
   /**
   /**
-   * Validate a component for all hosts. Return do we have errors or not
+   * Check for minimum required count of components to install.
+   *
    * @return {bool}
    * @return {bool}
    * @method validateEachComponent
    * @method validateEachComponent
    */
    */
@@ -476,17 +483,36 @@ App.WizardStep6Controller = Em.Controller.extend({
     var isError = false;
     var isError = false;
     var hosts = this.get('hosts');
     var hosts = this.get('hosts');
     var headers = this.get('headers');
     var headers = this.get('headers');
+    var componentsToInstall = [];
     headers.forEach(function (header) {
     headers.forEach(function (header) {
-      var all_false = true;
-      hosts.forEach(function (host) {
-        var checkboxes = host.get('checkboxes');
-        all_false = all_false && !checkboxes.findProperty('title', header.get('label')).checked;
-      });
-      isError = isError || all_false;
-    });
-    if (isError) {
-      this.set('errorMessage', Em.I18n.t('installer.step6.error.mustSelectOne'));
+      var checkboxes = hosts.mapProperty('checkboxes').reduce(function(cItem, pItem) { return cItem.concat(pItem); });
+      var selectedCount = checkboxes.filterProperty('component', header.get('name')).filterProperty('checked').length;
+      if (header.get('name') == 'CLIENT') {
+        var clientsMinCount = 0;
+        var serviceNames = this.get('installedServiceNames').concat(this.get('content.selectedServiceNames'));
+        // find max value for `minToInstall` property
+        serviceNames.forEach(function(serviceName) {
+          App.StackServiceComponent.find().filterProperty('stackService.serviceName', serviceName).filterProperty('isClient')
+            .mapProperty('minToInstall').forEach(function(ctMinCount) { clientsMinCount = ctMinCount > clientsMinCount ? ctMinCount : clientsMinCount; });
+        });
+        if (selectedCount < clientsMinCount) {
+          isError = true;
+          var requiredQuantity = (clientsMinCount > hosts.length ? hosts.length : clientsMinCount) - selectedCount;
+          componentsToInstall.push(requiredQuantity + ' ' + stringUtils.pluralize(requiredQuantity, Em.I18n.t('common.client')));
+        }
+      } else {
+        var stackComponent = App.StackServiceComponent.find().findProperty('componentName', header.get('name'));
+        if (selectedCount < stackComponent.get('minToInstall')) {
+          isError = true;
+          var requiredQuantity = (stackComponent.get('minToInstall') > hosts.length ? hosts.length : stackComponent.get('minToInstall')) - selectedCount;
+          componentsToInstall.push(requiredQuantity + ' ' + stringUtils.pluralize(requiredQuantity, stackComponent.get('displayName')));
+        }
+      }
+    }, this);
+    if (componentsToInstall.length) {
+      this.set('errorMessage', Em.I18n.t('installer.step6.error.mustSelectComponents').format(componentsToInstall.join(', ')));
     }
     }
+
     return !isError;
     return !isError;
   }
   }
 
 

+ 1 - 0
ambari-web/app/mappers/stack_service_mapper.js

@@ -41,6 +41,7 @@ App.stackServiceMapper = App.QuickDataMapper.create({
   component_config: {
   component_config: {
     id: 'component_name',
     id: 'component_name',
     component_name: 'component_name',
     component_name: 'component_name',
+    cardinality: 'cardinality',
     service_name: 'service_name',
     service_name: 'service_name',
     component_category: 'component_category',
     component_category: 'component_category',
     is_master: 'is_master',
     is_master: 'is_master',

+ 1 - 0
ambari-web/app/messages.js

@@ -608,6 +608,7 @@ Em.I18n.translations = {
   'installer.step6.body':'Assign slave and client components to hosts you want to run them on.<br/>Hosts that are assigned master components are shown with <i class=icon-asterisks>&#10037;</i>. <br/>&quot;Client&quot; will install ',
   'installer.step6.body':'Assign slave and client components to hosts you want to run them on.<br/>Hosts that are assigned master components are shown with <i class=icon-asterisks>&#10037;</i>. <br/>&quot;Client&quot; will install ',
   'installer.step6.error.mustSelectOne':'You must assign at least one host to each component.',
   'installer.step6.error.mustSelectOne':'You must assign at least one host to each component.',
   'installer.step6.error.mustSelectOneForHost':'You must assign at least one slave/client component to each.',
   'installer.step6.error.mustSelectOneForHost':'You must assign at least one slave/client component to each.',
+  'installer.step6.error.mustSelectComponents': 'You must assign at least: {0}',
   'installer.step6.wizardStep6Host.title':'master components hosted on {0}',
   'installer.step6.wizardStep6Host.title':'master components hosted on {0}',
   'installer.step6.addHostWizard.body':'Assign HBase master and ZooKeeper server.',
   'installer.step6.addHostWizard.body':'Assign HBase master and ZooKeeper server.',
   'installer.step6.error.mustSelectOneForSlaveHost': 'You must assign at least one slave/client component to each host with no master component',
   'installer.step6.error.mustSelectOneForSlaveHost': 'You must assign at least one slave/client component to each host with no master component',

+ 76 - 36
ambari-web/app/models/stack_service_component.js

@@ -17,12 +17,14 @@
  */
  */
 
 
 var App = require('app');
 var App = require('app');
+var numberUtils = require('utils/number_utils');
 /**
 /**
  * This model loads all serviceComponents supported by the stack
  * This model loads all serviceComponents supported by the stack
  * @type {*}
  * @type {*}
  */
  */
 App.StackServiceComponent = DS.Model.extend({
 App.StackServiceComponent = DS.Model.extend({
   componentName: DS.attr('string'),
   componentName: DS.attr('string'),
+  cardinality: DS.attr('string'),
   dependencies: DS.attr('array'),
   dependencies: DS.attr('array'),
   serviceName: DS.attr('string'),
   serviceName: DS.attr('string'),
   componentCategory: DS.attr('string'),
   componentCategory: DS.attr('string'),
@@ -33,6 +35,25 @@ App.StackServiceComponent = DS.Model.extend({
   stackService: DS.belongsTo('App.StackService'),
   stackService: DS.belongsTo('App.StackService'),
   serviceComponentId: DS.attr('number', {defaultValue: 1}), // this is used on Assign Master page for multiple masters
   serviceComponentId: DS.attr('number', {defaultValue: 1}), // this is used on Assign Master page for multiple masters
 
 
+  /**
+   * Minimum required count for installation.
+   *
+   * @property {Number} minToInstall
+   **/
+  minToInstall: function() {
+    return numberUtils.getCardinalityValue(this.get('cardinality'), false);
+  }.property('cardinality'),
+
+  /**
+   * Maximum required count for installation.
+   *
+   * @property {Number} maxToInstall
+   **/
+  maxToInstall: function() {
+    return numberUtils.getCardinalityValue(this.get('cardinality'), true);
+  }.property('cardinality'),
+
+  /** @property {String} displayName**/
   displayName: function() {
   displayName: function() {
     if (App.format.role(this.get('componentName'))) {
     if (App.format.role(this.get('componentName'))) {
       return App.format.role(this.get('componentName'));
       return App.format.role(this.get('componentName'));
@@ -41,50 +62,71 @@ App.StackServiceComponent = DS.Model.extend({
     }
     }
   }.property('componentName'),
   }.property('componentName'),
 
 
+  /** @property {Boolean} isRequired - component required to install **/
+  isRequired: function() {
+    return this.get('minToInstall') > 0;
+  }.property('cardinality'),
+
+  /** @property {Boolean} isMultipleAllowed - component can be assigned for more than one host **/
+  isMultipleAllowed: function() {
+    return this.get('maxToInstall') > 1;
+  }.property('cardinality'),
+
+  /** @property {Boolean} isSlave **/
   isSlave: function() {
   isSlave: function() {
    return this.get('componentCategory') === 'SLAVE';
    return this.get('componentCategory') === 'SLAVE';
   }.property('componentCategory'),
   }.property('componentCategory'),
 
 
+  /** @property {Boolean} isRestartable - component supports restart action **/
   isRestartable: function() {
   isRestartable: function() {
     return !this.get('isClient');
     return !this.get('isClient');
   }.property('isClient'),
   }.property('isClient'),
 
 
+  /** @property {Boolean} isReassignable - component supports reassign action **/
   isReassignable: function() {
   isReassignable: function() {
     return ['NAMENODE', 'SECONDARY_NAMENODE', 'JOBTRACKER', 'RESOURCEMANAGER'].contains(this.get('componentName'));
     return ['NAMENODE', 'SECONDARY_NAMENODE', 'JOBTRACKER', 'RESOURCEMANAGER'].contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  isDeletable: function() {
-    return ['SUPERVISOR', 'HBASE_MASTER', 'DATANODE', 'TASKTRACKER', 'NODEMANAGER', 'HBASE_REGIONSERVER', 'GANGLIA_MONITOR', 'ZOOKEEPER_SERVER'].contains(this.get('componentName'));
-  }.property('componentName'),
-
+  /** @property {Boolean} isRollinRestartAllowed - component supports rolling restart action **/
   isRollinRestartAllowed: function() {
   isRollinRestartAllowed: function() {
-    return ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER", "SUPERVISOR"].contains(this.get('componentName'));
+    return this.get('isSlave') && !this.get('isMasterBehavior');
   }.property('componentName'),
   }.property('componentName'),
 
 
+  /** @property {Boolean} isDecommissionAllowed - component supports decommission action **/
   isDecommissionAllowed: function() {
   isDecommissionAllowed: function() {
     return ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER"].contains(this.get('componentName'));
     return ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER"].contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
+  /** @property {Boolean} isRefreshConfigsAllowed - component supports refresh configs action **/
   isRefreshConfigsAllowed: function() {
   isRefreshConfigsAllowed: function() {
     return ["FLUME_HANDLER"].contains(this.get('componentName'));
     return ["FLUME_HANDLER"].contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
+  /** @property {Boolean} isAddableToHost - component can be added on host details page **/
   isAddableToHost: function() {
   isAddableToHost: function() {
-    return ["DATANODE", "TASKTRACKER", "NODEMANAGER", "HBASE_REGIONSERVER", "HBASE_MASTER", "ZOOKEEPER_SERVER", "SUPERVISOR", "GANGLIA_MONITOR"].contains(this.get('componentName'));
+    return ((this.get('isMasterAddableInstallerWizard') || (this.get('isSlave') && this.get('maxToInstall') > 2)) && !this.get('isHAComponentOnly'));
   }.property('componentName'),
   }.property('componentName'),
 
 
+  /** @property {Boolean} isDeletable - component supports delete action **/
+  isDeletable: function() {
+    return this.get('isAddableToHost');
+  }.property('componentName'),
+
+  /** @property {Boolean} isShownOnInstallerAssignMasterPage - component visible on "Assign Masters" step of Install Wizard **/
   isShownOnInstallerAssignMasterPage: function() {
   isShownOnInstallerAssignMasterPage: function() {
     var component = this.get('componentName');
     var component = this.get('componentName');
     var mastersNotShown = ['MYSQL_SERVER'];
     var mastersNotShown = ['MYSQL_SERVER'];
     return ((this.get('isMaster') && !mastersNotShown.contains(component)) || component === 'APP_TIMELINE_SERVER');
     return ((this.get('isMaster') && !mastersNotShown.contains(component)) || component === 'APP_TIMELINE_SERVER');
   }.property('isMaster','componentName'),
   }.property('isMaster','componentName'),
 
 
+  /** @property {Boolean} isShownOnInstallerSlaveClientPage - component visible on "Assign Slaves and Clients" step of Install Wizard**/
   isShownOnInstallerSlaveClientPage: function() {
   isShownOnInstallerSlaveClientPage: function() {
     var component = this.get('componentName');
     var component = this.get('componentName');
     var slavesNotShown = ['JOURNALNODE','ZKFC','APP_TIMELINE_SERVER','GANGLIA_MONITOR'];
     var slavesNotShown = ['JOURNALNODE','ZKFC','APP_TIMELINE_SERVER','GANGLIA_MONITOR'];
     return this.get('isSlave') && !slavesNotShown.contains(component);
     return this.get('isSlave') && !slavesNotShown.contains(component);
   }.property('isSlave','componentName'),
   }.property('isSlave','componentName'),
 
 
+  /** @property {Boolean} isShownOnAddServiceAssignMasterPage - component visible on "Assign Masters" step of Add Service Wizard **/
   isShownOnAddServiceAssignMasterPage: function() {
   isShownOnAddServiceAssignMasterPage: function() {
     var isVisible = this.get('isShownOnInstallerAssignMasterPage');
     var isVisible = this.get('isShownOnInstallerAssignMasterPage');
     if (App.get('isHaEnabled')) {
     if (App.get('isHaEnabled')) {
@@ -93,51 +135,60 @@ App.StackServiceComponent = DS.Model.extend({
     return isVisible;
     return isVisible;
   }.property('isShownOnInstallerAssignMasterPage','App.isHaEnabled'),
   }.property('isShownOnInstallerAssignMasterPage','App.isHaEnabled'),
 
 
+  /** @property {Boolean} isMasterWithMultipleInstances **/
   isMasterWithMultipleInstances: function() {
   isMasterWithMultipleInstances: function() {
-    var masters = ['ZOOKEEPER_SERVER', 'HBASE_MASTER', 'NAMENODE', 'JOURNALNODE', 'RESOURCEMANAGER'];
-    return masters.contains(this.get('componentName'));
-  }.property('componentName'),
-
+    // @todo: safe removing JOURNALNODE from masters list
+    return (this.get('isMaster') && this.get('isMultipleAllowed')) || this.get('componentName') == 'JOURNALNODE';
+  }.property('componentName'),
+
+  /**
+   * Master component list that could be assigned for more than 1 host.
+   * Some components like NameNode and ResourceManager have range cardinality value
+   * like 1-2. We can assign only components with cardinality 1+/0+. Strict range value
+   * show that this components will be assigned for 2 hosts only if HA mode activated.
+   *
+   * @property {Boolean} isMasterAddableInstallerWizard
+   **/
   isMasterAddableInstallerWizard: function() {
   isMasterAddableInstallerWizard: function() {
-    var masters = ['ZOOKEEPER_SERVER', 'HBASE_MASTER'];
-    return masters.contains(this.get('componentName'));
+    return this.get('isMaster') && this.get('isMultipleAllowed') && this.get('maxToInstall') > 2;
   }.property('componentName'),
   }.property('componentName'),
 
 
-  /** Some non master components can be assigned as master **/
+  /** @property {Boolean} isMasterBehavior - Some non master components can be assigned as master **/
   isMasterBehavior: function() {
   isMasterBehavior: function() {
     var componentsName = ['APP_TIMELINE_SERVER'];
     var componentsName = ['APP_TIMELINE_SERVER'];
     return componentsName.contains(this.get('componentName'));
     return componentsName.contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  /** Some non client components can be assigned as clients **/
+  /** @property {Boolean} isClientBehavior - Some non client components can be assigned as clients.
+   *
+   * Used for ignoring such components as Ganglia Monitor on Installer "Review" step.
+   **/
   isClientBehavior: function() {
   isClientBehavior: function() {
     var componentName = ['GANGLIA_MONITOR'];
     var componentName = ['GANGLIA_MONITOR'];
     return componentName.contains(this.get('componentName'));
     return componentName.contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  /** Components that can be installed only if HA enabled **/
+  /** @property {Boolean} isHAComponentOnly - Components that can be installed only if HA enabled **/
   isHAComponentOnly: function() {
   isHAComponentOnly: function() {
     var HAComponentNames = ['ZKFC','JOURNALNODE'];
     var HAComponentNames = ['ZKFC','JOURNALNODE'];
     return HAComponentNames.contains(this.get('componentName'));
     return HAComponentNames.contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  // Is It require to install the components on all hosts. used in step-6 wizard controller
+  /** @property {Boolean} isRequiredOnAllHosts - Is It require to install the components on all hosts. used in step-6 wizard controller **/
   isRequiredOnAllHosts: function() {
   isRequiredOnAllHosts: function() {
-    var service = this.get('stackService');
-    return service.get('isMonitoringService') && this.get('isSlave') ;
+    return this.get('minToInstall') == Infinity;
   }.property('stackService','isSlave'),
   }.property('stackService','isSlave'),
 
 
-  // components that are not to be installed with ambari server
+  /** components that are not to be installed with ambari server **/
   isNotPreferableOnAmbariServerHost: function() {
   isNotPreferableOnAmbariServerHost: function() {
     var service = ['STORM_UI_SERVER', 'DRPC_SERVER', 'STORM_REST_API', 'NIMBUS', 'GANGLIA_SERVER', 'NAGIOS_SERVER', 'HUE_SERVER'];
     var service = ['STORM_UI_SERVER', 'DRPC_SERVER', 'STORM_REST_API', 'NIMBUS', 'GANGLIA_SERVER', 'NAGIOS_SERVER', 'HUE_SERVER'];
     return service.contains(this.get('componentName'));
     return service.contains(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  // default number of master hosts on Assign Master page:
+  /** @property {Number} defaultNoOfMasterHosts - default number of master hosts on Assign Master page: **/
   defaultNoOfMasterHosts: function() {
   defaultNoOfMasterHosts: function() {
-    var componentName = this.get('componentName');
      if (this.get('isMasterAddableInstallerWizard')) {
      if (this.get('isMasterAddableInstallerWizard')) {
-       return App.StackServiceComponent.cardinality(componentName).min;
+       return this.get('componentName') == 'ZOOKEEPER_SERVER' ? 3 : this.get('minToInstall');
      }
      }
   }.property('componentName'),
   }.property('componentName'),
 
 
@@ -145,7 +196,7 @@ App.StackServiceComponent = DS.Model.extend({
     return App.StackServiceComponent.selectionScheme(this.get('componentName'));
     return App.StackServiceComponent.selectionScheme(this.get('componentName'));
   }.property('componentName'),
   }.property('componentName'),
 
 
-  // components that are co-hosted with this component
+  /** @property {Boolean} coHostedComponents - components that are co-hosted with this component **/
   coHostedComponents: function() {
   coHostedComponents: function() {
     var componentName = this.get('componentName');
     var componentName = this.get('componentName');
     var key, coHostedComponents = [];
     var key, coHostedComponents = [];
@@ -157,12 +208,12 @@ App.StackServiceComponent = DS.Model.extend({
     return coHostedComponents;
     return coHostedComponents;
   }.property('componentName'),
   }.property('componentName'),
 
 
-  // Is any other component co-hosted with this component
+  /** @property {Boolean} isOtherComponentCoHosted - Is any other component co-hosted with this component **/
   isOtherComponentCoHosted: function() {
   isOtherComponentCoHosted: function() {
     return !!this.get('coHostedComponents').length;
     return !!this.get('coHostedComponents').length;
   }.property('coHostedComponents'),
   }.property('coHostedComponents'),
 
 
-  // Is this component co-hosted with other component
+  /** @property {Boolean} isCoHostedComponent - Is this component co-hosted with other component **/
   isCoHostedComponent: function() {
   isCoHostedComponent: function() {
     var componentName = this.get('componentName');
     var componentName = this.get('componentName');
     return !!App.StackServiceComponent.coHost[componentName];
     return !!App.StackServiceComponent.coHost[componentName];
@@ -197,17 +248,6 @@ App.StackServiceComponent.selectionScheme = function (componentName){
   }
   }
 };
 };
 
 
-App.StackServiceComponent.cardinality = function (componentName) {
-  switch (componentName) {
-    case 'ZOOKEEPER_SERVER':
-      return {min: 3};
-    case 'HBASE_MASTER':
-      return {min: 1};
-    default:
-      return {min:1, max:1};
-  }
-};
-
 App.StackServiceComponent.coHost = {
 App.StackServiceComponent.coHost = {
   'HIVE_METASTORE': 'HIVE_SERVER',
   'HIVE_METASTORE': 'HIVE_SERVER',
   'WEBHCAT_SERVER': 'HIVE_SERVER'
   'WEBHCAT_SERVER': 'HIVE_SERVER'

+ 18 - 0
ambari-web/app/utils/number_utils.js

@@ -85,5 +85,23 @@ module.exports = {
       }
       }
     }
     }
     return null;
     return null;
+  },
+  /**
+   * @param {String|Number} cardinality - value to parse
+   * @param {Boolean} isMax - return maximum count
+   * @return {Number}
+   **/
+  getCardinalityValue: function(cardinality, isMax) {
+    if (cardinality) {
+      var isOptional = cardinality.toString().split('-').length > 1;
+      if (isOptional) {
+        return parseInt(cardinality.split('-')[(isMax) ? 1 : 0]);
+      } else {
+        if (isMax) return /^\d+\+/.test(cardinality) || cardinality == 'ALL' ? Infinity : parseInt(cardinality);
+        return cardinality == 'ALL' ? Infinity : parseInt(cardinality.toString().replace('+', ''))
+      }
+    } else {
+      return 0;
+    }
   }
   }
 };
 };

+ 283 - 201
ambari-web/test/models/stack_service_component_test.js

@@ -21,224 +21,306 @@ var App = require('app');
 var modelSetup = require('test/init_model_test');
 var modelSetup = require('test/init_model_test');
 require('models/stack_service_component');
 require('models/stack_service_component');
 
 
-var stackServiceComponent,
-  stackServiceComponentData = {
-    id: 'ssc'
-  },
-  components = [
-    {
-      name: 'NAMENODE',
-      isReassignable: true
-    },
-    {
-      name: 'SECONDARY_NAMENODE',
-      isReassignable: true
-    },
-    {
-      name: 'JOBTRACKER',
-      isReassignable: true
-    },
-    {
-      name: 'RESOURCEMANAGER',
-      isReassignable: true
-    },
-    {
-      name: 'SUPERVISOR',
+/**
+
+  Component properties template:
+
+  {
+    componentName: 'SUPERVISOR',
+    expected: {
+      displayName: 'Supervisor',
+      minToInstall: 1,
+      maxToInstall: Infinity,
+      isRequired: true,
+      isMultipleAllowed: true,
+      isSlave: true,
+      isMaster: false,
+      isClient: false,
+      isRestartable: true,
+      isReassignable: false,
       isDeletable: true,
       isDeletable: true,
       isRollinRestartAllowed: true,
       isRollinRestartAllowed: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'HBASE_MASTER',
-      isDeletable: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'DATANODE',
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: true,
+      isShownOnInstallerAssignMasterPage: false,
+      isShownOnInstallerSlaveClientPage: true,
+      isShownOnAddServiceAssignMasterPage: false,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      defaultNoOfMasterHosts: 1,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false,
+      selectionSchemeForMasterComponent: {"else": 0}
+    }
+  }
+
+**/
+var componentPropertiesValidationTests = [
+  {
+    componentName: 'SUPERVISOR',
+    expected: {
+      displayName: 'Supervisor',
+      minToInstall: 1,
+      maxToInstall: Infinity,
+      isRequired: true,
+      isMultipleAllowed: true,
+      isSlave: true,
+      isRestartable: true,
+      isReassignable: false,
       isDeletable: true,
       isDeletable: true,
       isRollinRestartAllowed: true,
       isRollinRestartAllowed: true,
-      isDecommissionAllowed: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'TASKTRACKER',
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: true,
+      isShownOnInstallerSlaveClientPage: true,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'ZOOKEEPER_SERVER',
+    expected: {
+      minToInstall: 1,
+      maxToInstall: Infinity,
+      isRequired: true,
+      isMultipleAllowed: true,
+      isMaster: true,
+      isRestartable: true,
+      isReassignable: false,
+      isDeletable: true,
+      isRollinRestartAllowed: false,
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: true,
+      isShownOnInstallerAssignMasterPage: true,
+      isShownOnInstallerSlaveClientPage: false,
+      isShownOnAddServiceAssignMasterPage: true,
+      isMasterWithMultipleInstances: true,
+      isMasterAddableInstallerWizard: true,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      defaultNoOfMasterHosts: 3,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'APP_TIMELINE_SERVER',
+    expected: {
+      displayName: 'App Timeline Server',
+      minToInstall: 0,
+      maxToInstall: 1,
+      isRequired: false,
+      isMultipleAllowed: false,
+      isSlave: true,
+      isMaster: false,
+      isRestartable: true,
+      isReassignable: false,
+      isDeletable: false,
+      isRollinRestartAllowed: false,
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: false,
+      isShownOnInstallerAssignMasterPage: true,
+      isShownOnInstallerSlaveClientPage: false,
+      isShownOnAddServiceAssignMasterPage: true,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: true,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'GANGLIA_MONITOR',
+    expected: {
+      displayName: 'Ganglia Monitor',
+      minToInstall: Infinity,
+      maxToInstall: Infinity,
+      isRequired: true,
+      isMultipleAllowed: true,
+      isSlave: true,
+      isMaster: false,
+      isRestartable: true,
+      isReassignable: false,
       isDeletable: true,
       isDeletable: true,
       isRollinRestartAllowed: true,
       isRollinRestartAllowed: true,
-      isDecommissionAllowed: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'NODEMANAGER',
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: true,
+      isShownOnInstallerAssignMasterPage: false,
+      isShownOnInstallerSlaveClientPage: false,
+      isShownOnAddServiceAssignMasterPage: false,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: true,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'FLUME_HANDLER',
+    expected: {
+      displayName: 'Flume Agent',
+      minToInstall: 0,
+      maxToInstall: Infinity,
+      isRequired: false,
+      isMultipleAllowed: true,
+      isSlave: true,
+      isMaster: false,
+      isRestartable: true,
+      isReassignable: false,
       isDeletable: true,
       isDeletable: true,
       isRollinRestartAllowed: true,
       isRollinRestartAllowed: true,
-      isDecommissionAllowed: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'HBASE_REGIONSERVER',
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: true,
+      isAddableToHost: true,
+      isShownOnInstallerAssignMasterPage: false,
+      isShownOnInstallerSlaveClientPage: true,
+      isShownOnAddServiceAssignMasterPage: false,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'HIVE_METASTORE',
+    expected: {
+      displayName: 'Hive Metastore',
+      minToInstall: 1,
+      maxToInstall: 1,
+      isRequired: true,
+      isMultipleAllowed: false,
+      isSlave: false,
+      isMaster: true,
+      isRestartable: true,
+      isReassignable: false,
+      isDeletable: false,
+      isRollinRestartAllowed: false,
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: false,
+      isShownOnInstallerAssignMasterPage: true,
+      isShownOnInstallerSlaveClientPage: false,
+      isShownOnAddServiceAssignMasterPage: true,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: true
+    }
+  },
+  {
+    componentName: 'HIVE_SERVER',
+    expected: {
+      displayName: 'HiveServer2',
+      minToInstall: 1,
+      maxToInstall: 1,
+      isRequired: true,
+      isMultipleAllowed: false,
+      isSlave: false,
+      isMaster: true,
+      isRestartable: true,
+      isReassignable: false,
+      isDeletable: false,
+      isRollinRestartAllowed: false,
+      isDecommissionAllowed: false,
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: false,
+      isShownOnInstallerAssignMasterPage: true,
+      isShownOnInstallerSlaveClientPage: false,
+      isShownOnAddServiceAssignMasterPage: true,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: ['HIVE_METASTORE','WEBHCAT_SERVER'],
+      isOtherComponentCoHosted: true,
+      isCoHostedComponent: false
+    }
+  },
+  {
+    componentName: 'DATANODE',
+    expected: {
+      displayName: 'DataNode',
+      minToInstall: 1,
+      maxToInstall: Infinity,
+      isRequired: true,
+      isMultipleAllowed: true,
+      isSlave: true,
+      isMaster: false,
+      isRestartable: true,
+      isReassignable: false,
       isDeletable: true,
       isDeletable: true,
       isRollinRestartAllowed: true,
       isRollinRestartAllowed: true,
       isDecommissionAllowed: true,
       isDecommissionAllowed: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'GANGLIA_MONITOR',
-      isDeletable: true,
-      isAddableToHost: true
-    },
-    {
-      name: 'FLUME_HANDLER',
-      isRefreshConfigsAllowed: true
-    },
-    {
-      name: 'ZOOKEEPER_SERVER',
-      isAddableToHost: true
-    },
-    {
-      name: 'MYSQL_SERVER',
-      mastersNotShown: true
-    },
-    {
-      name: 'JOURNALNODE',
-      mastersNotShown: true
+      isRefreshConfigsAllowed: false,
+      isAddableToHost: true,
+      isShownOnInstallerAssignMasterPage: false,
+      isShownOnInstallerSlaveClientPage: true,
+      isShownOnAddServiceAssignMasterPage: false,
+      isMasterWithMultipleInstances: false,
+      isMasterAddableInstallerWizard: false,
+      isMasterBehavior: false,
+      isHAComponentOnly: false,
+      isRequiredOnAllHosts: false,
+      isNotPreferableOnAmbariServerHost: false,
+      coHostedComponents: [],
+      isOtherComponentCoHosted: false,
+      isCoHostedComponent: false
     }
     }
-  ],
-  reassignable = components.filterProperty('isReassignable').mapProperty('name'),
-  deletable = components.filterProperty('isDeletable').mapProperty('name'),
-  rollingRestartable = components.filterProperty('isRollinRestartAllowed').mapProperty('name'),
-  decommissionable = components.filterProperty('isDecommissionAllowed').mapProperty('name'),
-  refreshable = components.filterProperty('isRefreshConfigsAllowed').mapProperty('name'),
-  addable = components.filterProperty('isAddableToHost').mapProperty('name'),
-  mastersNotShown = components.filterProperty('mastersNotShown').mapProperty('name');
-
-describe('App.StackServiceComponent', function () {
+  }
+];
 
 
-  beforeEach(function () {
-    stackServiceComponent = App.StackServiceComponent.createRecord(stackServiceComponentData);
+describe('App.StackServiceComponent', function() {
+  before(function() {
+    modelSetup.setupStackServiceComponent();
   });
   });
 
 
-  afterEach(function () {
-    modelSetup.deleteRecord(stackServiceComponent);
-  });
-
-  describe('#isSlave', function () {
-    it('should be true', function () {
-      stackServiceComponent.set('componentCategory', 'SLAVE');
-      expect(stackServiceComponent.get('isSlave')).to.be.true;
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentCategory', 'cc');
-      expect(stackServiceComponent.get('isSlave')).to.be.false;
-    });
-  });
-
-  describe('#isRestartable', function () {
-    it('should be true', function () {
-      stackServiceComponent.set('isClient', false);
-      expect(stackServiceComponent.get('isRestartable')).to.be.true;
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('isClient', true);
-      expect(stackServiceComponent.get('isRestartable')).to.be.false;
-    });
-  });
-
-  describe('#isReassignable', function () {
-    reassignable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isReassignable')).to.be.true;
-      });
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isReassignable')).to.be.false;
-    });
-  });
-
-  describe('#isDeletable', function () {
-    deletable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isDeletable')).to.be.true;
-      });
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isDeletable')).to.be.false;
-    });
-  });
-
-  describe('#isRollinRestartAllowed', function () {
-    rollingRestartable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isRollinRestartAllowed')).to.be.true;
-      });
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isRollinRestartAllowed')).to.be.false;
-    });
-  });
-
-  describe('#isDecommissionAllowed', function () {
-    decommissionable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isDecommissionAllowed')).to.be.true;
+  describe('component properties validation', function() {
+    componentPropertiesValidationTests.forEach(function(test) {
+      describe('properties validation for ' + test.componentName + ' component', function() {
+        var component = App.StackServiceComponent.find(test.componentName);
+        var properties = Em.keys(test.expected);
+        properties.forEach(function(property) {
+          it('#{0} should be {1}'.format(property, JSON.stringify(test.expected[property])), function() {
+            expect(component.get(property)).to.be.eql(test.expected[property]);
+          })
+        });
       });
       });
     });
     });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isDecommissionAllowed')).to.be.false;
-    });
   });
   });
 
 
-  describe('#isRefreshConfigsAllowed', function () {
-    refreshable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isRefreshConfigsAllowed')).to.be.true;
-      });
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isRefreshConfigsAllowed')).to.be.false;
-    });
-  });
-
-  describe('#isAddableToHost', function () {
-    addable.forEach(function (item) {
-      it('should be true', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isAddableToHost')).to.be.true;
-      });
-    });
-    it('should be false', function () {
-      stackServiceComponent.set('componentName', 'name');
-      expect(stackServiceComponent.get('isAddableToHost')).to.be.false;
-    });
+  after(function() {
+    modelSetup.cleanStackServiceComponent();
   });
   });
-
-  describe('#isShownOnInstallerAssignMasterPage', function () {
-    mastersNotShown.forEach(function (item) {
-      it('should be false', function () {
-        stackServiceComponent.set('componentName', item);
-        expect(stackServiceComponent.get('isShownOnInstallerAssignMasterPage')).to.be.false;
-      });
-    });
-    it('should be true', function () {
-      stackServiceComponent.set('componentName', 'APP_TIMELINE_SERVER');
-      expect(stackServiceComponent.get('isShownOnInstallerAssignMasterPage')).to.be.true;
-    });
-    it('should be true', function () {
-      stackServiceComponent.setProperties({
-        componentName: 'name',
-        isMaster: true
-      });
-      expect(stackServiceComponent.get('isShownOnInstallerAssignMasterPage')).to.be.true;
-    });
-  });
-
-});
+});

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 357 - 165
ambari-web/test/service_components.js


+ 29 - 1
ambari-web/test/utils/number_utils_test.js

@@ -18,7 +18,7 @@
 
 
 var numberUtils = require('utils/number_utils');
 var numberUtils = require('utils/number_utils');
 
 
-describe('', function() {
+describe('utils/number_utils', function() {
 
 
   describe('#bytesToSize', function() {
   describe('#bytesToSize', function() {
 
 
@@ -245,4 +245,32 @@ describe('', function() {
     });
     });
   });
   });
 
 
+  describe('#getCardinalityValue()', function() {
+    var generateTestObject = function(cardinality, isMax, expected) {
+      return {
+        cardinality: cardinality,
+        isMax: isMax,
+        e: expected
+      }
+    };
+    var tests = [
+      generateTestObject(null, true, 0),
+      generateTestObject(undefined, true, 0),
+      generateTestObject('1', true, 1),
+      generateTestObject('1', false, 1),
+      generateTestObject('0+', true, Infinity),
+      generateTestObject('0+', false, 0),
+      generateTestObject('1+', true, Infinity),
+      generateTestObject('1-2', false, 1),
+      generateTestObject('1-2', true, 2),
+      generateTestObject('ALL', true, Infinity),
+      generateTestObject('ALL', false, Infinity)
+    ];
+    var message = 'cardinality `{0}`. {1} value should be {2}';
+    tests.forEach(function(test) {
+      it(message.format('' + test.cardinality, (test.isMax ? 'maximum' : 'minimum'), test.e), function() {
+        expect(numberUtils.getCardinalityValue(test.cardinality, test.isMax)).to.be.eql(test.e);
+      });
+    })
+  });
 });
 });

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است