瀏覽代碼

AMBARI-10060 Enhanced Configs: Create mapper for configGroup, and StackConfigProperty. (ababiichuk)

aBabiichuk 10 年之前
父節點
當前提交
39157df9d9

+ 66 - 0
ambari-web/app/assets/data/configurations/config_groups.json

@@ -0,0 +1,66 @@
+{
+  "href" : "http://c6401:8080/api/v1/clusters/1/config_groups?fields=*",
+  "items" : [
+    {
+      "href" : "http://c6401:8080/api/v1/clusters/1/config_groups/2",
+      "ConfigGroup" : {
+        "cluster_name" : "1",
+        "description" : "1",
+        "desired_configs" : [
+          {
+            "tag" : "version1426088081862",
+            "type" : "hadoop-env",
+            "href" : "http://c6401:8080/api/v1/clusters/1/configurations?type=hadoop-env&tag=version1426088081862"
+          }
+        ],
+        "group_name" : "1",
+        "hosts" : [
+          {
+            "host_name" : "c6401.ambari.apache.org",
+            "href" : "http://c6401:8080/api/v1/clusters/1/hosts/c6401.ambari.apache.org"
+          }
+        ],
+        "id" : 2,
+        "tag" : "HDFS"
+      }
+    },
+    {
+      "href" : "http://c6401:8080/api/v1/clusters/1/config_groups/3",
+      "ConfigGroup" : {
+        "cluster_name" : "1",
+        "description" : "hdfs2",
+        "desired_configs" : [ ],
+        "group_name" : "hdfs2",
+        "hosts" : [
+          {
+            "host_name" : "c6402.ambari.apache.org",
+            "href" : "http://c6401:8080/api/v1/clusters/1/hosts/c6402.ambari.apache.org"
+          }
+        ],
+        "id" : 3,
+        "tag" : "HDFS"
+      }
+    },
+    {
+      "href" : "http://c6401:8080/api/v1/clusters/1/config_groups/4",
+      "ConfigGroup" : {
+        "cluster_name" : "1",
+        "description" : "yarn1",
+        "desired_configs" : [ ],
+        "group_name" : "yarn1",
+        "hosts" : [
+          {
+            "host_name" : "c6401.ambari.apache.org",
+            "href" : "http://c6401:8080/api/v1/clusters/1/hosts/c6401.ambari.apache.org"
+          },
+          {
+            "host_name" : "c6402.ambari.apache.org",
+            "href" : "http://c6401:8080/api/v1/clusters/1/hosts/c6402.ambari.apache.org"
+          }
+        ],
+        "id" : 4,
+        "tag" : "YARN"
+      }
+    }
+  ]
+}

文件差異過大導致無法顯示
+ 134 - 0
ambari-web/app/assets/data/stacks/HDP-2.2/configurations.json


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

@@ -130,6 +130,8 @@ var files = ['test/init_model_test',
   'test/mappers/status_mapper_test',
   'test/mappers/users_mapper_test',
   'test/mappers/stack_mapper_test',
+  'test/mappers/configs/stack_config_properties_mapper_test',
+  'test/mappers/configs/config_groups_mapper_test',
   'test/mixins/common/chart/storm_linear_time_test',
   'test/mixins/common/localStorage_test',
   'test/mixins/common/serverValidator_test',

+ 2 - 0
ambari-web/app/mappers.js

@@ -21,6 +21,8 @@ require('mappers/server_data_mapper');
 require('mappers/stack_service_mapper');
 require('mappers/stack_mapper');
 require('mappers/stack_version_mapper');
+require('mappers/configs/stack_config_properties_mapper');
+require('mappers/configs/config_groups_mapper');
 require('mappers/repository_version_mapper');
 require('mappers/hosts_mapper');
 require('mappers/cluster_mapper');

+ 96 - 0
ambari-web/app/mappers/configs/config_groups_mapper.js

@@ -0,0 +1,96 @@
+/**
+ * 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.configGroupsMapper = App.QuickDataMapper.create({
+  model: App.ServiceConfigGroup,
+  config: {
+    id: 'id',
+    config_group_id: 'ConfigGroup.id',
+    name: 'ConfigGroup.group_name',
+    service_name: 'ConfigGroup.tag',
+    description: 'ConfigGroup.description',
+    host_names: 'host_names',
+    service_id: 'ConfigGroup.tag'
+  },
+
+  map: function (json, serviceNames) {
+    if (serviceNames && serviceNames.length > 0) {
+      var configGroups = [];
+
+      /**
+       * ex: { "HDFS": ["host1", "host2"], "YARN": ["host1"] }
+       * this property is used to store host names for default config group.
+       * While parsing data for not default groups host names will be excluded from this list.
+       * In case there is no not default config groups for some service <code>hostNamesForService<code>
+       * will not contain property for this service which mean all host belongs to default group
+       */
+      var hostNamesForService = {};
+
+      if (json && json.items) {
+        json.items.forEach(function(configroup) {
+          configroup.id = configroup.ConfigGroup.tag + configroup.ConfigGroup.id;
+          configroup.host_names = configroup.ConfigGroup.hosts.mapProperty('host_name');
+
+          /**
+           * creating (if not exists) field in <code>hostNamesForService<code> with host names for default group
+           */
+          if (!hostNamesForService[configroup.ConfigGroup.tag]) {
+            hostNamesForService[configroup.ConfigGroup.tag] = $.merge([], App.get('allHostNames'));
+          }
+
+          /**
+           * excluding host names that belongs for current config group from default group
+           */
+          configroup.host_names.forEach(function(host) {
+            hostNamesForService[configroup.ConfigGroup.tag].splice(hostNamesForService[configroup.ConfigGroup.tag].indexOf(host), 1);
+          });
+
+          configGroups.push(this.parseIt(configroup, this.get('config')));
+        }, this);
+      }
+
+      /**
+       * generating default config groups
+       */
+      serviceNames.forEach(function(serviceName) {
+        configGroups.push(this.generateDefaultGroup(serviceName, hostNamesForService[serviceName]));
+      }, this);
+
+      App.store.loadMany(this.get('model'), configGroups);
+    }
+  },
+
+  /**
+   * generate mock object for default config group
+   * @param {string} serviceName
+   * @param {string[]} [hostNames=null]
+   * @returns {{id: string, config_group_id: string, name: string, service_name: string, description: string, host_names: [string], service_id: string}}
+   */
+  generateDefaultGroup: function(serviceName, hostNames) {
+    return {
+      id: serviceName + '0',
+      config_group_id: '-1',
+      name: serviceName + ' Default',
+      service_name: serviceName,
+      description: 'Default cluster level '+ serviceName +' configuration',
+      host_names: hostNames ? hostNames : App.get('allHostNames'),
+      service_id: serviceName
+    }
+  }
+});

+ 101 - 0
ambari-web/app/mappers/configs/stack_config_properties_mapper.js

@@ -0,0 +1,101 @@
+/**
+ * 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.stackConfigPropertiesMapper = App.QuickDataMapper.create({
+  model: App.StackConfigProperty,
+  config: {
+    id: 'id',
+    name: 'StackConfigurations.property_name',
+    display_name: 'StackConfigurations.property_name',
+    file_name: 'StackConfigurations.type',
+    description: 'StackConfigurations.property_description',
+    default_value: 'StackConfigurations.property_value',
+    type: 'StackConfigurations.property_type',
+    service_name: 'StackConfigurations.service_name',
+    stack_name: 'StackConfigurations.stack_name',
+    stack_version: 'StackConfigurations.stack_version',
+    property_depended_by: 'StackConfigurations.property_depended_by',
+    value_attributes: 'StackConfigurations.property_value_attributes',
+    default_is_final: 'default_is_final',
+    supports_final: 'supports_final',
+    widget: 'widget'
+    //display_type: 'display_type', //not used for now
+    //category_name: 'category_name' //not used for now
+  },
+
+  map: function (json) {
+    console.time('stackConfigMapper execution time');
+    if (json && json.items) {
+      var configs = [];
+      json.items.forEach(function(stackItem) {
+        var configTypeInfo = Em.get(stackItem, 'StackServices.config_types');
+
+        stackItem.configurations.forEach(function(config) {
+          var configType = App.config.getConfigTagFromFileName(config.StackConfigurations.type);
+          config.id = config.StackConfigurations.property_name + configType;
+          config.default_is_final = config.StackConfigurations.final === "true";
+          config.supports_final = !!configTypeInfo[configType] && configTypeInfo[configType].supports.final === "true";
+          /**
+           * merging stack info with that is stored on UI
+           * for now is not used; uncomment in will be needed
+           * this.mergeWithUI(config);
+           */
+          configs.push(this.parseIt(config, this.get('config')));
+        }, this);
+      }, this);
+      App.store.loadMany(this.get('model'), configs);
+    }
+    console.timeEnd('stackConfigMapper execution time');
+  }
+
+  /******************* METHODS TO MERGE STACK PROPERTIES WITH STORED ON UI (NOT USED FOR NOW)*********************************/
+
+  /**
+   * configs that are stored on UI
+   * @type {Object[]};
+   *
+  preDefinedSiteProperties: function () {
+    var file = App.get('isHadoop22Stack') ? require('data/HDP2.2/site_properties') : require('data/HDP2/site_properties');
+    return file.configProperties;
+  }.property('App.isHadoop22Stack'),
+
+  /**
+   * find UI config with current name and fileName
+   * if there is such property - adds some info to config object
+   * @param {Object} config
+   * @method mergeWithUI
+   *
+  mergeWithUI: function(config) {
+    var uiConfigProperty = this.getUIConfig(config.StackConfigurations.property_name, config.StackConfigurations.type);
+    config.category_name = uiConfigProperty ? uiConfigProperty.category : 'General';
+    config.display_type = uiConfigProperty ? uiConfigProperty.displayType : 'string';
+  },
+
+  /**
+   * returns config with such name and fileName if there is such on UI
+   * otherwise returns null
+   * @param propertyName
+   * @param siteName
+   * @returns {Object|null}
+   * @method getUIConfig
+   *
+  getUIConfig: function(propertyName, siteName) {
+    return this.get('preDefinedSiteProperties').filterProperty('filename', siteName).findProperty('name', propertyName);
+  }*/
+});

+ 3 - 1
ambari-web/app/models/configs/config_group.js

@@ -20,7 +20,9 @@
 var App = require('app');
 
 App.ServiceConfigGroup = DS.Model.extend({
-  id: DS.attr('number'),
+  id: DS.attr('string'),
+  //
+  configGroupId: DS.attr('number'),
   name: DS.attr('string'),
   serviceName: DS.attr('string'),
   description: DS.attr('string'),

+ 32 - 0
ambari-web/app/utils/ajax/ajax.js

@@ -600,6 +600,38 @@ var urls = {
     }
   },
 
+  /*************************CONFIG GROUPS***************************************/
+
+  'configs.config_groups.load.all': {
+    'real': '/clusters/{clusterName}/config_groups?fields=*',
+    'mock': '/data/configurations/config_groups.json'
+  },
+
+  'configs.config_groups.load.services': {
+    'real': '/clusters/{clusterName}/config_groups?ConfigGroup/tag.in({serviceList})&fields=*',
+    'mock': '/data/configurations/config_groups.json'
+  },
+
+  /*************************STACK CONFIGS**************************************/
+
+  'configs.stack_configs.load.all': {
+    'real': '{stackVersionUrl}/services?fields=configurations/*,StackServices/config_types/*',
+    'mock': '/data/stacks/HDP-2.2/configurations.json'
+  },
+
+  'configs.stack_configs.load.services': {
+    'real': '{stackVersionUrl}/services?StackServices/service_name.in({serviceList})?fields=configurations/*,StackServices/config_types/*',
+    'mock': '/data/stacks/HDP-2.2/configurations.json'
+  },
+
+  'configs.stack_configs.load.service': {
+    'real': '{stackVersionUrl}/services/{serviceName}?fields=configurations/*,StackServices/config_types/*',
+    'mock': '/data/stacks/HDP-2.2/configurations.json'
+  },
+
+  /*************************CONFIG VERSIONS*************************************/
+    //TODO
+
   'service.load_config_groups': {
     'real': '/clusters/{clusterName}/config_groups?ConfigGroup/tag={serviceName}&fields=*',
     'mock': '/data/configurations/config_group.json'

+ 63 - 0
ambari-web/app/utils/config.js

@@ -1833,5 +1833,68 @@ App.config = Em.Object.create({
       globValue = globValue.replace(/,/g, '\\,');
     }
     return value.replace(express, globValue);
+  },
+
+  /**
+   * load stack configs from server and run mapper
+   * @param {String[]} [serviceNames=null]
+   * @returns {$.ajax}
+   * @method loadConfigsFromStack
+   */
+  loadConfigsFromStack: function(serviceNames) {
+    serviceNames = serviceNames || [];
+    var name = serviceNames.length > 0 ? 'configs.stack_configs.load.services' : 'configs.stack_configs.load.all';
+    return App.ajax.send({
+      name: name,
+      sender: this,
+      data: {
+        stackVersionUrl: App.get('stackVersionURL'),
+        serviceList: serviceNames.join(',')
+      },
+      success: 'saveConfigsToModel'
+    });
+  },
+
+  /**
+   * runs <code>stackConfigPropertiesMapper<code>
+   * @param data
+   */
+  saveConfigsToModel: function(data) {
+    App.stackConfigPropertiesMapper.map(data);
+  },
+
+  /**
+   * load config groups
+   * @param {String[]} serviceNames
+   * @returns {$.Deferred()}
+   * @method loadConfigGroups
+   */
+  loadConfigGroups: function(serviceNames) {
+    var dfd = $.Deferred();
+    if (!serviceNames || serviceNames.length === 0) {
+      dfd.resolve();
+    } else {
+      App.ajax.send({
+        name: 'configs.config_groups.load.services',
+        sender: this,
+        data: {
+          serviceList: serviceNames.join(','),
+          dfd: dfd
+        },
+        success: 'saveConfigGroupsToModel'
+      });
+    }
+    return dfd.promise();
+  },
+
+  /**
+   * runs <code>configGroupsMapper<code>
+   * @param data
+   * @param opt
+   * @param params
+   */
+  saveConfigGroupsToModel: function(data, opt, params) {
+    App.configGroupsMapper.map(data, params.serviceList.split(','));
+    params.dfd.resolve();
   }
 });

+ 182 - 0
ambari-web/test/mappers/configs/config_groups_mapper_test.js

@@ -0,0 +1,182 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+require('mappers/configs/config_groups_mapper');
+
+describe('App.configGroupsMapper', function () {
+
+  var allHosts = App.get('allHostNames');
+  var defaultAllHosts = ['host1', 'host2', 'host3'];
+  beforeEach(function () {
+    App.set('allHostNames', defaultAllHosts);
+  });
+  afterEach(function(){
+    App.set('allHostNames', allHosts);
+  });
+
+  describe("#map", function() {
+
+    var json = {
+      "items" : [
+        {
+          "ConfigGroup" : {
+            "cluster_name" : "1",
+            "description" : "1",
+            "desired_configs" : [
+              {
+                "tag" : "version1426088081862",
+                "type" : "hadoop-env"
+              }
+            ],
+            "group_name" : "1",
+            "hosts" : [
+              {
+                "host_name" : "host1"
+              }
+            ],
+            "id" : 2,
+            "tag" : "Service1"
+          }
+        },
+        {
+          "ConfigGroup" : {
+            "cluster_name" : "1",
+            "description" : "hdfs2",
+            "desired_configs" : [ ],
+            "group_name" : "hdfs2",
+            "hosts" : [
+              {
+                "host_name" : "host2"
+              }
+            ],
+            "id" : 3,
+            "tag" : "Service1"
+          }
+        },
+        {
+          "ConfigGroup" : {
+            "cluster_name" : "1",
+            "description" : "yarn1",
+            "desired_configs" : [ ],
+            "group_name" : "yarn1",
+            "hosts" : [
+              {
+                "host_name" : "host1"
+              },
+              {
+                "host_name" : "host2"
+              }
+            ],
+            "id" : 4,
+            "tag" : "Service2"
+          }
+        }
+      ]
+    };
+
+    beforeEach(function () {
+      App.resetDsStoreTypeMap(App.ServiceConfigGroup);
+      App.resetDsStoreTypeMap(App.Service);
+      sinon.stub(App.store, 'commit', Em.K);
+    });
+    afterEach(function(){
+      App.store.commit.restore();
+    });
+
+    it('should not do anything as there is no serviceName', function() {
+      App.configGroupsMapper.map(json);
+      expect(App.ServiceConfigGroup.find().get('length')).to.equal(0);
+    });
+
+    it('should generate default groups for services', function() {
+      App.Service.createRecord({'id': 'Service1'});
+      App.configGroupsMapper.map(null, ["Service1"]);
+      expect(App.ServiceConfigGroup.find().get('length')).to.equal(1);
+      expect(App.ServiceConfigGroup.find('Service10').get('id')).to.eql('Service10');
+      expect(App.ServiceConfigGroup.find('Service10').get('configGroupId')).to.eql(-1);
+      expect(App.ServiceConfigGroup.find('Service10').get('name')).to.eql('Service1 Default');
+      expect(App.ServiceConfigGroup.find('Service10').get('description')).to.eql('Default cluster level Service1 configuration');
+      expect(App.ServiceConfigGroup.find('Service10').get('hostNames')).to.eql(defaultAllHosts);
+      expect(App.ServiceConfigGroup.find('Service10').get('serviceName')).to.eql('Service1');
+      expect(App.ServiceConfigGroup.find('Service10').get('service.id')).to.eql('Service1');
+    });
+
+    it('should generate groups form json and default config groups', function() {
+      App.Service.createRecord({'id': 'Service1'});
+      App.Service.createRecord({'id': 'Service2'});
+      App.configGroupsMapper.map(json, ["Service1", "Service2"]);
+      expect(App.ServiceConfigGroup.find().get('length')).to.equal(5);
+      expect(App.ServiceConfigGroup.find().mapProperty('id')).to.eql(["Service12", "Service13", "Service24", "Service10", "Service20"]);
+    });
+
+    it('should generate groups form json and default config groups and check data', function() {
+      App.Service.createRecord({'id': 'Service1'});
+      App.Service.createRecord({'id': 'Service2'});
+      App.configGroupsMapper.map(json, ["Service1", "Service2"]);
+
+      expect(App.ServiceConfigGroup.find('Service12').get('id')).to.eql('Service12');
+      expect(App.ServiceConfigGroup.find('Service12').get('configGroupId')).to.eql(2);
+      expect(App.ServiceConfigGroup.find('Service12').get('name')).to.eql('1');
+      expect(App.ServiceConfigGroup.find('Service12').get('description')).to.eql('1');
+      expect(App.ServiceConfigGroup.find('Service12').get('hostNames')).to.eql(["host1"]);
+      expect(App.ServiceConfigGroup.find('Service12').get('serviceName')).to.eql('Service1');
+      expect(App.ServiceConfigGroup.find('Service12').get('service.id')).to.eql('Service1');
+    });
+  });
+
+  describe("generateDefaultGroup", function() {
+    var tests = [
+      {
+        service: 's1',
+        hosts: ['h1'],
+        res: {
+          id: 's10',
+          config_group_id: '-1',
+          name: 's1 Default',
+          service_name: 's1',
+          description: 'Default cluster level s1 configuration',
+          host_names: ['h1'],
+          service_id: 's1'
+        },
+        m: 'with hosts'
+      },
+      {
+        service: 's1',
+        res: {
+          id: 's10',
+          config_group_id: '-1',
+          name: 's1 Default',
+          service_name: 's1',
+          description: 'Default cluster level s1 configuration',
+          host_names: defaultAllHosts,
+          service_id: 's1'
+        },
+        m: 'without hosts'
+      }
+    ];
+
+
+    tests.forEach(function(t) {
+      it('generates default config group mock object ' + t.m, function() {
+        expect(App.configGroupsMapper.generateDefaultGroup(t.service, t.hosts)).to.be.eql(t.res);
+      });
+    });
+
+  });
+});

+ 168 - 0
ambari-web/test/mappers/configs/stack_config_properties_mapper_test.js

@@ -0,0 +1,168 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+require('mappers/configs/stack_config_properties_mapper');
+
+describe('App.stackConfigPropertiesMapper', function () {
+
+  describe("#map", function() {
+
+    var json = { items: [
+      {
+        "StackServices" : {
+          "service_name" : "HBASE",
+          "stack_name" : "HDP",
+          "stack_version" : "2.2",
+          "config_types" : {
+            "site1" : {
+              "supports" : {
+                "adding_forbidden" : "false",
+                "do_not_extend" : "false",
+                "final" : "true"
+              }
+            }
+          }
+        },
+        "configurations" : [
+          {
+            "StackConfigurations" : {
+              "final" : "false",
+              "property_description" : "desc1",
+              "property_name" : "p1",
+              "property_type" : [ ],
+              "property_value" : "v1",
+              "service_name" : "s1",
+              "stack_name" : "HDP",
+              "stack_version" : "2.2",
+              "type" : "site1.xml",
+              "property_value_attributes": {
+                "type": "int",
+                "minimum": "512",
+                "maximum": "10240",
+                "unit": "MB"
+              },
+              "property_depended_by": [
+                {
+                  "property_type": "site4",
+                  "property_name": "p4"
+                }
+              ]
+            }
+          }
+        ]
+      },
+      {
+        "StackServices" : {
+          "service_name" : "HDFS",
+          "stack_name" : "HDP",
+          "stack_version" : "2.2",
+          "config_types" : {
+            "site2" : {
+              "supports" : {
+                "adding_forbidden" : "false",
+                "do_not_extend" : "false",
+                "final" : "true"
+              }
+            },
+            "site3" : {
+              "supports" : {
+                "adding_forbidden" : "false",
+                "do_not_extend" : "false",
+                "final" : "true"
+              }
+            }
+          }
+        },
+        "configurations" : [
+          {
+            "StackConfigurations" : {
+              "final" : "false",
+              "property_description" : "desc3",
+              "property_name" : "p2",
+              "property_type" : [ ],
+              "property_value" : "v2",
+              "service_name" : "s2",
+              "stack_name" : "HDP",
+              "stack_version" : "2.2",
+              "type" : "site2.xml"
+            }
+          },
+          {
+            "StackConfigurations" : {
+              "final" : "false",
+              "property_description" : "desc3",
+              "property_name" : "p3",
+              "property_type" : [ ],
+              "property_value" : "v3",
+              "service_name" : "s2",
+              "stack_name" : "HDP",
+              "stack_version" : "2.2",
+              "type" : "site3.xml"
+            }
+          }
+        ]
+      }
+    ]};
+
+    beforeEach(function () {
+      App.resetDsStoreTypeMap(App.StackConfigProperty);
+      sinon.stub(App.store, 'commit', Em.K);
+    });
+    afterEach(function(){
+      App.store.commit.restore();
+    });
+
+    it('should not do anything as there is no json', function() {
+      App.stackConfigPropertiesMapper.map(null);
+      expect(App.StackConfigProperty.find().get('length')).to.equal(0);
+    });
+
+    it('should load data to model', function() {
+      App.stackConfigPropertiesMapper.map(json);
+      expect(App.StackConfigProperty.find().get('length')).to.equal(3);
+      expect(App.StackConfigProperty.find().mapProperty('id')).to.eql(['p1site1','p2site2','p3site3']);
+
+      expect(App.StackConfigProperty.find('p1site1').get('name')).to.eql('p1');
+      expect(App.StackConfigProperty.find('p1site1').get('displayName')).to.eql('p1');
+      expect(App.StackConfigProperty.find('p1site1').get('description')).to.eql('desc1');
+      expect(App.StackConfigProperty.find('p1site1').get('defaultValue')).to.eql('v1');
+      expect(App.StackConfigProperty.find('p1site1').get('defaultIsFinal')).to.be.false;
+      expect(App.StackConfigProperty.find('p1site1').get('serviceName')).to.eql('s1');
+      expect(App.StackConfigProperty.find('p1site1').get('stackName')).to.eql('HDP');
+      expect(App.StackConfigProperty.find('p1site1').get('stackVersion')).to.eql('2.2');
+      expect(App.StackConfigProperty.find('p1site1').get('type')).to.eql([]);
+      expect(App.StackConfigProperty.find('p1site1').get('fileName')).to.eql('site1.xml');
+      expect(App.StackConfigProperty.find('p1site1').get('propertyDependedBy')).to.eql([
+        {
+          "property_type": "site4",
+          "property_name": "p4"
+        }
+      ]);
+      expect(App.StackConfigProperty.find('p1site1').get('valueAttributes')).to.eql({
+        "type": "int",
+        "minimum": "512",
+        "maximum": "10240",
+        "unit": "MB"
+      });
+      expect(App.StackConfigProperty.find('p1site1').get('supportsFinal')).to.be.true;
+    });
+  });
+
+});
+

部分文件因文件數量過多而無法顯示