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 2015/04/29 14:10:21 UTC

[2/4] ambari git commit: AMBARI-10826 Move App.ServiceConfigProperty object to separate file. (ababiichuk)

http://git-wip-us.apache.org/repos/asf/ambari/blob/11a09c57/ambari-web/app/utils/configs/config_property_helper.js
----------------------------------------------------------------------
diff --git a/ambari-web/app/utils/configs/config_property_helper.js b/ambari-web/app/utils/configs/config_property_helper.js
new file mode 100644
index 0000000..76b294f
--- /dev/null
+++ b/ambari-web/app/utils/configs/config_property_helper.js
@@ -0,0 +1,554 @@
+/**
+ * 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');
+
+module.exports = {
+
+  initialValue: function (configProperty, localDB, dependencies) {
+    var masterComponentHostsInDB = localDB.masterComponentHosts;
+    var slaveComponentHostsInDB = localDB.slaveComponentHosts;
+    var isOnlyFirstOneNeeded = true;
+    var hostWithPort = "([\\w|\\.]*)(?=:)";
+    var hostWithPrefix = ":\/\/" + hostWithPort;
+    switch (configProperty.get('name')) {
+      case 'namenode_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'NAMENODE').mapProperty('hostName'));
+        break;
+      case 'dfs.namenode.rpc-address':
+      case 'dfs.http.address':
+      case 'dfs.namenode.http-address':
+      case 'dfs.https.address':
+      case 'dfs.namenode.https-address':
+        var nnHost =  masterComponentHostsInDB.findProperty('component', 'NAMENODE').hostName;
+        this.setDefaultValue(configProperty, hostWithPort,nnHost);
+        break;
+      case 'fs.default.name':
+      case 'fs.defaultFS':
+      case 'hbase.rootdir':
+      case 'instance.volumes':
+        var nnHost = masterComponentHostsInDB.filterProperty('component', 'NAMENODE').mapProperty('hostName');
+        this.setDefaultValue(configProperty, hostWithPrefix,'://' + nnHost);
+        break;
+      case 'snamenode_host':
+        // Secondary NameNode does not exist when NameNode HA is enabled
+        var snn = masterComponentHostsInDB.findProperty('component', 'SECONDARY_NAMENODE');
+        if (snn) {
+          configProperty.set('value', snn.hostName);
+        }
+        break;
+      case 'dfs.secondary.http.address':
+      case 'dfs.namenode.secondary.http-address':
+        var snnHost = masterComponentHostsInDB.findProperty('component', 'SECONDARY_NAMENODE');
+        if (snnHost) {
+          this.setDefaultValue(configProperty, hostWithPort,snnHost.hostName);
+        }
+        break;
+      case 'datanode_hosts':
+        configProperty.set('value', slaveComponentHostsInDB.findProperty('componentName', 'DATANODE').hosts.mapProperty('hostName'));
+        break;
+      case 'nfsgateway_hosts':
+        var gwyHost = slaveComponentHostsInDB.findProperty('componentName', 'NFS_GATEWAY');
+        if(gwyHost) {
+          configProperty.set('value', gwyHost.hosts.mapProperty('hostName'));
+        }
+        break;
+      case 'hs_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'HISTORYSERVER').mapProperty('hostName'));
+        break;
+      case 'yarn.log.server.url':
+        var hsHost = masterComponentHostsInDB.filterProperty('component', 'HISTORYSERVER').mapProperty('hostName');
+        this.setDefaultValue(configProperty, hostWithPrefix,'://' + hsHost);
+        break;
+      case 'mapreduce.jobhistory.webapp.address':
+      case 'mapreduce.jobhistory.address':
+        var hsHost = masterComponentHostsInDB.filterProperty('component', 'HISTORYSERVER').mapProperty('hostName');
+        this.setDefaultValue(configProperty, hostWithPort,hsHost);
+        break;
+      case 'rm_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'RESOURCEMANAGER').hostName);
+        break;
+      case 'ats_host':
+        var atsHost =  masterComponentHostsInDB.findProperty('component', 'APP_TIMELINE_SERVER');
+        if (atsHost)
+          configProperty.set('value', atsHost.hostName);
+        else
+          configProperty.set('value', 'false');
+        break;
+      case 'yarn.resourcemanager.hostname':
+        var rmHost = masterComponentHostsInDB.findProperty('component', 'RESOURCEMANAGER').hostName;
+        configProperty.set('defaultValue',rmHost);
+        configProperty.set('value',configProperty.get('defaultValue'));
+        break;
+      case 'yarn.resourcemanager.resource-tracker.address':
+      case 'yarn.resourcemanager.webapp.https.address':
+      case 'yarn.resourcemanager.webapp.address':
+      case 'yarn.resourcemanager.scheduler.address':
+      case 'yarn.resourcemanager.address':
+      case 'yarn.resourcemanager.admin.address':
+        var rmHost = masterComponentHostsInDB.findProperty('component', 'RESOURCEMANAGER').hostName;
+        this.setDefaultValue(configProperty, hostWithPort,rmHost);
+        break;
+      case 'yarn.timeline-service.webapp.address':
+      case 'yarn.timeline-service.webapp.https.address':
+      case 'yarn.timeline-service.address':
+        var atsHost =  masterComponentHostsInDB.findProperty('component', 'APP_TIMELINE_SERVER');
+        if (atsHost && atsHost.hostName) {
+          this.setDefaultValue(configProperty, hostWithPort,atsHost.hostName);
+        }
+        break;
+      case 'nm_hosts':
+        configProperty.set('value', slaveComponentHostsInDB.findProperty('componentName', 'NODEMANAGER').hosts.mapProperty('hostName'));
+        break;
+      case 'jobtracker_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'JOBTRACKER').hostName);
+        break;
+      case 'mapred.job.tracker':
+      case 'mapred.job.tracker.http.address':
+        var jtHost = masterComponentHostsInDB.findProperty('component', 'JOBTRACKER').hostName;
+        this.setDefaultValue(configProperty, hostWithPort,jtHost);
+        break;
+      case 'mapreduce.history.server.http.address':
+        var jtHost = masterComponentHostsInDB.findProperty('component', 'HISTORYSERVER').hostName;
+        this.setDefaultValue(configProperty, hostWithPort,jtHost);
+        break;
+      case 'tasktracker_hosts':
+        configProperty.set('value', slaveComponentHostsInDB.findProperty('componentName', 'TASKTRACKER').hosts.mapProperty('hostName'));
+        break;
+      case 'hbasemaster_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'HBASE_MASTER').mapProperty('hostName'));
+        break;
+      case 'regionserver_hosts':
+        configProperty.set('value', slaveComponentHostsInDB.findProperty('componentName', 'HBASE_REGIONSERVER').hosts.mapProperty('hostName'));
+        break;
+      case 'hivemetastore_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'HIVE_METASTORE').mapProperty('hostName'));
+        break;
+      case 'hive_ambari_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'HIVE_SERVER').hostName);
+        break;
+      case 'hive_master_hosts':
+        var hostNames = masterComponentHostsInDB.filter(function (masterComponent) {
+          return ['HIVE_METASTORE', 'HIVE_SERVER'].contains(masterComponent.component);
+        });
+        configProperty.set('value', hostNames.mapProperty('hostName').uniq().join(','));
+        break;
+      case 'hive_database':
+        var newMySQLDBOption = configProperty.get('options').findProperty('displayName', 'New MySQL Database');
+        if (newMySQLDBOption) {
+          var isNewMySQLDBOptionHidden = !App.get('supports.alwaysEnableManagedMySQLForHive') && App.get('router.currentState.name') != 'configs' &&
+            !App.get('isManagedMySQLForHiveEnabled');
+          if (isNewMySQLDBOptionHidden && configProperty.get('value') == 'New MySQL Database') {
+            configProperty.set('value', 'Existing MySQL Database');
+          }
+          Em.set(newMySQLDBOption, 'hidden', isNewMySQLDBOptionHidden);
+        }
+        break;
+      case 'oozieserver_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'OOZIE_SERVER').mapProperty('hostName'));
+        break;
+      case 'oozie.base.url':
+        var oozieHost = masterComponentHostsInDB.findProperty('component', 'OOZIE_SERVER').hostName;
+        this.setDefaultValue(configProperty, hostWithPrefix,'://' + oozieHost);
+        break;
+      case 'webhcatserver_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'WEBHCAT_SERVER').hostName);
+        break;
+      case 'oozie_ambari_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'OOZIE_SERVER').hostName);
+        break;
+      case 'hadoop_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'NAMENODE').mapProperty('hostName'));
+        break;
+      case 'hive_existing_mysql_host':
+      case 'hive_existing_postgresql_host':
+      case 'hive_existing_oracle_host':
+      case 'hive_existing_mssql_server_host':
+      case 'hive_existing_mssql_server_2_host':
+        var hiveServerHost = masterComponentHostsInDB.findProperty('component', 'HIVE_SERVER').hostName;
+        configProperty.set('value', hiveServerHost).set('defaultValue', hiveServerHost);
+        break;
+      case 'hive.metastore.uris':
+        var hiveMSUris = this.getHiveMetastoreUris(masterComponentHostsInDB, dependencies['hive.metastore.uris']);
+        if (hiveMSUris) {
+          this.setDefaultValue(configProperty, "(.*)", hiveMSUris);
+        }
+        break;
+      case 'oozie_existing_mysql_host':
+      case 'oozie_existing_postgresql_host':
+      case 'oozie_existing_oracle_host':
+      case 'oozie_existing_mssql_server_host':
+      case 'oozie_existing_mssql_server_2_host':
+        var oozieServerHost = masterComponentHostsInDB.findProperty('component', 'OOZIE_SERVER').hostName;
+        configProperty.set('value', oozieServerHost).set('defaultValue', oozieServerHost);
+        break;
+      case 'storm.zookeeper.servers':
+      case 'zookeeperserver_hosts':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'ZOOKEEPER_SERVER').mapProperty('hostName'));
+        break;
+      case 'nimbus.host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'NIMBUS').hostName);
+        break;
+      case 'falconserver_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'FALCON_SERVER').hostName);
+        break;
+      case 'drpcserver_host':
+        var drpcHost = masterComponentHostsInDB.findProperty('component', 'DRPC_SERVER');
+        if (drpcHost) {
+          configProperty.set('value', drpcHost.hostName);
+        }
+        break;
+      case 'stormuiserver_host':
+        configProperty.set('value', masterComponentHostsInDB.findProperty('component', 'STORM_UI_SERVER').hostName);
+        break;
+      case 'storm_rest_api_host':
+        var stormRresApiHost = masterComponentHostsInDB.findProperty('component', 'STORM_REST_API');
+        if(stormRresApiHost) {
+          configProperty.set('value', stormRresApiHost.hostName);
+        }
+        break;
+      case 'supervisor_hosts':
+        configProperty.set('value', slaveComponentHostsInDB.findProperty('componentName', 'SUPERVISOR').hosts.mapProperty('hostName'));
+        break;
+      case 'knox_gateway_host':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'KNOX_GATEWAY').mapProperty('hostName'));
+        break;
+      case 'kafka_broker_hosts':
+        configProperty.set('value', masterComponentHostsInDB.filterProperty('component', 'KAFKA_BROKER').mapProperty('hostName'));
+        break;
+      case 'kafka.ganglia.metrics.host':
+        var gangliaHost =  masterComponentHostsInDB.findProperty('component', 'GANGLIA_SERVER');
+        if (gangliaHost) {
+          configProperty.set('value', gangliaHost.hostName);
+        }
+        break;
+      case 'hbase.zookeeper.quorum':
+        if (configProperty.get('filename') == 'hbase-site.xml') {
+          var zkHosts = masterComponentHostsInDB.filterProperty('component', 'ZOOKEEPER_SERVER').mapProperty('hostName');
+          this.setDefaultValue(configProperty, "(\\w*)", zkHosts);
+        }
+        break;
+      case 'yarn.resourcemanager.zk-address':
+        var value = masterComponentHostsInDB.findProperty('component', 'ZOOKEEPER_SERVER').hostName + ':' + dependencies.clientPort;
+        configProperty.setProperties({
+          value: value,
+          defaultValue: value
+        });
+        break;
+      case 'zookeeper.connect':
+      case 'hive.zookeeper.quorum':
+      case 'templeton.zookeeper.hosts':
+      case 'hadoop.registry.zk.quorum':
+      case 'hive.cluster.delegation.token.store.zookeeper.connectString':
+      case 'instance.zookeeper.host': // for accumulo
+        var zkHosts = masterComponentHostsInDB.filterProperty('component', 'ZOOKEEPER_SERVER').mapProperty('hostName');
+        var zkHostPort = zkHosts;
+        var regex = "\\w*:(\\d+)";   //regex to fetch the port
+        var portValue = configProperty.get('defaultValue').match(new RegExp(regex));
+        if (!portValue) return;
+        if (portValue[1]) {
+          for ( var i = 0; i < zkHosts.length; i++ ) {
+            zkHostPort[i] = zkHosts[i] + ":" + portValue[1];
+          }
+        }
+        this.setDefaultValue(configProperty, "(.*)", zkHostPort);
+        break;
+      case 'templeton.hive.properties':
+        var hiveMSUris = this.getHiveMetastoreUris(masterComponentHostsInDB, dependencies['hive.metastore.uris']).replace(',', '\\,');
+        if (/\/\/localhost:/g.test(configProperty.get('value'))) {
+          configProperty.set('defaultValue', configProperty.get('value') + ',hive.metastore.execute.setugi=true');
+        }
+        this.setDefaultValue(configProperty, "(hive\\.metastore\\.uris=)([^\\,]+)", "$1" + hiveMSUris);
+        break;
+      case 'dfs.name.dir':
+      case 'dfs.namenode.name.dir':
+      case 'dfs.data.dir':
+      case 'dfs.datanode.data.dir':
+      case 'yarn.nodemanager.local-dirs':
+      case 'yarn.nodemanager.log-dirs':
+      case 'mapred.local.dir':
+      case 'log.dirs':  // for Kafka Broker
+        this.unionAllMountPoints(configProperty, !isOnlyFirstOneNeeded, localDB);
+        break;
+      case 'hbase.tmp.dir':
+        if (configProperty.get('filename') == 'hbase-site.xml') {
+          this.unionAllMountPoints(configProperty, isOnlyFirstOneNeeded, localDB);
+        }
+        break;
+      case 'fs.checkpoint.dir':
+      case 'dfs.namenode.checkpoint.dir':
+      case 'yarn.timeline-service.leveldb-timeline-store.path':
+      case 'dataDir':
+      case 'oozie_data_dir':
+      case 'storm.local.dir':
+      case '*.falcon.graph.storage.directory':
+      case '*.falcon.graph.serialize.path':
+        this.unionAllMountPoints(configProperty, isOnlyFirstOneNeeded, localDB);
+        break;
+      case '*.broker.url':
+        var falconServerHost = masterComponentHostsInDB.findProperty('component', 'FALCON_SERVER').hostName;
+        this.setDefaultValue(configProperty, 'localhost', falconServerHost);
+        break;
+      case 'RANGER_HOST':
+        var rangerAdminHost = masterComponentHostsInDB.findProperty('component', 'RANGER_ADMIN');
+        if(rangerAdminHost) {
+          configProperty.set('value', rangerAdminHost.hostName);
+        } else {
+          configProperty.set('isVisible', 'false');
+          configProperty.set('isRequired', 'false');
+        }
+        break;
+    }
+  },
+
+  /**
+   * Get hive.metastore.uris initial value
+   * @param hosts
+   * @param defaultValue
+   * @returns {string}
+   */
+  getHiveMetastoreUris: function (hosts, defaultValue) {
+    var hiveMSHosts = hosts.filterProperty('component', 'HIVE_METASTORE').mapProperty('hostName'),
+      hiveMSUris = hiveMSHosts,
+      regex = "\\w*:(\\d+)",
+      portValue = defaultValue && defaultValue.match(new RegExp(regex));
+
+    if (!portValue) return '';
+    if (portValue[1]) {
+      for (var i = 0; i < hiveMSHosts.length; i++) {
+        hiveMSUris[i] = "thrift://" + hiveMSHosts[i] + ":" + portValue[1];
+      }
+    }
+    return hiveMSUris.join(',');
+  },
+
+  /**
+   * @param regex : String
+   * @param replaceWith : String
+   * @param configProperty
+   */
+  setDefaultValue: function(configProperty, regex, replaceWith) {
+    var defaultValue = configProperty.get('defaultValue');
+    var re = new RegExp(regex);
+    defaultValue = defaultValue.replace(re,replaceWith);
+    configProperty.set('defaultValue',defaultValue);
+    configProperty.set('value',configProperty.get('defaultValue'));
+  },
+
+  unionAllMountPoints: function (configProperty, isOnlyFirstOneNeeded, localDB) {
+    var hostname = '';
+    var mountPointsPerHost = [];
+    var mountPointAsRoot;
+    var masterComponentHostsInDB = localDB.masterComponentHosts;
+    var slaveComponentHostsInDB = localDB.slaveComponentHosts;
+    var hostsInfo = localDB.hosts; // which we are setting in installerController in step3.
+    //all hosts should be in local storage without using App.Host model
+    App.Host.find().forEach(function(item){
+      if(!hostsInfo[item.get('id')]){
+        hostsInfo[item.get('id')] = {
+          name: item.get('id'),
+          cpu: item.get('cpu'),
+          memory: item.get('memory'),
+          disk_info: item.get('diskInfo'),
+          bootStatus: "REGISTERED",
+          isInstalled: true
+        };
+      }
+    });
+    var temp = '';
+    var setOfHostNames = [];
+    var components = [];
+    switch (configProperty.get('name')) {
+      case 'dfs.namenode.name.dir':
+      case 'dfs.name.dir':
+        components = masterComponentHostsInDB.filterProperty('component', 'NAMENODE');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'fs.checkpoint.dir':
+      case 'dfs.namenode.checkpoint.dir':
+        components = masterComponentHostsInDB.filterProperty('component', 'SECONDARY_NAMENODE');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'dfs.data.dir':
+      case 'dfs.datanode.data.dir':
+        temp = slaveComponentHostsInDB.findProperty('componentName', 'DATANODE');
+        temp.hosts.forEach(function (host) {
+          setOfHostNames.push(host.hostName);
+        }, this);
+        break;
+      case 'mapred.local.dir':
+        temp = slaveComponentHostsInDB.findProperty('componentName', 'TASKTRACKER') || slaveComponentHostsInDB.findProperty('componentName', 'NODEMANAGER');
+        temp.hosts.forEach(function (host) {
+          setOfHostNames.push(host.hostName);
+        }, this);
+        break;
+      case 'yarn.nodemanager.log-dirs':
+      case 'yarn.nodemanager.local-dirs':
+        temp = slaveComponentHostsInDB.findProperty('componentName', 'NODEMANAGER');
+        temp.hosts.forEach(function (host) {
+          setOfHostNames.push(host.hostName);
+        }, this);
+        break;
+      case 'yarn.timeline-service.leveldb-timeline-store.path':
+        components = masterComponentHostsInDB.filterProperty('component', 'APP_TIMELINE_SERVER');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'dataDir':
+        components = masterComponentHostsInDB.filterProperty('component', 'ZOOKEEPER_SERVER');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'oozie_data_dir':
+        components = masterComponentHostsInDB.filterProperty('component', 'OOZIE_SERVER');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'hbase.tmp.dir':
+        temp = slaveComponentHostsInDB.findProperty('componentName', 'HBASE_REGIONSERVER');
+        temp.hosts.forEach(function (host) {
+          setOfHostNames.push(host.hostName);
+        }, this);
+        break;
+      case 'storm.local.dir':
+        temp = slaveComponentHostsInDB.findProperty('componentName', 'SUPERVISOR');
+        temp.hosts.forEach(function (host) {
+          setOfHostNames.push(host.hostName);
+        }, this);
+        components = masterComponentHostsInDB.filterProperty('component', 'NIMBUS');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case '*.falcon.graph.storage.directory':
+      case '*.falcon.graph.serialize.path':
+        components = masterComponentHostsInDB.filterProperty('component', 'FALCON_SERVER');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+      case 'log.dirs':
+        components = masterComponentHostsInDB.filterProperty('component', 'KAFKA_BROKER');
+        components.forEach(function (component) {
+          setOfHostNames.push(component.hostName);
+        }, this);
+        break;
+    }
+
+    // In Add Host Wizard, if we did not select this slave component for any host, then we don't process any further.
+    if (setOfHostNames.length === 0) {
+      return;
+    }
+
+    var allMountPoints = [];
+    for (var i = 0; i < setOfHostNames.length; i++) {
+      hostname = setOfHostNames[i];
+
+      mountPointsPerHost = hostsInfo[hostname].disk_info;
+
+      mountPointAsRoot = mountPointsPerHost.findProperty('mountpoint', '/');
+
+      // If Server does not send any host details information then atleast one mountpoint should be presumed as root
+      // This happens in a single container Linux Docker environment.
+      if (!mountPointAsRoot) {
+        mountPointAsRoot = {mountpoint: '/'};
+      }
+
+      mountPointsPerHost = mountPointsPerHost.filter(function (mPoint) {
+        return !(['/', '/home'].contains(mPoint.mountpoint)
+          || ['/etc/resolv.conf', '/etc/hostname', '/etc/hosts'].contains(mPoint.mountpoint) // docker specific mount points
+          || mPoint.mountpoint && (mPoint.mountpoint.startsWith('/boot') || mPoint.mountpoint.startsWith('/mnt'))
+          || ['devtmpfs', 'tmpfs', 'vboxsf', 'CDFS'].contains(mPoint.type)
+          || mPoint.available == 0);
+      });
+
+      mountPointsPerHost.forEach(function (mPoint) {
+        if( !allMountPoints.findProperty("mountpoint", mPoint.mountpoint)) {
+          allMountPoints.push(mPoint);
+        }
+      }, this);
+    }
+    if (allMountPoints.length == 0) {
+      allMountPoints.push(mountPointAsRoot);
+    }
+    configProperty.set('value', '');
+    var winRegex = /^([a-z]):\\?$/;
+    if (!isOnlyFirstOneNeeded) {
+      allMountPoints.forEach(function (eachDrive) {
+        var mPoint = configProperty.get('value');
+        if (!mPoint) {
+          mPoint = "";
+        }
+        if (eachDrive.mountpoint === "/") {
+          mPoint += configProperty.get('defaultDirectory') + "\n";
+        } else if(winRegex.test(eachDrive.mountpoint.toLowerCase())) {
+          switch (configProperty.get('name')) {
+            case 'dfs.name.dir':
+            case 'dfs.namenode.name.dir':
+            case 'dfs.data.dir':
+            case 'dfs.datanode.data.dir':
+              var winDriveUrl = eachDrive.mountpoint.toLowerCase().replace(winRegex, "file:///$1:");
+              mPoint += winDriveUrl + configProperty.get('defaultDirectory') + "\n";
+              break;
+            default:
+              var winDrive = eachDrive.mountpoint.toLowerCase().replace(winRegex, "$1:");
+              var winDir = configProperty.get('defaultDirectory').replace(/\//g, "\\");
+              mPoint += winDrive + winDir + "\n";
+          }
+        } else {
+          mPoint += eachDrive.mountpoint + configProperty.get('defaultDirectory') + "\n";
+        }
+        configProperty.set('value', mPoint);
+        configProperty.set('defaultValue', mPoint);
+      }, this);
+    } else {
+      var mPoint = allMountPoints[0].mountpoint;
+      if (mPoint === "/") {
+        mPoint = configProperty.get('defaultDirectory');
+      } else if(winRegex.test(mPoint.toLowerCase())) {
+        switch (configProperty.get('name')) {
+          case 'fs.checkpoint.dir':
+          case 'dfs.namenode.checkpoint.dir':
+            var winDriveUrl = mPoint.toLowerCase().replace(winRegex, "file:///$1:");
+            mPoint = winDriveUrl + configProperty.get('defaultDirectory') + "\n";
+            break;
+          case 'zk_data_dir':
+            var winDrive = mPoint.toLowerCase().replace(winRegex, "$1:");
+            var winDir = configProperty.get('defaultDirectory').replace(/\//g, "\\\\");
+            mPoint = winDrive + winDir + "\n";
+            break;
+          default:
+            var winDrive = mPoint.toLowerCase().replace(winRegex, "$1:");
+            var winDir = configProperty.get('defaultDirectory').replace(/\//g, "\\");
+            mPoint = winDrive + winDir + "\n";
+        }
+      } else {
+        mPoint = mPoint + configProperty.get('defaultDirectory');
+      }
+      configProperty.set('value', mPoint);
+      configProperty.set('defaultValue', mPoint);
+    }
+  }
+};
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/11a09c57/ambari-web/test/models/configs/objects/service_config_category_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/configs/objects/service_config_category_test.js b/ambari-web/test/models/configs/objects/service_config_category_test.js
new file mode 100644
index 0000000..449c874
--- /dev/null
+++ b/ambari-web/test/models/configs/objects/service_config_category_test.js
@@ -0,0 +1,184 @@
+
+/**
+ * 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');
+var configPropertyHelper = require('utils/configs/config_property_helper');
+
+require('models/configs/objects/service_config_category');
+require('models/configs/objects/service_config_property');
+
+var serviceConfigCategory,
+  nameCases = [
+    {
+      name: 'DataNode',
+      primary: 'DATANODE'
+    },
+    {
+      name: 'TaskTracker',
+      primary: 'TASKTRACKER'
+    },
+    {
+      name: 'RegionServer',
+      primary: 'HBASE_REGIONSERVER'
+    },
+    {
+      name: 'name',
+      primary: null
+    }
+  ],
+  components = [
+    {
+      name: 'NameNode',
+      master: true
+    },
+    {
+      name: 'SNameNode',
+      master: true
+    },
+    {
+      name: 'JobTracker',
+      master: true
+    },
+    {
+      name: 'HBase Master',
+      master: true
+    },
+    {
+      name: 'Oozie Master',
+      master: true
+    },
+    {
+      name: 'Hive Metastore',
+      master: true
+    },
+    {
+      name: 'WebHCat Server',
+      master: true
+    },
+    {
+      name: 'ZooKeeper Server',
+      master: true
+    },
+    {
+      name: 'Ganglia',
+      master: true
+    },
+    {
+      name: 'DataNode',
+      slave: true
+    },
+    {
+      name: 'TaskTracker',
+      slave: true
+    },
+    {
+      name: 'RegionServer',
+      slave: true
+    }
+  ],
+  masters = components.filterProperty('master'),
+  slaves = components.filterProperty('slave'),
+  groupsData = {
+    groups: [
+      Em.Object.create({
+        errorCount: 1
+      }),
+      Em.Object.create({
+        errorCount: 2
+      })
+    ]
+  };
+
+describe('App.ServiceConfigCategory', function () {
+
+  beforeEach(function () {
+    serviceConfigCategory = App.ServiceConfigCategory.create();
+  });
+
+  describe('#primaryName', function () {
+    nameCases.forEach(function (item) {
+      it('should return ' + item.primary, function () {
+        serviceConfigCategory.set('name', item.name);
+        expect(serviceConfigCategory.get('primaryName')).to.equal(item.primary);
+      })
+    });
+  });
+
+  describe('#isForMasterComponent', function () {
+    masters.forEach(function (item) {
+      it('should be true for ' + item.name, function () {
+        serviceConfigCategory.set('name', item.name);
+        expect(serviceConfigCategory.get('isForMasterComponent')).to.be.true;
+      });
+    });
+    it('should be false', function () {
+      serviceConfigCategory.set('name', 'name');
+      expect(serviceConfigCategory.get('isForMasterComponent')).to.be.false;
+    });
+  });
+
+  describe('#isForSlaveComponent', function () {
+    slaves.forEach(function (item) {
+      it('should be true for ' + item.name, function () {
+        serviceConfigCategory.set('name', item.name);
+        expect(serviceConfigCategory.get('isForSlaveComponent')).to.be.true;
+      });
+    });
+    it('should be false', function () {
+      serviceConfigCategory.set('name', 'name');
+      expect(serviceConfigCategory.get('isForSlaveComponent')).to.be.false;
+    });
+  });
+
+  describe('#slaveErrorCount', function () {
+    it('should be 0', function () {
+      serviceConfigCategory.set('slaveConfigs', []);
+      expect(serviceConfigCategory.get('slaveErrorCount')).to.equal(0);
+    });
+    it('should sum all errorCount values', function () {
+      serviceConfigCategory.set('slaveConfigs', groupsData);
+      expect(serviceConfigCategory.get('slaveErrorCount')).to.equal(3);
+    });
+  });
+
+  describe('#errorCount', function () {
+    it('should sum all errors for category', function () {
+      serviceConfigCategory.reopen({
+        slaveErrorCount: 1
+      });
+      expect(serviceConfigCategory.get('errorCount')).to.equal(1);
+      serviceConfigCategory.set('nonSlaveErrorCount', 2);
+      expect(serviceConfigCategory.get('errorCount')).to.equal(3);
+      serviceConfigCategory.set('slaveErrorCount', 0);
+      expect(serviceConfigCategory.get('errorCount')).to.equal(2);
+    });
+  });
+
+  describe('#isAdvanced', function () {
+    it('should be true', function () {
+      serviceConfigCategory.set('name', 'Advanced');
+      expect(serviceConfigCategory.get('isAdvanced')).to.be.true;
+    });
+    it('should be false', function () {
+      serviceConfigCategory.set('name', 'name');
+      expect(serviceConfigCategory.get('isAdvanced')).to.be.false;
+    });
+  });
+
+});
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/ambari/blob/11a09c57/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
new file mode 100644
index 0000000..9235d6e
--- /dev/null
+++ b/ambari-web/test/models/configs/objects/service_config_property_test.js
@@ -0,0 +1,474 @@
+/**
+ * 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');
+var configPropertyHelper = require('utils/configs/config_property_helper');
+
+require('models/configs/objects/service_config_category');
+require('models/configs/objects/service_config_property');
+
+var serviceConfigProperty,
+  serviceConfigPropertyInit,
+  configsData = [
+    Ember.Object.create({
+      category: 'c0',
+      overrides: [
+        {
+          error: true,
+          errorMessage: 'error'
+        },
+        {
+          error: true
+        },
+        {}
+      ]
+    }),
+    Ember.Object.create({
+      category: 'c1',
+      isValid: false,
+      isVisible: true
+    }),
+    Ember.Object.create({
+      category: 'c0',
+      isValid: true,
+      isVisible: true
+    }),
+    Ember.Object.create({
+      category: 'c1',
+      isValid: false,
+      isVisible: false
+    })
+  ],
+
+  components = [
+    {
+      name: 'NameNode',
+      master: true
+    },
+    {
+      name: 'SNameNode',
+      master: true
+    },
+    {
+      name: 'JobTracker',
+      master: true
+    },
+    {
+      name: 'HBase Master',
+      master: true
+    },
+    {
+      name: 'Oozie Master',
+      master: true
+    },
+    {
+      name: 'Hive Metastore',
+      master: true
+    },
+    {
+      name: 'WebHCat Server',
+      master: true
+    },
+    {
+      name: 'ZooKeeper Server',
+      master: true
+    },
+    {
+      name: 'Ganglia',
+      master: true
+    },
+    {
+      name: 'DataNode',
+      slave: true
+    },
+    {
+      name: 'TaskTracker',
+      slave: true
+    },
+    {
+      name: 'RegionServer',
+      slave: true
+    }
+  ],
+  overridableFalseData = [
+    {
+      isOverridable: false
+    },
+    {
+      isEditable: false,
+      overrides: configsData[0].overrides
+    },
+    {
+      displayType: 'masterHost'
+    }
+  ],
+  overridableTrueData = [
+    {
+      isOverridable: true,
+      isEditable: true
+    },    {
+      isOverridable: true,
+      overrides: []
+    },
+    {
+      isOverridable: true
+    }
+  ],
+  overriddenFalseData = [
+    {
+      overrides: null,
+      isOriginalSCP: true
+    },
+    {
+      overrides: [],
+      isOriginalSCP: true
+    }
+  ],
+  overriddenTrueData = [
+    {
+      overrides: configsData[0].overrides
+    },
+    {
+      isOriginalSCP: false
+    }
+  ],
+  removableFalseData = [
+    {
+      isEditable: false
+    },
+    {
+      hasOverrides: true
+    },
+    {
+      isUserProperty: false,
+      isOriginalSCP: true
+    }
+  ],
+  removableTrueData = [
+    {
+      isEditable: true,
+      hasOverrides: false,
+      isUserProperty: true
+    },
+    {
+      isEditable: true,
+      hasOverrides: false,
+      isOriginalSCP: false
+    }
+  ],
+  initPropertyData = [
+    {
+      initial: {
+        displayType: 'password',
+        value: 'value'
+      },
+      result: {
+        retypedPassword: 'value'
+      }
+    },
+    {
+      initial: {
+        id: 'puppet var',
+        value: '',
+        defaultValue: 'default'
+      },
+      result: {
+        value: 'default'
+      }
+    }
+  ],
+  notDefaultFalseData = [
+    {
+      isEditable: false
+    },
+    {
+      defaultValue: null
+    },
+    {
+      value: 'value',
+      defaultValue: 'value'
+    }
+  ],
+  notDefaultTrueData = {
+    isEditable: true,
+    value: 'value',
+    defaultValue: 'default'
+  },
+  types = ['masterHost', 'slaveHosts', 'masterHosts', 'slaveHost', 'radio button'],
+  classCases = [
+    {
+      initial: {
+        displayType: 'checkbox'
+      },
+      viewClass: App.ServiceConfigCheckbox
+    },
+    {
+      initial: {
+        displayType: 'checkbox',
+        dependentConfigPattern: 'somPattern'
+      },
+      viewClass: App.ServiceConfigCheckboxWithDependencies
+    },
+    {
+      initial: {
+        displayType: 'password'
+      },
+      viewClass: App.ServiceConfigPasswordField
+    },
+    {
+      initial: {
+        displayType: 'combobox'
+      },
+      viewClass: App.ServiceConfigComboBox
+    },
+    {
+      initial: {
+        displayType: 'radio button'
+      },
+      viewClass: App.ServiceConfigRadioButtons
+    },
+    {
+      initial: {
+        displayType: 'directories'
+      },
+      viewClass: App.ServiceConfigTextArea
+    },
+    {
+      initial: {
+        displayType: 'content'
+      },
+      viewClass: App.ServiceConfigTextAreaContent
+
+    },
+    {
+      initial: {
+        displayType: 'multiLine'
+      },
+      viewClass: App.ServiceConfigTextArea
+    },
+    {
+      initial: {
+        displayType: 'custom'
+      },
+      viewClass: App.ServiceConfigBigTextArea
+    },
+    {
+      initial: {
+        displayType: 'masterHost'
+      },
+      viewClass: App.ServiceConfigMasterHostView
+    },
+    {
+      initial: {
+        displayType: 'masterHosts'
+      },
+      viewClass: App.ServiceConfigMasterHostsView
+    },
+    {
+      initial: {
+        displayType: 'slaveHosts'
+      },
+      viewClass: App.ServiceConfigSlaveHostsView
+    },
+    {
+      initial: {
+        unit: true,
+        displayType: 'type'
+      },
+      viewClass: App.ServiceConfigTextFieldWithUnit
+    },
+    {
+      initial: {
+        unit: false,
+        displayType: 'type'
+      },
+      viewClass: App.ServiceConfigTextField
+    },
+    {
+      initial: {
+        unit: false,
+        displayType: 'supportTextConnection'
+      },
+      viewClass: App.checkConnectionView
+    }
+  ];
+
+describe('App.ServiceConfigProperty', function () {
+
+  beforeEach(function () {
+    serviceConfigProperty = App.ServiceConfigProperty.create();
+  });
+
+  describe('#overrideErrorTrigger', function () {
+    it('should be an increment', function () {
+      serviceConfigProperty.set('overrides', configsData[0].overrides);
+      expect(serviceConfigProperty.get('overrideErrorTrigger')).to.equal(1);
+      serviceConfigProperty.set('overrides', []);
+      expect(serviceConfigProperty.get('overrideErrorTrigger')).to.equal(2);
+    });
+  });
+
+  describe('#isPropertyOverridable', function () {
+    overridableFalseData.forEach(function (item) {
+      it('should be false', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isPropertyOverridable')).to.be.false;
+      });
+    });
+    overridableTrueData.forEach(function (item) {
+      it('should be true', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isPropertyOverridable')).to.be.true;
+      });
+    });
+  });
+
+  describe('#isOverridden', function () {
+    overriddenFalseData.forEach(function (item) {
+      it('should be false', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isOverridden')).to.be.false;
+      });
+    });
+    overriddenTrueData.forEach(function (item) {
+      it('should be true', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isOverridden')).to.be.true;
+      });
+    });
+  });
+
+  describe('#isRemovable', function () {
+    removableFalseData.forEach(function (item) {
+      it('should be false', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isRemovable')).to.be.false;
+      });
+    });
+    removableTrueData.forEach(function (item) {
+      it('should be true', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isRemovable')).to.be.true;
+      });
+    });
+  });
+
+  describe('#init', function () {
+    initPropertyData.forEach(function (item) {
+      it('should set initial data', function () {
+        serviceConfigPropertyInit = App.ServiceConfigProperty.create(item.initial);
+        Em.keys(item.result).forEach(function (prop) {
+          expect(serviceConfigPropertyInit.get(prop)).to.equal(item.result[prop]);
+        });
+      });
+    });
+  });
+
+  describe('#isNotDefaultValue', function () {
+    notDefaultFalseData.forEach(function (item) {
+      it('should be false', function () {
+        Em.keys(item).forEach(function (prop) {
+          serviceConfigProperty.set(prop, item[prop]);
+        });
+        expect(serviceConfigProperty.get('isNotDefaultValue')).to.be.false;
+      });
+    });
+    it('should be true', function () {
+      Em.keys(notDefaultTrueData).forEach(function (prop) {
+        serviceConfigProperty.set(prop, notDefaultTrueData[prop]);
+      });
+      expect(serviceConfigProperty.get('isNotDefaultValue')).to.be.true;
+    });
+  });
+
+  describe('#cantBeUndone', function () {
+    types.forEach(function (item) {
+      it('should be true', function () {
+        serviceConfigProperty.set('displayType', item);
+        expect(serviceConfigProperty.get('cantBeUndone')).to.be.true;
+      });
+    });
+    it('should be false', function () {
+      serviceConfigProperty.set('displayType', 'type');
+      expect(serviceConfigProperty.get('cantBeUndone')).to.be.false;
+    });
+  });
+
+  describe('#isValid', function () {
+    it('should be true', function () {
+      serviceConfigProperty.set('errorMessage', '');
+      expect(serviceConfigProperty.get('isValid')).to.be.true;
+    });
+    it('should be false', function () {
+      serviceConfigProperty.set('errorMessage', 'message');
+      expect(serviceConfigProperty.get('isValid')).to.be.false;
+    });
+  });
+
+  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('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;
+    });
+  });
+
+});

http://git-wip-us.apache.org/repos/asf/ambari/blob/11a09c57/ambari-web/test/models/configs/objects/service_config_test.js
----------------------------------------------------------------------
diff --git a/ambari-web/test/models/configs/objects/service_config_test.js b/ambari-web/test/models/configs/objects/service_config_test.js
new file mode 100644
index 0000000..cfa015c
--- /dev/null
+++ b/ambari-web/test/models/configs/objects/service_config_test.js
@@ -0,0 +1,165 @@
+/**
+ * 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');
+var configPropertyHelper = require('utils/configs/config_property_helper');
+
+require('models/configs/objects/service_config');
+
+var serviceConfig,
+  group,
+  configsData = [
+    Ember.Object.create({
+      category: 'c0',
+      overrides: [
+        {
+          error: true,
+          errorMessage: 'error'
+        },
+        {
+          error: true
+        },
+        {}
+      ]
+    }),
+    Ember.Object.create({
+      category: 'c1',
+      isValid: false,
+      isVisible: true
+    }),
+    Ember.Object.create({
+      category: 'c0',
+      isValid: true,
+      isVisible: true
+    }),
+    Ember.Object.create({
+      category: 'c1',
+      isValid: false,
+      isVisible: false
+    })
+  ],
+  configCategoriesData = [
+    Em.Object.create({
+      name: 'c0',
+      slaveErrorCount: 1
+    }),
+    Em.Object.create({
+      name: 'c1',
+      slaveErrorCount: 2
+    })
+  ],
+  components = [
+    {
+      name: 'NameNode',
+      master: true
+    },
+    {
+      name: 'SNameNode',
+      master: true
+    },
+    {
+      name: 'JobTracker',
+      master: true
+    },
+    {
+      name: 'HBase Master',
+      master: true
+    },
+    {
+      name: 'Oozie Master',
+      master: true
+    },
+    {
+      name: 'Hive Metastore',
+      master: true
+    },
+    {
+      name: 'WebHCat Server',
+      master: true
+    },
+    {
+      name: 'ZooKeeper Server',
+      master: true
+    },
+    {
+      name: 'Ganglia',
+      master: true
+    },
+    {
+      name: 'DataNode',
+      slave: true
+    },
+    {
+      name: 'TaskTracker',
+      slave: true
+    },
+    {
+      name: 'RegionServer',
+      slave: true
+    }
+  ],
+  masters = components.filterProperty('master'),
+  slaves = components.filterProperty('slave'),
+  groupNoErrorsData = [].concat(configsData.slice(2)),
+  groupErrorsData = [configsData[1]];
+
+describe('App.ServiceConfig', function () {
+
+  beforeEach(function () {
+    serviceConfig = App.ServiceConfig.create();
+  });
+
+  describe('#errorCount', function () {
+    it('should be 0', function () {
+      serviceConfig.setProperties({
+        configs: [],
+        configCategories: []
+      });
+      expect(serviceConfig.get('errorCount')).to.equal(0);
+    });
+    it('should sum counts of all errors', function () {
+      serviceConfig.setProperties({
+        configs: configsData,
+        configCategories: configCategoriesData
+      });
+      expect(serviceConfig.get('errorCount')).to.equal(6);
+      expect(serviceConfig.get('configCategories').findProperty('name', 'c0').get('nonSlaveErrorCount')).to.equal(2);
+      expect(serviceConfig.get('configCategories').findProperty('name', 'c1').get('nonSlaveErrorCount')).to.equal(1);
+    });
+  });
+
+});
+
+describe('App.Group', function () {
+
+  beforeEach(function () {
+    group = App.Group.create();
+  });
+
+  describe('#errorCount', function () {
+    it('should be 0', function () {
+      group.set('properties', groupNoErrorsData);
+      expect(group.get('errorCount')).to.equal(0);
+    });
+    it('should be 1', function () {
+      group.set('properties', groupErrorsData);
+      expect(group.get('errorCount')).to.equal(1);
+    });
+  });
+
+});