浏览代码

AMBARI-10174 Add possibility to save configs from the model. (ababiichuk)

aBabiichuk 10 年之前
父节点
当前提交
f424e9c300

+ 1 - 0
ambari-web/app/assets/test/tests.js

@@ -134,6 +134,7 @@ var files = ['test/init_model_test',
   'test/mappers/configs/config_groups_mapper_test',
   'test/mappers/configs/config_versions_mapper_test',
   'test/mappers/configs/themes_mapper_test',
+  'test/mixins/common/configs/enhanced_configs_test',
   'test/mixins/common/chart/storm_linear_time_test',
   'test/mixins/common/localStorage_test',
   'test/mixins/common/serverValidator_test',

+ 59 - 114
ambari-web/app/controllers/main/service/info/configs.js

@@ -21,7 +21,7 @@ require('controllers/wizard/slave_component_groups_controller');
 var batchUtils = require('utils/batch_scheduled_requests');
 var lazyLoading = require('utils/lazy_loading');
 
-App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorMixin, {
+App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorMixin, App.EnhancedConfigsMixin, {
   name: 'mainServiceInfoConfigsController',
   isHostsConfigsPage: false,
   forceTransition: false,
@@ -1241,63 +1241,6 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM
     return dirChanged;
   },
 
-  /**
-   *
-   * @param modifiedProperties
-   * @param configs
-   * @returns {Array}
-   */
-  generateModifiedConfigs: function(modifiedProperties, configs) {
-
-    var modifiedConfigs = modifiedProperties
-      // get file names and add file names that was modified, for example after property removing
-      .mapProperty('filename').concat(this.get('modifiedFileNames')).uniq()
-      // get configs by filename
-      .map(function(fileName) {
-        return configs.filterProperty('filename', fileName);
-      });
-
-    /**
-     * make same operations to find what configs need to be saved
-     * for configs from model
-     * @type {Ember.Enumerable}
-     */
-    var modifiedFileNames = App.ConfigProperty.find().filter(function(cp) {
-      return cp.get('isNotDefaultValue') && this.get('dependentFileNames').contains(cp.get('fileName'));
-    }, this).mapProperty('fileName').uniq();
-
-    /**
-     * create default ServiceConfigProperty objects from model
-     * and pushing properties that need to be saved to modified configs
-     * @type {Array}
-     */
-    App.ConfigProperty.find().filter(function(cp) {
-      return modifiedFileNames.contains(cp.get('fileName'));
-    }).forEach(function(configFromModel) {
-      var configData = {
-        name: configFromModel.get('name'),
-        displayName: configFromModel.get('stackConfigProperty.displayName'),
-        serviceName: configFromModel.get('stackConfigProperty.serviceName'),
-        value: configFromModel.get('value'),
-        defaultValue: configFromModel.get('defaultValue'),
-        filename: App.config.getOriginalFileName(configFromModel.get('fileName')),
-        isFinal: configFromModel.get('isFinal')
-      };
-      if (configFromModel.get('stackConfigProperty.serviceName') === this.get('content.serviceName')) {
-        var confObject = modifiedConfigs.findProperty('name', configFromModel.get('name'));
-        if (confObject && configFromModel.get('allowSave')) {
-          confObject.set('value', configFromModel.get('value'));
-        } else {
-          modifiedConfigs.push(App.ServiceConfigProperty.create(configData));
-        }
-      } else {
-        modifiedConfigs.push(App.ServiceConfigProperty.create(configData));
-      }
-    }, this);
-
-    return modifiedConfigs;
-  },
-
   /**
    * Save changed configs and config groups
    */
@@ -1311,6 +1254,14 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM
         configs = App.config.textareaIntoFileConfigs(configs, 'capacity-scheduler.xml');
       }
 
+      if (App.get('supports.enhancedConfigs')) {
+        if (this.get('content.serviceName') === 'HIVE') {
+          this.setHiveHostName(configs);
+        } else if (this.get('content.serviceName') === 'OOZIE') {
+          this.setOozieHostName(configs);
+        }
+      }
+
       /**
        * generates list of properties that was chabged
        * @type {Array}
@@ -1319,51 +1270,36 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM
         // get only modified and created configs
         .filter(function(config) { return config.get('isNotDefaultValue') || config.get('isNotSaved'); });
 
-      /**
-       * check if some of properties that was changed has dependencies;
-       */
-      modifiedProperties.forEach(function(p) {
-        /**
-         * step configs don't have <code>propertyDependedBy<code> property
-         * so we need to look in <code>stackConfigProperty<code>;
-         * use <code>App.ConfigProperty<code> that has link to <code>stackConfigProperty<code>
-         */
-        var cfgFromModel = App.ConfigProperty.find().find(function(cp) {
-          return cp.get('name') === p.get('name') && cp.get('fileName') === p.get('filename');
-        }, this);
+      if (App.get('supports.enhancedConfigs')) {
+        this.loadStepConfigsToModel(modifiedProperties, self.get('selectedVersion'));
+      }
+      this.getRecommendationsForDependencies(this.get('changedConfigWithDependencies')).done(function() {
 
-        if (cfgFromModel) {
+        if (App.get('supports.enhancedConfigs')) {
+          self.saveEnhancedConfigs();
+        } else {
 
-          cfgFromModel.set('defaultValue', p.get('value'));
-          /**
-           * generates <code>changedConfigWithDependencies<code>
-           * this array will be send for recommendations as <code>changed_configurations<code>
-           */
-          if (cfgFromModel.get('stackConfigProperty.propertyDependedBy.length') > 0) {
-            this.get('changedConfigWithDependencies').push({
-              "type": cfgFromModel.get('fileName'),
-              "name": cfgFromModel.get("name")
+          var modifiedConfigs = modifiedProperties
+            // get file names and add file names that was modified, for example after property removing
+            .mapProperty('filename').concat(this.get('modifiedFileNames')).uniq()
+            // get configs by filename
+            .map(function(fileName) {
+              return configs.filterProperty('filename', fileName);
             });
-          }
-        }
-      }, this);
 
-      this.getRecommendationsForDependencies(this.get('changedConfigWithDependencies')).done(function() {
-
-        var modifiedConfigs = self.generateModifiedConfigs(modifiedProperties, configs);
+          if (!!modifiedConfigs.length) {
+            // concatenate results
+            modifiedConfigs = modifiedConfigs.reduce(function(current, prev) { return current.concat(prev); });
+          }
+          // save modified original configs that have no group
+          self.saveSiteConfigs(modifiedConfigs.filter(function(config) { return !config.get('group'); }));
 
-        if (!!modifiedConfigs.length) {
-          // concatenate results
-          modifiedConfigs = modifiedConfigs.reduce(function(current, prev) { return current.concat(prev); });
+          /**
+           * First we put cluster configurations, which automatically creates /configurations
+           * resources. Next we update host level overrides.
+           */
+          self.doPUTClusterConfigurations();
         }
-        // save modified original configs that have no group
-        self.saveSiteConfigs(modifiedConfigs.filter(function(config) { return !config.get('group'); }));
-
-        /**
-         * First we put cluster configurations, which automatically creates /configurations
-         * resources. Next we update host level overrides.
-         */
-        self.doPUTClusterConfigurations();
       });
 
     } else {
@@ -1375,23 +1311,32 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM
       // find custom original properties that assigned to selected config group
       overridenConfigs = overridenConfigs.concat(configs.filterProperty('group')
         .filter(function(config) { return config.get('group.name') == self.get('selectedConfigGroup.name'); }));
-      this.formatConfigValues(overridenConfigs);
-      selectedConfigGroup.get('hosts').forEach(function (hostName) {
-        groupHosts.push({"host_name": hostName});
-      });
 
-      this.putConfigGroupChanges({
-        ConfigGroup: {
-          "id": selectedConfigGroup.get('id'),
-          "cluster_name": App.get('clusterName'),
-          "group_name": selectedConfigGroup.get('name'),
-          "tag": selectedConfigGroup.get('service.id'),
-          "description": selectedConfigGroup.get('description'),
-          "hosts": groupHosts,
-          "service_config_version_note": this.get('serviceConfigVersionNote'),
-          "desired_configs": this.buildGroupDesiredConfigs(overridenConfigs)
-        }
-      }, true);
+      if (App.get('supports.enhancedConfigs')) {
+
+        this.loadStepConfigsToModel(overridenConfigs, this.get('selectedVersion'));
+
+        this.saveEnhancedConfigsAndGroup(this.get('selectedConfigGroup'));
+
+      } else {
+        this.formatConfigValues(overridenConfigs);
+        selectedConfigGroup.get('hosts').forEach(function (hostName) {
+          groupHosts.push({"host_name": hostName});
+        });
+
+        this.putConfigGroupChanges({
+          ConfigGroup: {
+            "id": selectedConfigGroup.get('id'),
+            "cluster_name": App.get('clusterName'),
+            "group_name": selectedConfigGroup.get('name'),
+            "tag": selectedConfigGroup.get('service.id'),
+            "description": selectedConfigGroup.get('description'),
+            "hosts": groupHosts,
+            "service_config_version_note": this.get('serviceConfigVersionNote'),
+            "desired_configs": this.buildGroupDesiredConfigs(overridenConfigs)
+          }
+        }, true);
+      }
     }
   },
 
@@ -2167,7 +2112,7 @@ App.MainServiceInfoConfigsController = Em.Controller.extend(App.ServerValidatorM
    *   }
    * }
    * @returns {boolean}
-   * @method isConfigChanged
+   * @method isAttributesChanged
    */
   isAttributesChanged: function (oldAttributes, newAttributes) {
     oldAttributes = oldAttributes.final || {};

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

@@ -338,6 +338,12 @@ Em.I18n.translations = {
   'popup.invalid.KDC.admin.principal': 'Admin principal',
   'popup.invalid.KDC.admin.password': 'Admin password',
 
+  'popup.dependent.configs.header': 'Dependent Properties',
+  'popup.dependent.configs.title': 'Properties that was changed has dependent properties. It\'s recommended to update these properties!',
+  'popup.dependent.configs.table.saveProperty': 'Save property',
+  'popup.dependent.configs.table.currentValue': 'Current value',
+  'popup.dependent.configs.table.recommendedValue': 'Recommended value',
+
   'login.header':'Sign in',
   'login.username':'Username',
   'login.loginButton':'Sign in',

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

@@ -35,3 +35,4 @@ require('mixins/wizard/wizardEnableDone');
 require('mixins/wizard/selectHost');
 require('mixins/wizard/addSecurityConfigs');
 require('mixins/wizard/wizard_menu_view');
+require('mixins/common/configs/enhanced_configs');

+ 292 - 0
ambari-web/app/mixins/common/configs/enhanced_configs.js

@@ -0,0 +1,292 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.EnhancedConfigsMixin = Em.Mixin.create({
+
+  modifiedFileNames: [],
+
+  /**
+   * merge step configs from model
+   * for default config group properties should be list
+   * of changed properties
+   * @param properties
+   * @param currentVersionNumber
+   */
+  loadConfigsToModel: function(properties, currentVersionNumber) {
+    var serviceName = this.get('content.serviceName');
+    if (properties && properties.length) {
+      properties.forEach(function(p) {
+        var configFromModel = App.ConfigProperty.find(p.get('name') + '_' + App.config.getConfigTagFromFileName(p.get('filename')) + '_' + currentVersionNumber);
+        if (configFromModel && configFromModel.get('name')) {
+          configFromModel.setProperties({
+            'value': p.get('value'),
+            'isFinal': p.get('isFinal'),
+            'defaultValue': p.get('defaultValue'),
+            'defaultIsFinal': p.get('defaultIsFinal'),
+            'isRequiredByAgent': p.get('isRequiredByAgent'),
+            'isNotSaved': p.get('isNotSaved')
+          });
+        } else {
+          App.store.load(App.ConfigProperty, {
+            id: p.get('name') + '_' + App.config.getConfigTagFromFileName(p.get('filename')) + '_' + currentVersionNumber,
+            name: p.get('name'),
+            file_name: p.get('filename'),
+            value: p.get('value'),
+            is_final: p.get('isFinal'),
+            default_value: p.get('defaultValue'),
+            default_is_final: p.get('defaultIsFinal'),
+            is_required_by_agent: p.get('isRequiredByAgent'),
+            is_not_saved: p.get('isNotSaved'),
+            is_required: false,
+            config_version_id: serviceName + '_' + currentVersionNumber
+          })
+        }
+      });
+    }
+  },
+
+  /**
+   *
+   * @param modifiedProperties
+   * @param versionNumber
+   */
+  loadStepConfigsToModel: function(modifiedProperties, versionNumber) {
+
+    this.loadConfigsToModel(modifiedProperties, versionNumber);
+
+    this.generateChangedConfigWithDependencies();
+
+  },
+
+  /**
+   * generates <code>changedConfigWithDependencies<code>
+   * this array will be send for recommendations as <code>changed_configurations<code>
+   * @method generateChangedConfigWithDependencies
+   */
+  generateChangedConfigWithDependencies: function() {
+    App.ConfigProperty.find().forEach(function(cp) {
+      if (cp.get('isNotDefaultValue') && cp.get('stackConfigProperty.propertyDependedBy.length') > 0) {
+        this.get('changedConfigWithDependencies').push({
+          "type": cp.get('fileName'),
+          "name": cp.get("name")
+        });
+      }
+    }, this);
+  },
+
+
+  /**
+   * generates data and save configs for default group
+   * @method saveEnhancedConfigs
+   */
+  saveEnhancedConfigs: function() {
+
+    var fileNamesToSave = this.getFileNamesToSave(this.get('modifiedFileNames'));
+
+    var configsToSave = this.getConfigsToSave(fileNamesToSave);
+
+    var desired_configs = this.generateDesiredConfigsJSON(configsToSave, fileNamesToSave, this.get('serviceConfigNote'));
+
+    this.doPUTClusterConfigurationSites(desired_configs);
+  },
+
+  /**
+   * generates data and save configs for not default group
+   * @param selectedConfigGroup
+   * @method saveEnhancedConfigsAndGroup
+   */
+  saveEnhancedConfigsAndGroup: function(selectedConfigGroup) {
+    //TODO update for dependent configs
+    var serviceConfigVersion = App.ConfigVersion.find().findProperty('groupName', selectedConfigGroup.get('name'));
+
+    var overridenConfigs = App.ConfigProperty.find().filter(function(cp) {
+      return cp.get('configVersion.groupId') === selectedConfigGroup.get('id') || cp.get('isNotDefaultValue');
+    });
+
+    var hostNames = serviceConfigVersion.get('hosts').map(function(hostName) {
+      return  {
+        "host_name": hostName
+      }
+    });
+
+    var fileNamesToSave = overridenConfigs.mapProperty('fileName').uniq();
+
+    this.putConfigGroupChanges({
+      ConfigGroup: {
+        "id": selectedConfigGroup.get('id'),
+        "cluster_name": App.get('clusterName'),
+        "group_name": selectedConfigGroup.get('name'),
+        "tag": selectedConfigGroup.get('service.id'),
+        "description": selectedConfigGroup.get('description'),
+        "hosts": hostNames,
+        "service_config_version_note": this.get('serviceConfigNote'),
+        "desired_configs": this.generateDesiredConfigsJSON(overridenConfigs, fileNamesToSave, null, true)
+      }
+    }, true);
+  },
+
+  /**
+   * get file names that need t obe saved
+   * @param {Array} modifiedFileNames
+   * @returns {Ember.Enumerable}
+   */
+  getFileNamesToSave: function(modifiedFileNames) {
+    return App.ConfigProperty.find().filter(function(cp) {
+      return cp.get('isNotDefaultValue') || cp.get('isNotSaved');
+    }, this).mapProperty('fileName').concat(modifiedFileNames).uniq();
+  },
+
+  /**
+   * get configs that need to be saved, for default group
+   * @param fileNamesToSave
+   * @returns {App.ConfigProperty[]}
+   */
+  getConfigsToSave: function(fileNamesToSave) {
+    if (Em.isArray(fileNamesToSave) && fileNamesToSave.length) {
+      return App.ConfigProperty.find().filter(function(cp) {
+        return fileNamesToSave.contains(cp.get('fileName'));
+      });
+    } else {
+      return Em.A([]);
+    }
+  },
+
+  /**
+   * generating common JSON object for desired configs
+   * @param configsToSave
+   * @param fileNamesToSave
+   * @param serviceConfigNote
+   * @param {boolean} [isNotDefaultGroup=false]
+   * @returns {Array}
+   */
+  generateDesiredConfigsJSON: function(configsToSave, fileNamesToSave, serviceConfigNote, isNotDefaultGroup) {
+    var desired_config = [];
+    if (Em.isArray(configsToSave) && Em.isArray(fileNamesToSave) && fileNamesToSave.length && configsToSave.length) {
+      serviceConfigNote = serviceConfigNote || "";
+      var tagVersion = "version" + (new Date).getTime();
+
+      fileNamesToSave.forEach(function(fName) {
+        if (this.allowSaveSite(fName)) {
+          var properties = configsToSave.filterProperty('fileName', fName);
+          var type = App.config.getConfigTagFromFileName(fName);
+          desired_config.push(this.createDesiredConfig(type, tagVersion, properties, serviceConfigNote, isNotDefaultGroup));
+        }
+      }, this);
+    }
+    return desired_config;
+  },
+
+  /**
+   * for some file names we have a restriction
+   * and can't save them, in this this method will return false
+   * @param fName
+   * @returns {boolean}
+   */
+  allowSaveSite: function(fName) {
+    switch (fName) {
+      case 'mapred-queue-acls.xml':
+        return false;
+      case 'core-site.xml':
+        return ['HDFS', 'GLUSTERFS'].contains(this.get('content.serviceName'));
+      default :
+        return true;
+    }
+  },
+
+  /**
+   * generating common JSON object for desired config
+   * @param {string} type - file name without '.xml'
+   * @param {string} tagVersion - version + timestamp
+   * @param {App.ConfigProperty[]} properties - array of properties from model
+   * @param {string} serviceConfigNote
+   * @param {boolean} [isNotDefaultGroup=false]
+   * @returns {{type: string, tag: string, properties: {}, properties_attributes: {}|undefined, service_config_version_note: string|undefined}}
+   */
+  createDesiredConfig: function(type, tagVersion, properties, serviceConfigNote, isNotDefaultGroup) {
+    Em.assert('type and tagVersion should be defined', type && tagVersion);
+    var desired_config = {
+      "type": type,
+      "tag": tagVersion,
+      "properties": {}
+    };
+    if (!isNotDefaultGroup) {
+      desired_config.service_config_version_note = serviceConfigNote || "";
+    }
+    var attributes = { final: {} };
+    if (Em.isArray(properties)) {
+      properties.forEach(function(property) {
+
+        if (property.get('isRequiredByAgent')) {
+          desired_config.properties[property.get('name')] = this.formatValueBeforeSave(property);
+          /**
+           * add is final value
+           */
+          if (property.get('isFinal')) {
+            attributes.final[property.get('name')] = "true";
+          }
+        }
+      }, this);
+    }
+
+    if (Object.keys(attributes.final).length) {
+      desired_config.properties_attributes = attributes;
+    }
+    return desired_config;
+  },
+
+  /**
+   * format value before save performs some changing of values
+   * according to the rules that includes heapsizeException trimming and some custom rules
+   * @param {App.ConfigProperty} property
+   * @returns {string}
+   */
+  formatValueBeforeSave: function(property) {
+    var name = property.get('name');
+    var value = property.get('value');
+    //TODO check for core-site
+    if (this.get('heapsizeRegExp').test(name) && !this.get('heapsizeException').contains(name) && !(value).endsWith("m")) {
+      return value += "m";
+    }
+    if (typeof property.get('value') === "boolean") {
+      return property.get('value').toString();
+    }
+    switch (name) {
+      case 'storm.zookeeper.servers':
+        if (Object.prototype.toString.call(value) === '[object Array]' ) {
+          return JSON.stringify(value).replace(/"/g, "'");
+        } else {
+          return value;
+        }
+        break;
+      default:
+        return App.config.trimProperty(property, true);
+    }
+  },
+
+  /**
+   * overriden in controller
+   */
+  doPUTClusterConfigurationSites: Em.K,
+
+  /**
+   * overriden in controller
+   */
+  putConfigGroupChanges: Em.K
+});

+ 14 - 3
ambari-web/app/mixins/common/serverValidator.js

@@ -150,10 +150,12 @@ App.ServerValidatorMixin = Em.Mixin.create({
         services: this.get('serviceNames'),
         recommendations: recommendations
       };
+      /** TODO uncomment when be will be ready
       if (App.get('supports.enhancedConfigs')) {
         dataToSend.recommend = 'configuration-dependencies';
         dataToSend.changed_configurations = changedConfigs;
       }
+       **/
       App.ajax.send({
         name: 'config.recommendations',
         sender: this,
@@ -185,12 +187,21 @@ App.ServerValidatorMixin = Em.Mixin.create({
       for (var propertyName in configs[key].properties) {
         var property = currentProperties.findProperty('name', propertyName)
         if (property) {
-          property.set('value', configs[key].properties[propertyName]);
+          property.set('recommendedValue', configs[key].properties[propertyName]);
         }
       }
     }
-    var configsToShow = currentProperties.filterProperty('isNotDefaultValue');
-    App.showDependentConfigsPopup(configsToShow, params.dfd);
+
+    var configsToShow = currentProperties.filter(function(p) {
+      return p.get('recommendedValue') && p.get('recommendedValue') !== p.get('value');
+    });
+
+    if (configsToShow.length > 0) {
+      App.showDependentConfigsPopup(configsToShow, params.dfd);
+    } else {
+      params.dfd.resolve();
+    }
+
   },
 
   /**

+ 28 - 1
ambari-web/app/models/configs/config_property.js

@@ -49,6 +49,13 @@ App.ConfigProperty = DS.Model.extend({
    */
   defaultValue: DS.attr('string'),
 
+  /**
+   * recommended value of property
+   * that is returned from server
+   * @property {string}
+   */
+  recommendedValue: DS.attr('string'),
+
   /**
    * defines if property is final
    * @property {boolean}
@@ -113,6 +120,19 @@ App.ConfigProperty = DS.Model.extend({
    */
   isSecureConfig: DS.attr('boolean', {defaultValue: false}),
 
+  /**
+   * if false - don't save property
+   * @property {boolean}
+   */
+  isRequiredByAgent: DS.attr('boolean', {defaultValue: true}),
+
+  /**
+   * if true - property is not saved
+   * used for properties added by user
+   * @property {boolean}
+   */
+  isNotSaved: DS.attr('boolean', {defaultValue: false}),
+
   /**
    * if true - don't show property
    * @property {boolean}
@@ -123,7 +143,7 @@ App.ConfigProperty = DS.Model.extend({
    * properties with this flag set to false will not be saved
    * @property {boolean}
    */
-  allowSave: DS.attr('boolean', {defaultValue: true}),
+  saveRecommended: DS.attr('boolean', {defaultValue: true}),
 
   /**
    * Don't show "Undo" for hosts on Installer Step7
@@ -190,6 +210,13 @@ App.ConfigProperty = DS.Model.extend({
     return this.get('configVersion.isForCompare');
   }.property('configVersion.isForCompare'),
 
+  /**
+   * if this property can be final
+   * @property {boolean}
+   */
+  supportsFinal: function () {
+    return this.get('stackConfigProperty.supportsFinal') || this.get('isUserProperty');
+  }.property('stackConfigProperty.supportsFinal', 'isUserProperty'),
   /**
    * Indicates when value is not the default value.
    * Returns false when there is no default value.

+ 2 - 2
ambari-web/app/templates/common/modal_popups/dependent_configs_list.hbs

@@ -34,12 +34,12 @@
     <tbody>
     {{#each config in view.parentView.configs}}
       <tr>
-        <td>{{view Em.Checkbox checkedBinding="config.allowSave"}}</td>
+        <td>{{view Em.Checkbox checkedBinding="config.saveRecommended"}}</td>
         <td>{{config.name}}</td>
         <td>{{config.fileName}}</td>
         <td>{{config.stackConfigProperty.serviceName}}</td>
-        <td>{{config.defaultValue}}</td>
         <td>{{config.value}}</td>
+        <td>{{config.recommendedValue}}</td>
       </tr>
     {{/each}}
     </tbody>

+ 1 - 1
ambari-web/app/utils/ajax/ajax.js

@@ -662,7 +662,7 @@ var urls = {
   },
 
   'configs.config_versions.load.current_versions': {
-    'real': '/clusters/{clusterName}/configurations/service_config_versions?service_name.in({serviceNames})&group_id=-1&is_current=true&fields=*',
+    'real': '/clusters/{clusterName}/configurations/service_config_versions?service_name.in({serviceNames})&is_current=true&fields=*',
     'mock': '/data/configurations/config_versions.json'
   },
 

+ 11 - 0
ambari-web/app/views/common/configs/overriddenProperty_view.js

@@ -76,6 +76,17 @@ App.ServiceConfigView.SCPOverriddenRowsView = Ember.View.extend({
       var group = controller.get('selectedService.configGroups').findProperty('name', controller.get('selectedConfigGroup.name'));
       group.get('properties').removeObject(scpToBeRemoved);
     }
+    if (App.get('supports.enhancedConfigs')) {
+      var deletedConfig = App.ConfigProperty.find().find(function(cp) {
+        return cp.get('name') === scpToBeRemoved.get('name')
+          && cp.get('fileName') === scpToBeRemoved.get('filename')
+          && cp.get('configVersion.groupName') === this.get('controller.selectedConfigGroup.name');
+      }, this);
+      if (deletedConfig) {
+        deletedConfig.deleteRecord();
+        App.store.commit();
+      }
+    }
     overrides = overrides.without(scpToBeRemoved);
     this.set('serviceConfigProperty.overrides', overrides);
     Em.$('body>.tooltip').remove(); //some tooltips get frozen when their owner's DOM element is removed

+ 9 - 0
ambari-web/app/views/common/configs/service_configs_by_category_view.js

@@ -579,6 +579,15 @@ App.ServiceConfigsByCategoryView = Em.View.extend(App.UserPref, {
   removeProperty: function (event) {
     var serviceConfigProperty = event.contexts[0];
     this.get('serviceConfigs').removeObject(serviceConfigProperty);
+    if (App.get('supports.enhancedConfigs')) {
+      var deletedConfig = App.ConfigProperty.find().find(function(cp) {
+        return cp.get('name') === serviceConfigProperty.get('name')
+          && cp.get('fileName') === serviceConfigProperty.get('filename')
+          && cp.get('isDefault');
+      });
+      deletedConfig.deleteRecord();
+      App.store.commit();
+    }
     // push config's file name if this config was stored on server
     if (!serviceConfigProperty.get('isNotSaved')) {
       this.get('controller').get('modifiedFileNames').push(serviceConfigProperty.get('filename'));

+ 3 - 3
ambari-web/app/views/common/modal_popups/dependent_configs_list_popup.js

@@ -42,8 +42,8 @@ App.showDependentConfigsPopup = function (configs, dfd) {
     }),
     onPrimary: function () {
       this.hide();
-      configs.filterProperty('allowSave', false).forEach(function(c) {
-        c.set('value', c.get('defaultValue'));
+      configs.filterProperty('saveRecommended', true).forEach(function(c) {
+        c.set('value', c.get('recommendedValue'));
       });
       dfd.resolve();
     },
@@ -59,7 +59,7 @@ App.showDependentConfigsPopup = function (configs, dfd) {
       dfd.reject();
       this.hide();
     },
-    onClose:  function () {
+    onClose: function () {
       this.onSecondary();
     }
   });

+ 143 - 0
ambari-web/test/mixins/common/configs/enhanced_configs_test.js

@@ -0,0 +1,143 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+describe('App.EnhancedConfigsMixin', function() {
+
+  var mixinObject =  Em.Controller.extend(App.EnhancedConfigsMixin, {});
+  var instanceObject = mixinObject.create({});
+  describe('#getFileNamesToSave()', function() {
+
+    beforeEach(function() {
+      App.resetDsStoreTypeMap(App.ConfigProperty);
+    });
+
+    it('returns file names that was changed', function() {
+      App.ConfigProperty.createRecord({id: 'p1_c1', value:'1', defaultValue: '2', fileName: 'file1'});
+      App.ConfigProperty.createRecord({id: 'p2_c1', value:'1', defaultValue: '1', fileName: 'file2'});
+      expect(instanceObject.getFileNamesToSave(['file3'])).to.eql(['file1','file3'])
+    });
+
+    it('returns file names that was changed by adding property', function() {
+      App.ConfigProperty.createRecord({id: 'p1_c1', value:'1', defaultValue: '1', fileName: 'file1', isNotSaved: false});
+      App.ConfigProperty.createRecord({id: 'p2_c1', value:'1', defaultValue: '1', fileName: 'file2', isNotSaved: true});
+      expect(instanceObject.getFileNamesToSave(['file3'])).to.eql(['file2','file3'])
+    });
+  });
+
+  describe('#allowSaveSite()', function() {
+
+    beforeEach(function() {
+      instanceObject.set('content', {});
+    });
+
+    it('returns true by default', function() {
+      expect(instanceObject.allowSaveSite('some-site')).to.be.true
+    });
+
+    it('returns false for mapred-queue-acls.xml', function() {
+      expect(instanceObject.allowSaveSite('mapred-queue-acls.xml')).to.be.false
+    });
+
+    it('returns false for core-site but not proper service', function() {
+      instanceObject.set('content.serviceName', 'ANY');
+      expect(instanceObject.allowSaveSite('core-site.xml')).to.be.false
+    });
+
+    it('returns true for core-site and proper service', function() {
+      instanceObject.set('content.serviceName', 'HDFS');
+      expect(instanceObject.allowSaveSite('core-site.xml')).to.be.true
+    });
+  });
+
+  describe('#createDesiredConfig()', function() {
+    beforeEach(function() {
+      sinon.stub(instanceObject, 'formatValueBeforeSave', function(property) {
+        return property.get('value');
+      })
+    });
+    afterEach(function() {
+      instanceObject.formatValueBeforeSave.restore();
+    });
+
+    it('generates config wil throw error', function() {
+      expect(instanceObject.createDesiredConfig.bind(instanceObject)).to.throw(Error, 'assertion failed');
+    });
+
+    it('generates config without properties', function() {
+      expect(instanceObject.createDesiredConfig('type1', 'version1')).to.eql({
+        "type": 'type1',
+        "tag": 'version1',
+        "properties": {},
+        "service_config_version_note": ""
+      })
+    });
+
+    it('generates config with properties', function() {
+      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: true})], "note")).to.eql({
+        "type": 'type1',
+        "tag": 'version1',
+        "properties": {
+          "p1": 'v1',
+          "p2": 'v2'
+        },
+        "service_config_version_note": 'note'
+      })
+    });
+
+    it('generates config with properties and skip isRequiredByAgent', function() {
+      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: false})], "note")).to.eql({
+        "type": 'type1',
+        "tag": 'version1',
+        "properties": {
+          p1: 'v1'
+        },
+        "service_config_version_note": 'note'
+      })
+    });
+
+    it('generates config with properties and skip service_config_version_note', function() {
+      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isRequiredByAgent: true})], "note", true)).to.eql({
+        "type": 'type1',
+        "tag": 'version1',
+        "properties": {
+          p1: 'v1'
+        }
+      })
+    });
+
+    it('generates config with final', function() {
+      expect(instanceObject.createDesiredConfig('type1', 'version1', [Em.Object.create({name: 'p1', value: 'v1', isFinal: true, isRequiredByAgent: true}), Em.Object.create({name: 'p2', value: 'v2', isRequiredByAgent: true})], "note")).to.eql({
+        "type": 'type1',
+        "tag": 'version1',
+        "properties": {
+          p1: 'v1',
+          p2: 'v2'
+        },
+        "properties_attributes": {
+          final: {
+            'p1': "true"
+          }
+        },
+        "service_config_version_note": 'note'
+      })
+    })
+  })
+});
+

+ 2 - 2
ambari-web/test/mixins/common/serverValidator_test.js

@@ -61,7 +61,7 @@ describe('App.ServerValidatorMixin', function() {
           { prop: 'configValidationWarning', value: true },
           { prop: 'configValidationGlobalMessage.length', value: 1 },
           { prop: 'configValidationGlobalMessage[0].serviceName', value: 'Some Service' },
-          { prop: 'configValidationGlobalMessage[0].propertyName', value: 'prop2' },
+          { prop: 'configValidationGlobalMessage[0].propertyName', value: 'prop2' }
         ],
         message: 'validation failed on absent property from step configs. global message should be showed.'
       },
@@ -74,7 +74,7 @@ describe('App.ServerValidatorMixin', function() {
         ]),
         resources: [
           genResponse([
-            ['prop1', 'some-site', 'WARN', 'Some warn'],
+            ['prop1', 'some-site', 'WARN', 'Some warn']
           ])
         ],
         expected: [