You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by ab...@apache.org on 2016/04/28 19:19:15 UTC

ambari git commit: AMBARI-16166 Refactor config validation.(ababiichuk)

Repository: ambari
Updated Branches:
  refs/heads/trunk 1b49c6c97 -> c26e2ff61


AMBARI-16166 Refactor config validation.(ababiichuk)


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

Branch: refs/heads/trunk
Commit: c26e2ff61b2fd02c2c72232de154d62401287847
Parents: 1b49c6c
Author: ababiichuk <ab...@hortonworks.com>
Authored: Thu Apr 28 19:38:41 2016 +0300
Committer: ababiichuk <ab...@hortonworks.com>
Committed: Thu Apr 28 20:18:51 2016 +0300

----------------------------------------------------------------------
 .../hawq/activateStandby/step2_controller.js    |   1 -
 .../hawq/addStandby/step3_controller.js         |   1 -
 .../nameNode/step3_controller.js                |   1 -
 .../resourceManager/step3_controller.js         |   1 -
 .../app/controllers/wizard/step7_controller.js  |   1 -
 .../configs/stack_config_properties_mapper.js   |   1 +
 ambari-web/app/messages.js                      |  14 +-
 .../configs/config_recommendation_parser.js     |   2 -
 .../configs/objects/service_config_property.js  | 262 ++-----------------
 ambari-web/app/utils/config.js                  | 154 ++++++++++-
 ambari-web/app/utils/validator.js               |   2 +-
 .../notification_configs_view.js                |   7 -
 .../configs/widgets/list_config_widget_view.js  |   7 +-
 .../config_recommendation_parser_test.js        |  21 +-
 .../objects/service_config_property_test.js     |  86 ------
 ambari-web/test/models/form_test.js             |  22 --
 ambari-web/test/utils/config_test.js            |  24 ++
 .../notification_configs_view_test.js           |  18 +-
 18 files changed, 224 insertions(+), 401 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js
index a14817a..ff338de 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/hawq/activateStandby/step2_controller.js
@@ -90,7 +90,6 @@ App.ActivateHawqStandbyWizardStep2Controller = Em.Controller.extend({
       var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable'));
-      serviceConfigProperty.validate();
     }, this);
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js
index fd28ad5..3378cd5 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/hawq/addStandby/step3_controller.js
@@ -133,7 +133,6 @@ App.AddHawqStandbyWizardStep3Controller = Em.Controller.extend({
       var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable'));
-      serviceConfigProperty.validate();
     }, this);
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js
index 7305861..1dcf2b7 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/nameNode/step3_controller.js
@@ -228,7 +228,6 @@ App.HighAvailabilityWizardStep3Controller = Em.Controller.extend({
       var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable'));
-      serviceConfigProperty.validate();
     }, this);
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js
index 00ae0c3..36dcfb8 100644
--- a/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js
+++ b/ambari-web/app/controllers/main/admin/highAvailability/resourceManager/step3_controller.js
@@ -170,7 +170,6 @@ App.RMHighAvailabilityWizardStep3Controller = Em.Controller.extend(App.Blueprint
       var serviceConfigProperty = App.ServiceConfigProperty.create(_serviceConfigProperty);
       componentConfig.configs.pushObject(serviceConfigProperty);
       serviceConfigProperty.set('isEditable', serviceConfigProperty.get('isReconfigurable'));
-      serviceConfigProperty.validate();
     }, this);
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/controllers/wizard/step7_controller.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/controllers/wizard/step7_controller.js b/ambari-web/app/controllers/wizard/step7_controller.js
index 8ed465d..c97de10 100644
--- a/ambari-web/app/controllers/wizard/step7_controller.js
+++ b/ambari-web/app/controllers/wizard/step7_controller.js
@@ -748,7 +748,6 @@ App.WizardStep7Controller = Em.Controller.extend(App.ServerValidatorMixin, App.E
       if (!this.get('content.serviceConfigProperties.length') && !serviceConfigProperty.get('hasInitialValue')) {
         App.ConfigInitializer.initialValue(serviceConfigProperty, localDB, dependencies);
       }
-      serviceConfigProperty.validate();
       configsByService[_config.serviceName].pushObject(serviceConfigProperty);
     }, this);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/mappers/configs/stack_config_properties_mapper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mappers/configs/stack_config_properties_mapper.js b/ambari-web/app/mappers/configs/stack_config_properties_mapper.js
index 59f9916..5c34f62 100644
--- a/ambari-web/app/mappers/configs/stack_config_properties_mapper.js
+++ b/ambari-web/app/mappers/configs/stack_config_properties_mapper.js
@@ -144,6 +144,7 @@ App.stackConfigPropertiesMapper = App.QuickDataMapper.create({
           var v = Em.isNone(staticConfigInfo.recommendedValue) ? staticConfigInfo.recommendedValue : staticConfigInfo.value;
           staticConfigInfo.value = staticConfigInfo.recommendedValue = App.config.formatPropertyValue(staticConfigInfo, v);
           staticConfigInfo.isSecureConfig = App.config.getIsSecure(staticConfigInfo.name);
+          staticConfigInfo.description = App.config.getDescription(staticConfigInfo.description, staticConfigInfo.displayType);
           staticConfigInfo.isUserProperty = false;
           App.configsCollection.add(staticConfigInfo);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/messages.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index a1ed7be..e195b7a 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -480,7 +480,6 @@ Em.I18n.translations = {
 
   'users.userName.validationFail': 'Only lowercase letters and numbers are recommended; must start with a letter',
   'host.spacesValidation': 'Cannot contain whitespace',
-  'host.trimspacesValidation': 'Cannot contain leading or trailing whitespace',
 
   'services.hdfs.rebalance.title' : 'HDFS Rebalance',
   'services.ganglia.description':'Ganglia Metrics Collection system',
@@ -994,7 +993,6 @@ Em.I18n.translations = {
   'form.validator.alertGroupName':'Invalid Alert Group Name. Only alphanumerics, hyphens, spaces and underscores are allowed.',
   'form.validator.alertNotificationName':'Invalid Alert Notification Name. Only alphanumerics, hyphens, spaces and underscores are allowed.',
   'form.validator.configKey.specific':'"{0}" is invalid Key. Only alphanumerics, hyphens, underscores, asterisks and periods are allowed.',
-  'form.validator.error.trailingSpaces': 'Cannot contain trailing whitespace',
 
   'alerts.add.header': 'Create Alert Definition',
   'alerts.add.step1.header': 'Choose Type',
@@ -2964,6 +2962,18 @@ Em.I18n.translations = {
   'config.warnMessage.outOfBoundaries.greater': 'Values greater than {0} are not recommended',
   'config.warnMessage.outOfBoundaries.less': 'Values smaller than {0} are not recommended',
 
+  'errorMessage.config.required': 'This is required',
+  'errorMessage.config.number.integer': 'Must contain digits only',
+  'errorMessage.config.number.float': 'Must be a valid number',
+  'errorMessage.config.mail': 'Must be a valid email address',
+  'errorMessage.config.user': 'Value is not valid',
+  'errorMessage.config.password': 'Passwords do not match',
+  'errorMessage.config.directory.heterogeneous': 'dir format is wrong, can be "[{storage type}]/{dir name}"',
+  'errorMessage.config.directory.default': 'Must be a slash or drive at the start, and must not contain white spaces',
+  'errorMessage.config.directory.allowed': 'Can\'t start with "home(s)"',
+  'errorMessage.config.spaces.trailing': 'Cannot contain trailing whitespace',
+  'errorMessage.config.spaces.trim': 'Cannot contain leading or trailing whitespace',
+
   'utils.ajax.errorMessage': 'Error message',
   'utils.ajax.defaultErrorPopupBody.message': 'received on {0} method for API: {1}',
   'utils.ajax.defaultErrorPopupBody.statusCode': '{0} status code',

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/mixins/common/configs/config_recommendation_parser.js b/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
index 82916aa..65cc20f 100644
--- a/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
+++ b/ambari-web/app/mixins/common/configs/config_recommendation_parser.js
@@ -171,8 +171,6 @@ App.ConfigRecommendationParser = Em.Mixin.create(App.ConfigRecommendations, {
       newConfig = App.config.getDefaultConfig(name, fileName, coreObject),
       addedPropertyObject = App.ServiceConfigProperty.create(newConfig);
 
-    addedPropertyObject.validate();
-
     this.applyRecommendation(name, fileName, "Default",
       recommendedValue, null, parentProperties);
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/models/configs/objects/service_config_property.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/models/configs/objects/service_config_property.js b/ambari-web/app/models/configs/objects/service_config_property.js
index 8e6327b..a244dfd 100644
--- a/ambari-web/app/models/configs/objects/service_config_property.js
+++ b/ambari-web/app/models/configs/objects/service_config_property.js
@@ -17,7 +17,6 @@
  */
 
 var App = require('app');
-var validator = require('utils/validator');
 
 /**
  * @class ServiceConfigProperty
@@ -143,8 +142,11 @@ App.ServiceConfigProperty = Em.Object.extend({
   isComparison: false,
   hasCompareDiffs: false,
   showLabel: true,
-  error: false,
-  warn: false,
+
+  error: Em.computed.bool('errorMessage.length'),
+  warn: Em.computed.bool('warnMessage.length'),
+  isValid: Em.computed.equal('errorMessage', ''),
+
   previousValue: null, // cached value before changing config <code>value</code>
 
   /**
@@ -159,17 +161,9 @@ App.ServiceConfigProperty = Em.Object.extend({
    * true if property has warning or error
    * @type {boolean}
    */
-  hasIssues: function () {
-    var originalSCPIssued = (this.get('errorMessage') + this.get('warnMessage')) !== "";
-    var overridesIssue = false;
-    (this.get('overrides') || []).forEach(function(override) {
-      if (override.get('errorMessage') + override.get('warnMessage') !== "") {
-        overridesIssue = true;
-        return;
-      }
-    });
-    return originalSCPIssued || overridesIssue;
-  }.property('errorMessage', 'warnMessage', 'overrides.@each.warnMessage', 'overrides.@each.errorMessage'),
+  hasIssues: Em.computed.or('error', 'warn', 'overridesWithIssues.length'),
+
+  overridesWithIssues: Em.computed.filterBy('overrides', 'hasIssues', true),
 
   index: null, //sequence number in category
   editDone: false, //Text field: on focusOut: true, on focusIn: false
@@ -253,6 +247,13 @@ App.ServiceConfigProperty = Em.Object.extend({
   }.property('isUserProperty', 'isOriginalSCP', 'overrides.length', 'isRequiredByAgent'),
 
   init: function () {
+    this.setInitialValues();
+    this.set('viewClass', App.config.getViewClass(this.get("displayType"), this.get('dependentConfigPattern'), this.get('unit')));
+    this.set('validator', App.config.getValidator(this.get("displayType")));
+    this.validate();
+  },
+
+  setInitialValues: function () {
     if (Em.isNone(this.get('value'))) {
       if (!Em.isNone(this.get('savedValue'))) {
         this.set('value', this.get('savedValue'));
@@ -260,12 +261,11 @@ App.ServiceConfigProperty = Em.Object.extend({
         this.set('value', this.get('recommendedValue'));
       }
     }
-    if(this.get("displayType") === "password") {
+    if (this.get("displayType") === "password") {
       this.set('retypedPassword', this.get('value'));
       this.set('recommendedValue', '');
     }
     this.set('initialValue', this.get('value'));
-    this.updateDescription();
   },
 
   /**
@@ -313,211 +313,19 @@ App.ServiceConfigProperty = Em.Object.extend({
    */
   cantBeUndone: Em.computed.existsIn('displayType', ["componentHost", "componentHosts", "radio button"]),
 
-  isValid: Em.computed.equal('errorMessage', ''),
-
-  viewClass: function () {
-    switch (this.get('displayType')) {
-      case 'checkbox':
-      case 'boolean':
-        if (this.get('dependentConfigPattern')) {
-          return App.ServiceConfigCheckboxWithDependencies;
-        } else {
-          return App.ServiceConfigCheckbox;
-        }
-      case 'password':
-        return App.ServiceConfigPasswordField;
-      case 'combobox':
-        return App.ServiceConfigComboBox;
-      case 'radio button':
-        return App.ServiceConfigRadioButtons;
-        break;
-      case 'directories':
-        return App.ServiceConfigTextArea;
-        break;
-      case 'directory':
-        return App.ServiceConfigTextField;
-        break;
-      case 'content':
-        return App.ServiceConfigTextAreaContent;
-        break;
-      case 'multiLine':
-        return App.ServiceConfigTextArea;
-        break;
-      case 'custom':
-        return App.ServiceConfigBigTextArea;
-      case 'componentHost':
-        return App.ServiceConfigMasterHostView;
-      case 'label':
-        return App.ServiceConfigLabelView;
-      case 'componentHosts':
-        return App.ServiceConfigComponentHostsView;
-      case 'supportTextConnection':
-        return App.checkConnectionView;
-      case 'capacityScheduler':
-        return App.CapacitySceduler;
-      default:
-        if (this.get('unit')) {
-          return App.ServiceConfigTextFieldWithUnit;
-        } else {
-          return App.ServiceConfigTextField;
-        }
-    }
-  }.property('displayType'),
-
   validate: function () {
-    var value = this.get('value');
-    var supportsFinal = this.get('supportsFinal');
-    var isFinal = this.get('isFinal');
-    var valueRange = this.get('valueRange');
-
-    var isError = false;
-    var isWarn = false;
-
-    if (typeof value === 'string' && value.length === 0) {
-      if (this.get('isRequired') && this.get('widgetType') != 'test-db-connection') {
-        this.set('errorMessage', 'This is required');
-        isError = true;
-      } else {
-        return;
-      }
-    }
-
-    if (!isError) {
-      switch (this.get('displayType')) {
-        case 'int':
-          if (('' + value).trim().length === 0) {
-            this.set('errorMessage', '');
-            isError = false;
-            return;
-          }
-          if (validator.isConfigValueLink(value)) {
-            isError = false;
-          } else if (!validator.isValidInt(value)) {
-            this.set('errorMessage', 'Must contain digits only');
-            isError = true;
-          } else {
-            if(valueRange){
-              if(value < valueRange[0] || value > valueRange[1]){
-                this.set('errorMessage', 'Must match the range');
-                isError = true;
-              }
-            }
-          }
-          break;
-        case 'float':
-          if (validator.isConfigValueLink(value)) {
-            isError = false;
-          } else if (!validator.isValidFloat(value)) {
-            this.set('errorMessage', 'Must be a valid number');
-            isError = true;
-          }
-          break;
-        case 'checkbox':
-          break;
-        case 'directories':
-        case 'directory':
-          if (this.get('configSupportHeterogeneous')) {
-            if (!validator.isValidDataNodeDir(value)) {
-              this.set('errorMessage', 'dir format is wrong, can be "[{storage type}]/{dir name}"');
-              isError = true;
-            }
-          } else {
-            if (!validator.isValidDir(value)) {
-              this.set('errorMessage', 'Must be a slash or drive at the start, and must not contain white spaces');
-              isError = true;
-            }
-          }
-          if (!isError) {
-            if (!validator.isAllowedDir(value)) {
-              this.set('errorMessage', 'Can\'t start with "home(s)"');
-              isError = true;
-            } else {
-              // Invalidate values which end with spaces.
-              if (value !== ' ' && validator.isNotTrimmedRight(value)) {
-                this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces'));
-                isError = true;
-              }
-            }
-          }
-          break;
-        case 'custom':
-          break;
-        case 'email':
-          if (!validator.isValidEmail(value)) {
-            this.set('errorMessage', 'Must be a valid email address');
-            isError = true;
-          }
-          break;
-        case 'supportTextConnection':
-        case 'host':
-          var connectionProperties = ['kdc_hosts'];
-          if ((validator.isNotTrimmed(value) && connectionProperties.contains(this.get('name')) || validator.isNotTrimmed(value))) {
-            this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
-            isError = true;
-          }
-          break;
-        case 'password':
-          // retypedPassword is set by the retypePasswordView child view of App.ServiceConfigPasswordField
-          if (value !== this.get('retypedPassword')) {
-            this.set('errorMessage', 'Passwords do not match');
-            isError = true;
-          }
-          break;
-        case 'user':
-        case 'database':
-        case 'db_user':
-          if (!validator.isValidDbName(value)){
-            this.set('errorMessage', 'Value is not valid');
-            isError = true;
-          }
-          break;
-        case 'multiLine':
-        case 'content':
-        default:
-          if(this.get('name')=='javax.jdo.option.ConnectionURL' || this.get('name')=='oozie.service.JPAService.jdbc.url') {
-            if (validator.isConfigValueLink(value)) {
-              isError = false;
-            } else if (validator.isNotTrimmed(value)) {
-              this.set('errorMessage', Em.I18n.t('host.trimspacesValidation'));
-              isError = true;
-            }
-          } else {
-            // Avoid single space values which is work around for validate empty properties.
-            // Invalidate values which end with spaces.
-            if (value !== ' ' && validator.isNotTrimmedRight(value)) {
-              this.set('errorMessage', Em.I18n.t('form.validator.error.trailingSpaces'));
-              isError = true;
-            }
-          }
-          break;
-      }
-    }
-
-    if (!isWarn || isError) { // Errors get priority
-      this.set('warnMessage', '');
-      this.set('warn', false);
+    if (!this.get('isEditable')) {
+      this.set('errorMessage', ''); // do not perform validation for not editable configs
+    } else if ((this.get('value') + '').length === 0) {
+      this.set('errorMessage', this.get('isRequired') ? Em.I18n.t('errorMessage.config.required') : '');
     } else {
-      this.set('warn', true);
+      this.set('errorMessage', this.validator(this.get('value'), this.get('name'), this.get('retypedPassword')));
     }
+  }.observes('value', 'retypedPassword', 'isEditable'),
 
-    if (!isError) {
-      this.set('errorMessage', '');
-      this.set('error', false);
-    } else {
-      this.set('error', true);
-    }
-  }.observes('value', 'isFinal', 'retypedPassword'),
+  viewClass: App.ServiceConfigTextField,
 
-  /**
-   * defines specific directory properties that
-   * allows setting drive type before dir name
-   * ex: [SSD]/usr/local/my_dir
-   * @param config
-   * @returns {*|Boolean|boolean}
-   */
-  configSupportHeterogeneous: function() {
-    return ['directories', 'directory'].contains(this.get('displayType')) && ['dfs.datanode.data.dir'].contains(this.get('name'));
-  }.property('displayType', 'name'),
+  validator: function() { return '' },
 
   /**
    * Get override for selected group
@@ -531,28 +339,6 @@ App.ServiceConfigProperty = Em.Object.extend({
       return this.get('overrides').findProperty('group.name', groupName);
     }
     return null;
-  },
-
-  /**
-   * Update description for `password`-config
-   * Add extra-message about their comparison
-   *
-   * @method updateDescription
-   */
-  updateDescription: function () {
-    var description = this.get('description');
-    var displayType = this.get('displayType');
-    var additionalDescription = Em.I18n.t('services.service.config.password.additionalDescription');
-    if ('password' === displayType) {
-      if (description) {
-        if (!description.contains(additionalDescription)) {
-          description += '<br />' + additionalDescription;
-        }
-      } else {
-        description = additionalDescription;
-      }
-    }
-    this.set('description', description);
   }
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/utils/config.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/config.js b/ambari-web/app/utils/config.js
index 8b12a50..2bc5273 100644
--- a/ambari-web/app/utils/config.js
+++ b/ambari-web/app/utils/config.js
@@ -238,9 +238,7 @@ App.config = Em.Object.create({
       }
 
       if (useEmberObject) {
-        var serviceConfigProperty = App.ServiceConfigProperty.create(serviceConfigObj);
-        serviceConfigProperty.validate();
-        configs.push(serviceConfigProperty);
+        configs.push(App.ServiceConfigProperty.create(serviceConfigObj));
       } else {
         configs.push(serviceConfigObj);
       }
@@ -422,13 +420,7 @@ App.config = Em.Object.create({
    * @returns {string}
    */
   getDefaultCategory: function(stackConfigProperty, fileName) {
-    var tag = this.getConfigTagFromFileName(fileName);
-    switch (tag) {
-      case 'capacity-scheduler':
-        return 'CapacityScheduler';
-      default :
-        return (stackConfigProperty ? 'Advanced ' : 'Custom ') + tag;
-    }
+    return (stackConfigProperty ? 'Advanced ' : 'Custom ') + this.getConfigTagFromFileName(fileName);
   },
 
   /**
@@ -441,6 +433,146 @@ App.config = Em.Object.create({
   },
 
   /**
+   * Returns description, formatted if needed
+   *
+   * @param {String} description
+   * @param {String} displayType
+   * @returns {String}
+   */
+  getDescription: function(description, displayType) {
+    var additionalDescription = Em.I18n.t('services.service.config.password.additionalDescription');
+    if ('password' === displayType) {
+      if (description && !description.contains(additionalDescription)) {
+        return description + '<br />' + additionalDescription;
+      } else {
+        return additionalDescription;
+      }
+    }
+    return description
+  },
+
+  /**
+   * Get view class based on display type of config
+   *
+   * @param displayType
+   * @param dependentConfigPattern
+   * @param unit
+   * @returns {*}
+   */
+  getViewClass: function (displayType, dependentConfigPattern, unit) {
+    switch (displayType) {
+      case 'checkbox':
+      case 'boolean':
+        return dependentConfigPattern ? App.ServiceConfigCheckboxWithDependencies : App.ServiceConfigCheckbox;
+      case 'password':
+        return App.ServiceConfigPasswordField;
+      case 'combobox':
+        return App.ServiceConfigComboBox;
+      case 'radio button':
+        return App.ServiceConfigRadioButtons;
+      case 'directories':
+        return App.ServiceConfigTextArea;
+      case 'directory':
+        return App.ServiceConfigTextField;
+      case 'content':
+        return App.ServiceConfigTextAreaContent;
+      case 'multiLine':
+        return App.ServiceConfigTextArea;
+      case 'custom':
+        return App.ServiceConfigBigTextArea;
+      case 'componentHost':
+        return App.ServiceConfigMasterHostView;
+      case 'label':
+        return App.ServiceConfigLabelView;
+      case 'componentHosts':
+        return App.ServiceConfigComponentHostsView;
+      case 'supportTextConnection':
+        return App.checkConnectionView;
+      case 'capacityScheduler':
+        return App.CapacitySceduler;
+      default:
+        return unit ? App.ServiceConfigTextFieldWithUnit : App.ServiceConfigTextField;
+    }
+  },
+
+  /**
+   * Returns validator function based on config type
+   *
+   * @param displayType
+   * @returns {Function}
+   */
+  getValidator: function (displayType) {
+    switch (displayType) {
+      case 'checkbox':
+      case 'custom':
+        return function () {
+          return ''
+        };
+      case 'int':
+        return function (value) {
+          return !validator.isValidInt(value) && !validator.isConfigValueLink(value)
+            ? Em.I18n.t('errorMessage.config.number.integer') : '';
+        };
+      case 'float':
+        return function (value) {
+          return !validator.isValidFloat(value) && !validator.isConfigValueLink(value)
+            ? Em.I18n.t('errorMessage.config.number.float') : '';
+        };
+      case 'directories':
+      case 'directory':
+        return function (value, name) {
+          if (App.config.isDirHeterogeneous(name)) {
+            if (!validator.isValidDataNodeDir(value)) return Em.I18n.t('errorMessage.config.directory.heterogeneous');
+          } else {
+            if (!validator.isValidDir(value)) return Em.I18n.t('errorMessage.config.directory.default');
+          }
+          if (!validator.isAllowedDir(value)) {
+            return Em.I18n.t('errorMessage.config.directory.allowed');
+          }
+          return validator.isNotTrimmedRight(value) ? Em.I18n.t('errorMessage.config.spaces.trailing') : '';
+        };
+      case 'email':
+        return function (value) {
+          return !validator.isValidEmail(value) ? Em.I18n.t('errorMessage.config.mail') : '';
+        };
+      case 'supportTextConnection':
+      case 'host':
+        return function (value) {
+          return validator.isNotTrimmed(value) ? Em.I18n.t('errorMessage.config.spaces.trim') : '';
+        };
+      case 'password':
+        return function (value, name, retypedPassword) {
+          return value !== retypedPassword ? Em.I18n.t('errorMessage.config.password') : '';
+        };
+      case 'user':
+      case 'database':
+      case 'db_user':
+        return function (value) {
+          return !validator.isValidDbName(value) ? Em.I18n.t('errorMessage.config.user') : '';
+        };
+      default:
+        return function (value, name) {
+          if (['javax.jdo.option.ConnectionURL', 'oozie.service.JPAService.jdbc.url'].contains(name)
+            && !validator.isConfigValueLink(value) && validator.isConfigValueLink(value)) {
+            return Em.I18n.t('errorMessage.config.spaces.trim');
+          } else {
+            return validator.isNotTrimmedRight(value) ? Em.I18n.t('errorMessage.config.spaces.trailing') : '';
+          }
+        };
+    }
+  },
+
+  /**
+   * Defines if config support heterogeneous devices
+   *
+   * @param {string} name
+   * @returns {boolean}
+   */
+  isDirHeterogeneous: function(name) {
+    return ['dfs.datanode.data.dir'].contains(name);
+  },
+
+  /**
    * format property value depending on displayType
    * and one exception for 'kdc_type'
    * @param serviceConfigProperty
@@ -636,6 +768,7 @@ App.config = Em.Object.create({
       'isFinal': isFinal,
       'savedIsFinal': savedIsFinal,
       'recommendedIsFinal': recommendedIsFinal,
+      'category': 'CapacityScheduler',
       'displayName': 'Capacity Scheduler',
       'description': 'Capacity Scheduler properties',
       'displayType': 'capacityScheduler'
@@ -944,7 +1077,6 @@ App.config = Em.Object.create({
     serviceConfigProperty.set('overrideValues', savedOverrides.mapProperty('savedValue'));
     serviceConfigProperty.set('overrideIsFinalValues', savedOverrides.mapProperty('savedIsFinal'));
 
-    newOverride.validate();
     return newOverride;
   },
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/utils/validator.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/validator.js b/ambari-web/app/utils/validator.js
index 896228b..9d11746 100644
--- a/ambari-web/app/utils/validator.js
+++ b/ambari-web/app/utils/validator.js
@@ -145,7 +145,7 @@ module.exports = {
    * @returns {Boolean} - <code>true</code> if ends with spaces
    */
   isNotTrimmedRight: function(value) {
-    return /\s+$/.test(("" + value).split(/\n/).slice(-1)[0]);
+    return value !== ' ' && /\s+$/.test(("" + value).split(/\n/).slice(-1)[0]);
   },
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js b/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js
index 3da387a..9ad8762 100644
--- a/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js
+++ b/ambari-web/app/views/common/configs/custom_category_views/notification_configs_view.js
@@ -122,13 +122,6 @@ App.NotificationsConfigsView = App.ServiceConfigsByCategoryView.extend({
   updateConfig: function (config, flag) {
     config.set('isRequired', flag);
     config.set('isEditable', flag);
-    if (flag) {
-      config.validate();
-    }
-    else {
-      config.set('errorMessage', '');
-      config.propertyDidChange('isValid');
-    }
   }
 
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js b/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js
index 3bcd81e..92e814c 100644
--- a/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js
+++ b/ambari-web/app/views/common/configs/widgets/list_config_widget_view.js
@@ -219,8 +219,11 @@ App.ListConfigWidgetView = App.ConfigWidgetView.extend({
       currentlySelected = this.get('options').filterProperty('isSelected').length,
       selectionDisabled = allowedToSelect <= currentlySelected;
     this.get('options').filterProperty('isSelected', false).setEach('isDisabled', selectionDisabled);
-    this.set('config.errorMessage', currentlySelected < neededToSelect ? 'You should select at least ' + neededToSelect + ' item(s)' : '');
-    this.get('config').validate();
+    if (currentlySelected < neededToSelect) {
+      this.set('config.errorMessage', 'You should select at least ' + neededToSelect + ' item(s)');
+    } else {
+      this.get('config').validate();
+    }
   },
 
   /**

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js b/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js
index 403ab98..ae668c7 100644
--- a/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js
+++ b/ambari-web/test/mixins/common/configs/config_recommendation_parser_test.js
@@ -166,10 +166,8 @@ describe('App.ConfigRecommendationParser', function() {
           it ('adds new property', function() {
             expect(instanceObject._createNewProperty.calledWith('p1', 'file-name', 'serviceName1', 'v1', [])).to.be.true;
 
-            expect(stepConfig.get('configs.0')).to.eql(App.ServiceConfigProperty.create({
-              'name': 'p1',
-              'filename': 'file-name'
-            }));
+            expect(stepConfig.get('configs.0.name')).to.equal('p1');
+            expect(stepConfig.get('configs.0.filename')).to.equal('file-name');
           });
 
         } else {
@@ -276,15 +274,22 @@ describe('App.ConfigRecommendationParser', function() {
     });
     
     it('adds new config', function() {
-      expect(instanceObject._createNewProperty('name', 'fileName', 'recommendedValue', null)).to.eql(App.ServiceConfigProperty.create({
+      var res = {
         'value': 'recommendedValue',
         'recommendedValue': 'recommendedValue',
-        'initialValue': 'initialValue',
         'savedValue': null,
         'name': 'name',
-        'filename': 'fileName'
-      }));
+        'filename': 'fileName',
+        'errorMessage': ''
+      };
+
+      var test = instanceObject._createNewProperty('name', 'fileName', 'recommendedValue', null);
 
+      for (var k in res) {
+        if (res.hasOwnProperty(k)) {
+          expect(test.get(k)).to.eql(res[k]);
+        }
+      }
       expect(instanceObject.applyRecommendation.calledOnce).to.be.true;
     });
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/models/configs/objects/service_config_property_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/configs/objects/service_config_property_test.js b/ambari-web/test/models/configs/objects/service_config_property_test.js
index b8a6f4e..3a1d14c 100644
--- a/ambari-web/test/models/configs/objects/service_config_property_test.js
+++ b/ambari-web/test/models/configs/objects/service_config_property_test.js
@@ -385,96 +385,10 @@ describe('App.ServiceConfigProperty', function () {
     });
   });
 
-  describe('#viewClass', function () {
-    classCases.forEach(function (item) {
-      it ('should be ' + item.viewClass, function () {
-        Em.keys(item.initial).forEach(function (prop) {
-          serviceConfigProperty.set(prop, item.initial[prop]);
-        });
-        expect(serviceConfigProperty.get('viewClass')).to.eql(item.viewClass);
-      });
-    });
-  });
-
-  describe('#validate', function () {
-    it('not required', function () {
-      serviceConfigProperty.setProperties({
-        isRequired: false,
-        value: ''
-      });
-      expect(serviceConfigProperty.get('errorMessage')).to.be.empty;
-      expect(serviceConfigProperty.get('error')).to.be.false;
-    });
-    it('test-db-connection widget', function () {
-      serviceConfigProperty.setProperties({
-        isRequired: true,
-        widgetType: 'test-db-connection',
-        value: ''
-      });
-      expect(serviceConfigProperty.get('errorMessage')).to.be.empty;
-      expect(serviceConfigProperty.get('error')).to.be.false;
-    });
-    it('should validate', function () {
-      serviceConfigProperty.setProperties({
-        isRequired: true,
-        value: 'value'
-      });
-      expect(serviceConfigProperty.get('errorMessage')).to.be.empty;
-      expect(serviceConfigProperty.get('error')).to.be.false;
-    });
-    it('should fail', function () {
-      serviceConfigProperty.setProperties({
-        isRequired: true,
-        value: 'value'
-      });
-      serviceConfigProperty.set('value', '');
-      expect(serviceConfigProperty.get('errorMessage')).to.equal('This is required');
-      expect(serviceConfigProperty.get('error')).to.be.true;
-    });
-  });
-
   describe('#overrideIsFinalValues', function () {
     it('should be defined as empty array', function () {
       expect(serviceConfigProperty.get('overrideIsFinalValues')).to.eql([]);
     });
   });
 
-  describe('#updateDescription', function () {
-
-    beforeEach(function () {
-      serviceConfigProperty.setProperties({
-        displayType: 'password',
-        description: ''
-      });
-    });
-
-    it('should add extra-message to the description for `password`-configs', function () {
-
-      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
-      serviceConfigProperty.updateDescription();
-      expect(serviceConfigProperty.get('description')).to.contain(extraMessage);
-
-    });
-
-    it('should not add extra-message to the description if it already contains it', function () {
-
-      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
-      serviceConfigProperty.updateDescription();
-      serviceConfigProperty.updateDescription();
-      serviceConfigProperty.updateDescription();
-      expect(serviceConfigProperty.get('description')).to.contain(extraMessage);
-      var subd = serviceConfigProperty.get('description').replace(extraMessage, '');
-      expect(subd).to.not.contain(extraMessage);
-    });
-
-    it('should add extra-message to the description if description is not defined', function () {
-
-      serviceConfigProperty.set('description', undefined);
-      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
-      serviceConfigProperty.updateDescription();
-      expect(serviceConfigProperty.get('description')).to.contain(extraMessage);
-    });
-
-  });
-
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/models/form_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/form_test.js b/ambari-web/test/models/form_test.js
index 2d45ada..573deae 100644
--- a/ambari-web/test/models/form_test.js
+++ b/ambari-web/test/models/form_test.js
@@ -185,28 +185,6 @@ describe('App.FormField', function () {
     });
   });
 
-  describe('#viewClass', function () {
-    displayTypeCases.forEach(function (item) {
-      it('should be ' + item.classString, function () {
-        formField.set('displayType', item.type);
-        expect(formField.get('viewClass').toString()).to.contain(item.classString);
-      });
-    });
-  });
-
-  /*eslint-disable mocha-cleanup/asserts-limit */
-  describe('#validate', function () {
-    it('should return error message', function () {
-      formField.set('isRequired', true);
-      expectError('This is required');
-    });
-    it('should return empty error message', function () {
-      formField.set('isRequired', false);
-      expectError('');
-      formField.set('value', 'value');
-      expectError('');
-    });
-  });
   /*eslint-enable mocha-cleanup/asserts-limit */
 
   describe('#isHiddenField', function () {

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/utils/config_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/utils/config_test.js b/ambari-web/test/utils/config_test.js
index fd7c188..97c3cd7 100644
--- a/ambari-web/test/utils/config_test.js
+++ b/ambari-web/test/utils/config_test.js
@@ -891,4 +891,28 @@ describe('App.config', function () {
     });
   });
 
+  describe('#getDescription', function () {
+
+    it('should add extra-message to the description for `password`-configs', function () {
+      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
+      expect(App.config.getDescription('', 'password')).to.contain(extraMessage);
+    });
+
+    it('should not add extra-message to the description if it already contains it', function () {
+
+      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
+      var res = App.config.getDescription(extraMessage, 'password');
+      expect(res).to.contain(extraMessage);
+      expect(res).to.contain(extraMessage);
+      var subd = res.replace(extraMessage, '');
+      expect(subd).to.not.contain(extraMessage);
+    });
+
+    it('should add extra-message to the description if description is not defined', function () {
+
+      var extraMessage = Em.I18n.t('services.service.config.password.additionalDescription');
+      expect(App.config.getDescription(undefined, 'password')).to.contain(extraMessage);
+    });
+
+  });
 });

http://git-wip-us.apache.org/repos/asf/ambari/blob/c26e2ff6/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js b/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js
index 3af85ac..8f03434 100644
--- a/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js
+++ b/ambari-web/test/views/common/configs/custom_category_views/notification_configs_view_test.js
@@ -217,14 +217,7 @@ describe('App.NotificationsConfigsView', function () {
     var config;
 
     beforeEach(function () {
-      config = Em.Object.create({
-        validate: Em.K
-      });
-      sinon.spy(config, 'validate');
-    });
-
-    afterEach(function () {
-      config.validate.restore();
+      config = Em.Object.create();
     });
 
     describe("flag is true", function () {
@@ -238,9 +231,6 @@ describe('App.NotificationsConfigsView', function () {
       it('isEditable is true', function () {
         expect(config.get('isEditable')).to.be.true;
       });
-      it('validate is called once', function () {
-        expect(config.validate.calledOnce).to.be.true;
-      });
     });
 
     describe("flag is false", function () {
@@ -253,12 +243,6 @@ describe('App.NotificationsConfigsView', function () {
       it('isEditable is false', function () {
         expect(config.get('isEditable')).to.be.false;
       });
-      it('errorMessage is empty', function () {
-        expect(config.get('errorMessage')).to.be.empty;
-      });
-      it('validate is not called', function () {
-        expect(config.validate.called).to.be.false;
-      });
     });
   });
 });